diff --git a/.github/workflows/analyzers.yml b/.github/workflows/analyzers.yml new file mode 100644 index 0000000000..8bf6a462b9 --- /dev/null +++ b/.github/workflows/analyzers.yml @@ -0,0 +1,15 @@ +name: Static Analyzers + +on: [push, pull_request] + +jobs: + clang_format: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@50fbc62 + - name: Get clang-format 8 + env: + DEBIAN_FRONTEND: noninteractive + run: sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-8 1000 + - name: Clang Format + run: ci/check-commit-format.sh \ No newline at end of file diff --git a/.github/workflows/beta_artifacts.yml b/.github/workflows/beta_artifacts.yml new file mode 100644 index 0000000000..04d2c76744 --- /dev/null +++ b/.github/workflows/beta_artifacts.yml @@ -0,0 +1,94 @@ +name: Beta + +on: + push: + tags: + - V*RC* + - V*DB* +env: + BETA: 1 + artifact: 1 + +jobs: + osx_job: + runs-on: macOS-latest + env: + BOOST_ROOT: /tmp/boost + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/osx/install_deps.sh + - name: Build Artifact + run: TRAVIS_TAG=${TAG} ci/build-deploy.sh "/tmp/qt/lib/cmake/Qt5"; + - name: Deploy Artifact + run: ci/actions/deploy.sh + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 + + linux_job: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Build Artifact + run: docker run -v ${GITHUB_WORKSPACE}:/workspace nanocurrency/nano-env:gcc /bin/bash -c "cd /workspace && BETA=1 TRAVIS_TAG=${TAG} ci/build-deploy.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + - name: Deploy Artifact + run: ci/actions/deploy.sh + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 + + linux_docker_job: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Deploy Docker (nanocurrency/nano-beta) + run: TRAVIS_TAG=${TAG} ci/actions/linux/deploy-docker.sh + env: + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + windows_job: + runs-on: windows-latest + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: | + $TRAVIS_TAG=git describe --tags $GITHUB_SHA + echo "::set-env name=TAG::$TRAVIS_TAG" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/windows/install_deps.ps1 + - name: Build Artifact + run: ci/actions/windows/build.ps1 + env: + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + - name: Deploy Artifact + run: ci/actions/windows/deploy.ps1 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 \ No newline at end of file diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml new file mode 100644 index 0000000000..6bfe426e68 --- /dev/null +++ b/.github/workflows/develop.yml @@ -0,0 +1,19 @@ +name: Develop + +on: + push: + branches: + - develop +jobs: + linux_job: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Deploy Docker (nanocurrency/nano-env) + run: ci/actions/linux/deploy-docker.sh + env: + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/live_artifacts.yml b/.github/workflows/live_artifacts.yml new file mode 100644 index 0000000000..0c85a7fad0 --- /dev/null +++ b/.github/workflows/live_artifacts.yml @@ -0,0 +1,93 @@ +name: Live + +on: + push: + tags-ignore: + - '*RC*' + - '*DB*' +env: + artifact: 1 + +jobs: + osx_job: + runs-on: macOS-latest + env: + BOOST_ROOT: /tmp/boost + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/osx/install_deps.sh + - name: Build Artifact + run: TRAVIS_TAG=${TAG} ci/build-deploy.sh "/tmp/qt/lib/cmake/Qt5"; + - name: Deploy Artifact + run: ci/actions/deploy.sh + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 + + linux_job: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Build Artifact + run: docker run -v ${GITHUB_WORKSPACE}:/workspace nanocurrency/nano-env:gcc /bin/bash -c "cd /workspace && TRAVIS_TAG=${TAG} ci/build-deploy.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + - name: Deploy Artifact + run: ci/actions/deploy.sh + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 + + linux_docker_job: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: echo "::set-env name=TAG::`git describe --tags $GITHUB_SHA`" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Deploy Docker (nanocurrency/nano) + run: TRAVIS_TAG=${TAG} ci/actions/linux/deploy-docker.sh + env: + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + windows_job: + runs-on: windows-latest + steps: + - uses: actions/checkout@722adc6 + - uses: chrislennon/action-aws-cli@f0f8671 + - name: tag + run: | + $TRAVIS_TAG=git describe --tags $GITHUB_SHA + echo "::set-env name=TAG::$TRAVIS_TAG" + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/windows/install_deps.ps1 + - name: Build Artifact + run: ci/actions/windows/build.ps1 + env: + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + - name: Deploy Artifact + run: ci/actions/windows/deploy.ps1 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 \ No newline at end of file diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml new file mode 100644 index 0000000000..5ea8160154 --- /dev/null +++ b/.github/workflows/release_test.yml @@ -0,0 +1,59 @@ +name: Release Tests + +on: + push: + tags: '*' + +env: + RELEASE: 1 + artifact: 0 + +jobs: + osx_test: + runs-on: macOS-latest + env: + BOOST_ROOT: /tmp/boost + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: TEST=1 ci/actions/osx/install_deps.sh + - name: Run Tests + run: ci/build-travis.sh "/tmp/qt/lib/cmake/Qt5"; + + gcc_test: + runs-on: ubuntu-18.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Run Tests + run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:gcc /bin/bash -c "cd /workspace && RELEASE=1 ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + + clang_test: + runs-on: ubuntu-18.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Run Tests + run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:clang /bin/bash -c "cd /workspace && RELEASE=1 ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + + windows_test: + runs-on: windows-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/windows/install_deps.ps1 + - name: Run Tests + run: ci/actions/windows/build.ps1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000000..a9ade4d623 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,59 @@ +name: Tests + +on: [push, pull_request] + +env: + RELEASE: 0 + artifact: 0 + +jobs: + osx_test: + runs-on: macOS-latest + env: + BOOST_ROOT: /tmp/boost + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: TEST=1 ci/actions/osx/install_deps.sh + - name: Run Tests + run: ci/build-travis.sh "/tmp/qt/lib/cmake/Qt5"; + + gcc_test: + runs-on: ubuntu-18.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Run Tests + run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:gcc /bin/bash -c "cd /workspace && ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + + clang_test: + runs-on: ubuntu-18.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/linux/install_deps.sh + - name: Run Tests + run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:clang /bin/bash -c "cd /workspace && ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + + windows_test: + runs-on: windows-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@722adc6 + - name: Windows Defender + run: ci/actions/windows/disable_windows_defender.ps1 + - name: Checkout Submodules + run: git submodule update --init --recursive + - name: Fetch Deps + run: ci/actions/windows/install_deps.ps1 + - name: Run Tests + run: ci/actions/windows/build.ps1 diff --git a/.gitignore b/.gitignore index d7e797092a..4500baf364 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,9 @@ qrc_resources.cpp qt_system resources.qrc.depends +# Autogenerated Flatbuffers source files +nano/ipc_flatbuffers_lib/generated/flatbuffers/nanoapi_generated.h + # CMake artifacts _CPack_Packages CPack* diff --git a/.gitmodules b/.gitmodules index 5d31006c60..20b188274e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,3 +21,6 @@ [submodule "nano-pow-server"] path = nano-pow-server url = https://github.com/nanocurrency/nano-pow-server.git +[submodule "flatbuffers"] + path = flatbuffers + url = https://github.com/google/flatbuffers.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 97b013524c..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,201 +0,0 @@ -language: cpp - -stages: - - build_base - - build_env - - master_beta_docker - - tag_test - - test - - artifacts_beta - - artifacts_live -jobs: - include: - - stage: build_base - name: "base" - if: branch=master AND type=push - script: - - if [ -n "$DOCKER_PASSWORD" ]; then ci/deploy-docker.sh; fi; - - stage: build_env - name: "clang" - if: branch=master AND type=push - script: - - if [ -n "$DOCKER_PASSWORD" ]; then ci/deploy-docker.sh; fi; - - name: "gcc" - if: branch=master AND type=push - script: - - if [ -n "$DOCKER_PASSWORD" ]; then ci/deploy-docker.sh; fi; - - stage: master_beta_docker - name: "beta docker master tag" - if: tag =~RC|DB - script: - - if [ -n "$DOCKER_PASSWORD" ]; then TRAVIS_TAG="${TRAVIS_TAG}" ci/deploy-docker.sh; fi; - - - stage: tag_test - name: "gcc" - if: tag is present - os: linux - compiler: gcc - env: - - RELEASE=1 - before_install: - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-gcc nanocurrency/nano-env:gcc; - - name: "clang" - if: tag is present - os: linux - compiler: clang - env: - - RELEASE=1 - before_install: - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-clang nanocurrency/nano-env:clang; - - name: "osx clang" - if: tag is present - os: osx - compiler: clang - env: - - RELEASE=1 - before_install: - - brew update; - - brew cask install xquartz; - - brew upgrade cmake; - - brew install rocksdb; - - util/build_prep/fetch_boost.sh - - util/build_prep/macosx/build_qt.sh - install: - - brew install ccache; - - export PATH="/usr/local/opt/ccache/libexec:$PATH"; - - - stage: test - name: "GCC + ONE_TIME_TESTS" - os: linux - compiler: gcc - dist: trusty - sudo: required - env: - - ONE_TIME_TESTS=true - addons: - apt: - packages: - - doxygen - before_install: - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-gcc nanocurrency/nano-env:gcc; - - name: "clang" - os: linux - compiler: clang - dist: trusty - sudo: required - before_install: - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-clang nanocurrency/nano-env:clang; - - name: "osx" - os: osx - compiler: clang - before_install: - - brew update; - - brew cask install xquartz; - - brew upgrade cmake; - - brew install rocksdb; - - util/build_prep/fetch_boost.sh - - util/build_prep/macosx/build_qt.sh - install: - - brew install ccache; - - export PATH="/usr/local/opt/ccache/libexec:$PATH"; - - - stage: artifacts_live - name: "live docker" - if: tag IS present AND !tag=~RC|DB - script: - if [ -n "$DOCKER_PASSWORD" ]; then TRAVIS_TAG="${TRAVIS_TAG}" ci/deploy-docker.sh; fi; - - name: "live linux" - os: linux - compiler: gcc - dist: trusty - if: tag IS present AND !tag=~RC|DB - before_install: - - sudo apt-get update -y && sudo apt-get install -y python-pip - - pip install --user awscli - - aws --version - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-gcc nanocurrency/nano-env:gcc; - script: - - docker run -v $TRAVIS_BUILD_DIR:/workspace -v $HOME/.ccache:/ccache nanocurrency/nano-env:$TRAVIS_COMPILER /bin/bash -c "apt install ccache; cd /workspace && TRAVIS_TAG=${TRAVIS_TAG} CCACHE_DIR=/ccache ci/build-deploy.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}"; - - ci/deploy-travis.sh; - - name: "live osx" - os: osx - compiler: clang - before_install: - - brew update; - - brew cask install xquartz; - - brew upgrade cmake; - - util/build_prep/fetch_rocksdb.sh - - util/build_prep/fetch_boost.sh - - util/build_prep/macosx/build_qt.sh - install: - - pip install --user awscli - - brew install ccache; - - export PATH="$HOME/Library/Python/2.7/bin:/usr/local/opt/ccache/libexec:$PATH"; - - aws --version - script: - - ci/build-deploy.sh "/tmp/qt/lib/cmake/Qt5"; - - if [[ -n "$TRAVIS_TAG" ]]; then if [[ ! "${TRAVIS_TAG-0}" =~ ("RC"|"DB") ]]; then ci/deploy-travis.sh; fi; fi; - - - stage: artifacts_beta - name: "beta docker" - if: tag =~RC|DB - script: - - if [ -n "$DOCKER_PASSWORD" ]; then TRAVIS_TAG="${TRAVIS_TAG}" ci/deploy-docker.sh; fi; - - name: "beta linux" - os: linux - env: - - BETA=1 - compiler: gcc - dist: trusty - if: tag =~RC|DB - before_install: - - sudo apt-get update -y && sudo apt-get install -y python-pip - - pip install --user awscli - - aws --version - - sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; - - ci/build-docker-image.sh docker/ci/Dockerfile-gcc nanocurrency/nano-env:gcc; - script: - - docker run -v $TRAVIS_BUILD_DIR:/workspace -v $HOME/.ccache:/ccache nanocurrency/nano-env:$TRAVIS_COMPILER /bin/bash -c "apt install ccache; cd /workspace && TRAVIS_TAG=${TRAVIS_TAG} BETA=1 CCACHE_DIR=/ccache ci/build-deploy.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" - - ci/deploy-travis.sh; - - name: "beta osx" - os: osx - compiler: clang - env: - - BETA=1 - if: tag =~RC|DB - before_install: - - brew update; - - brew cask install xquartz; - - brew upgrade cmake; - - util/build_prep/fetch_rocksdb.sh - - util/build_prep/fetch_boost.sh - - util/build_prep/macosx/build_qt.sh - install: - - pip install --user awscli - - brew install ccache; - - export PATH="$HOME/Library/Python/2.7/bin:/usr/local/opt/ccache/libexec:$PATH"; - - aws --version - script: - - ci/build-deploy.sh "/tmp/qt/lib/cmake/Qt5"; - - ci/deploy-travis.sh; -cache: - - ccache: true - - directories: - - $HOME/.local - - $HOME/Library/Caches/Homebrew - - $TRAVIS_BUILD_DIR/load-tester/target -script: - - if [ -n "$ONE_TIME_TESTS" ]; then ci/check-commit-format.sh; fi - - if [ -n "$ONE_TIME_TESTS" ]; then doxygen doxygen.config; fi # TODO also deploy the built HTML - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then RELEASE=${RELEASE} ci/build-travis.sh "/tmp/qt/lib/cmake/Qt5"; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker run -v $TRAVIS_BUILD_DIR:/workspace -v $HOME/.ccache:/ccache nanocurrency/nano-env:$TRAVIS_COMPILER /bin/bash -c "apt install ccache; cd /workspace && RELEASE=${RELEASE} ASAN=${ASAN} TSAN=${TSAN} CCACHE_DIR=/ccache ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}"; fi -env: - global: - - secure: "k8kmpD+xRS57ukdvlvaXT0WN4H0rr/aHSjV+l5IUUFpKx5N+DEsb+7ElIepKzqQrGG6qE71cFwDyn6rDwW/Objb9aiEITnvJBzk1XrOVgbc5AnlqDm8LKvqToD/VnQiojyXhBQe2wa//nEZ3PC9dv7hE5zb/K/U5z+LaE9T1cPPk1jHQMCUAFT7QGCK0YeX/gAZqPbLZdHHQChEi+Gu/XY0gc5Bl8Idbp8W7Aky9Ps06lKXPORkE1G2xQfJFrNPB3CKjxev/eoXGBJmNYzxkJlUHmyenjwgdDh9TWiOK2uKH1K6olLIx/qFuIgFRVJFv0QFzCjqqjOJJF1EN9i1M21Lm4wi1iJxYShAP86ZKkC/WmtRn1xNTEMHZJeZ3TXX+B3ybLEWTamHS1Ia8HOif18nrQE3O0aRC/NNfH1kewX+94UNxmSfHtL5Waa41shxeG5waemyQg+HR5zCEtrb5l1btgbfGrR8BMbHYLLe4ywJqMx3n8Iy6ZzC6Xx8+X1zTZZ3zDYPBHUalA+ZoYu2rrFG2+SARP0W/VKqCIKaB+lQKYpbv7ojXGrrDJe7MA/raTLL2pTfSkcx0qxJvcsbPLGI1MdG3mD7M8HncrZbw+sKI1LZ04gyWt3til6d3vSlbIkd6kCxxZh69nd1/KJy8rYrMYjqxxNSTctkGyVb2DtY=" - - RELEASE=0 - - AWS_BUCKET=repo.nano.org diff --git a/CMakeLists.txt b/CMakeLists.txt index f90dea203b..01a76f29df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,19 @@ cmake_minimum_required (VERSION 3.4) + +if (CMAKE_VERSION VERSION_GREATER 3.12 OR CMAKE_VERSION VERSION_EQUAL 3.12) + #find_package uses _ROOT variables + cmake_policy(SET CMP0074 NEW) +endif() +if (CMAKE_VERSION VERSION_GREATER 3.13 OR CMAKE_VERSION VERSION_EQUAL 3.13) + #option honors normal variables + cmake_policy(SET CMP0077 NEW) +endif() + +if (CMAKE_VERSION VERSION_LESS 3.13) + # compatibility for boost import targets use bundled 3.13 FindBoost.cmake + list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/legacyModules") +endif () + # compatibility for osx sierra and on # needs to be set before project set (CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "") @@ -14,10 +29,16 @@ execute_process( ) option (CI_BUILD false) +set (CI_TEST 0 CACHE STRING "") + +if(MSVC) + add_definitions(/MP) +endif() set (CPACK_PACKAGE_VERSION_MAJOR "21") set (CPACK_PACKAGE_VERSION_MINOR "0") set (CPACK_PACKAGE_VERSION_PATCH "0") +set (CPACK_PACKAGE_VERSION_PRE_RELEASE "0") set (CPACK_PACKAGE_VENDOR "Nano Currency") if (CI_BUILD) @@ -26,7 +47,12 @@ else() set (TAG_VERSION_STRING "V${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") endif() -set (CMAKE_INSTALL_RPATH "@executable_path/../Frameworks") +if (APPLE) + set (CMAKE_INSTALL_RPATH "@executable_path/../Frameworks;@executable_path/../boost/lib") +else() + set (CMAKE_INSTALL_RPATH "$ORIGIN/lib") +endif() + # Create all libraries and executables in the root binary dir set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -37,10 +63,14 @@ set (NANO_ROCKSDB OFF CACHE BOOL "") set (NANO_POW_SERVER OFF CACHE BOOL "") set (NANO_WARN_TO_ERR OFF CACHE BOOL "") set (NANO_TIMED_LOCKS 0 CACHE STRING "") +set (NANO_FUZZER_TEST OFF CACHE BOOL "") option (NANO_STACKTRACE_BACKTRACE "Use BOOST_STACKTRACE_USE_BACKTRACE in stacktraces, for POSIX" OFF) if (NANO_STACKTRACE_BACKTRACE) add_definitions(-DNANO_STACKTRACE_BACKTRACE=1) + if (BACKTRACE_INCLUDE) + add_definitions(-DBOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE=${BACKTRACE_INCLUDE}) + endif() endif () if (${NANO_TIMED_LOCKS} GREATER 0) @@ -77,7 +107,8 @@ if (WIN32) -DWINVER=0x0600 -DWIN32_LEAN_AND_MEAN -DMINIUPNP_STATICLIB - -D_CRT_SECURE_NO_WARNINGS) + -D_CRT_SECURE_NO_WARNINGS + /EHsc) if (${USING_TSAN} OR ${USING_ASAN} OR ${USING_ASAN_INT}) message (WARNING "Cannot use TSAN or ASAN on Windows, sanitizers ignored") @@ -108,6 +139,11 @@ else () add_definitions(-DED25519_NO_INLINE_ASM) endif() + if (NANO_FUZZER_TEST) + add_compile_options (-fsanitize=fuzzer-no-link -fno-omit-frame-pointer) + add_definitions (-DNANO_FUZZER_TEST) + endif () + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86(_64)?)$") if (NANO_SIMD_OPTIMIZATIONS OR RAIBLOCKS_SIMD_OPTIMIZATIONS OR ENABLE_AVX2) add_compile_options(-msse4) @@ -149,7 +185,11 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) #set(CMAKE_C_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 14) +set(NANO_SUPPORTED_CPP_STANDARD "17" CACHE STRING "Supported C++ standard (14 or 17)") +if (CI_BUILD OR CI_TEST) + set(NANO_SUPPORTED_CPP_STANDARD "14") +endif() +set(CMAKE_CXX_STANDARD ${NANO_SUPPORTED_CPP_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED ON) #set(CMAKE_CXX_EXTENSIONS OFF) @@ -173,6 +213,9 @@ else () set (PLATFORM_LINK_FLAGS "${PLATFORM_LINK_FLAGS} -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/tsan_clang_blacklist") endif() endif() + if (NANO_FUZZER_TEST) + set (PLATFORM_LINK_FLAGS "${PLATFORM_LINK_FLAGS} -fsanitize=fuzzer-no-link") + endif () endif () SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINK_FLAGS}" ) @@ -191,12 +234,28 @@ endif () include_directories (${CMAKE_SOURCE_DIR}) -set(Boost_USE_STATIC_LIBS ON) -set(Boost_USE_MULTITHREADED ON) +if (WIN32 AND NANO_TEST AND NANO_SHARED_BOOST) + message (SEND_ERROR + " Linking errors occur if NANO_SHARED_BOOST is used with tests on Windows" + " Disable NANO_SHARED_BOOST or NANO_TEST on Windows") + set(NANO_SHARED_BOOST) +endif() + +set(NANO_SHARED_BOOST OFF CACHE BOOL "Build Nano with shared boost") + +if (NANO_SHARED_BOOST) + SET(Boost_USE_STATIC_LIBS OFF) + SET(Boost_USE_STATIC_RUNTIME OFF) + SET(Boost_NO_BOOST_CMAKE ON) + add_definitions( -DBOOST_ALL_DYN_LINK -DBoost_ALL_NO_LIB) +else() + set(Boost_USE_STATIC_LIBS ON) +endif() +set(Boost_USE_MULTITHREADED ON) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules") -find_package (Boost 1.67.0 REQUIRED COMPONENTS filesystem log log_setup thread program_options system) +find_package (Boost 1.69.0 REQUIRED COMPONENTS filesystem log log_setup thread program_options system) if (NANO_ROCKSDB) find_package (RocksDB REQUIRED) @@ -214,6 +273,9 @@ endif () include_directories(cpptoml/include) add_subdirectory(crypto/ed25519-donna) +add_subdirectory(nano/ipc_flatbuffers_lib) +add_subdirectory(nano/ipc_flatbuffers_test) + set (UPNPC_BUILD_SHARED OFF CACHE BOOL "") add_subdirectory (miniupnp/miniupnpc EXCLUDE_FROM_ALL) # FIXME: This fixes miniupnpc include directories without modifying miniupnpc's @@ -350,6 +412,14 @@ add_subdirectory(nano/nano_node) add_subdirectory(nano/rpc) add_subdirectory(nano/nano_rpc) +if (NANO_FUZZER_TEST) + if (NOT WIN32) + add_subdirectory (nano/fuzzer_test) + else () + message (WARNING "Fuzzing is not supported on Windows") + endif () +endif () + if (NANO_TEST OR RAIBLOCKS_TEST) if(WIN32) if(MSVC_VERSION) @@ -476,6 +546,19 @@ if (NANO_GUI OR RAIBLOCKS_GUI) install (DIRECTORY ${Qt5_DIR}/../../QtTest.framework DESTINATION Nano.app/Contents/Frameworks) install (DIRECTORY ${Qt5_DIR}/../../QtWidgets.framework DESTINATION Nano.app/Contents/Frameworks) install (FILES "${Qt5_DIR}/../../../plugins/platforms/libqcocoa.dylib" DESTINATION Nano.app/Contents/PlugIns/platforms) + if (NANO_SHARED_BOOST) + get_filename_component(Boost_LIB_DIR ${BOOST_ROOT}/lib ABSOLUTE) + install (FILES ${Boost_LIB_DIR}/libboost_log.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_filesystem.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_log_setup.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_regex.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_program_options.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_system.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_thread.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_date_time.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_chrono.dylib DESTINATION Nano.app/Contents/boost/lib) + install (FILES ${Boost_LIB_DIR}/libboost_atomic.dylib DESTINATION Nano.app/Contents/boost/lib) + endif() if (NANO_POW_SERVER) install (TARGETS nano_pow_server DESTINATION Nano.app/Contents/MacOS) install (DIRECTORY ${PROJECT_SOURCE_DIR}/nano-pow-server/public DESTINATION Nano.app/Contents/MacOS) @@ -504,6 +587,23 @@ if (NANO_GUI OR RAIBLOCKS_GUI) get_filename_component (Qt5_bin_DIR ${Qt5_DIR}/../../../bin ABSOLUTE) install (TARGETS nano_wallet DESTINATION .) install (TARGETS nano_wallet_com DESTINATION .) + if (NANO_SHARED_BOOST) + foreach(boost_lib IN LISTS Boost_LIBRARIES) + if (${CMAKE_BUILD_TYPE} MATCHES "Rel") + string(REGEX MATCH "(.+/.*boost_[^-]+-.+-mt-x64.+\)(.lib|a)" boost_lib_name ${boost_lib}) + set (boost_dll "${CMAKE_MATCH_1}.dll") + if (${boost_dll} MATCHES "boost") + install (FILES ${boost_dll} DESTINATION .) + endif() + else () + string(REGEX MATCH "(.+/.*boost_[^-]+-.+-mt-.+-x64.+\)(.lib|a)" boost_lib_name ${boost_lib}) + set (boost_dll "${CMAKE_MATCH_1}.dll") + if (${boost_dll} MATCHES "boost") + install (FILES ${boost_dll} DESTINATION .) + endif() + endif() + endforeach(boost_lib) + endif() if (NANO_POW_SERVER) install (TARGETS nano_pow_server DESTINATION .) install (DIRECTORY ${PROJECT_SOURCE_DIR}/nano-pow-server/public DESTINATION .) @@ -516,13 +616,28 @@ if (NANO_GUI OR RAIBLOCKS_GUI) install (FILES ${Qt5_bin_DIR}/Qt5WinExtras.dll DESTINATION .) install (FILES ${Qt5WindowsPlugin} DESTINATION platforms) else () - set(CPACK_GENERATOR "TBZ2") + set(CPACK_GENERATOR "TBZ2;DEB") + set(CPACK_DEBIAN_PACKAGE_DEPENDS qt5-default) + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "russel@nano.org") install(TARGETS nano_wallet - RUNTIME DESTINATION . + RUNTIME DESTINATION ./bin ) + if (NANO_SHARED_BOOST) + get_filename_component(Boost_LIB_DIR ${BOOST_ROOT}/lib ABSOLUTE) + install (FILES ${Boost_LIB_DIR}/libboost_log.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_filesystem.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_log_setup.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_regex.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_program_options.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_system.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_thread.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_date_time.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_chrono.so.${Boost_VERSION_STRING} DESTINATION ./lib) + install (FILES ${Boost_LIB_DIR}/libboost_atomic.so.${Boost_VERSION_STRING} DESTINATION ./lib) + endif() if (NANO_POW_SERVER) - install (TARGETS nano_pow_server DESTINATION .) - install (DIRECTORY ${PROJECT_SOURCE_DIR}/nano-pow-server/public DESTINATION .) + install (TARGETS nano_pow_server DESTINATION ./bin) + install (DIRECTORY ${PROJECT_SOURCE_DIR}/nano-pow-server/public DESTINATION ./bin) endif () endif () endif () diff --git a/LICENSE b/LICENSE index 6a8143b90b..c827026d36 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,11 @@ -Copyright (c) 2014-2019 Colin LeMahieu -All rights reserved. +Copyright 2014-2020 Colin LeMahieu and NanoLabs, Inc. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* 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. +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. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. diff --git a/README.md b/README.md index b32c520706..66dc76ab48 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,12 @@
-[![Build Status](https://travis-ci.org/nanocurrency/nano-node.svg?branch=master)](https://travis-ci.org/nanocurrency/nano-node) -[![Build status](https://ci.appveyor.com/api/projects/status/q66rbt2ux6apjj03/branch/master?svg=true)](https://ci.appveyor.com/project/argakiig/raiblocks/branch/master) +[![Live Artifacts](https://github.com/nanocurrency/nano-node/workflows/Live/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ALive) +[![Beta Artifacts](https://github.com/nanocurrency/nano-node/workflows/Beta/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ABeta) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nanocurrency/nano-node)](https://github.com/nanocurrency/nano-node/releases/latest) [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/nanocurrency/nano-node?color=darkblue&label=beta)](https://github.com/nanocurrency/nano-node/tags) +[![Tests](https://github.com/nanocurrency/nano-node/workflows/Tests/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ATests) +[![RelWithDebug Tests](https://github.com/nanocurrency/nano-node/workflows/Release%20Tests/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3A%22Release+Tests%22) [![Discord](https://img.shields.io/badge/discord-join%20chat-orange.svg)](https://chat.nano.org) --- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..184f2889da --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +## Active Versions + +The Nano network is designed to allow peering between multiple versions of the node software, with older versions being periodically de-peered. The active versions currently peering and being supported can be found in the Node Releases page of our documentation: https://docs.nano.org/releases/node-releases/ + +## Security Audit + +In December 2018 the Nano node codebase was audited by Red4Sec and found to have no critical vulnerabilities. The following vulnerability was resolved: + +**Risk**: High +**Report Location**: Pages 34-35 +**Resolution**: [Pull Request #1563](https://github.com/nanocurrency/nano-node/pull/1563) in [release V17.1](https://github.com/nanocurrency/nano-node/releases/tag/V17.1) + +All other notices from the report were classified as informative and are continuously improved on over time (e.g. code styling). The full report is available here: https://content.nano.org/Nano_Final_Security_Audit_v3.pdf + +## Reporting a Vulnerability + +To report security issues in the Nano protocol, please send an email to security@nano.org and CC the following security team members. It is strongly recommended to encrypt the email using GPG and the pubkeys below can be used for this purpose. + +| GitHub Username | Email | GPG Pubkey | +|-----------------------|--------|-----------------| +| [clemahieu](https://github.com/clemahieu) | clemahieu { at } gmail.com | [clemahieu.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/clemahieu.asc) | +| [argakiig](https://github.com/argakiig) | russel { at } nano.org | [argakiig.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/argakiig.asc) | +| [wezrule](https://github.com/wezrule) | wezrule { at } hotmail.com | [wezrule.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/wezrule.asc) | +| [sergiysw](https://github.com/sergiysw) | sergiysw { at } gmail.com | [sergiysw.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/sergiysw.asc) | +| [guilhermelawless](https://github.com/guilhermelawless) | guilherme { at } nano.org | [guilhermelawless.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/guilhermelawless.asc) | +| [zhyatt](https://github.com/zhyatt) | zach { at } nano.org | [zhyatt.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/zhyatt.asc) | + +For details on how to send a GPG encrypted email, see the tutorial here: https://www.linode.com/docs/security/encryption/gpg-keys-to-send-encrypted-messages/. + +For general support and other non-sensitive inquiries, please visit https://forum.nano.org. diff --git a/api/flatbuffers/nanoapi.fbs b/api/flatbuffers/nanoapi.fbs new file mode 100755 index 0000000000..8099f24427 --- /dev/null +++ b/api/flatbuffers/nanoapi.fbs @@ -0,0 +1,318 @@ +/* + + Flatbuffers schema for the Nano IPC API. + + This file contains IPC message definitions from which code and other artifacts are + generated. + + Rules for editing this file: + + - Only append or deprecate fields; never rearrange or remove fields. This ensures + binary backwards- and forwards compatibility. + + - Renaming fields retain binary compatibility, but should still be considered + a breaking change in most cases. This is because the old name may be expected + in JSON messages and generated API accessors (which may lead to issues in dynamic + languages) + + - Renaming a message is considered a breaking change, as the name appears in endpoints + and JSON message envelopes. + + - Use 64-bit integer types only when the value fits in 2^53-1. This ensures exact + values in all supported bindings, including Javascript (which only supports the + double type) As an example, timestamps such as milliseconds-since-epoch can be + used safely. + + - For integers larger than 2^53-1 where full precision is needed, use a string. Numbers + in strings are considered big int or big decimal, balances being a prominent example. + + - Use the naming pattern SomeMessageName for requests, and SomeMessageNameResponse for + responses if an existing response type isn't available. + + - Subscribe messages must be prefixed Topic and messages belonging to a subscription + must be prefixed Event. Using confirmations as an example, this means using the + message names TopicConfirmation and EventConfirmation respectively. + + - Includes and multiple namespaces must not be used yet due to known issues with + language bindings. + + - Do not use Flatbuffer structs, these are much harder to evolve and the benefits + are minor. + + - Use the (required) attribute only when it's obvious it will never be optional in + the future. Required fields make it harder to obtain full compatibility. + + Other considerations: + + - If a message accrue many deprecations, consider making a minor-versioned message. + This should be done through nominal typing, i.e. a new message type with a suffix + indicating the version, like AccountWeight_v2_1. The response type can be an existing + type, but can also be versioned in the same manner. This naming pattern may be used + for endpoint mapping in the future, such as /api/v2_1/AccountWeight. + + - If several messages need refactoring or improvements, a new API version should be + considered. This requires establishing a new endpoint (e.g. /api/v3) + New and old versions can co-exist. + + Note that none of these rules apply to APIs considered internal, as these are versioned + by their enclosing binary. That is, all binaries (node, rpc, wallet, etc) using internal + APIs are expected to be upgraded at the same time and are thus versioned together. The + same relaxation applies to fields and messages considered "debug" or "experimental". + + See also https://google.github.io/flatbuffers/md__schemas.html for recommended + schema evolution practices. + +*/ + +namespace nanoapi; + +/** Returns the voting weight for the given account */ +table AccountWeight { + /** A nano_ address */ + account: string (required); +} + +/** Response to AccountWeight */ +table AccountWeightResponse { + /** Voting weight as a decimal number*/ + voting_weight: string (required); +} + +/** + * Block subtype for state blocks. + * Note that the node makes no distinction between open and receive subtypes. + */ +enum BlockSubType: byte { + invalid = 0, + receive, + send, + change, + epoch +} + +/** Block union */ +union Block { + BlockState, + BlockOpen, + BlockReceive, + BlockSend, + BlockChange +} + +table BlockOpen { + /** Hash of this block */ + hash: string; + /** Account being opened */ + account: string; + /** Hash of send block */ + source: string; + /** Representative address */ + representative: string; + /** Signature as a hex string */ + signature: string; + /** Work is a hex string representing work. This is a string as the work value may not fit in native numeric types. */ + work: string; +} + +table BlockReceive { + /** Hash of this block */ + hash: string; + /** Hash of previous block */ + previous: string; + /** Source hash */ + source: string; + /** Signature as a hex string */ + signature: string; + /** Work is a hex string representing work. This is a string as the work value may not fit in native numeric types. */ + work: string; +} + +table BlockSend { + /** Hash of this block */ + hash: string; + /** Hash of previous block */ + previous: string; + /** Destination account */ + destination: string; + /** Balance in raw */ + balance: string; + /** Signature as a hex string */ + signature: string; + /** Work is a hex string representing work. This is a string as the work value may not fit in native numeric types. */ + work: string; +} + +table BlockChange { + /** Hash of this block */ + hash: string; + /** Hash of previous block */ + previous: string; + /** Representative address */ + representative: string; + /** Signature as a hex string */ + signature: string; + /** Work is a hex string representing work. This is a string as the work value may not fit in native numeric types. */ + work: string; +} + +table BlockState { + /** Hash of this block */ + hash: string; + /** Account as nano_ string */ + account: string; + /** Hash of previous block */ + previous: string; + /** Representative as nano_ string */ + representative: string; + /** Balance in raw */ + balance: string; + /** Link as a hex string */ + link: string; + /** Link interpreted as a nano_ address */ + link_as_account: string; + /** Signature as a hex string */ + signature: string; + /** Work is a hex string representing work. This is a string as the work value may not fit in native numeric types. */ + work: string; + /** Subtype of this state block */ + subtype: BlockSubType; +} + +/** Information about a block */ +table BlockInfo { + block: Block; +} + +/** Called by a service (usually an external process) to register itself */ +table ServiceRegister { + service_name: string; +} + +/** Request the node to send an EventServiceStop event to the given service */ +table ServiceStop { + /** Name of service to stop. */ + service_name: string (required); + /** If true, restart the service */ + restart: bool = false; +} + +/** + * Subscribe or unsubscribe to EventServiceStop events. + * The service must first have registered itself on the same session. + */ +table TopicServiceStop { + /** Set to true to unsubscribe */ + unsubscribe: bool; +} + +/** Sent to a service to request it to stop itself */ +table EventServiceStop { +} + +/** + * All subscriptions are acknowledged. Use the envelope's correlation id + * if you need to match the ack with the subscription. + */ +table EventAck { +} + +/** Requested confirmation type */ +enum TopicConfirmationTypeFilter : byte { all, active, active_quorum, active_confirmation_height, inactive } + +/** Type of block confirmation */ +enum TopicConfirmationType : byte { active_quorum, active_confirmation_height, inactive } + +/** Subscribe or unsubscribe to block confirmations of type EventConfirmation */ +table TopicConfirmation { + /** Set to true to unsubscribe */ + unsubscribe: bool; + options: TopicConfirmationOptions; +} + +table TopicConfirmationOptions { + confirmation_type_filter: TopicConfirmationTypeFilter = all; + all_local_accounts: bool; + accounts: [string]; + include_block: bool = true; + include_election_info: bool = true; +} + +/** Notification of block confirmation. */ +table EventConfirmation { + confirmation_type: TopicConfirmationType; + account: string; + amount: string; + hash: string; + block: Block; + election_info: ElectionInfo; +} + +table ElectionInfo { + duration: uint64; + time: uint64; + tally: string; + request_count: uint64; + block_count: uint64; + voter_count: uint64; +} + +/** Error response. All fields are optional */ +table Error { + /** Error code. May be negative or positive. */ + code: int; + /** Error category code */ + category: int; + /** Error message */ + message: string; +} + +/** + * A general purpose success response for messages that don't return a message. + * The response includes an optional message text. + */ +table Success { + message: string; +} + +/** IsAlive request and response. Any node issues will be reported through an error in the envelope. */ +table IsAlive { +} + +/** + * A union is the idiomatic way in Flatbuffers to transmit messages of multiple types. + * All top-level message types (including response types) must be listed here. + * @warning To ensure compatibility, only append and deprecate message types. + */ +union Message { + Error, + Success, + IsAlive, + EventAck, + BlockInfo, + AccountWeight, + AccountWeightResponse, + TopicConfirmation, + EventConfirmation, + ServiceRegister, + ServiceStop, + TopicServiceStop, + EventServiceStop +} + +/** + * All messages are wrapped in an envelope which contains information such as + * message type, credentials and correlation id. For responses, the message may be an Error. + */ +table Envelope { + /** Milliseconds since epoch when the message was sent. */ + time: uint64; + /** An optional and arbitrary string used for authentication. The corresponding http header for api keys is "nano-api-key" */ + credentials: string; + /** Correlation id is an optional and arbitrary string. The corresponding http header is "nano-correlation-id" */ + correlation_id: string; + /** The contained message. A 'message_type' property will be automatically added to JSON messages. */ + message: Message; +} + +/** The Envelope is the type marshalled over IPC, and also serves as the top-level JSON type */ +root_type Envelope; diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 61ce811ad3..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: 1.0.{build} -pull_requests: - do_not_increment_build_number: true -skip_branch_with_pr: true -max_jobs: 2 -image: Visual Studio 2017 -configuration: Release -platform: x64 -environment: - VCPKG_DIR: C:\Tools\vcpkg - matrix: - - network: nano_live_network - configuration: Release - - network: nano_beta_network - configuration: RelWithDebInfo -clone_folder: C:\projects\myproject -cache: $VCPKG_DIR%\installed\ -install: - - git submodule update --init --recursive - - echo set (VCPKG_LIBRARY_LINKAGE static) > temp.txt - - type %VCPKG_DIR%\ports\rocksdb\portfile.cmake >> temp.txt - - type temp.txt > %VCPKG_DIR%\ports\rocksdb\portfile.cmake - - del temp.txt - - echo set(VCPKG_BUILD_TYPE release) >> C:\Tools\vcpkg\triplets\x64-windows.cmake - - vcpkg install rocksdb:%PLATFORM%-windows - - IF NOT DEFINED APPVEYOR_REPO_TAG_NAME (set APPVEYOR_REPO_TAG_NAME=NOT_ARTIFACT) - - set TRAVIS_TAG=%APPVEYOR_REPO_TAG_NAME% - - cmake -DNANO_GUI=ON -DNANO_POW_SERVER=ON -DCI_BUILD=ON -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DACTIVE_NETWORK=%NETWORK% -DQt5_DIR="C:\Qt\5.9\msvc2017_64\lib\cmake\Qt5" -DNANO_SIMD_OPTIMIZATIONS=TRUE -DBoost_COMPILER="-vc141" -DBOOST_ROOT="C:/Libraries/boost_1_67_0" -DBOOST_LIBRARYDIR="C:/Libraries/boost_1_67_0/lib64-msvc-14.1" -G "Visual Studio 15 2017 Win64" -DNANO_ROCKSDB=ON -DROCKSDB_LIBRARIES="%VCPKG_DIR%\installed\x64-windows\lib\rocksdb.lib" -DROCKSDB_INCLUDE_DIRS="%VCPKG_DIR%\installed\x64_windows\include" -DZLIB_LIBRARY=%VCPKG_DIR%\installed\x64-windows\lib\zlib.lib -DZLIB_INCLUDE_DIR=%VCPKG_DIR%\installed\x64-windows\include -DIPHLPAPI_LIBRARY="C:/Program Files (x86)/Windows Kits/10/Lib/10.0.14393.0/um/x64/iphlpapi.lib" -DWINSOCK2_LIBRARY="C:/Program Files (x86)/Windows Kits/10/Lib/10.0.14393.0/um/x64/WS2_32.lib" . - - - ps: Invoke-WebRequest -Uri https://aka.ms/vs/15/release/vc_redist.x64.exe -OutFile .\vc_redist.x64.exe -build: - project: INSTALL.vcxproj - parallel: true - verbosity: minimal -after_build: -- ps: | - if (Test-Path env:CSC_LINK) { - $path = Join-Path -Path "$env:TMP" -ChildPath csc.p12 - [IO.File]::WriteAllBytes($path, [Convert]::FromBase64String($env:CSC_LINK)) - - $args = -split 'sign /a /ph /tr http://timestamp.digicert.com /td sha256 /fd sha256' - $args += @('/f', $path, '/p', $env:CSC_KEY_PASSWORD, "$env:APPVEYOR_BUILD_FOLDER\$env:CONFIGURATION\*.exe") - . 'C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe' $args - } -- cmd: >- - cpack -C %CONFIGURATION% --verbose --config ./CPackConfig.cmake - - cpack -G ZIP -C %CONFIGURATION% --verbose --config ./CPackConfig.cmake -artifacts: -- path: nano*.zip - name: nano_release_%network% -- path: nano-node-*.exe - name: nano-node-%network% diff --git a/ci/actions/deploy.sh b/ci/actions/deploy.sh new file mode 100755 index 0000000000..d2bebef214 --- /dev/null +++ b/ci/actions/deploy.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o xtrace +OS=`uname` + +if [[ "${BETA-0}" -eq 1 ]]; then + BUILD="beta" +else + BUILD="live" +fi + +if [[ "$OS" == 'Linux' ]]; then + sha256sum $GITHUB_WORKSPACE/build/nano-node-*-Linux.tar.bz2 | cut -f1 -d' ' > $GITHUB_WORKSPACE/nano-node-$TAG-Linux.tar.bz2.sha256 + sha256sum $GITHUB_WORKSPACE/build/nano-node-*-Linux.deb | cut -f1 -d' ' > $GITHUB_WORKSPACE/nano-node-$TAG-Linux.deb.sha256 + aws s3 cp $GITHUB_WORKSPACE/build/nano-node-*-Linux.tar.bz2 s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Linux.tar.bz2 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers + aws s3 cp $GITHUB_WORKSPACE/nano-node-$TAG-Linux.tar.bz2.sha256 s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Linux.tar.bz2.sha256 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers + aws s3 cp $GITHUB_WORKSPACE/build/nano-node-*-Linux.deb s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Linux.deb --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers + aws s3 cp $GITHUB_WORKSPACE/nano-node-$TAG-Linux.deb.sha256 s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Linux.deb.sha256 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers +else + sha256sum $GITHUB_WORKSPACE/build/nano-node-*-Darwin.dmg | cut -f1 -d' ' > $GITHUB_WORKSPACE/build/nano-node-$TAG-Darwin.dmg.sha256 + aws s3 cp $GITHUB_WORKSPACE/build/nano-node-*-Darwin.dmg s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Darwin.dmg --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers + aws s3 cp $GITHUB_WORKSPACE/build/nano-node-$TAG-Darwin.dmg.sha256 s3://repo.nano.org/$BUILD/binaries/nano-node-$TAG-Darwin.dmg.sha256 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers +fi diff --git a/ci/actions/linux/deploy-docker.sh b/ci/actions/linux/deploy-docker.sh new file mode 100755 index 0000000000..4dba20bad4 --- /dev/null +++ b/ci/actions/linux/deploy-docker.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -e + +if [ -n "$DOCKER_PASSWORD" ]; then + echo "$DOCKER_PASSWORD" | docker login -u nanoreleaseteam --password-stdin + + scripts="$PWD/ci" + TRAVIS_BRANCH=`git branch| cut -f2 -d' '` + if [[ "$GITHUB_WORKFLOW" = "Develop" ]]; then + "$scripts"/custom-timeout.sh 30 docker push "nanocurrency/nano-env:base" + "$scripts"/custom-timeout.sh 30 docker push "nanocurrency/nano-env:gcc" + "$scripts"/custom-timeout.sh 30 docker push "nanocurrency/nano-env:clang" + echo "Deployed nano-env" + exit 0 + else + tags=() + if [ -n "$TRAVIS_TAG" ]; then + tags+=("$TRAVIS_TAG" latest) + if [[ "$GITHUB_WORKFLOW" = "Beta" ]]; then + tags+=(latest-including-rc) + fi + elif [ -n "$TRAVIS_BRANCH" ]; then + TRAVIS_TAG=$TRAVIS_BRANCH + tags+=("$TRAVIS_BRANCH") + fi + if [[ "$GITHUB_WORKFLOW" = "Live" ]]; then + echo "Live" + network_tag_suffix='' + network="live" + elif [[ "$GITHUB_WORKFLOW" = "Beta" ]]; then + echo "Beta" + network_tag_suffix="-beta" + network="beta" + else + echo "Nothing to deploy" + exit 1 + fi + docker_image_name="nanocurrency/nano${network_tag_suffix}" + "$scripts"/custom-timeout.sh 30 docker build --build-arg NETWORK="$network" --build-arg CI_BUILD=true --build-arg TRAVIS_TAG="$TRAVIS_TAG" -f docker/node/Dockerfile -t "$docker_image_name" . + for tag in "${tags[@]}"; do + # Sanitize docker tag + # https://docs.docker.com/engine/reference/commandline/tag/ + tag="$(printf '%s' "$tag" | tr -c '[a-z][A-Z][0-9]_.-' -)" + if [ "$tag" != "latest" ]; then + docker tag "$docker_image_name" "${docker_image_name}:$tag" + fi + "$scripts"/custom-timeout.sh 30 docker push "${docker_image_name}:$tag" + done + fi + echo "$docker_image_name with tags ${tags[*]} deployed" +else + echo "\$DOCKER_PASSWORD environment variable required" + exit 1 +fi \ No newline at end of file diff --git a/ci/actions/linux/install_deps.sh b/ci/actions/linux/install_deps.sh new file mode 100755 index 0000000000..45c6f55496 --- /dev/null +++ b/ci/actions/linux/install_deps.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +sudo mkdir -p /etc/docker && echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json && sudo service docker restart; + +ci/build-docker-image.sh docker/ci/Dockerfile-base nanocurrency/nano-env:base +ci/build-docker-image.sh docker/ci/Dockerfile-gcc nanocurrency/nano-env:gcc +ci/build-docker-image.sh docker/ci/Dockerfile-clang nanocurrency/nano-env:clang diff --git a/ci/actions/osx/install_deps.sh b/ci/actions/osx/install_deps.sh new file mode 100755 index 0000000000..8f740877cb --- /dev/null +++ b/ci/actions/osx/install_deps.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +brew update +brew install coreutils +brew cask install xquartz +if [[ ${TEST-0} -eq 1 ]]; then + brew install rocksdb; +else + util/build_prep/fetch_rocksdb.sh +fi +sudo util/build_prep/fetch_boost.sh +util/build_prep/macosx/build_qt.sh diff --git a/ci/actions/windows/build.bat b/ci/actions/windows/build.bat new file mode 100644 index 0000000000..9cd4e8a3ef --- /dev/null +++ b/ci/actions/windows/build.bat @@ -0,0 +1,29 @@ +@echo off +set exit_code=0 + +goto %RUN% + +:test +cmake --build . ^ + --target core_test ^ + --config %BUILD_TYPE% ^ + -- /m:2 +set exit_code=%errorlevel% +if %exit_code% neq 0 goto exit +cmake --build . ^ + --target rpc_test ^ + --config %BUILD_TYPE% ^ + -- /m:2 +set exit_code=%errorlevel% +goto exit + +:artifact +cmake --build . ^ + --target INSTALL ^ + --config %BUILD_TYPE% ^ + -- /m:2 +set exit_code=%errorlevel% +goto exit + +:exit +exit /B %exit_code% \ No newline at end of file diff --git a/ci/actions/windows/build.ps1 b/ci/actions/windows/build.ps1 new file mode 100644 index 0000000000..40c150dac4 --- /dev/null +++ b/ci/actions/windows/build.ps1 @@ -0,0 +1,65 @@ +$ErrorActionPreference = "Continue" + +if (${env:artifact} -eq 1) { + if ( ${env:BETA} -eq 1 ) { + $env:NETWORK_CFG = "beta" + $env:BUILD_TYPE = "RelWithDebInfo" + } + else { + $env:NETWORK_CFG = "live" + $env:BUILD_TYPE = "Release" + } + $env:NANO_SHARED_BOOST = "ON" + $env:ROCKS_LIB = '-DROCKSDB_LIBRARIES="c:\vcpkg\installed\x64-windows-static\lib\rocksdb.lib"' + $env:NANO_TEST = "-DNANO_TEST=OFF" + $env:TRAVIS_TAG = ${env:TAG} + + $env:CI = "-DCI_BUILD=ON" + $env:RUN = "artifact" +} +else { + if ( ${env:RELEASE} -eq 1 ) { + $env:BUILD_TYPE = "RelWithDebInfo" + $env:ROCKS_LIB = '-DROCKSDB_LIBRARIES="c:\vcpkg\installed\x64-windows-static\lib\rocksdb.lib"' + } + else { + $env:BUILD_TYPE = "Debug" + $env:ROCKS_LIB = '-DROCKSDB_LIBRARIES="c:\vcpkg\installed\x64-windows-static\debug\lib\rocksdbd.lib"' + } + $env:NANO_SHARED_BOOST = "OFF" + $env:NETWORK_CFG = "test" + $env:NANO_TEST = "-DNANO_TEST=ON" + $env:CI = '-DCI_TEST="1"' + $env:RUN = "test" +} + +mkdir build +Push-Location build +$env:BOOST_ROOT = ${env:BOOST_ROOT_1_69_0} + +#accessibility of Boost dlls for generating config samples +$ENV:PATH = "$ENV:PATH;$ENV:BOOST_ROOT\lib" + +& ..\ci\actions\windows\configure.bat +if (${LastExitCode} -ne 0) { + throw "Failed to configure" +} + +if (${env:RUN} -eq "artifact") { + $p = Get-Location + Invoke-WebRequest -Uri https://aka.ms/vs/16/release/vc_redist.x64.exe -OutFile "$p\vc_redist.x64.exe" +} + +& ..\ci\actions\windows\build.bat +if (${LastExitCode} -ne 0) { + throw "Failed to build ${env:RUN}" +} +$env:cmake_path = Split-Path -Path(get-command cmake.exe).Path +. "$PSScriptRoot\signing.ps1" + +& ..\ci\actions\windows\run.bat +if (${LastExitCode} -ne 0) { + throw "Failed to Pass Test ${env:RUN}" +} + +Pop-Location \ No newline at end of file diff --git a/ci/actions/windows/configure.bat b/ci/actions/windows/configure.bat new file mode 100644 index 0000000000..b37de8b262 --- /dev/null +++ b/ci/actions/windows/configure.bat @@ -0,0 +1,31 @@ +@echo off +set exit_code=0 + +echo "BUILD TYPE %BUILD_TYPE%" +echo "RUN %RUN%" + +cmake .. ^ + -Ax64 ^ + %NANO_TEST% ^ + %CI% ^ + -DNANO_ROCKSDB=ON ^ + %ROCKS_LIB% ^ + -DROCKSDB_INCLUDE_DIRS="c:\vcpkg\installed\x64-windows-static\include" ^ + -DZLIB_LIBRARY_RELEASE="c:\vcpkg\installed\x64-windows-static\lib\zlib.lib" ^ + -DZLIB_LIBRARY_DEBUG="c:\vcpkg\installed\x64-windows-static\debug\lib\zlibd.lib" ^ + -DZLIB_INCLUDE_DIR="c:\vcpkg\installed\x64-windows-static\include" ^ + -DQt5_DIR="c:\qt\5.13.1\msvc2017_64\lib\cmake\Qt5" ^ + -DNANO_GUI=ON ^ + -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^ + -DACTIVE_NETWORK=nano_%NETWORK_CFG%_network ^ + -DNANO_SIMD_OPTIMIZATIONS=TRUE ^ + -Dgtest_force_shared_crt=on ^ + -DBoost_NO_SYSTEM_PATHS=TRUE ^ + -DBoost_NO_BOOST_CMAKE=TRUE ^ + -DNANO_SHARED_BOOST=%NANO_SHARED_BOOST% + +set exit_code=%errorlevel% +if %exit_code% neq 0 goto exit + +:exit +exit /B %exit_code% \ No newline at end of file diff --git a/ci/actions/windows/deploy.ps1 b/ci/actions/windows/deploy.ps1 new file mode 100644 index 0000000000..4f71b5cad4 --- /dev/null +++ b/ci/actions/windows/deploy.ps1 @@ -0,0 +1,19 @@ +$ErrorActionPreference = "Continue" + +if ( ${env:BETA} -eq 1 ) { + $network_cfg = "beta" +} +else { + $network_cfg = "live" +} + +$exe = Resolve-Path -Path $env:GITHUB_WORKSPACE\build\nano-node-*-win64.exe +$zip = Resolve-Path -Path $env:GITHUB_WORKSPACE\build\nano-node-*-win64.zip + +(Get-FileHash $exe).hash | Out-file -FilePath "$exe.sha256" +(Get-FileHash $zip).hash | Out-file -FilePath "$zip.sha256" + +aws s3 cp $exe s3://repo.nano.org/$network_cfg/binaries/nano-node-$env:TAG-win64.exe --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers +aws s3 cp "$exe.sha256" s3://repo.nano.org/$network_cfg/binaries/nano-node-$env:TAG-win64.exe.sha256 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers +aws s3 cp "$zip" s3://repo.nano.org/$network_cfg/binaries/nano-node-$env:TAG-win64.zip --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers +aws s3 cp "$zip.sha256" s3://repo.nano.org/$network_cfg/binaries/nano-node-$env:TAG-win64.zip.sha256 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers \ No newline at end of file diff --git a/ci/actions/windows/disable_windows_defender.ps1 b/ci/actions/windows/disable_windows_defender.ps1 new file mode 100644 index 0000000000..4d674308e9 --- /dev/null +++ b/ci/actions/windows/disable_windows_defender.ps1 @@ -0,0 +1,5 @@ +$ErrorActionPreference = "Continue" + +Set-MpPreference -DisableArchiveScanning $true +Set-MpPreference -DisableRealtimeMonitoring $true +Set-MpPreference -DisableBehaviorMonitoring $true \ No newline at end of file diff --git a/ci/actions/windows/install_deps.ps1 b/ci/actions/windows/install_deps.ps1 new file mode 100644 index 0000000000..4edbe17fb0 --- /dev/null +++ b/ci/actions/windows/install_deps.ps1 @@ -0,0 +1,70 @@ +$ErrorActionPreference = "Continue" + +function Get-RedirectedUri { + <# + .SYNOPSIS + Gets the real download URL from the redirection. + .DESCRIPTION + Used to get the real URL for downloading a file, this will not work if downloading the file directly. + .EXAMPLE + Get-RedirectedURL -URL "https://download.mozilla.org/?product=firefox-latest&os=win&lang=en-US" + .PARAMETER URL + URL for the redirected URL to be un-obfuscated + .NOTES + Code from: Redone per issue #2896 in core https://github.com/PowerShell/PowerShell/issues/2896 + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Uri + ) + process { + do { + try { + $request = Invoke-WebRequest -Method Head -Uri $Uri + if ($null -ne $request.BaseResponse.ResponseUri) { + # This is for Powershell 5 + $redirectUri = $request.BaseResponse.ResponseUri.AbsoluteUri + } + elseif ($null -ne $request.BaseResponse.RequestMessage.RequestUri) { + # This is for Powershell core + $redirectUri = $request.BaseResponse.RequestMessage.RequestUri.AbsoluteUri + } + + $retry = $false + } + catch { + if (($_.Exception.GetType() -match "HttpResponseException") -and ($_.Exception -match "302")) { + $Uri = $_.Exception.Response.Headers.Location.AbsoluteUri + $retry = $true + } + else { + throw $_ + } + } + } while ($retry) + + $redirectUri + } +} + +$qt5_root = "c:\qt" +$rocksdb_url = Get-RedirectedUri "https://repo.nano.org/artifacts/rocksdb-msvc14.1-latest.7z" +$qt5base_url = Get-RedirectedUri "https://repo.nano.org/artifacts/5.13.1-0-201909031231qtbase-Windows-Windows_10-MSVC2017-Windows-Windows_10-X86_64.7z" +$qt5winextra_url = Get-RedirectedUri "https://repo.nano.org/artifacts/5.13.1-0-201909031231qtwinextras-Windows-Windows_10-MSVC2017-Windows-Windows_10-X86_64.7z" +$rocksdb_artifact = "${env:TMP}\rocksdb.7z" +$qt5base_artifact = "${env:TMP}\qt5base.7z" +$qt5winextra_artifact = "${env:TMP}\qt5winextra.7z" + +(New-Object System.Net.WebClient).DownloadFile($qt5base_url, $qt5base_artifact) +(New-Object System.Net.WebClient).DownloadFile($qt5winextra_url, $qt5winextra_artifact) +mkdir $qt5_root +Push-Location $qt5_root +7z x "${env:TMP}\qt5*.7z" +Pop-Location + +Push-Location ${env:VCPKG_INSTALLATION_ROOT} +(New-Object System.Net.WebClient).DownloadFile($rocksdb_url, $rocksdb_artifact) +7z x $rocksdb_artifact +Pop-Location \ No newline at end of file diff --git a/ci/actions/windows/run.bat b/ci/actions/windows/run.bat new file mode 100644 index 0000000000..97e2c16c10 --- /dev/null +++ b/ci/actions/windows/run.bat @@ -0,0 +1,26 @@ +@echo off +set exit_code=0 +set count=0 + +goto %RUN% + +:test +if %count% equ 10 goto rpc_test +call %BUILD_TYPE%\core_test.exe +set core_code=%errorlevel% +set /a count=count+1 +if %core_code% neq 0 goto test + +:rpc_test +call %BUILD_TYPE%\rpc_test.exe +set rpc_code=%errorlevel% +echo Core Test %core_code% +echo RPC Test %rpc_code% + +exit /B %core_code% + +:artifact +echo "Packaging NSIS" +call "%cmake_path%\cpack.exe" -C %BUILD_TYPE% +echo "Packaging ZIP" +call "%cmake_path%\cpack.exe" -G ZIP -C %BUILD_TYPE% diff --git a/ci/actions/windows/signing.ps1 b/ci/actions/windows/signing.ps1 new file mode 100644 index 0000000000..78de388617 --- /dev/null +++ b/ci/actions/windows/signing.ps1 @@ -0,0 +1,9 @@ +$ErrorActionPreference = "Continue" + +if (Test-Path env:CSC_LINK) { + $path = Join-Path -Path "$env:TMP" -ChildPath csc.p12 + [IO.File]::WriteAllBytes($path, [Convert]::FromBase64String($env:CSC_LINK)) + $args = -split 'sign /a /ph /tr http://timestamp.digicert.com /td sha256 /fd sha256' + $args += @('/f', $path, '/p', $env:CSC_KEY_PASSWORD, "$env:BUILD_TYPE\*.exe") + . "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" $args +} \ No newline at end of file diff --git a/ci/build-deploy.sh b/ci/build-deploy.sh index bf396f6535..8a3d5d36de 100755 --- a/ci/build-deploy.sh +++ b/ci/build-deploy.sh @@ -30,8 +30,8 @@ cmake \ -DCMAKE_BUILD_TYPE=${CONFIGURATION} \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DBOOST_ROOT=/tmp/boost/ \ + -DNANO_SHARED_BOOST=ON \ -DQt5_DIR=${qt_dir} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCI_BUILD=true \ .. diff --git a/ci/build-travis.sh b/ci/build-travis.sh index d3691ef972..1478d18d71 100755 --- a/ci/build-travis.sh +++ b/ci/build-travis.sh @@ -10,16 +10,22 @@ OS=`uname` # This is to prevent out of scope access in async_write from asio which is not picked up by static analysers if [[ $(grep -rl --exclude="*asio.hpp" "asio::async_write" ./nano) ]]; then - echo "using boost::asio::async_write directly is not permitted (except in nano/lib/asio.hpp). Use nano::async_write instead" + echo "Using boost::asio::async_write directly is not permitted (except in nano/lib/asio.hpp). Use nano::async_write instead" exit 1 fi # prevent unsolicited use of std::lock_guard & std::unique_lock outside of allowed areas -if [[ $(grep -rl --exclude={"*random_pool.cpp","*random_pool.hpp","*locks.hpp","*locks.cpp"} "std::unique_lock\|std::lock_guard\|std::condition_variable" ./nano) ]]; then - echo "using std::unique_lock, std::lock_guard or std::condition_variable is not permitted (except in nano/lib/locks.hpp and non-nano dependent libraries). Use the nano::* versions instead" +if [[ $(grep -rl --exclude={"*random_pool.cpp","*random_pool.hpp","*random_pool_shuffle.hpp","*locks.hpp","*locks.cpp"} "std::unique_lock\|std::lock_guard\|std::condition_variable" ./nano) ]]; then + echo "Using std::unique_lock, std::lock_guard or std::condition_variable is not permitted (except in nano/lib/locks.hpp and non-nano dependent libraries). Use the nano::* versions instead" exit 1 fi +if [[ $(grep -rlP "^\s*assert \(" ./nano) ]]; then + echo "Using assert is not permitted. Use debug_assert instead." + exit 1 +fi + +# prevent unsolicited use of std::lock_guard & std::unique_lock outside of allowed areas mkdir build pushd build @@ -44,11 +50,17 @@ ulimit -S -n 8192 if [[ "$OS" == 'Linux' ]]; then ROCKSDB="-DROCKSDB_LIBRARIES=/tmp/rocksdb/lib/librocksdb.a \ -DROCKSDB_INCLUDE_DIRS=/tmp/rocksdb/include" + if clang --version; then + BACKTRACE="-DNANO_STACKTRACE_BACKTRACE=ON \ + -DBACKTRACE_INCLUDE=" + else + BACKTRACE="-DNANO_STACKTRACE_BACKTRACE=ON" + fi else ROCKSDB="" + BACKTRACE="" fi - cmake \ -G'Unix Makefiles' \ -DACTIVE_NETWORK=nano_test_network \ @@ -60,8 +72,10 @@ cmake \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DBOOST_ROOT=/tmp/boost/ \ + -DNANO_SHARED_BOOST=ON \ -DQt5_DIR=${qt_dir} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCI_TEST="1" \ + ${BACKTRACE} \ ${SANITIZERS} \ .. diff --git a/ci/check-commit-format.sh b/ci/check-commit-format.sh index e9b85d5756..0147ae341d 100755 --- a/ci/check-commit-format.sh +++ b/ci/check-commit-format.sh @@ -10,4 +10,7 @@ if [ "$RESULT" != "no modified files to format" ] && [ "$RESULT" != "clang-forma echo echo "Code formatting differs from expected - please run ci/clang-format-all.sh" exit 1 +else + echo "clang-format passed" + exit 0 fi diff --git a/ci/record_rep_weights.py b/ci/record_rep_weights.py index 82d82a9b8a..d02eafa73e 100644 --- a/ci/record_rep_weights.py +++ b/ci/record_rep_weights.py @@ -1,6 +1,5 @@ import requests import argparse -import string from binascii import unhexlify from base64 import b32decode from binascii import hexlify, unhexlify @@ -17,49 +16,49 @@ p = r.json() reps = [ ] -tbl = string.maketrans('13456789abcdefghijkmnopqrstuwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') +tbl = bytes.maketrans(b'13456789abcdefghijkmnopqrstuwxyz', b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') for acc in p["representatives"]: reps.append({ 'account': acc, - 'weight': long(p["representatives"][acc]) + 'weight': int(p["representatives"][acc]) }) r = requests.post(args.rpc, data='{"action":"block_count"}') p = r.json() block_height = max(0, int(p["count"]) - args.cutoff) -print "cutoff block height is %d" % block_height +print("cutoff block height is %d" % block_height) reps.sort(key=lambda x: x["weight"], reverse=True) -supplymax = long(0) +supplymax = int(0) for rep in reps: supplymax += rep["weight"] -supplymax /= long('1000000000000000000000000000000') -supplymax = long(supplymax * args.limit) -supplymax *= long('1000000000000000000000000000000') +supplymax /= int('1000000000000000000000000000000') +supplymax = int(supplymax * args.limit) +supplymax *= int('1000000000000000000000000000000') with open(args.output, 'wb') as of: of.write(unhexlify("%032X" % block_height)) - total = long(0) + total = int(0) count = 0 for rep in reps: if rep["weight"] == 0: break - acc_val = long(hexlify(b32decode(rep["account"].encode("utf-8").replace("nano_", "").translate(tbl) + "====")), 16) + acc_val = int(hexlify(b32decode(rep["account"].encode('utf-8').replace(b"nano_", b"").translate(tbl) + b"====")), 16) acc_bytes = unhexlify("%064X" % (((acc_val >> 36) & ((1 << 256) - 1)))) weight_bytes = unhexlify("%032X" % rep["weight"]) of.write(acc_bytes) of.write(weight_bytes) total += rep["weight"] count += 1 - print rep["account"] + ": " + str(rep["weight"]) + print(rep["account"] + ": " + str(rep["weight"])) if total >= supplymax: break - print "wrote %d rep weights" % count - print "max supply %d" % supplymax + print ("wrote %d rep weights" % count) + print ("max supply %d" % supplymax) of.close() diff --git a/ci/test.sh b/ci/test.sh index 97cad4f842..a01d5120a9 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -44,7 +44,7 @@ run_tests() { TIMEOUT_TIME_ARG="" fi - if [ "$(date +%s)" -lt 1577836799 ]; then + if [ "$(date +%s)" -lt 1593561600 ]; then tries=(_initial_ 1 2 3 4 5 6 7 8 9) else tries=(_initial_) @@ -71,7 +71,7 @@ run_tests() { xvfb_run_ ./qt_test qt_test_res=${?} - ${TIMEOUT_CMD} ${TIMEOUT_TIME_ARG} ${TIMEOUT_SEC-${TIMEOUT_DEFAULT}} ./load_test -s 150 + ${TIMEOUT_CMD} ${TIMEOUT_TIME_ARG} ${TIMEOUT_SEC-${TIMEOUT_DEFAULT}} ./load_test -s 150 -n 5 load_test_res=${?} echo "Core Test return code: ${core_test_res}" diff --git a/cmake/Modules/FindBoost.cmake b/cmake/legacyModules/FindBoost.cmake similarity index 86% rename from cmake/Modules/FindBoost.cmake rename to cmake/legacyModules/FindBoost.cmake index 0c589074d9..22406ea26f 100644 --- a/cmake/Modules/FindBoost.cmake +++ b/cmake/legacyModules/FindBoost.cmake @@ -1,242 +1,240 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. -#[=======================================================================[.rst: -FindBoost ---------- - -Find Boost include dirs and libraries - -Use this module by invoking find_package with the form:: - - find_package(Boost - [version] [EXACT] # Minimum or EXACT version e.g. 1.67.0 - [REQUIRED] # Fail with error if Boost is not found - [COMPONENTS ...] # Boost libraries by their canonical name - # e.g. "date_time" for "libboost_date_time" - [OPTIONAL_COMPONENTS ...] - # Optional Boost libraries by their canonical name) - ) # e.g. "date_time" for "libboost_date_time" - -This module finds headers and requested component libraries OR a CMake -package configuration file provided by a "Boost CMake" build. For the -latter case skip to the "Boost CMake" section below. For the former -case results are reported in variables:: - - Boost_FOUND - True if headers and requested libraries were found - Boost_INCLUDE_DIRS - Boost include directories - Boost_LIBRARY_DIRS - Link directories for Boost libraries - Boost_LIBRARIES - Boost component libraries to be linked - Boost__FOUND - True if component was found ( is upper-case) - Boost__LIBRARY - Libraries to link for component (may include - target_link_libraries debug/optimized keywords) - Boost_VERSION - BOOST_VERSION value from boost/version.hpp - Boost_LIB_VERSION - Version string appended to library filenames - Boost_MAJOR_VERSION - Boost major version number (X in X.y.z) - Boost_MINOR_VERSION - Boost minor version number (Y in x.Y.z) - Boost_SUBMINOR_VERSION - Boost subminor version number (Z in x.y.Z) - Boost_VERSION_STRING - Boost version number in x.y.z format - Boost_LIB_DIAGNOSTIC_DEFINITIONS (Windows) - - Pass to add_definitions() to have diagnostic - information about Boost's automatic linking - displayed during compilation - -Note that Boost Python components require a Python version suffix -(Boost 1.67 and later), e.g. ``python36`` or ``python27`` for the -versions built against Python 3.6 and 2.7, respectively. This also -applies to additional components using Python including -``mpi_python`` and ``numpy``. Earlier Boost releases may use -distribution-specific suffixes such as ``2``, ``3`` or ``2.7``. -These may also be used as suffixes, but note that they are not -portable. - -This module reads hints about search locations from variables:: - - BOOST_ROOT - Preferred installation prefix - (or BOOSTROOT) - BOOST_INCLUDEDIR - Preferred include directory e.g. /include - BOOST_LIBRARYDIR - Preferred library directory e.g. /lib - Boost_NO_SYSTEM_PATHS - Set to ON to disable searching in locations not - specified by these hint variables. Default is OFF. - Boost_ADDITIONAL_VERSIONS - - List of Boost versions not known to this module - (Boost install locations may contain the version) - -and saves search results persistently in CMake cache entries:: - - Boost_INCLUDE_DIR - Directory containing Boost headers - Boost_LIBRARY_DIR_RELEASE - Directory containing release Boost libraries - Boost_LIBRARY_DIR_DEBUG - Directory containing debug Boost libraries - Boost__LIBRARY_DEBUG - Component library debug variant - Boost__LIBRARY_RELEASE - Component library release variant - -The following :prop_tgt:`IMPORTED` targets are also defined:: - - Boost::boost - Target for header-only dependencies - (Boost include directory) - Boost:: - Target for specific component dependency - (shared or static library); is lower- - case - Boost::diagnostic_definitions - interface target to enable diagnostic - information about Boost's automatic linking - during compilation (adds BOOST_LIB_DIAGNOSTIC) - Boost::disable_autolinking - interface target to disable automatic - linking with MSVC (adds BOOST_ALL_NO_LIB) - Boost::dynamic_linking - interface target to enable dynamic linking - linking with MSVC (adds BOOST_ALL_DYN_LINK) - -Implicit dependencies such as Boost::filesystem requiring -Boost::system will be automatically detected and satisfied, even -if system is not specified when using find_package and if -Boost::system is not added to target_link_libraries. If using -Boost::thread, then Threads::Threads will also be added automatically. - -It is important to note that the imported targets behave differently -than variables created by this module: multiple calls to -find_package(Boost) in the same directory or sub-directories with -different options (e.g. static or shared) will not override the -values of the targets created by the first call. - -Users may set these hints or results as cache entries. Projects -should not read these entries directly but instead use the above -result variables. Note that some hint names start in upper-case -"BOOST". One may specify these as environment variables if they are -not specified as CMake variables or cache entries. - -This module first searches for the Boost header files using the above -hint variables (excluding BOOST_LIBRARYDIR) and saves the result in -Boost_INCLUDE_DIR. Then it searches for requested component libraries -using the above hints (excluding BOOST_INCLUDEDIR and -Boost_ADDITIONAL_VERSIONS), "lib" directories near Boost_INCLUDE_DIR, -and the library name configuration settings below. It saves the -library directories in Boost_LIBRARY_DIR_DEBUG and -Boost_LIBRARY_DIR_RELEASE and individual library -locations in Boost__LIBRARY_DEBUG and Boost__LIBRARY_RELEASE. -When one changes settings used by previous searches in the same build -tree (excluding environment variables) this module discards previous -search results affected by the changes and searches again. - -Boost libraries come in many variants encoded in their file name. -Users or projects may tell this module which variant to find by -setting variables:: - - Boost_USE_DEBUG_LIBS - Set to ON or OFF to specify whether to search - and use the debug libraries. Default is ON. - Boost_USE_RELEASE_LIBS - Set to ON or OFF to specify whether to search - and use the release libraries. Default is ON. - Boost_USE_MULTITHREADED - Set to OFF to use the non-multithreaded - libraries ('mt' tag). Default is ON. - Boost_USE_STATIC_LIBS - Set to ON to force the use of the static - libraries. Default is OFF. - Boost_USE_STATIC_RUNTIME - Set to ON or OFF to specify whether to use - libraries linked statically to the C++ runtime - ('s' tag). Default is platform dependent. - Boost_USE_DEBUG_RUNTIME - Set to ON or OFF to specify whether to use - libraries linked to the MS debug C++ runtime - ('g' tag). Default is ON. - Boost_USE_DEBUG_PYTHON - Set to ON to use libraries compiled with a - debug Python build ('y' tag). Default is OFF. - Boost_USE_STLPORT - Set to ON to use libraries compiled with - STLPort ('p' tag). Default is OFF. - Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS - - Set to ON to use libraries compiled with - STLPort deprecated "native iostreams" - ('n' tag). Default is OFF. - Boost_COMPILER - Set to the compiler-specific library suffix - (e.g. "-gcc43"). Default is auto-computed - for the C++ compiler in use. A list may be - used if multiple compatible suffixes should - be tested for, in decreasing order of - preference. - Boost_ARCHITECTURE - Set to the architecture-specific library suffix - (e.g. "-x64"). Default is auto-computed for the - C++ compiler in use. - Boost_THREADAPI - Suffix for "thread" component library name, - such as "pthread" or "win32". Names with - and without this suffix will both be tried. - Boost_NAMESPACE - Alternate namespace used to build boost with - e.g. if set to "myboost", will search for - myboost_thread instead of boost_thread. - -Other variables one may set to control this module are:: - - Boost_DEBUG - Set to ON to enable debug output from FindBoost. - Please enable this before filing any bug report. - Boost_DETAILED_FAILURE_MSG - - Set to ON to add detailed information to the - failure message even when the REQUIRED option - is not given to the find_package call. - Boost_REALPATH - Set to ON to resolve symlinks for discovered - libraries to assist with packaging. For example, - the "system" component library may be resolved to - "/usr/lib/libboost_system.so.1.67.0" instead of - "/usr/lib/libboost_system.so". This does not - affect linking and should not be enabled unless - the user needs this information. - Boost_LIBRARY_DIR - Default value for Boost_LIBRARY_DIR_RELEASE and - Boost_LIBRARY_DIR_DEBUG. - -On Visual Studio and Borland compilers Boost headers request automatic -linking to corresponding libraries. This requires matching libraries -to be linked explicitly or available in the link library search path. -In this case setting Boost_USE_STATIC_LIBS to OFF may not achieve -dynamic linking. Boost automatic linking typically requests static -libraries with a few exceptions (such as Boost.Python). Use:: - - add_definitions(${Boost_LIB_DIAGNOSTIC_DEFINITIONS}) - -to ask Boost to report information about automatic linking requests. - -Example to find Boost headers only:: - - find_package(Boost 1.36.0) - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - add_executable(foo foo.cc) - endif() - -Example to find Boost libraries and use imported targets:: - - find_package(Boost 1.56 REQUIRED COMPONENTS - date_time filesystem iostreams) - add_executable(foo foo.cc) - target_link_libraries(foo Boost::date_time Boost::filesystem - Boost::iostreams) - -Example to find Boost Python 3.6 libraries and use imported targets:: - - find_package(Boost 1.67 REQUIRED COMPONENTS - python36 numpy36) - add_executable(foo foo.cc) - target_link_libraries(foo Boost::python36 Boost::numpy36) - -Example to find Boost headers and some *static* (release only) libraries:: - - set(Boost_USE_STATIC_LIBS ON) # only find static libs - set(Boost_USE_DEBUG_LIBS OFF) # ignore debug libs and - set(Boost_USE_RELEASE_LIBS ON) # only find release libs - set(Boost_USE_MULTITHREADED ON) - set(Boost_USE_STATIC_RUNTIME OFF) - find_package(Boost 1.66.0 COMPONENTS date_time filesystem system ...) - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - add_executable(foo foo.cc) - target_link_libraries(foo ${Boost_LIBRARIES}) - endif() - -Boost CMake -^^^^^^^^^^^ - -If Boost was built using the boost-cmake project it provides a package -configuration file for use with find_package's Config mode. This -module looks for the package configuration file called -BoostConfig.cmake or boost-config.cmake and stores the result in cache -entry "Boost_DIR". If found, the package configuration file is loaded -and this module returns with no further action. See documentation of -the Boost CMake package configuration for details on what it provides. - -Set Boost_NO_BOOST_CMAKE to ON to disable the search for boost-cmake. -#]=======================================================================] +#.rst: +# FindBoost +# --------- +# +# Find Boost include dirs and libraries +# +# Use this module by invoking find_package with the form:: +# +# find_package(Boost +# [version] [EXACT] # Minimum or EXACT version e.g. 1.67.0 +# [REQUIRED] # Fail with error if Boost is not found +# [COMPONENTS ...] # Boost libraries by their canonical name +# # e.g. "date_time" for "libboost_date_time" +# [OPTIONAL_COMPONENTS ...] +# # Optional Boost libraries by their canonical name) +# ) # e.g. "date_time" for "libboost_date_time" +# +# This module finds headers and requested component libraries OR a CMake +# package configuration file provided by a "Boost CMake" build. For the +# latter case skip to the "Boost CMake" section below. For the former +# case results are reported in variables:: +# +# Boost_FOUND - True if headers and requested libraries were found +# Boost_INCLUDE_DIRS - Boost include directories +# Boost_LIBRARY_DIRS - Link directories for Boost libraries +# Boost_LIBRARIES - Boost component libraries to be linked +# Boost__FOUND - True if component was found ( is upper-case) +# Boost__LIBRARY - Libraries to link for component (may include +# target_link_libraries debug/optimized keywords) +# Boost_VERSION - BOOST_VERSION value from boost/version.hpp +# Boost_LIB_VERSION - Version string appended to library filenames +# Boost_MAJOR_VERSION - Boost major version number (X in X.y.z) +# Boost_MINOR_VERSION - Boost minor version number (Y in x.Y.z) +# Boost_SUBMINOR_VERSION - Boost subminor version number (Z in x.y.Z) +# Boost_LIB_DIAGNOSTIC_DEFINITIONS (Windows) +# - Pass to add_definitions() to have diagnostic +# information about Boost's automatic linking +# displayed during compilation +# +# Note that Boost Python components require a Python version suffix +# (Boost 1.67 and later), e.g. ``python36`` or ``python27`` for the +# versions built against Python 3.6 and 2.7, respectively. This also +# applies to additional components using Python including +# ``mpi_python`` and ``numpy``. Earlier Boost releases may use +# distribution-specific suffixes such as ``2``, ``3`` or ``2.7``. +# These may also be used as suffixes, but note that they are not +# portable. +# +# This module reads hints about search locations from variables:: +# +# BOOST_ROOT - Preferred installation prefix +# (or BOOSTROOT) +# BOOST_INCLUDEDIR - Preferred include directory e.g. /include +# BOOST_LIBRARYDIR - Preferred library directory e.g. /lib +# Boost_NO_SYSTEM_PATHS - Set to ON to disable searching in locations not +# specified by these hint variables. Default is OFF. +# Boost_ADDITIONAL_VERSIONS +# - List of Boost versions not known to this module +# (Boost install locations may contain the version) +# +# and saves search results persistently in CMake cache entries:: +# +# Boost_INCLUDE_DIR - Directory containing Boost headers +# Boost_LIBRARY_DIR_RELEASE - Directory containing release Boost libraries +# Boost_LIBRARY_DIR_DEBUG - Directory containing debug Boost libraries +# Boost__LIBRARY_DEBUG - Component library debug variant +# Boost__LIBRARY_RELEASE - Component library release variant +# +# The following :prop_tgt:`IMPORTED` targets are also defined:: +# +# Boost::boost - Target for header-only dependencies +# (Boost include directory) +# Boost:: - Target for specific component dependency +# (shared or static library); is lower- +# case +# Boost::diagnostic_definitions - interface target to enable diagnostic +# information about Boost's automatic linking +# during compilation (adds BOOST_LIB_DIAGNOSTIC) +# Boost::disable_autolinking - interface target to disable automatic +# linking with MSVC (adds BOOST_ALL_NO_LIB) +# Boost::dynamic_linking - interface target to enable dynamic linking +# linking with MSVC (adds BOOST_ALL_DYN_LINK) +# +# Implicit dependencies such as Boost::filesystem requiring +# Boost::system will be automatically detected and satisfied, even +# if system is not specified when using find_package and if +# Boost::system is not added to target_link_libraries. If using +# Boost::thread, then Threads::Threads will also be added automatically. +# +# It is important to note that the imported targets behave differently +# than variables created by this module: multiple calls to +# find_package(Boost) in the same directory or sub-directories with +# different options (e.g. static or shared) will not override the +# values of the targets created by the first call. +# +# Users may set these hints or results as cache entries. Projects +# should not read these entries directly but instead use the above +# result variables. Note that some hint names start in upper-case +# "BOOST". One may specify these as environment variables if they are +# not specified as CMake variables or cache entries. +# +# This module first searches for the Boost header files using the above +# hint variables (excluding BOOST_LIBRARYDIR) and saves the result in +# Boost_INCLUDE_DIR. Then it searches for requested component libraries +# using the above hints (excluding BOOST_INCLUDEDIR and +# Boost_ADDITIONAL_VERSIONS), "lib" directories near Boost_INCLUDE_DIR, +# and the library name configuration settings below. It saves the +# library directories in Boost_LIBRARY_DIR_DEBUG and +# Boost_LIBRARY_DIR_RELEASE and individual library +# locations in Boost__LIBRARY_DEBUG and Boost__LIBRARY_RELEASE. +# When one changes settings used by previous searches in the same build +# tree (excluding environment variables) this module discards previous +# search results affected by the changes and searches again. +# +# Boost libraries come in many variants encoded in their file name. +# Users or projects may tell this module which variant to find by +# setting variables:: +# +# Boost_USE_DEBUG_LIBS - Set to ON or OFF to specify whether to search +# and use the debug libraries. Default is ON. +# Boost_USE_RELEASE_LIBS - Set to ON or OFF to specify whether to search +# and use the release libraries. Default is ON. +# Boost_USE_MULTITHREADED - Set to OFF to use the non-multithreaded +# libraries ('mt' tag). Default is ON. +# Boost_USE_STATIC_LIBS - Set to ON to force the use of the static +# libraries. Default is OFF. +# Boost_USE_STATIC_RUNTIME - Set to ON or OFF to specify whether to use +# libraries linked statically to the C++ runtime +# ('s' tag). Default is platform dependent. +# Boost_USE_DEBUG_RUNTIME - Set to ON or OFF to specify whether to use +# libraries linked to the MS debug C++ runtime +# ('g' tag). Default is ON. +# Boost_USE_DEBUG_PYTHON - Set to ON to use libraries compiled with a +# debug Python build ('y' tag). Default is OFF. +# Boost_USE_STLPORT - Set to ON to use libraries compiled with +# STLPort ('p' tag). Default is OFF. +# Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS +# - Set to ON to use libraries compiled with +# STLPort deprecated "native iostreams" +# ('n' tag). Default is OFF. +# Boost_COMPILER - Set to the compiler-specific library suffix +# (e.g. "-gcc43"). Default is auto-computed +# for the C++ compiler in use. A list may be +# used if multiple compatible suffixes should +# be tested for, in decreasing order of +# preference. +# Boost_ARCHITECTURE - Set to the architecture-specific library suffix +# (e.g. "-x64"). Default is auto-computed for the +# C++ compiler in use. +# Boost_THREADAPI - Suffix for "thread" component library name, +# such as "pthread" or "win32". Names with +# and without this suffix will both be tried. +# Boost_NAMESPACE - Alternate namespace used to build boost with +# e.g. if set to "myboost", will search for +# myboost_thread instead of boost_thread. +# +# Other variables one may set to control this module are:: +# +# Boost_DEBUG - Set to ON to enable debug output from FindBoost. +# Please enable this before filing any bug report. +# Boost_DETAILED_FAILURE_MSG +# - Set to ON to add detailed information to the +# failure message even when the REQUIRED option +# is not given to the find_package call. +# Boost_REALPATH - Set to ON to resolve symlinks for discovered +# libraries to assist with packaging. For example, +# the "system" component library may be resolved to +# "/usr/lib/libboost_system.so.1.67.0" instead of +# "/usr/lib/libboost_system.so". This does not +# affect linking and should not be enabled unless +# the user needs this information. +# Boost_LIBRARY_DIR - Default value for Boost_LIBRARY_DIR_RELEASE and +# Boost_LIBRARY_DIR_DEBUG. +# +# On Visual Studio and Borland compilers Boost headers request automatic +# linking to corresponding libraries. This requires matching libraries +# to be linked explicitly or available in the link library search path. +# In this case setting Boost_USE_STATIC_LIBS to OFF may not achieve +# dynamic linking. Boost automatic linking typically requests static +# libraries with a few exceptions (such as Boost.Python). Use:: +# +# add_definitions(${Boost_LIB_DIAGNOSTIC_DEFINITIONS}) +# +# to ask Boost to report information about automatic linking requests. +# +# Example to find Boost headers only:: +# +# find_package(Boost 1.36.0) +# if(Boost_FOUND) +# include_directories(${Boost_INCLUDE_DIRS}) +# add_executable(foo foo.cc) +# endif() +# +# Example to find Boost libraries and use imported targets:: +# +# find_package(Boost 1.56 REQUIRED COMPONENTS +# date_time filesystem iostreams) +# add_executable(foo foo.cc) +# target_link_libraries(foo Boost::date_time Boost::filesystem +# Boost::iostreams) +# +# Example to find Boost Python 3.6 libraries and use imported targets:: +# +# find_package(Boost 1.67 REQUIRED COMPONENTS +# python36 numpy36) +# add_executable(foo foo.cc) +# target_link_libraries(foo Boost::python36 Boost::numpy36) +# +# Example to find Boost headers and some *static* (release only) libraries:: +# +# set(Boost_USE_STATIC_LIBS ON) # only find static libs +# set(Boost_USE_DEBUG_LIBS OFF) # ignore debug libs and +# set(Boost_USE_RELEASE_LIBS ON) # only find release libs +# set(Boost_USE_MULTITHREADED ON) +# set(Boost_USE_STATIC_RUNTIME OFF) +# find_package(Boost 1.66.0 COMPONENTS date_time filesystem system ...) +# if(Boost_FOUND) +# include_directories(${Boost_INCLUDE_DIRS}) +# add_executable(foo foo.cc) +# target_link_libraries(foo ${Boost_LIBRARIES}) +# endif() +# +# Boost CMake +# ^^^^^^^^^^^ +# +# If Boost was built using the boost-cmake project it provides a package +# configuration file for use with find_package's Config mode. This +# module looks for the package configuration file called +# BoostConfig.cmake or boost-config.cmake and stores the result in cache +# entry "Boost_DIR". If found, the package configuration file is loaded +# and this module returns with no further action. See documentation of +# the Boost CMake package configuration for details on what it provides. +# +# Set Boost_NO_BOOST_CMAKE to ON to disable the search for boost-cmake. # Save project's policies cmake_policy(PUSH) @@ -411,12 +409,15 @@ endmacro() #------------------------------------------------------------------------------- -# Convert CMAKE_CXX_COMPILER_VERSION to boost compiler suffix version. +# +# Runs compiler with "-dumpversion" and parses major/minor +# version with a regex. +# function(_Boost_COMPILER_DUMPVERSION _OUTPUT_VERSION _OUTPUT_VERSION_MAJOR _OUTPUT_VERSION_MINOR) string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\1" - _boost_COMPILER_VERSION_MAJOR "${CMAKE_CXX_COMPILER_VERSION}") + _boost_COMPILER_VERSION_MAJOR ${CMAKE_CXX_COMPILER_VERSION}) string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\2" - _boost_COMPILER_VERSION_MINOR "${CMAKE_CXX_COMPILER_VERSION}") + _boost_COMPILER_VERSION_MINOR ${CMAKE_CXX_COMPILER_VERSION}) set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}${_boost_COMPILER_VERSION_MINOR}") @@ -862,22 +863,8 @@ function(_Boost_COMPONENT_DEPENDENCIES component _ret) set(_Boost_TIMER_DEPENDENCIES chrono system) set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) - elseif(NOT Boost_VERSION VERSION_LESS 106900 AND Boost_VERSION VERSION_LESS 107000) - set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) - set(_Boost_COROUTINE_DEPENDENCIES context) - set(_Boost_FIBER_DEPENDENCIES context) - set(_Boost_IOSTREAMS_DEPENDENCIES regex) - set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) - set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) - set(_Boost_MPI_DEPENDENCIES serialization) - set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) - set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) - set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) - set(_Boost_TIMER_DEPENDENCIES chrono system) - set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) - set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) else() - if(NOT Boost_VERSION VERSION_LESS 107000) + if(NOT Boost_VERSION VERSION_LESS 106900) set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) set(_Boost_COROUTINE_DEPENDENCIES context) set(_Boost_FIBER_DEPENDENCIES context) @@ -892,7 +879,7 @@ function(_Boost_COMPONENT_DEPENDENCIES component _ret) set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) endif() - if(NOT Boost_VERSION VERSION_LESS 107100) + if(NOT Boost_VERSION VERSION_LESS 107000) message(WARNING "New Boost version may have incorrect or missing dependencies and imported targets") endif() endif() @@ -1140,7 +1127,7 @@ else() # _Boost_COMPONENT_HEADERS. See the instructions at the top of # _Boost_COMPONENT_DEPENDENCIES. set(_Boost_KNOWN_VERSIONS ${Boost_ADDITIONAL_VERSIONS} - "1.70.0" "1.70" "1.69.0" "1.69" + "1.69.0" "1.69" "1.68.0" "1.68" "1.67.0" "1.67" "1.66.0" "1.66" "1.65.1" "1.65.0" "1.65" "1.64.0" "1.64" "1.63.0" "1.63" "1.62.0" "1.62" "1.61.0" "1.61" "1.60.0" "1.60" "1.59.0" "1.59" "1.58.0" "1.58" "1.57.0" "1.57" "1.56.0" "1.56" "1.55.0" "1.55" @@ -1298,7 +1285,7 @@ if(NOT Boost_INCLUDE_DIR) list(APPEND _boost_INCLUDE_SEARCH_DIRS NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) else() if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") - foreach(ver ${_boost_TEST_VERSIONS}) + foreach(ver ${_Boost_KNOWN_VERSIONS}) string(REPLACE "." "_" ver "${ver}") list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS "C:/local/boost_${ver}") endforeach() @@ -1383,7 +1370,6 @@ if(Boost_INCLUDE_DIR) math(EXPR Boost_MAJOR_VERSION "${Boost_VERSION} / 100000") math(EXPR Boost_MINOR_VERSION "${Boost_VERSION} / 100 % 1000") math(EXPR Boost_SUBMINOR_VERSION "${Boost_VERSION} % 100") - set(Boost_VERSION_STRING "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") string(APPEND Boost_ERROR_REASON "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}\nBoost include path: ${Boost_INCLUDE_DIR}") @@ -1443,13 +1429,6 @@ if ( NOT Boost_NAMESPACE ) set(Boost_NAMESPACE "boost") endif() -if(Boost_DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_LIB_PREFIX = ${Boost_LIB_PREFIX}") - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " - "Boost_NAMESPACE = ${Boost_NAMESPACE}") -endif() - # ------------------------------------------------------------------------ # Suffix initialization and compiler suffix detection. # ------------------------------------------------------------------------ @@ -1625,7 +1604,7 @@ foreach(c DEBUG RELEASE) if( Boost_NO_SYSTEM_PATHS ) list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) else() - foreach(ver ${_boost_TEST_VERSIONS}) + foreach(ver ${_Boost_KNOWN_VERSIONS}) string(REPLACE "." "_" ver "${ver}") _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/local/boost_${ver}") endforeach() diff --git a/docker/ci/Dockerfile-clang b/docker/ci/Dockerfile-clang index 6d1ba36590..adf04df737 100644 --- a/docker/ci/Dockerfile-clang +++ b/docker/ci/Dockerfile-clang @@ -15,5 +15,10 @@ RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100 ENV BOOST_ROOT=/tmp/boost ADD util/build_prep/fetch_boost.sh fetch_boost.sh - RUN ./fetch_boost.sh + +# workaround to get a path that can be easily passed into cmake for +# BOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE +# see https://www.boost.org/doc/libs/1_70_0/doc/html/stacktrace/configuration_and_build.html#stacktrace.configuration_and_build.f3 + +RUN ln -s /usr/lib/gcc/x86_64-linux-gnu/5/include/backtrace.h /tmp/backtrace.h diff --git a/docker/ci/Dockerfile-clang-6 b/docker/ci/Dockerfile-clang-6 new file mode 100644 index 0000000000..8178aa7c8e --- /dev/null +++ b/docker/ci/Dockerfile-clang-6 @@ -0,0 +1,28 @@ +FROM nanocurrency/nano-env:base + +RUN apt-get update && apt-get install -yqq software-properties-common && \ + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main" && \ + apt-get update -qq && apt-get install -yqq \ + clang-6.0 lldb-6.0 libfuzzer-6.0-dev git + +ADD util/build_prep/fetch_rocksdb.sh fetch_rocksdb.sh +RUN ./fetch_rocksdb.sh + +ENV CXX=/usr/bin/clang++ +ENV CC=/usr/bin/clang +RUN ln -s /usr/bin/clang-6.0 /usr/bin/clang +RUN ln -s /usr/bin/clang++-6.0 /usr/bin/clang++ +RUN update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 +RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100 +ENV BOOST_ROOT=/tmp/boost + +ADD util/build_prep/fetch_boost.sh fetch_boost.sh + +RUN COMPILER=clang-6 ./fetch_boost.sh + +# workaround to get a path that can be easily passed into cmake for +# BOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE +# see https://www.boost.org/doc/libs/1_70_0/doc/html/stacktrace/configuration_and_build.html#stacktrace.configuration_and_build.f3 + +RUN ln -s /usr/lib/gcc/x86_64-linux-gnu/5/include/backtrace.h /tmp/backtrace.h diff --git a/docker/ci/Dockerfile-gcc b/docker/ci/Dockerfile-gcc index 9378cf3e67..8b588f2245 100644 --- a/docker/ci/Dockerfile-gcc +++ b/docker/ci/Dockerfile-gcc @@ -9,4 +9,4 @@ ENV BOOST_ROOT=/tmp/boost ADD util/build_prep/fetch_boost.sh fetch_boost.sh -RUN TRAVIS_COMPILER=gcc ./fetch_boost.sh +RUN COMPILER=gcc ./fetch_boost.sh diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile index 0248c2d0e5..7ecdf41860 100644 --- a/docker/node/Dockerfile +++ b/docker/node/Dockerfile @@ -9,7 +9,7 @@ RUN mkdir /tmp/build && \ cd /tmp/build && \ cmake /tmp/src -DCI_BUILD=${CI_BUILD} -DBOOST_ROOT=${BOOST_ROOT} -DACTIVE_NETWORK=nano_${NETWORK}_network \ -DNANO_ROCKSDB=ON -DNANO_POW_SERVER=ON -DROCKSDB_LIBRARIES=/tmp/rocksdb/lib/librocksdb.a \ - -DROCKSDB_INCLUDE_DIRS=/tmp/rocksdb/include && \ + -DROCKSDB_INCLUDE_DIRS=/tmp/rocksdb/include -DNANO_SHARED_BOOST=ON && \ make nano_node -j $(nproc) && \ make nano_rpc -j $(nproc) && \ make nano_pow_server -j $(nproc) && \ @@ -24,11 +24,14 @@ RUN groupadd --gid 1000 nanocurrency && \ COPY --from=0 /tmp/build/nano_node /usr/bin COPY --from=0 /tmp/build/nano_rpc /usr/bin COPY --from=0 /tmp/build/nano_pow_server /usr/bin +COPY --from=0 /tmp/src/api/ /usr/bin/api/ COPY --from=0 /etc/nano-network /etc COPY docker/node/entry.sh /usr/bin/entry.sh COPY docker/node/config /usr/share/nano/config +COPY --from=0 /tmp/boost/lib/* /usr/local/lib/ RUN chmod +x /usr/bin/entry.sh RUN ln -s /usr/bin/nano_node /usr/bin/rai_node +RUN ldconfig WORKDIR /root USER root diff --git a/docker/node/config/config-node.toml b/docker/node/config/config-node.toml index 09ea6f0907..31fa3f92a8 100644 --- a/docker/node/config/config-node.toml +++ b/docker/node/config/config-node.toml @@ -1,12 +1,15 @@ [node.websocket] -# WebSocket server bind address +# WebSocket server bind address. # type:string,ip address = "::ffff:0.0.0.0" +# Enable or disable WebSocket server. +# type:bool +enable = true [rpc] -# Enable or disable RPC +# Enable or disable RPC. # type:bool enable = true diff --git a/etc/gpg/clemahieu.asc b/etc/gpg/clemahieu.asc new file mode 100644 index 0000000000..306c2a04e2 --- /dev/null +++ b/etc/gpg/clemahieu.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEXZTdLxYJKwYBBAHaRw8BAQdAPXgGtAVcgz+RNJRvSgk1YrV5bzEYxG1QY8g6 +g1J/Hbu0JENvbGluIExlTWFoaWV1IDxjbGVtYWhpZXVAZ21haWwuY29tPoiQBBMW +CAA4FiEEOp9RXuwHACGmDlkdQ3CFIMjfuTgFAl2U3S8CGwMFCwkIBwIGFQoJCAsC +BBYCAwECHgECF4AACgkQQ3CFIMjfuTjO1wD/fD8d8f1Vv7HUiBc/rFDUZWDS4eYq +VvYxoTVows4otP4A/1DDpZs7xfM1uZjhgIZhUP1pMmLpDj5qmnK1w+9GGj4EuDgE +XZTdLxIKKwYBBAGXVQEFAQEHQM+vDx1fYPjMlF8aMTdJF7iTe+17VQWsQeEwDOG5 +qCISAwEIB4h4BBgWCAAgFiEEOp9RXuwHACGmDlkdQ3CFIMjfuTgFAl2U3S8CGwwA +CgkQQ3CFIMjfuTgi+QEA/Nnz32RFBCzErI7WwvsEVL6NJCihqEiRiKG7FvaDd20A +/R87rAQzAkyuCekif5eCQ7V6HD1uBTSTDCIOKM/QDcoA +=A+a5 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/etc/gpg/guilhermelawless.asc b/etc/gpg/guilhermelawless.asc new file mode 100644 index 0000000000..7147b9ff7f --- /dev/null +++ b/etc/gpg/guilhermelawless.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBF1MQt4BDADehEnB9zwynIoiUYI+JK/e3PQLEP5ORNpVysu5ooFGpyDjisQ6 +9KQntJ80sCxVkoZFo0gKqyqfMyIPVT+fLbx6aprWgB4gOIi7cUTKINODsg2Pi/ml +ZbQV7HCtBsy+YiCE6Ts3I7PReIooD0Dz7z+/qaEmo+6MAPyYjrkPQkYSfRSaMOT6 +op4BjthN3k4R0wVddBEtjB67PdZrJCK/RfaYL9nfwUw8EZ1XSs3Cl8ewR9NeW76h +OGtMhZ775ArbLsL26Ixfra5whFKaRQKbqaCyPE2/ZPAI6PPL4uo+aHS2qGhhpexT +k7lHowtr8VfJ5WY+h0eF6r2ySp0J8A+v1z7XdGH5vVMWSny4wlclu9IpxKZH4Eag +9YaENRvvCvY0m263PDY+1nShZuIYXCrP0PrbPexhoDYVd4hgxdktA1C85oGF5xMo +LaNfkV4wXMB9ks/bvAk5CyI5U7H0Uz9LyDuUaP6hIh2twizMAduN6QTtTg3wlP14 +pjTHCK0MOuxpPvcAEQEAAbQ3R3VpbGhlcm1lIExhd2xlc3MgKEd1aWxoZXJtZSBO +YW5vKSA8Z3VpbGhlcm1lQG5hbm8ub3JnPokB1AQTAQoAPhYhBDWj4SVgiq4nSocv +l+jE32CRo/k9BQJdTELeAhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA +AAoJEOjE32CRo/k9HBsMAKH0dslihViSZaeMQvmpCHscR0zKAgz1PV3dBWYsj3aU +OXLDf2xZBPM4XQcszOCzyGbeoQCV/JstYqHmWvHFN+UwxYxjiAe3LRzD7W/jL2UB +NQMnNOS91tGDNoPSULAxAm933LXoqEW2VSGKYssamr0xRsTdrayVslEuQ7paBqQ9 +Yp2GEwh9enbvsChuNGsHSmFW69YQvr4RSh47VJDOpxAfpCbye/RfxlxS1VQOXQTU +cdDDVmtT2fGImeg2Pue3dI3KA41uABRb6EFt4iISahJaWSgaro/IsxIUQyjAH8Vc +0xu8y11HEeQ4DpVYxXYIanp6kwbNdPATbQ9lDbDwcBpq5gL336snrWSGXBsxILcx +o8uaf0qEOmACX+pF2Iw2DCsWKvzE3sR/FjP6ytu1Wq8wGR78UUj8sR3pEwoBGsrU +MI+9QSjz4Lv7fxy1V6sDpgMUFIST9hCjcYoykfG6SUMSoYP3CANxh5/MIQuU4P9s +hXmLn9rsPaVV9e1SgUYS8LkBjQRdTELeAQwArOuHu1iBCGGfJWice/ivzmESKYGk +NBG0ZNvCRATuc/sf5kQSKSGoO6Le5lwTlvFj537X8cAngPr9V4sZ/wm2i6yEm7p9 +hogYKwpnTAROc0u9u/nUCUGPUtXnUtJNlEToovCJ2a4W5AFG3T/ERh2mx4aZrwG4 +OpcgcH8eDA17kmYmdhhBIdplWuT3G0EltWtD2M8Elkb0dBUS/PXms41x3QoEbR0u +G4Xt+WOMY9c42NT1aeYmzdlplAFiXCHWJjasZdGPwe8FfQx4FHlBzZWfi3wkrOtV +SDqhsf/DR8T9/csJZPbQocrYBaM1yo6EjRAJpohWTp3WP8Wo0zXy0hIHp0FiDBF+ +6Gbv/WOlkN5/O3Crl8pxkVaoWdWtCzW4xqFkzUWVkpaWaDnrbRbizYNPnhUn+vH1 +jy3NkG+5LJ2yxjlDzWNccJeFXO0McfYJ/Q2sfLmqc19upxNPFv7Ybmurd/uNrAD6 +0C14OShLNcLCax6LtlBiqZxdNqg5wMM84hntABEBAAGJAbwEGAEKACYWIQQ1o+El +YIquJ0qHL5foxN9gkaP5PQUCXUxC3gIbDAUJA8JnAAAKCRDoxN9gkaP5PfwGC/9W ++DWFp2ME+j9sqnNM6m0j96993zMbsKc9cQlX+Os+7nU2w+rF1YKbzSA9gKNfjrbX +p0lKdWvmG2WT+QMX00CF/EkR8kaAwOnUs7TbgJaVcRsXuqHAguXGs1tZn6G0Km4u +FLmlcpuP8h7goe8sLFdWpBKOs2hBEbZBNfNWnR+wYFGhyR/0AzMx2lo1CofkPgbO +4HLTTkpoQizLcbIuX9O/0xQ1eAP8KRbqTvSfRvAjeeCrfEbiTkvuGhz4pfaqrT3j +ooZVoFzp2J1qhY4bRSJuo10Lzaq2BZcfepVSI2rDb/5xL3wraTMhLUUP2wRFeRqr +TxQBOg3rkZNEUKdVZTxXYM6S3U3MuYFl8uOafm1r6HX7R1kRbz+gZcIC0G0cHzFo +tSCz0qsC5g13MlgoXgKDjcyv2ZHgR4fKjG6mC0kI+KBz5IIcU3vlLO2tTECdIofm +mTAvEnZtx/v11QYqZHS0PIRVixQIE0npiQ72IxQPgLCc1XrhmO0lYCDbrPKEQls= +=5J4z +-----END PGP PUBLIC KEY BLOCK----- diff --git a/etc/gpg/sergiysw.asc b/etc/gpg/sergiysw.asc new file mode 100644 index 0000000000..68a0173337 --- /dev/null +++ b/etc/gpg/sergiysw.asc @@ -0,0 +1,74 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFzoaUQBEACdKUeq/AOtv0vC/7HF0bAxQ71fm5FwEdTySudEJqXxaynhoh2x +mMc1/53JYbzUyfQ7xkfLBQdVgvyniRuVtkYzEvddBGwryoY4LO4rgas9C2xtGL4Y +1pf3v9roSvOvYzNF8dh2k2+qCJDhonlsmCvjf5S8/QxcfAvG0bpsOlFvY/dB+Yqv +sNZjq0MDGILhAhJQO82AaEBeGMFFg05lPLglX89AOj2vf8AT8KfinfYEzM+ZNMaD +GnSs/g1ceU9w3b2tox1wQMYNuPirTvdfcKaDuC5uCcub3EmvbvBkMbqkzJLV3gFv +6PXBCmCUoffyaShj/zD5LeSsLGQI966q9guNIsxR2TP+RZFXn049RbYmsBFyeJ2y +aShL63hczOP83/TBXgvnSLNFOnJXIdY4j1o/DtB5VGxnCEdXwepZca4h90QQde5a +SvhgvyWkfoTqNibtcgTzegvmFJsOzHQ/7103fro41TxO8W1jKG0mjvkSSdkzDuUp +vLU3i0Dz1lsH18rePKlKE4OSzx4QLJP3ud9+QXS+oF8HuOcWErnrp5eO92Zjktdg +wMYAdmSXhm/DRUzF6aZTOeBHvfg3nED+EMdxLjlRjpZTQMbWGfddcq6T7qqEZG+7 +rGh1P1xeNVPEjDaki85SFEERuJV1rnK85cqOwNVcAtu7pAMzt94vw93YIwARAQAB +tCRTZXJnZXkgS3Jvc2huaW4gPHNlcmdpeXN3QGdtYWlsLmNvbT6JAk4EEwEIADgW +IQQLEnt1ZzHGyIGIAtpz9StPL53ofAUCXVbk0QIbAwULCQgHAgYVCgkICwIEFgID +AQIeAQIXgAAKCRBz9StPL53ofDGnD/94cArzDjJhLGkHhRS+9iDME85JehFIXBSZ +BqDYOpYbTrSZBUSU/xbaEua6K7MXUisowmor2iU212kCf0maa8Y4MR6yB85QiG9V +1bD4fHIEANYTE+nfdPbE2xzcAzdcL1K+b/uBovXovAkfZKHVSDOYYjnHYFbEwPx2 +XqviSiJl8Ga9pwcZznbe2THQ5vO5zaff8gSHgMO8wtZ4nAHGKOg6p2XR/IY3rJB4 +IAJFXwMHclcMhL0ndu67Qls3XnClHYLgbRvYCsuSN+4vSk1oEYpyxENMvgeLEzki +ZLgs74QLWpKL+o45LsBuz0clLrrYxH1L1Zkkn8/0DNToDeu+phxmvXJJNNdaCzXE +7iuvMyMDIAaP7nwaIANp6StTm6YAi0WuU4FjHTPlRB2vMlDFRTlZB8r/tXsz333J +pcJDWKwKVInL6uzZqJzwlgl+TX/4Dok9UbfUA29fqBuvTCqu6OYNvCDt2JDzYSJf +UmZynF3qwr1AjahgaAbaLYsqBjpFgKy2yz+ty5HBZYF7/Fg8VPUGnyxGLCLniCF8 +WEpVjvZFEEoYF11EW9QARmhw84+1Eu16dzVW4+Ccob9Fg9k3yCpnQUs2cXg7/gah +8lLXZBrnxAx/55sVZciMqWfe/8Ym4/MuaGxFgvuOYmiL57Tk8xIhU8MppSDzATnm +1b8ToXCWh7kCDQRc6GlEARAAuIa1i/aPQsH+aLQpnAuYerWF/OeqCBiS3XdIrS5p +1dzNJSNtn6GBVJNOFnD44gJLCq5/ETdfmyCLDx7v9uM4LJHRd0PW0GsJT5a0jXPd +PYsIPVxrq1aEdzmKi69vAeADQeTVmHyn4gxPwWQVKzsoxCDxt6L8wb2AYyzaVdBD +YVB0bB2p4wLV2USHGGZbtI9MyMLDTFOPhnjVJRK+4nO0ZOraw4bvLMjfjY6yI1fn +Wf3Yq3K4mTOuFmJw6buqjHxvF/Mmwx74p+j+C3Vz7P1oZ5+ADYDDQK2u9E0by81U +QChK5zd6+IcH9KcGX3ij+Mk1L9Y6s089k0vSL/27MAZ8DvXqoP4n5UalV72IlEos +QZlrCu4weqfxyrWkL4p5moxedDa7OCNf2G2IW5c2kDkP5+3SdYU6ibdP9E50s3Vc +WWSja8/raldkPGJ4EeoDMmVvr4QlrNZuzXi3dtyK+8imO9XYSMsOa6GOVU60XRIN +M8uBy9W8dypEj09YG6PvSBbfu1Sp2FAMRbcFf1wU6nWBHY8EOos/8+1pHNNHqArU +qGt+toDq3iDWpxTZUDQpR9p7Zf06VpyrQz7bkxinc6sp8HSxap/3c+u1mxA6m1JX +QMAMZVnusptCqiyQwIRELa/6Kbz3YFQ+c1lq7vHz2n9IvIjF2f8npOmJOLjxZM9K +E1EAEQEAAYkCNgQYAQgAIBYhBAsSe3VnMcbIgYgC2nP1K08vneh8BQJc6GlEAhsM +AAoJEHP1K08vneh8ZNIQAJPJod76BW1tULC41QanhO7//WUFrCjN+v7cC2gBgmr/ +AMiXkYwSNAOYxRUd4rrMHifPJSb/NQIfuTLKSU3E0B8BBXEQq6DiJHaIETkBV41A +VlJmOky7CJiZUSpOaa0bQRC1o7WxRGnqNRl3tm6UCRKtdIshjNRGjODcQRM6Yarm +rhWk0+GRyHiG+/g14l77CoRmrCcsmbdSbaXNxFqx+ho3Bj7/UF1ZtCSg3VXpw4Ni +uzmu4gsqrfN40JA7hk35bzcVA8f4o/Drup7Vfemj7n/zgH5YFiceQORCRI1AS+H3 +cvCHK1FAPatbckdCCGav6C7f1B5CHDqkcuoxlFAPzlCOpB34rpU/jcdKUd4xMq/v +1zf4y5kyrM8RiS3yNN+T6X6P0ryiBon/l924ogC9f9MSyQcKAeV7NeJmSAOCb/aC +UhAllsJYbQFEp30vh8Gn33O9vpzXeN3KtMVoKTDfg2/WbxhKHko/GdWe+GYaCVzs +8Z8U0oIFb/yxaQNChruWyN5NDRhR8Z0NXM8VOgit1YS94Z+r0Y0+UakgdXPXqfRL +U8rYGEH5Wtn+Yh5VyRgzqTQOOWNEgIfa+us4VBL5i7xXHJ/BmK8NsxloCIQTL0zH +13QY1qjfP06MpuE+A5+Usr7TkZpXm6nLTPPKDYorz9kkUH55Q34jFF41OB9lhshF +uQINBFzoak0BEAC5tUhuSATwwpDT+CMnPgM8xP52QWb6FxjPeHf1acZmHH1OKXA5 +el+obmUGbgZBDJJqxRmaYm5TcmsrKw0ruhhhMaq541eDCpacrNft6vtruhmWVpnX +GV6t80Z9EY/T7I2UmLbDdr7HvkHE0O+BZCSGZ7fxCSSH2czH8+g0VmEci1YojbBB +AWNqhfFXVXst8XghqeEStohx0l8hAVsd/5m8fPTFprbCoDjg1kUFYCO9owgLkxh7 ++Y3zJtNcuwvuEDrmxmidsErR5QjWt4u/mMI+sr7tTvzkJUnrv3uZXCTW24UYu89C +KPIj7fWRkFxqgC7VqnNGuhBqmrHatI3EJ+kRtRhtSsdwXWL8BCz+AftLbZpwAvvn +SsYShUfGXfqsr5N+zFuKGalf9ZfcVMnv/Zy0KppTDIecdkTxQfmvYz9dZU3hvoR3 +Ctmp9vHKJWR684VZ5CdVg+1xJQbpLFgc9JwkrVYWd2b0Hw+oafEm1MO84f6cF0EO +oxyfq9pTaAou8Wbwgi1VaaRvqV6wHf0zlqE0T5er8RdpiylJRuCHDM3hEBCMYXek +kuWh6DWeTX0TCaMeFdoBG32pZzSUZZ+bTprkkrMqnRdvsjlSK1OOBbXfBrx9hYbR +cOkvsGEcaonzYals53oskQLozoywdWxITgUPbzXjdSSG2cTTs4+3Er6qtwARAQAB +iQI2BBgBCAAgFiEECxJ7dWcxxsiBiALac/UrTy+d6HwFAlzoak0CGyAACgkQc/Ur +Ty+d6HynGg//WqBlcKYi/0j0ILn+xJPWi2nNEvouVuN8/zJa6V+sxUT3njD0LmUg +yIo/3gx8KVKhLUb2VyBPz8NhasA+wzcaYPn2ozoNK3l/WSmWAQGv2HaPaoSJCPlX +LQiPuigwXEpgkgCYSmE0FZenm+K+RaKyaWL5Jh9C4CQoZZxIRc2AwqbLYpu00Qbv +sh7y2wse/CW3PwODJsfFElgX6i9jQ84KJTFrvE1VRYTfH02HwhDRGLUQFgVZq4k7 +Ik2XseIHz/AK5pSqZeKaSH6uCAFjYMPs3lXlQR/g809TMmNNYqcbQWwwrrBdxS16 +CfoKXIpO4XOCejkezfS3meKr3+mILsVNrQe8+ckdUTmsHyeZln0ovP8+dr5SoAJl +QXOlRyj5UGQqlweiDRwMNaXrbO54Y7jl+suDrBs8YgDqpoTTU23IboYKuv4e9c94 +mKi0l8lRd9YGDcRE7sjR/9uU3mAbCFpj/j0f/Z0QuovdkfJf2qCyOc5ucKNWK1hE +hFWrrZnwDoVe37aAGsIDRCKj4j32RTvuVhgLUP4W5fbhLRBdzDvU51yefW3H7xmh +BvJg7gqwi5NfFPGNJYw/9e5vCjOJ+T5g2pz586pTQ//7C93DCu531hZkutLWH6U1 +2N3z+MD1QzOIDm2XvhQ9P+2qHM8vSAsVw4dptyS19+2OVGdwRjsRkw8= +=tJbg +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/etc/gpg/wezrule.asc b/etc/gpg/wezrule.asc new file mode 100644 index 0000000000..9e2b1af2e2 --- /dev/null +++ b/etc/gpg/wezrule.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBF1XEZIBEADItYo8/8hvfrtNxPl4oEjC0GrTIw4zrIvoPskS3BHoy3VCzJV/ +CCojg9rmeUmInF8h0dbRcvXpQ11DZ/aZx5TgqdlN8yNLqxfG9N/X1y0MWSDKn1eb +pZpXn4cMRzpKtUGGsCVo1RdhL80ciZvM1eigcdhiKGZi9+tntNnVk+EYOMBGn2CF +Y5VqHR2GuMcSozB6+27tn1gnaQqO3Ldni5Q6ZfG/sZhQOq+OkujN5vekz2LqyyA2 +Lulg4VcCAjEDbsYYhgcY78SVUc2OLUPhXsD4cLMJ2UjiIoOeTjIu5UF60bNMF4kD +6SOlJk34qEhyolbo3KZjfNUQmDD4uJ0FoPvLryylrlyoBtQTxqP4btXzG3SSmFCt +VQMEQgTYUvJzfMbVgjD/9Tpr56Ai54dip8ggmuJ9sHcytc5Rn7Qrl6I8qcCMMAL4 +Kr5euTy2zbYNhGPhshkJ2/1rr/Sl2rhD+MG0ZNmvVSwnXdY8NDzyjfwbnOjdLEuv +fuIMMH8NlGz/guIG4Qjz/0pNvId6q202jXIEHTgX7eH6rYGVLLrZ5L3axkKkNV3f +KRuEj9tAcZeA/aiqdU65rs9r4vb64WMmQtF3MkSZzH1+igcpndD28iP6Xqg2iZ2Z +l6MjFF+G/WsSL1W7X5FlZt9X/9ai/0eDCAru2nV+6LBJi67rp0UGA2m/XQARAQAB +tClXZXNsZXkgU2hpbGxpbmdmb3JkIDx3ZXpydWxlQGhvdG1haWwuY29tPokCTgQT +AQgAOBYhBG3yGb7loSHgNgNjIft32wAoOc9cBQJdVxGSAhsDBQsJCAcCBhUKCQgL +AgQWAgMBAh4BAheAAAoJEPt32wAoOc9caKoQAMbGdteLj9mJ6d0m1ValUxHca7tc +seBojG4pfIKmYa8wIoDSQRHqpZZKvSaSTUA8EdL8dDvqQHEFtUWgNzX9LjNhjJSy +kXvK+CdeBXB+RsiVNQ4qGt6xa6BHmnCYiC+mwURkeJRlELegRa0j4e4VFJlOM/BI +y+K21a11A1ZK7iEgbuWC/1GVFUR2jaOzFE64TPZUBZmS5LOephvTpCp296l2vlLj +5Pg1vo7ILSWQ4S5gw/bGBRqz0nSaLdfJ66i5xM6vmCtPIvEpuPMyLfnPqypTMa2H +8ciA7vxrhVLJYmQRu75iiXQ00O3EGv+ymtjVKy4zxxnyCdoqLa6rf8rYWIvtqXyp +rYr/toxNVtbRDsGGPSS27VgnRnFgkbfueIkaFOG0uBb0JHrxWPlmBeCs7KNNMU2f +Av+2zvY7stJWrXJ8Y/vJuhjdqE6AgHziJLPgw0GJsQ94vtIOmERCM08gHc0m4tDq +JamVvDVHeLQeAsTOzBf3oek1kFcCctb2IPL14YQQXB2mEhb49XX0U9dzcjptdAEy +I0VY55bDT2EDGfFIlzGGZ+ZTX2peJU5ChiswgEt2KanDXXizUknoRsEHZdb4LJKC +ZfHze/1rnMOx9SAl2sElrEwdoMgW4K4YC+FasJhqG7MBuwGrGYplan4jPFfk8TpG +J3qzbDkMQGMYm3bTuQINBF1XEZIBEADHpMXbJrzTKaPxzd1sJ14T0lSgPnN6sfBY +NEL3WfsyHq2GVGms4uQhZZSpc4X+L3RoPFFitbymnqRD2QbaqDZCf5oQ2NM9vJiz +95NJDmWT2MOvQN3ZNkPDCDOGpen7JE9PQ9brzdgjRZCT4Vb0nL6AhudsLSVKb4il +w+pcbPz2uZ6H/R0Zibt9AbAvnzS9nfFayxDc0jQGZjieelJJTWm3sl9JnYFhVckH +J5Q0ftERGyzyxtV59bw8WrmjIjLZShI16QlUzkIATL5X+tVgnqH5mCPKvA8zslMa +F+XZPy3xth/fSn/dAtklUJzZss0j+G3CFXHM+MkxxiHdKVQ1AX/h7VnbtY+YTIQH +HlcHXfmm1TYoAFrX16O170Ir3o6Cavma1OxC9QTsAcIMRj+qyiInrxPe40UUEDCH +ZGCwk2RRbP+o9MLMq4VkyqImYcXvQGvSRwsL6Mi/OYydl1qA3qhl6XNu7dtYTdwf +M7BZmlJUHquSYzz+0iWur3Xzs+MdE3v29sw9szXbGRbCyBb7QHwryVDs9w/3wdng +5WHVPEseYpRhFR1fLkutSi7uBQ+0NkXZDLza9N4lmeePlVbTFTSsPJ/tLnLKNQAW +MRAwcUV2BWcdVTGRWnM2QtY8iN+wghNFN5VTm8iO3MS2Vx/Ksuy46+mDWGhyEKgs +ueqvbebuXwARAQABiQI2BBgBCAAgFiEEbfIZvuWhIeA2A2Mh+3fbACg5z1wFAl1X +EZICGwwACgkQ+3fbACg5z1wFAQ/9Fk95/XbfKxy8yYa3ldkuVpPk/eZJ1JXqd+3K +W8VfWFIBEPaxU/DMYeulS2LN6JnOrR1q0oJUk7QThPfNCUFB2rK9LYZx1wLV1RUO +MqFNqEmgmF7Gu5PDCzcJdMy+wtoWg7WQSUyRjF7a6mFNcVzhuUyhLsPxKJM8FjeX +tOaz63C8vqglFRtNoXYTz5cUPCO8xB24hd963TaS6iuP862EpHbkR/T72nwXHUO0 ++tAHDVaYrbKcNUVy+834efzEhX9j7LSoWCGIdPpYSEkb3EyTDUY/ZqPYUSyL6QD4 +eR8sGz8oZFalg4LlFjM8qKr4tcu7WSoc8U9F7/KFSaH6iHXWv2Zd7rUs4nzBUfee +Fm3Js5/exQou5BOHdnvzQ2XSPYM2I4WWZO30MSh5Cc3ajKrMm4Ucik1tFHjXb/3N +sqJs1JMUMqCX+ph12GQhA5JOEtjnr//s8LHR1Q5zNJPr94HbHfFLq8kYCAkqDeZF +WnxvScDL/RQvzZ3j0H+cFR0AjcXdLBzQRPkVMa+BZ53lbVgP9OLhUv0466KR8FOo +dCau1AkDaMA8viSEkry4fk287z+bKcKXIxbXg/C9Hc5MlEg9/SCbJNGE2OjyDZF4 +vcODykBBOKF25drMk+bhtaejGxPrMEl2bIpmPZPZJyhnIvbNnh4gBMN9lUUjIwK4 ++Ts8oK4= +=vzqx +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/etc/gpg/zhyatt.asc b/etc/gpg/zhyatt.asc new file mode 100644 index 0000000000..0c2f497efd --- /dev/null +++ b/etc/gpg/zhyatt.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBF6LUisBEACl2tE6yV1Nfvxq3sCBUNmntHCYZ+itf625O04zTm9AxzczyZJX +yLQDDCIHLz8qgG0QSSSD5vUlnuurOHtXFmC/5+vbZsF1F/vDDCzbA/7Wqft5uUOM +F9GKsZhRYXHueffh6SDQZ8aQMV7bXjC+lvvePry9LW1JYvI+E3Wj9QoC29O6mU/w +1Ty89XF69tZrrSDVEY1o016vKTTbkKgRh1CmuQZxyMFsZz9h+bmxWv6bcPFpH6Cs +Q2gY0HtHJnZJUNHQ+8ZZOOxO+cSPs+48tvs6RLib3hrHF+95cXaCsgWYtJ6ErixK +4WsU5BEwS+moRnJN9uH1Zjzz0Z33Swag65HPAosrM8FGOV/nx7EBjehSc10URWt5 +cNkCERbmxxL+41KxlfqL7AafrxPKGT+i2qhqTcTMuiMf+AxFP0AkKKc6jtdunwGF +TrGtZ06kc+tcF20PZ8lnpw/Fzm2o5o5yeaEGoCRVLewRR8EHLvBsJIaM76UTTaui +A2+TVTiXsOPvuB5g8m64Jp2zMR5JsjB6afszawIvqLRXEnE0lIoaStjKVmZLLt2p +H7aftxJIO7M+k0uhi7eP35iu0a1BzuE8KIdFSlzZMhbEdAKGsNuqHfswi1cAoWz7 +ZmZVcNI94zQ14fEkF7o9m5EM0ngUuNt3cqu/q6wYVuHgm6JELL4GYFO/IwARAQAB +tBpaYWNoIEh5YXR0IDx6YWNoQG5hbm8ub3JnPokCVAQTAQgAPhYhBOvutE+e032G +7sQ4/21pVo0MgE6tBQJei1IrAhsDBQkB4TOABQsJCAcCBhUKCQgLAgQWAgMBAh4B +AheAAAoJEG1pVo0MgE6t6T8P/3DSoBILLdm59VIIHsoKk4USY9aAQvmPPpYSZK2z +OJDvPPW5bHZe0/d9v+orFTMGBMnzDFKq4TSJThJ8alX66kvijpYrVclkc50wY4vP +TXAPudvpXjTc8miuHq3aNMka3SoRy4rAVCiOJ4RAnVpRxFJ5e9Tv2Dzojc0E7VSv +q7bylas3gBfPBpPWoQPzSKSdidvpB8r5hBdTMDrKDLmdvcD6NLAymzpdmjFfUoW7 +Bo8/y4pj6KneHSb4pF/IpmQeZf8hGXZpp6qh45KXhRaoOtNGRbzg8/emCUPzl4tE +SO7CwHf1utXg2jpdsyl7YyMdDtRrM9lo4ssu2GkmPldZ3+2O0oQssxWD4KK5Ib63 +9iIEGT/lxIK8MmZeRI5FhzSBlCEn2qADrKvwOEd5rJgbZPDr6e2fqvsJfMh8E+Z7 +ES27qWVGt8WOYLMEws7RKnoK9OoIUQTiFQk5OjooCIK+0jNQWICuXkL1/UAbDqAE +fob78e3iuLu1TgKAfJUOUPr4i7ySEqQyyjcgIIVINJSmRNF/RjeHDCQ9xL88FxIN +FGj35uwN7sbuHoFTFjYGV2yUzpyaYj/S3jfMseP+R5yi4fwXsJISEKvahaDkLG94 +4X6SGxblssutA5LnFgN1Q//hRTGQ82dTgSdZKOt51JJnoP+MsPJn8SutvkuSOobB +fFt6uQINBF6LUisBEAC4awv+bdoK3EfPpoPddrNnq34kqxtgg0thX/ig/09jVa5t +Wy2LOqr7GdO7Cz+SKnVn/uZwzTGwco5vUJt5+yRKFDiZyiG5kOOaHEsx39nmGgbU +6LPo0c2ByVg1beBdqA1opsvwmwV+vCzAvnzXL5wDHmg6H7pX9CwInkDUFwAwrhqY +c0SYHTooEPaHnpH67y8Du4P0jkO1vQAfEMNy+UpzI1NLP8tRfKcn9VZZmlEJ2pyV +oidmuxy2aZTTtGqbDSiq9pbCBpgadFPC7A+Yxq9m/b7y+Ff3L5r8MoTnZCV9QPs8 +ctZSKMlRO55cs4CJj7eIb3/pOamjUPE2p3b6epcBqkQYdQqUyAqxoxGkKjUb5JKL +QcRmTa53vtcHn7ajWqreztv83ckinCBS6cAi9xK3a+MuOaDYP8U4E8ppaTH6Uy6p +Foeh2oXqElzkuaMKJOfMRThHUBEIyRwaauB41Xv5xbQudZq93oS66V7UR3GplA4y +zhQ0+bBQbQyvLBs7OpgCrQUZHcFSZEiLyZgTWGocouJDQWW4cfQtGY6TPPtFJ1oS +671Xdy0hfA9SMPYXsaMRx1WmSOr66Z0kExb1HyU2VDrtpjuaFKyYcWcL+VCg9HMW +MAwP/YDaTQJFNJjEkjnqfVVkFA5VHPQLnLibsB16EOa1N4sKOAiAL7hpGp7xVwAR +AQABiQI8BBgBCAAmFiEE6+60T57TfYbuxDj/bWlWjQyATq0FAl6LUisCGwwFCQHh +M4AACgkQbWlWjQyATq2++g/+NgoKtfCXQYYqgfXB8u3egfK4yDQuYi6U5nh05kkP +V/x0yDA33I2zooNQDZpwJ3ZH4o3l8clYpNgZ2ccYpfdAO815O931C/tQBJZqkWfP +JGrG/COgRpBdz0dJk+xVbCqqdPAfG8RjO3sx4oBTP1DuaWWA4EUJ4Pg4C7BOnaKI +43oHrNlt/RIRjR7Lz9IELseK+jnKjrVzsN9daKUKSWk3VDnIKckdXgvqAGT7KHou +sXRQxAnrHacXF6bLuAyk6egQ2ScQUYzvguE/rmSLXDNzQSzm14Vhx9gTR9gbG3KZ +zgTTyPTmunRvE0xkQwNTgUDB3LgIoa/LEUPLp28Pmyli5tFlZvRvTw+GA2aBM0hQ ++e39bsYTlQPwnMNvJTrjoOUAoBw6wqC9e6+sN7JiOdna6h2cdrfW3Feb7AxMtLZD +/oNe0l0QFAfwhfJxW9IAglPM0hNsdBYZrv6cg9UOxWv2Pfe/PkYkJ77ReBxXgBmT +hetLu0qSL/cDRe0W0JgZPIO468vUEX5ugR2R8HalPbuXymaEDvyomlVp+tbiCoI0 +gr7x0BPj13eU2Okp7Rj6IiM0+5pbi2HSYnyswWQS4m7gZPQjpYYmYHGgdq9yxd5+ +QwzeJek5Xgd7I5NDg6yqDPl7z6HayczkwF6md+rc7R5CRTszt03gbBdSMU3e23gN +iM0= +=dvXO +-----END PGP PUBLIC KEY BLOCK----- diff --git a/flatbuffers b/flatbuffers new file mode 160000 index 0000000000..3b458f7a17 --- /dev/null +++ b/flatbuffers @@ -0,0 +1 @@ +Subproject commit 3b458f7a170154ed4c4a3a2a9f6116fb2d415ad5 diff --git a/nano-pow-server b/nano-pow-server index 32e7826865..00591aeae9 160000 --- a/nano-pow-server +++ b/nano-pow-server @@ -1 +1 @@ -Subproject commit 32e7826865ce4af069f79eceeefe2a1092e28b53 +Subproject commit 00591aeae94bc52d2f087bcb1bf255396f59b42f diff --git a/nano/boost/asio.hpp b/nano/boost/asio.hpp deleted file mode 100644 index fc9ba6cdad..0000000000 --- a/nano/boost/asio.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#ifdef _WIN32 -#pragma warning(push) -#pragma warning(disable : 4191) -#pragma warning(disable : 4242) -#endif - -#include - -#ifdef _WIN32 -#pragma warning(pop) -#endif diff --git a/nano/boost/asio/basic_stream_socket.hpp b/nano/boost/asio/basic_stream_socket.hpp new file mode 100644 index 0000000000..51cc6dee5a --- /dev/null +++ b/nano/boost/asio/basic_stream_socket.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/bind_executor.hpp b/nano/boost/asio/bind_executor.hpp new file mode 100644 index 0000000000..0dd56f67d9 --- /dev/null +++ b/nano/boost/asio/bind_executor.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/buffer.hpp b/nano/boost/asio/buffer.hpp new file mode 100644 index 0000000000..3d6809a6ee --- /dev/null +++ b/nano/boost/asio/buffer.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/connect.hpp b/nano/boost/asio/connect.hpp new file mode 100644 index 0000000000..03933794f8 --- /dev/null +++ b/nano/boost/asio/connect.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/deadline_timer.hpp b/nano/boost/asio/deadline_timer.hpp new file mode 100644 index 0000000000..8112d4b36e --- /dev/null +++ b/nano/boost/asio/deadline_timer.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/dispatch.hpp b/nano/boost/asio/dispatch.hpp new file mode 100644 index 0000000000..3336b7d05c --- /dev/null +++ b/nano/boost/asio/dispatch.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/executor_work_guard.hpp b/nano/boost/asio/executor_work_guard.hpp new file mode 100644 index 0000000000..261693e67b --- /dev/null +++ b/nano/boost/asio/executor_work_guard.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/io_context.hpp b/nano/boost/asio/io_context.hpp new file mode 100644 index 0000000000..85ce31560c --- /dev/null +++ b/nano/boost/asio/io_context.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/ip/address.hpp b/nano/boost/asio/ip/address.hpp new file mode 100644 index 0000000000..3fed1bf8f0 --- /dev/null +++ b/nano/boost/asio/ip/address.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/ip/address_v6.hpp b/nano/boost/asio/ip/address_v6.hpp new file mode 100644 index 0000000000..6b1cd46a3c --- /dev/null +++ b/nano/boost/asio/ip/address_v6.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/ip/tcp.hpp b/nano/boost/asio/ip/tcp.hpp new file mode 100644 index 0000000000..e1f2d1619b --- /dev/null +++ b/nano/boost/asio/ip/tcp.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/ip/udp.hpp b/nano/boost/asio/ip/udp.hpp new file mode 100644 index 0000000000..1efc97c57f --- /dev/null +++ b/nano/boost/asio/ip/udp.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/local/stream_protocol.hpp b/nano/boost/asio/local/stream_protocol.hpp new file mode 100644 index 0000000000..31ca4ebedd --- /dev/null +++ b/nano/boost/asio/local/stream_protocol.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/post.hpp b/nano/boost/asio/post.hpp new file mode 100644 index 0000000000..80af5ef56f --- /dev/null +++ b/nano/boost/asio/post.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/read.hpp b/nano/boost/asio/read.hpp new file mode 100644 index 0000000000..e029b904fc --- /dev/null +++ b/nano/boost/asio/read.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/strand.hpp b/nano/boost/asio/strand.hpp new file mode 100644 index 0000000000..12ca02001a --- /dev/null +++ b/nano/boost/asio/strand.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/thread_pool.hpp b/nano/boost/asio/thread_pool.hpp new file mode 100644 index 0000000000..0b808cb3de --- /dev/null +++ b/nano/boost/asio/thread_pool.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/asio/write.hpp b/nano/boost/asio/write.hpp new file mode 100644 index 0000000000..48a83ffc6b --- /dev/null +++ b/nano/boost/asio/write.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_ASIO_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast.hpp b/nano/boost/beast.hpp deleted file mode 100644 index 462b1558da..0000000000 --- a/nano/boost/beast.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#ifdef _WIN32 -#pragma warning(push) -#pragma warning(disable : 4191) -#pragma warning(disable : 4242) -#endif - -#include - -#ifdef _WIN32 -#pragma warning(pop) -#endif diff --git a/nano/boost/beast/core.hpp b/nano/boost/beast/core.hpp new file mode 100644 index 0000000000..8bee46e2ea --- /dev/null +++ b/nano/boost/beast/core.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/core/flat_buffer.hpp b/nano/boost/beast/core/flat_buffer.hpp new file mode 100644 index 0000000000..1ec8513fe8 --- /dev/null +++ b/nano/boost/beast/core/flat_buffer.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/http.hpp b/nano/boost/beast/http.hpp new file mode 100644 index 0000000000..fdb5413c19 --- /dev/null +++ b/nano/boost/beast/http.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/http/message.hpp b/nano/boost/beast/http/message.hpp new file mode 100644 index 0000000000..ce2a336364 --- /dev/null +++ b/nano/boost/beast/http/message.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/http/string_body.hpp b/nano/boost/beast/http/string_body.hpp new file mode 100644 index 0000000000..a9da9dcbcd --- /dev/null +++ b/nano/boost/beast/http/string_body.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/version.hpp b/nano/boost/beast/version.hpp new file mode 100644 index 0000000000..eb55c0c328 --- /dev/null +++ b/nano/boost/beast/version.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/beast/websocket.hpp b/nano/boost/beast/websocket.hpp new file mode 100644 index 0000000000..cbd0fe92f8 --- /dev/null +++ b/nano/boost/beast/websocket.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/boost/private/macro_warnings.hpp b/nano/boost/private/macro_warnings.hpp new file mode 100644 index 0000000000..85fe8d8045 --- /dev/null +++ b/nano/boost/private/macro_warnings.hpp @@ -0,0 +1,30 @@ +#pragma once + +#ifdef _WIN32 +#define DISABLE_ASIO_WARNINGS \ + __pragma (warning (push)) \ + __pragma (warning (disable : 4191)) \ + __pragma (warning (disable : 4242)) + +#else +#define DISABLE_ASIO_WARNINGS +#endif + +#ifdef _WIN32 +#define REENABLE_WARNINGS \ + __pragma (warning (pop)) +#else +#define REENABLE_WARNINGS +#endif + +#define DISABLE_BEAST_WARNINGS DISABLE_ASIO_WARNINGS + +#ifdef _WIN32 +#define DISABLE_PROCESS_WARNINGS \ + __pragma (warning (push)) \ + __pragma (warning (disable : 4191)) \ + __pragma (warning (disable : 4242)) \ + __pragma (warning (disable : 4244)) +#else +#define DISABLE_PROCESS_WARNINGS +#endif diff --git a/nano/boost/process.hpp b/nano/boost/process.hpp deleted file mode 100644 index 6bd9b6a1de..0000000000 --- a/nano/boost/process.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#ifndef BOOST_PROCESS_SUPPORTED -#error BOOST_PROCESS_SUPPORTED must be set, check configuration -#endif - -#if BOOST_PROCESS_SUPPORTED - -#ifdef _WIN32 -#pragma warning(push) -#pragma warning(disable : 4191) -#pragma warning(disable : 4242) -#pragma warning(disable : 4244) -#endif - -#include - -#ifdef _WIN32 -#pragma warning(pop) -#endif - -#endif diff --git a/nano/boost/process/child.hpp b/nano/boost/process/child.hpp new file mode 100644 index 0000000000..f04947ba99 --- /dev/null +++ b/nano/boost/process/child.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +DISABLE_BEAST_WARNINGS +#include +REENABLE_WARNINGS diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 2482b24c54..81f8299d56 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -1,14 +1,20 @@ add_executable (core_test core_test_main.cc testutil.hpp + fakes/websocket_client.hpp + fakes/work_peer.hpp active_transactions.cpp block.cpp block_store.cpp bootstrap.cpp + cli.cpp + common.hpp confirmation_height.cpp + confirmation_solicitor.cpp conflicts.cpp difficulty.cpp distributed_work.cpp + election.cpp entry.cpp epochs.cpp gap_cache.cpp @@ -16,20 +22,24 @@ add_executable (core_test ledger.cpp locks.cpp logger.cpp - network.cpp - node.cpp message.cpp message_parser.cpp memory_pool.cpp + network.cpp + network_filter.cpp + node.cpp processor_service.cpp peer_container.cpp + request_aggregator.cpp signing.cpp socket.cpp + telemetry.cpp toml.cpp timer.cpp uint256_union.cpp utility.cpp versioning.cpp + vote_processor.cpp wallet.cpp wallets.cpp websocket.cpp @@ -39,5 +49,7 @@ target_compile_definitions(core_test PRIVATE -DTAG_VERSION_STRING=${TAG_VERSION_STRING} -DGIT_COMMIT_HASH=${GIT_COMMIT_HASH} - -DBOOST_PROCESS_SUPPORTED=${BOOST_PROCESS_SUPPORTED}) + -DBOOST_PROCESS_SUPPORTED=${BOOST_PROCESS_SUPPORTED} + -DCI=${CI_TEST}) + target_link_libraries (core_test node secure gtest libminiupnpc-static Boost::log_setup Boost::log Boost::boost) diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 841098d283..506bbdd034 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -1,22 +1,113 @@ #include #include +#include #include #include +#include + using namespace std::chrono_literals; -TEST (active_transactions, adjusted_difficulty_priority) +namespace nano +{ +TEST (active_transactions, confirm_active) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node1 = *system.add_node (node_flags); + nano::genesis genesis; + auto send (std::make_shared (genesis.hash (), nano::public_key (), nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node1.process (*send).code); + nano::node_config node_config2 (nano::get_available_port (), system.logging); + node_config2.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags2; + // The rep crawler would otherwise request confirmations in order to find representatives + node_flags2.disable_rep_crawler = true; + auto & node2 = *system.add_node (node_config2, node_flags2); + system.deadline_set (5s); + // Let node2 know about the block + while (node2.active.empty ()) + { + node1.network.flood_block (send, nano::buffer_drop_policy::no_limiter_drop); + ASSERT_NO_ERROR (system.poll ()); + } + // Save election to check request count afterwards + auto election = node2.active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + // Add key to node1 + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + // Add representative to disabled rep crawler + auto peers (node2.network.random_set (1)); + ASSERT_FALSE (peers.empty ()); + { + nano::lock_guard guard (node2.rep_crawler.probable_reps_mutex); + node2.rep_crawler.probable_reps.emplace (nano::test_genesis_key.pub, nano::genesis_amount, *peers.begin ()); + } + while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + // At least one confirmation request + ASSERT_GT (election->confirmation_request_count, 0u); + // Blocks were cleared (except for not_an_account) + ASSERT_EQ (1, election->blocks.size ()); +} +} + +namespace nano +{ +TEST (active_transactions, confirm_frontier) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node1 = *system.add_node (node_flags); + nano::genesis genesis; + auto send (std::make_shared (genesis.hash (), nano::public_key (), nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node1.process (*send).code); + nano::node_flags node_flags2; + // The rep crawler would otherwise request confirmations in order to find representatives + node_flags2.disable_rep_crawler = true; + auto & node2 = *system.add_node (node_flags2); + ASSERT_EQ (nano::process_result::progress, node2.process (*send).code); + system.deadline_set (5s); + while (node2.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + // Save election to check request count afterwards + auto election = node2.active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + // Add key to node1 + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + // Add representative to disabled rep crawler + auto peers (node2.network.random_set (1)); + ASSERT_FALSE (peers.empty ()); + { + nano::lock_guard guard (node2.rep_crawler.probable_reps_mutex); + node2.rep_crawler.probable_reps.emplace (nano::test_genesis_key.pub, nano::genesis_amount, *peers.begin ()); + } + system.deadline_set (5s); + while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_GT (election->confirmation_request_count, 0u); +} +} + +TEST (active_transactions, adjusted_multiplier_priority) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node1 = *system.add_node (node_config); - nano::genesis genesis; nano::keypair key1, key2, key3; - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); auto open1 (std::make_shared (key1.pub, 0, key1.pub, 10 * nano::xrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); @@ -24,6 +115,7 @@ TEST (active_transactions, adjusted_difficulty_priority) node1.process_active (send2); // genesis node1.process_active (open1); // key1 node1.process_active (open2); // key2 + nano::blocks_confirm (node1, { send1, send2, open1, open2 }); system.deadline_set (10s); while (node1.active.size () != 4) { @@ -33,43 +125,37 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty { nano::lock_guard active_guard (node1.active.mutex); + node1.active.update_adjusted_multiplier (); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_LT (node1.active.roots.find (send2->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send1->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (open1->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send1->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (open2->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send2->qualified_root ())->adjusted_difficulty); + ASSERT_LT (node1.active.roots.find (send2->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send1->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (open1->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send1->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (open2->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send2->qualified_root ())->adjusted_multiplier); } // Confirm elections - while (node1.active.size () != 0) + system.deadline_set (10s); + while (!node1.active.empty ()) { nano::lock_guard active_guard (node1.active.mutex); - auto it (node1.active.roots.begin ()); - while (!node1.active.roots.empty () && it != node1.active.roots.end ()) + if (!node1.active.roots.empty ()) { - auto election (it->election); - election->confirm_once (); - it = node1.active.roots.begin (); + node1.active.roots.begin ()->election->confirm_once (); } } + system.deadline_set (10s); + while (node1.ledger.cache.cemented_count < 5 || !node1.active.empty ()) { - system.deadline_set (10s); - nano::unique_lock active_lock (node1.active.mutex); - while (node1.active.confirmed.size () != 4) - { - active_lock.unlock (); - ASSERT_NO_ERROR (system.poll ()); - active_lock.lock (); - } + ASSERT_NO_ERROR (system.poll ()); } //genesis and key1,key2 are opened //start chain of 2 on each - auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, 9 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2->hash (), nano::difficulty::from_multiplier (1500, node1.network_params.network.publish_threshold)))); - auto send4 (std::make_shared (nano::test_genesis_key.pub, send3->hash (), nano::test_genesis_key.pub, 8 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3->hash (), nano::difficulty::from_multiplier (1500, node1.network_params.network.publish_threshold)))); - auto send5 (std::make_shared (key1.pub, open1->hash (), key1.pub, 9 * nano::xrb_ratio, key3.pub, key1.prv, key1.pub, *system.work.generate (open1->hash (), nano::difficulty::from_multiplier (100, node1.network_params.network.publish_threshold)))); - auto send6 (std::make_shared (key1.pub, send5->hash (), key1.pub, 8 * nano::xrb_ratio, key3.pub, key1.prv, key1.pub, *system.work.generate (send5->hash (), nano::difficulty::from_multiplier (100, node1.network_params.network.publish_threshold)))); - auto send7 (std::make_shared (key2.pub, open2->hash (), key2.pub, 9 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, *system.work.generate (open2->hash (), nano::difficulty::from_multiplier (500, node1.network_params.network.publish_threshold)))); - auto send8 (std::make_shared (key2.pub, send7->hash (), key2.pub, 8 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, *system.work.generate (send7->hash (), nano::difficulty::from_multiplier (500, node1.network_params.network.publish_threshold)))); + auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, 9 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2->hash (), nano::difficulty::from_multiplier (150, node1.network_params.network.publish_thresholds.base)))); + auto send4 (std::make_shared (nano::test_genesis_key.pub, send3->hash (), nano::test_genesis_key.pub, 8 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3->hash (), nano::difficulty::from_multiplier (150, node1.network_params.network.publish_thresholds.base)))); + auto send5 (std::make_shared (key1.pub, open1->hash (), key1.pub, 9 * nano::xrb_ratio, key3.pub, key1.prv, key1.pub, system.work_generate_limited (open1->hash (), nano::difficulty::from_multiplier (10, node1.network_params.network.publish_thresholds.base), nano::difficulty::from_multiplier (50, node1.network_params.network.publish_thresholds.base)))); + auto send6 (std::make_shared (key1.pub, send5->hash (), key1.pub, 8 * nano::xrb_ratio, key3.pub, key1.prv, key1.pub, system.work_generate_limited (send5->hash (), nano::difficulty::from_multiplier (10, node1.network_params.network.publish_thresholds.base), nano::difficulty::from_multiplier (50, node1.network_params.network.publish_thresholds.base)))); + auto send7 (std::make_shared (key2.pub, open2->hash (), key2.pub, 9 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, system.work_generate_limited (open2->hash (), nano::difficulty::from_multiplier (50, node1.network_params.network.publish_thresholds.base), nano::difficulty::from_multiplier (150, node1.network_params.network.publish_thresholds.base)))); + auto send8 (std::make_shared (key2.pub, send7->hash (), key2.pub, 8 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, system.work_generate_limited (send7->hash (), nano::difficulty::from_multiplier (50, node1.network_params.network.publish_thresholds.base), nano::difficulty::from_multiplier (150, node1.network_params.network.publish_thresholds.base)))); node1.process_active (send3); // genesis node1.process_active (send5); // key1 @@ -77,6 +163,7 @@ TEST (active_transactions, adjusted_difficulty_priority) node1.process_active (send4); // genesis node1.process_active (send6); // key1 node1.process_active (send8); // key2 + nano::blocks_confirm (node1, { send3, send4, send5, send6, send7, send8 }); system.deadline_set (10s); while (node1.active.size () != 6) @@ -86,137 +173,26 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty nano::lock_guard lock (node1.active.mutex); - uint64_t last_adjusted (0); + node1.active.update_adjusted_multiplier (); + double last_adjusted (0.0); for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i) { //first root has nothing to compare - if (last_adjusted != 0) + if (last_adjusted != 0.0) { - ASSERT_LT (i->adjusted_difficulty, last_adjusted); + ASSERT_LE (i->adjusted_multiplier, last_adjusted); } - last_adjusted = i->adjusted_difficulty; - } - ASSERT_LT (node1.active.roots.find (send4->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send3->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (send6->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send5->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (send8->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send7->qualified_root ())->adjusted_difficulty); -} - -TEST (active_transactions, adjusted_difficulty_overflow_max) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.enable_voting = false; - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node1 = *system.add_node (node_config); - nano::genesis genesis; - nano::keypair key1, key2; - - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - auto open1 (std::make_shared (key1.pub, 0, key1.pub, 10 * nano::xrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); - auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); - node1.process_active (send1); // genesis - node1.process_active (send2); // genesis - node1.process_active (open1); // key1 - node1.process_active (open2); // key2 - system.deadline_set (10s); - while (node1.active.size () != 4) - { - ASSERT_NO_ERROR (system.poll ()); - } - - { - nano::lock_guard active_guard (node1.active.mutex); - // Update difficulty to maximum - auto send1_root (node1.active.roots.find (send1->qualified_root ())); - auto send2_root (node1.active.roots.find (send2->qualified_root ())); - auto open1_root (node1.active.roots.find (open1->qualified_root ())); - auto open2_root (node1.active.roots.find (open2->qualified_root ())); - // clang-format off - auto modify_difficulty = [& roots = node1.active.roots](auto & existing_root) { - roots.modify (existing_root, [](nano::conflict_info & info_a) { - info_a.difficulty = std::numeric_limits::max (); - }); - }; - // clang-format on - modify_difficulty (send1_root); - modify_difficulty (send2_root); - modify_difficulty (open1_root); - modify_difficulty (open2_root); - node1.active.adjust_difficulty (send2->hash ()); - // Test overflow - ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_EQ (send1_root->adjusted_difficulty, std::numeric_limits::max ()); - ASSERT_LT (send2_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open1_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open2_root->adjusted_difficulty, send2_root->adjusted_difficulty); - } -} - -TEST (active_transactions, adjusted_difficulty_overflow_min) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.enable_voting = false; - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node1 = *system.add_node (node_config); - nano::genesis genesis; - nano::keypair key1, key2, key3; - - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - auto open1 (std::make_shared (key1.pub, 0, key1.pub, 10 * nano::xrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); - auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); - auto send3 (std::make_shared (key2.pub, open2->hash (), key2.pub, 9 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, *system.work.generate (open2->hash ()))); - node1.process_active (send1); // genesis - node1.process_active (send2); // genesis - node1.process_active (open1); // key1 - node1.process_active (open2); // key2 - node1.process_active (send3); // key2 - system.deadline_set (10s); - while (node1.active.size () != 5) - { - ASSERT_NO_ERROR (system.poll ()); - } - - { - nano::lock_guard active_guard (node1.active.mutex); - // Update difficulty to minimum - auto send1_root (node1.active.roots.find (send1->qualified_root ())); - auto send2_root (node1.active.roots.find (send2->qualified_root ())); - auto open1_root (node1.active.roots.find (open1->qualified_root ())); - auto open2_root (node1.active.roots.find (open2->qualified_root ())); - auto send3_root (node1.active.roots.find (send3->qualified_root ())); - // clang-format off - auto modify_difficulty = [& roots = node1.active.roots](auto & existing_root) { - roots.modify (existing_root, [](nano::conflict_info & info_a) { - info_a.difficulty = std::numeric_limits::min () + 1; - }); - }; - // clang-format on - modify_difficulty (send1_root); - modify_difficulty (send2_root); - modify_difficulty (open1_root); - modify_difficulty (open2_root); - modify_difficulty (send3_root); - node1.active.adjust_difficulty (send1->hash ()); - // Test overflow - ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_EQ (send1_root->adjusted_difficulty, std::numeric_limits::min () + 3); - ASSERT_LT (send2_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open1_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open2_root->adjusted_difficulty, send2_root->adjusted_difficulty); - ASSERT_LT (send3_root->adjusted_difficulty, open2_root->adjusted_difficulty); - ASSERT_EQ (send3_root->adjusted_difficulty, std::numeric_limits::min ()); - // Clear roots with too low difficulty to prevent issues - node1.active.roots.clear (); + last_adjusted = i->adjusted_multiplier; } + ASSERT_LT (node1.active.roots.find (send4->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send3->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (send6->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send5->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (send8->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send7->qualified_root ())->adjusted_multiplier); } TEST (active_transactions, keep_local) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.active_elections_size = 2; //bound to 2, wont drop wallet created transactions, but good to test dropping remote // Disable frontier confirmation to allow the test to finish before @@ -233,46 +209,42 @@ TEST (active_transactions, keep_local) auto send4 (wallet.send_action (nano::test_genesis_key.pub, key4.pub, node.config.receive_minimum.number ())); auto send5 (wallet.send_action (nano::test_genesis_key.pub, key5.pub, node.config.receive_minimum.number ())); auto send6 (wallet.send_action (nano::test_genesis_key.pub, key6.pub, node.config.receive_minimum.number ())); - system.deadline_set (10s); + system.deadline_set (5s); // should not drop wallet created transactions while (node.active.size () != 6) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (0, node.active.dropped_elections_cache_size ()); + ASSERT_EQ (0, node.active.recently_dropped.size ()); while (!node.active.empty ()) { nano::lock_guard active_guard (node.active.mutex); - auto it (node.active.roots.begin ()); - while (!node.active.roots.empty () && it != node.active.roots.end ()) + if (!node.active.roots.empty ()) { - (it->election)->confirm_once (); - it = node.active.roots.begin (); + node.active.roots.begin ()->election->confirm_once (); } } auto open1 (std::make_shared (key1.pub, 0, key1.pub, node.config.receive_minimum.number (), send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); - node.process_active (open1); - node.active.start (open1); auto open2 (std::make_shared (key2.pub, 0, key2.pub, node.config.receive_minimum.number (), send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); - node.process_active (open2); - node.active.start (open2); auto open3 (std::make_shared (key3.pub, 0, key3.pub, node.config.receive_minimum.number (), send3->hash (), key3.prv, key3.pub, *system.work.generate (key3.pub))); + node.process_active (open1); + node.process_active (open2); node.process_active (open3); - node.active.start (open3); - ASSERT_EQ (3, node.active.size ()); - system.deadline_set (10s); + node.block_processor.flush (); + system.deadline_set (5s); // bound elections, should drop after one loop while (node.active.size () != node_config.active_elections_size) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, node.active.dropped_elections_cache_size ()); + ASSERT_EQ (1, node.active.recently_dropped.size ()); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_drop)); } TEST (active_transactions, prioritize_chains) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.active_elections_size = 4; //bound to 4, wont drop wallet created transactions, but good to test dropping remote // Disable frontier confirmation to allow the test to finish before @@ -288,37 +260,32 @@ TEST (active_transactions, prioritize_chains) auto send4 (std::make_shared (key1.pub, send3->hash (), key1.pub, nano::xrb_ratio * 7, key2.pub, key1.prv, key1.pub, *system.work.generate (send3->hash ()))); auto send5 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); auto send6 (std::make_shared (nano::test_genesis_key.pub, send5->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 30 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5->hash ()))); - auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send5->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub, nano::difficulty::from_multiplier (50., node1.network_params.network.publish_threshold)))); - uint64_t difficulty1 (0); - nano::work_validate (*open2, &difficulty1); - uint64_t difficulty2 (0); - nano::work_validate (*send6, &difficulty2); + auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send5->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub, nano::difficulty::from_multiplier (50., node1.network_params.network.publish_thresholds.base)))); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (open2->difficulty (), nano::work_threshold (open2->work_version (), nano::block_details (nano::epoch::epoch_0, false, true, false))), node1.network_params.network.publish_thresholds.epoch_1)); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (send6->difficulty (), nano::work_threshold (open2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (send1); node1.process_active (open1); node1.process_active (send5); + nano::blocks_confirm (node1, { send1, open1, send5 }); system.deadline_set (10s); while (node1.active.size () != 3) { ASSERT_NO_ERROR (system.poll ()); } - while (node1.active.size () != 0) + while (!node1.active.empty ()) { nano::lock_guard active_guard (node1.active.mutex); - auto it (node1.active.roots.get<1> ().begin ()); - while (!node1.active.roots.empty () && it != node1.active.roots.get<1> ().end ()) + if (!node1.active.roots.empty ()) { - auto election (it->election); - election->confirm_once (); - it = node1.active.roots.get<1> ().begin (); + node1.active.roots.begin ()->election->confirm_once (); } } - node1.process_active (send2); node1.process_active (send3); node1.process_active (send4); node1.process_active (send6); - + nano::blocks_confirm (node1, { send2, send3, send4, send6 }); system.deadline_set (10s); while (node1.active.size () != 4) { @@ -334,10 +301,12 @@ TEST (active_transactions, prioritize_chains) } size_t seen (0); { + nano::lock_guard active_guard (node1.active.mutex); + node1.active.update_adjusted_multiplier (); auto it (node1.active.roots.get<1> ().begin ()); while (!node1.active.roots.empty () && it != node1.active.roots.get<1> ().end ()) { - if (it->difficulty == (difficulty1 || difficulty2)) + if (it->multiplier == multiplier1 || it->multiplier == multiplier2) { seen++; } @@ -350,155 +319,181 @@ TEST (active_transactions, prioritize_chains) TEST (active_transactions, inactive_votes_cache) { - nano::system system (24000, 1); - nano::block_hash latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::system system (1); + auto & node = *system.nodes[0]; + nano::block_hash latest (node.latest (nano::test_genesis_key.pub)); nano::keypair key; auto send (std::make_shared (latest, key.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, std::vector (1, send->hash ()))); - system.nodes[0]->vote_processor.vote (vote, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); + node.vote_processor.vote (vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + system.deadline_set (5s); + while (node.active.inactive_votes_cache_size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + node.process_active (send); + node.block_processor.flush (); + system.deadline_set (5s); + while (!node.ledger.block_confirmed (node.store.tx_begin_read (), send->hash ())) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); +} + +TEST (active_transactions, inactive_votes_cache_fork) +{ + nano::system system (1); + auto & node = *system.nodes[0]; + nano::block_hash latest (node.latest (nano::test_genesis_key.pub)); + nano::keypair key; + auto send1 (std::make_shared (latest, key.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); + auto send2 (std::make_shared (latest, key.pub, nano::genesis_amount - 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); + auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, std::vector (1, send1->hash ()))); + node.vote_processor.vote (vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + auto channel1 (node.network.udp_channels.create (node.network.endpoint ())); system.deadline_set (5s); - while (system.nodes[0]->active.inactive_votes_cache_size () != 1) + while (node.active.inactive_votes_cache_size () != 1) { ASSERT_NO_ERROR (system.poll ()); } - system.nodes[0]->process_active (send); - system.nodes[0]->block_processor.flush (); + node.network.process_message (nano::publish (send2), channel1); + node.block_processor.flush (); + ASSERT_NE (nullptr, node.block (send2->hash ())); + node.network.process_message (nano::publish (send1), channel1); + node.block_processor.flush (); bool confirmed (false); system.deadline_set (5s); while (!confirmed) { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - confirmed = system.nodes[0]->ledger.block_confirmed (transaction, send->hash ()); + auto transaction (node.store.tx_begin_read ()); + confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()) && node.active.empty (); ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[0]->stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); } TEST (active_transactions, inactive_votes_cache_existing_vote) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + auto & node = *system.add_node (node_config); + nano::block_hash latest (node.latest (nano::test_genesis_key.pub)); nano::keypair key; auto send (std::make_shared (latest, key.pub, nano::genesis_amount - 100 * nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); auto open (std::make_shared (key.pub, 0, key.pub, 100 * nano::Gxrb_ratio, send->hash (), key.prv, key.pub, *system.work.generate (key.pub))); // Increase key weight - node->process_active (send); - node->block_processor.add (open); - node->block_processor.flush (); + node.process_active (send); + node.block_processor.add (open); + node.block_processor.flush (); system.deadline_set (5s); - while (node->active.size () != 1) + while (node.active.size () != 1) { ASSERT_NO_ERROR (system.poll ()); } std::shared_ptr election; { - nano::lock_guard active_guard (node->active.mutex); - auto it (node->active.roots.begin ()); - ASSERT_NE (node->active.roots.end (), it); + nano::lock_guard active_guard (node.active.mutex); + auto it (node.active.roots.begin ()); + ASSERT_NE (node.active.roots.end (), it); election = it->election; } - ASSERT_GT (node->weight (key.pub), node->minimum_principal_weight ()); + ASSERT_GT (node.weight (key.pub), node.minimum_principal_weight ()); // Insert vote auto vote1 (std::make_shared (key.pub, key.prv, 1, std::vector (1, send->hash ()))); - node->vote_processor.vote (vote1, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); + node.vote_processor.vote (vote1, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); system.deadline_set (5s); bool done (false); while (!done) { - nano::unique_lock active_lock (node->active.mutex); + nano::unique_lock active_lock (node.active.mutex); done = (election->last_votes.size () == 2); active_lock.unlock (); ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[0]->stats.count (nano::stat::type::election, nano::stat::detail::vote_new)); - nano::lock_guard active_guard (node->active.mutex); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_new)); + nano::lock_guard active_guard (node.active.mutex); auto last_vote1 (election->last_votes[key.pub]); ASSERT_EQ (send->hash (), last_vote1.hash); ASSERT_EQ (1, last_vote1.sequence); // Attempt to change vote with inactive_votes_cache - node->active.add_inactive_votes_cache (send->hash (), key.pub); - ASSERT_EQ (1, node->active.find_inactive_votes_cache (send->hash ()).voters.size ()); - election->insert_inactive_votes_cache (); + node.active.add_inactive_votes_cache (send->hash (), key.pub); + ASSERT_EQ (1, node.active.find_inactive_votes_cache (send->hash ()).voters.size ()); + election->insert_inactive_votes_cache (send->hash ()); // Check that election data is not changed ASSERT_EQ (2, election->last_votes.size ()); auto last_vote2 (election->last_votes[key.pub]); ASSERT_EQ (last_vote1.hash, last_vote2.hash); ASSERT_EQ (last_vote1.sequence, last_vote2.sequence); ASSERT_EQ (last_vote1.time, last_vote2.time); - ASSERT_EQ (0, system.nodes[0]->stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); } TEST (active_transactions, inactive_votes_cache_multiple_votes) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::block_hash latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node = *system.add_node (node_config); + nano::block_hash latest (node.latest (nano::test_genesis_key.pub)); nano::keypair key1; auto send1 (std::make_shared (latest, key1.pub, nano::genesis_amount - 100 * nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); auto send2 (std::make_shared (send1->hash (), key1.pub, 100 * nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); // Decrease genesis weight to prevent election confirmation auto open (std::make_shared (key1.pub, 0, key1.pub, 100 * nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); // Increase key1 weight - node->block_processor.add (send1); - node->block_processor.add (send2); - node->block_processor.add (open); - node->block_processor.flush (); + node.block_processor.add (send1); + node.block_processor.add (send2); + node.block_processor.add (open); + node.block_processor.flush (); // Process votes auto vote1 (std::make_shared (key1.pub, key1.prv, 0, std::vector (1, send1->hash ()))); - system.nodes[0]->vote_processor.vote (vote1, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); + node.vote_processor.vote (vote1, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, std::vector (1, send1->hash ()))); - system.nodes[0]->vote_processor.vote (vote2, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); + node.vote_processor.vote (vote2, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); system.deadline_set (5s); while (true) { { - nano::lock_guard active_guard (system.nodes[0]->active.mutex); - if (system.nodes[0]->active.find_inactive_votes_cache (send1->hash ()).voters.size () == 2) + nano::lock_guard active_guard (node.active.mutex); + if (node.active.find_inactive_votes_cache (send1->hash ()).voters.size () == 2) { break; } } ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[0]->active.inactive_votes_cache_size ()); + ASSERT_EQ (1, node.active.inactive_votes_cache_size ()); // Start election - system.nodes[0]->active.start (send1); + node.active.insert (send1); { - nano::lock_guard active_guard (system.nodes[0]->active.mutex); - auto it (system.nodes[0]->active.roots.begin ()); - ASSERT_NE (system.nodes[0]->active.roots.end (), it); + nano::lock_guard active_guard (node.active.mutex); + auto it (node.active.roots.begin ()); + ASSERT_NE (node.active.roots.end (), it); ASSERT_EQ (3, it->election->last_votes.size ()); // 2 votes and 1 default not_an_acount } - ASSERT_EQ (2, system.nodes[0]->stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); } TEST (active_transactions, update_difficulty) { - nano::system system (24000, 2); + nano::system system (2); auto & node1 = *system.nodes[0]; auto & node2 = *system.nodes[1]; nano::genesis genesis; nano::keypair key1; // Generate blocks & start elections auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 100, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - uint64_t difficulty1 (0); - nano::work_validate (*send1, &difficulty1); + auto difficulty1 (send1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (send1->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 200, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - uint64_t difficulty2 (0); - nano::work_validate (*send2, &difficulty2); + auto difficulty2 (send2->difficulty ()); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (send2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (send1); node1.process_active (send2); node1.block_processor.flush (); - system.deadline_set (10s); - while (node1.active.size () != 2 || node2.active.size () != 2) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_NO_ERROR (system.poll_until_true (10s, [&node1, &node2] { return node1.active.size () == 2 && node2.active.size () == 2; })); // Update work with higher difficulty - auto work1 = node1.work_generate_blocking (send1->root (), difficulty1 + 1, boost::none); - auto work2 = node1.work_generate_blocking (send2->root (), difficulty2 + 1, boost::none); + auto work1 = node1.work_generate_blocking (send1->root (), difficulty1 + 1); + auto work2 = node1.work_generate_blocking (send2->root (), difficulty2 + 1); std::error_code ec; nano::state_block_builder builder; @@ -507,25 +502,13 @@ TEST (active_transactions, update_difficulty) send2 = std::shared_ptr (builder1.from (*send2).work (*work2).build (ec)); ASSERT_FALSE (ec); - auto modify_election = [&node1](auto block) { - auto root_l (block->root ()); - auto hash (block->hash ()); - nano::lock_guard active_guard (node1.active.mutex); - auto existing (node1.active.roots.find (block->qualified_root ())); - ASSERT_NE (existing, node1.active.roots.end ()); - auto election (existing->election); - ASSERT_EQ (election->status.winner->hash (), hash); - election->status.winner = block; - auto current (election->blocks.find (hash)); - assert (current != election->blocks.end ()); - current->second = block; - }; - - modify_election (send1); - modify_election (send2); node1.process_active (send1); node1.process_active (send2); node1.block_processor.flush (); + // Share the updated blocks + node1.network.flood_block (send1); + node1.network.flood_block (send2); + system.deadline_set (10s); bool done (false); while (!done) @@ -543,72 +526,746 @@ TEST (active_transactions, update_difficulty) ASSERT_NE (existing3, node2.active.roots.end ()); auto const existing4 (node2.active.roots.find (send2->qualified_root ())); ASSERT_NE (existing4, node2.active.roots.end ()); - done = (existing1->difficulty > difficulty1) && (existing2->difficulty > difficulty2) && (existing3->difficulty > difficulty1) && (existing4->difficulty > difficulty2); + auto updated1 = existing1->multiplier > multiplier1; + auto updated2 = existing2->multiplier > multiplier2; + auto propagated1 = existing3->multiplier > multiplier1; + auto propagated2 = existing4->multiplier > multiplier2; + done = updated1 && updated2 && propagated1 && propagated2; } ASSERT_NO_ERROR (system.poll ()); } } -TEST (active_transactions, restart_dropped) +namespace nano +{ +TEST (active_transactions, vote_replays) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node = *system.add_node (node_config); nano::genesis genesis; - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::xrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - // Process only in ledger and emulate dropping the election - ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + nano::keypair key; + std::error_code ec; + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + ASSERT_NE (nullptr, send1); + auto open1 (std::make_shared (key.pub, 0, key.pub, nano::Gxrb_ratio, send1->hash (), key.prv, key.pub, *system.work.generate (key.pub))); + ASSERT_NE (nullptr, open1); + node.process_active (send1); + node.process_active (open1); + nano::blocks_confirm (node, { send1, open1 }); + ASSERT_EQ (2, node.active.size ()); + // First vote is not a replay and confirms the election, second vote should be a replay since the election has confirmed but not yet removed + auto vote_send1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send1)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1)); + ASSERT_EQ (2, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1)); + // Wait until the election is removed, at which point the vote is still a replay since it's been recently confirmed + ASSERT_TIMELY (3s, node.active.size () == 1); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1)); + // Open new account + auto vote_open1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, open1)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1)); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + ASSERT_TIMELY (3s, node.active.empty ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub)); + + auto send2 (std::make_shared (key.pub, open1->hash (), key.pub, nano::Gxrb_ratio - 1, key.pub, key.prv, key.pub, *system.work.generate (open1->hash ()))); + ASSERT_NE (nullptr, send2); + node.process_active (send2); + nano::blocks_confirm (node, { send2 }); + ASSERT_EQ (1, node.active.size ()); + auto vote1_send2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2)); + auto vote2_send2 (std::make_shared (key.pub, key.prv, 0, send2)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2_send2)); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2)); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2)); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2)); + ASSERT_TIMELY (3s, node.active.empty ()); + ASSERT_EQ (0, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2)); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2)); + + // Removing blocks as recently confirmed makes every vote indeterminate { nano::lock_guard guard (node.active.mutex); - node.active.add_dropped_elections_cache (send1->qualified_root ()); + node.active.recently_confirmed.clear (); } - uint64_t difficulty1 (0); - nano::work_validate (*send1, &difficulty1); - // Generate higher difficulty work - auto work2 (*system.work.generate (send1->root (), difficulty1)); - uint64_t difficulty2 (0); - nano::work_validate (send1->root (), work2, &difficulty2); - ASSERT_GT (difficulty2, difficulty1); - // Process the same block with updated work - auto send2 (std::make_shared (*send1)); - send2->block_work_set (work2); - node.process_active (send2); - // Wait until the block is in elections + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1)); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1)); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2)); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2)); +} +} + +TEST (active_transactions, activate_dependencies) +{ + // Ensure that we attempt to backtrack if an election isn't getting confirmed and there are more uncemented blocks to start elections for + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.enable_voting = true; + nano::node_flags flags; + flags.disable_bootstrap_listener = true; + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node1 (system.add_node (config, flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, flags)); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::genesis genesis; + nano::block_builder builder; + std::shared_ptr block0 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (genesis.hash ()).value ()) + .build (); + // Establish a representative + node2->process_active (block0); + node2->block_processor.flush (); + system.deadline_set (10s); + while (node1->block (block0->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto block1 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (block0->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (block0->hash ()).value ()) + .build (); + // Wait for confirmation of the previous block, which tries to activate the successor + // We want to test that behavior through activating dependencies instead + ASSERT_TIMELY (3s, node2->block_confirmed (block0->hash ())); + { + auto transaction = node2->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node2->ledger.process (transaction, *block1).code); + } + std::shared_ptr block2 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (block1->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (block1->hash ()).value ()) + .build (); + node2->process_active (block2); + node2->block_processor.flush (); + node2->block_confirm (block2); + system.deadline_set (10s); + while (node1->block (block2->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_NE (nullptr, node1->block (block2->hash ())); + system.deadline_set (10s); + while (!node1->active.empty () || !node2->active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_TRUE (node1->block_confirmed_or_being_confirmed (node1->store.tx_begin_read (), block2->hash ())); + ASSERT_TRUE (node2->block_confirmed_or_being_confirmed (node2->store.tx_begin_read (), block2->hash ())); +} + +namespace nano +{ +TEST (active_transactions, activate_dependencies_invalid) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node (*system.add_node (flags)); + node.active.pending_dependencies.emplace_back (nano::genesis ().open->hash (), 10); + node.active.pending_dependencies.emplace_back (1, 1); + node.active.pending_dependencies.emplace_back (0, -1); + node.active.pending_dependencies.emplace_back (-1, 0); + { + nano::unique_lock lock (node.active.mutex); + node.active.activate_dependencies (lock); + } + ASSERT_TRUE (node.active.empty ()); + ASSERT_EQ (0, node.active.pending_dependencies.size ()); +} + +// Tests that blocks are correctly cleared from the duplicate filter for unconfirmed elections +TEST (active_transactions, dropped_cleanup) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + + nano::genesis genesis; + auto block = genesis.open; + block->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); + + // Add to network filter to ensure proper cleanup after the election is dropped + std::vector block_bytes; + { + nano::vectorstream stream (block_bytes); + block->serialize (stream); + } + ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); + ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); + + auto election (node.active.insert (block).election); + ASSERT_NE (nullptr, election); + + // Not yet removed + ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); + + // Now simulate dropping the election, which performs a cleanup in the background using the node worker + ASSERT_FALSE (election->confirmed ()); + { + nano::lock_guard guard (node.active.mutex); + election->cleanup (); + } + + // Push a worker task to ensure the cleanup is already performed + std::atomic flag{ false }; + node.worker.push_task ([&flag]() { + flag = true; + }); system.deadline_set (5s); - bool done{ false }; - while (!done) + while (!flag) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // The filter must have been cleared + ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); +} +} + +namespace nano +{ +// Blocks that won an election must always be seen as confirming or cemented +TEST (active_transactions, confirmation_consistency) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (node_config); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + for (unsigned i = 0; i < 10; ++i) { + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node.config.receive_minimum.number ())); + ASSERT_NE (nullptr, block); + system.deadline_set (5s); + while (!node.ledger.block_confirmed (node.store.tx_begin_read (), block->hash ())) { + ASSERT_FALSE (node.active.insert (block).inserted); + ASSERT_NO_ERROR (system.poll (5ms)); + } + ASSERT_NO_ERROR (system.poll_until_true (1s, [&node, &block, i] { nano::lock_guard guard (node.active.mutex); - auto existing (node.active.roots.find (send2->qualified_root ())); - done = existing != node.active.roots.end (); - if (done) - { - ASSERT_EQ (difficulty2, existing->difficulty); - } + EXPECT_EQ (i + 1, node.active.recently_confirmed.size ()); + EXPECT_EQ (block->qualified_root (), node.active.recently_confirmed.back ().first); + return i + 1 == node.active.recently_cemented.size (); // done after a callback + })); + } +} +} + +TEST (active_transactions, insertion_prioritization) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + // 10% of elections (1) are prioritized + node_config.active_elections_size = 10; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config, node_flags); + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); + auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 30 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2->hash ()))); + auto send4 (std::make_shared (nano::test_genesis_key.pub, send3->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 40 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3->hash ()))); + auto send5 (std::make_shared (nano::test_genesis_key.pub, send4->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 50 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send4->hash ()))); + auto send6 (std::make_shared (nano::test_genesis_key.pub, send5->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 60 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5->hash ()))); + auto send7 (std::make_shared (nano::test_genesis_key.pub, send6->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 70 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send6->hash ()))); + + // Sort by difficulty, descending + std::vector> blocks{ send1, send2, send3, send4, send5, send6, send7 }; + for (auto const & block : blocks) + { + ASSERT_EQ (nano::process_result::progress, node.process (*block).code); + } + std::sort (blocks.begin (), blocks.end (), [](auto const & blockl, auto const & blockr) { return blockl->difficulty () > blockr->difficulty (); }); + + auto update_active_multiplier = [&node] { + nano::unique_lock lock (node.active.mutex); + node.active.update_active_multiplier (lock); + }; + + ASSERT_TRUE (node.active.insert (blocks[2]).election->prioritized ()); + update_active_multiplier (); + ASSERT_FALSE (node.active.insert (blocks[3]).election->prioritized ()); + update_active_multiplier (); + ASSERT_TRUE (node.active.insert (blocks[1]).election->prioritized ()); + update_active_multiplier (); + ASSERT_FALSE (node.active.insert (blocks[4]).election->prioritized ()); + update_active_multiplier (); + ASSERT_TRUE (node.active.insert (blocks[0]).election->prioritized ()); + update_active_multiplier (); + ASSERT_FALSE (node.active.insert (blocks[5]).election->prioritized ()); + update_active_multiplier (); + ASSERT_FALSE (node.active.insert (blocks[6]).election->prioritized ()); + + ASSERT_EQ (4, node.stats.count (nano::stat::type::election, nano::stat::detail::election_non_priority)); + ASSERT_EQ (3, node.stats.count (nano::stat::type::election, nano::stat::detail::election_priority)); +} + +TEST (active_multiplier, less_than_one) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::unique_lock lock (node.active.mutex); + auto base_active_difficulty = node.network_params.network.publish_thresholds.epoch_1; + auto base_active_multiplier = 1.0; + auto min_active_difficulty = node.network_params.network.publish_thresholds.entry; + auto min_multiplier = nano::difficulty::to_multiplier (min_active_difficulty, base_active_difficulty); + ASSERT_EQ (node.active.trended_active_multiplier, base_active_multiplier); + for (int i = 0; i < node.active.multipliers_cb.size () - 1; ++i) + { + node.active.multipliers_cb.push_front (min_multiplier); + } + auto sum (std::accumulate (node.active.multipliers_cb.begin (), node.active.multipliers_cb.end (), double(0))); + auto multiplier = sum / node.active.multipliers_cb.size (); + node.active.multipliers_cb.push_front (min_multiplier); + node.active.update_active_multiplier (lock); + ASSERT_EQ (node.active.trended_active_multiplier, multiplier); +} + +TEST (active_multiplier, normalization) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + // Check normalization for epoch 1 + double multiplier1 (1.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier1, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (1.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier1 (nano::normalized_multiplier (multiplier1, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (1.0, norm_multiplier1, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier1, node.network_params.network.publish_thresholds.epoch_1), multiplier1, 1e-10); + double multiplier2 (5.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier2, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (1.5, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier2 (nano::normalized_multiplier (multiplier2, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (1.5, norm_multiplier2, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier2, node.network_params.network.publish_thresholds.epoch_1), multiplier2, 1e-10); + double multiplier3 (9.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier3, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (2.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier3 (nano::normalized_multiplier (multiplier3, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (2.0, norm_multiplier3, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier3, node.network_params.network.publish_thresholds.epoch_1), multiplier3, 1e-10); + double multiplier4 (17.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier4, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (3.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier4 (nano::normalized_multiplier (multiplier4, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (3.0, norm_multiplier4, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier4, node.network_params.network.publish_thresholds.epoch_1), multiplier4, 1e-10); + double multiplier5 (25.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier5, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (4.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier5 (nano::normalized_multiplier (multiplier5, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (4.0, norm_multiplier5, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier5, node.network_params.network.publish_thresholds.epoch_1), multiplier5, 1e-10); + double multiplier6 (57.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier6, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (8.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier6 (nano::normalized_multiplier (multiplier6, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (8.0, norm_multiplier6, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier6, node.network_params.network.publish_thresholds.epoch_1), multiplier6, 1e-10); + // Check normalization for epoch 2 receive + double multiplier10 (1.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (1.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier10 (nano::normalized_multiplier (multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (1.0, norm_multiplier10, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier10, 1e-10); + double multiplier11 (33.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (1.5, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier11 (nano::normalized_multiplier (multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (1.5, norm_multiplier11, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier11, 1e-10); + double multiplier12 (65.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (2.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier12 (nano::normalized_multiplier (multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (2.0, norm_multiplier12, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier12, 1e-10); + double multiplier13 (129.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (3.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier13 (nano::normalized_multiplier (multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (3.0, norm_multiplier13, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier13, 1e-10); + double multiplier14 (193.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (4.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier14 (nano::normalized_multiplier (multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (4.0, norm_multiplier14, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier14, 1e-10); + double multiplier15 (961.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (16.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier15 (nano::normalized_multiplier (multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (16.0, norm_multiplier15, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier15, 1e-10); +} + +namespace nano +{ +TEST (active_transactions, vote_generator_session) +{ + nano::system system (1); + auto node (system.nodes[0]); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::vote_generator_session generator_session (node->active.generator); + boost::thread thread ([node, &generator_session]() { + nano::thread_role::set (nano::thread_role::name::request_loop); + for (unsigned i = 0; i < 100; ++i) + { + generator_session.add (nano::genesis_hash); } - ASSERT_NO_ERROR (system.poll ()); + ASSERT_EQ (0, node->stats.count (nano::stat::type::vote, nano::stat::detail::vote_indeterminate)); + generator_session.flush (); + }); + thread.join (); + system.deadline_set (5s); + while (node->stats.count (nano::stat::type::vote, nano::stat::detail::vote_indeterminate) < (100 / nano::network::confirm_ack_hashes_max)) + { + ASSERT_NO_ERROR (system.poll (5ms)); } - // Verify the block was updated in the ledger +} +} + +TEST (active_transactions, election_difficulty_update_old) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_flags); + nano::genesis genesis; + nano::keypair key; + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send1_copy (std::make_shared (*send1)); + node.process_active (send1); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + auto multiplier = node.active.roots.begin ()->multiplier; { - auto block (node.store.block_get (node.store.tx_begin_read (), send1->hash ())); - ASSERT_EQ (work2, block->block_work ()); + nano::lock_guard guard (node.active.mutex); + ASSERT_EQ (node.active.normalized_multiplier (*send1), multiplier); } - // Drop election - node.active.erase (*send2); - // Try to restart election with the lower difficulty block, should not work since the block as lower work + // Should not update with a lower difficulty + send1_copy->block_work_set (0); + ASSERT_EQ (nano::process_result::old, node.process (*send1_copy).code); + ASSERT_FALSE (send1_copy->has_sideband ()); node.process_active (send1); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (node.active.roots.begin ()->multiplier, multiplier); + // Update work, even without a sideband it should find the block in the election and update the election multiplier + ASSERT_TRUE (node.work_generate_blocking (*send1_copy, send1->difficulty () + 1).is_initialized ()); + node.process_active (send1_copy); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_GT (node.active.roots.begin ()->multiplier, multiplier); + + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_difficulty_update)); +} + +TEST (active_transactions, election_difficulty_update_fork) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_flags); + + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + auto epoch2 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_2); + ASSERT_NE (nullptr, epoch2); + nano::keypair key; + auto send1 (std::make_shared (nano::test_genesis_key.pub, epoch2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (epoch2->hash ()))); + auto open1 (std::make_shared (key.pub, 0, key.pub, nano::Gxrb_ratio, send1->hash (), key.prv, key.pub, *system.work.generate (key.pub))); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); + ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*open1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*send2).code); + // Confirm blocks so far to allow starting elections for upcoming blocks + for (auto block : { open1, send2 }) + { + node.block_confirm (block); + { + auto election = node.active.election (block->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (2s, node.block_confirmed (block->hash ())); + node.active.erase (*block); + } + + // Verify an election with multiple blocks is correctly updated on arrival of another block + // Each subsequent block has difficulty at least higher than the previous one + auto fork_change (std::make_shared (key.pub, open1->hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key.prv, key.pub, *system.work.generate (open1->hash ()))); + auto fork_send (std::make_shared (key.pub, open1->hash (), key.pub, 0, key.pub, key.prv, key.pub, *system.work.generate (open1->hash (), fork_change->difficulty ()))); + auto fork_receive (std::make_shared (key.pub, open1->hash (), key.pub, 2 * nano::Gxrb_ratio, send2->hash (), key.prv, key.pub, *system.work.generate (open1->hash (), fork_send->difficulty ()))); + ASSERT_GT (fork_send->difficulty (), fork_change->difficulty ()); + ASSERT_GT (fork_receive->difficulty (), fork_send->difficulty ()); + + node.process_active (fork_change); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + auto multiplier_change = node.active.roots.begin ()->multiplier; + node.process_active (fork_send); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_block_conflict)); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_difficulty_update)); + auto multiplier_send = node.active.roots.begin ()->multiplier; + node.process_active (fork_receive); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::election_block_conflict)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::election_difficulty_update)); + auto multiplier_receive = node.active.roots.begin ()->multiplier; + + ASSERT_GT (multiplier_send, multiplier_change); + ASSERT_GT (multiplier_receive, multiplier_send); + + EXPECT_FALSE (fork_receive->has_sideband ()); + auto threshold = nano::work_threshold (fork_receive->work_version (), nano::block_details (nano::epoch::epoch_2, false, true, false)); + auto denormalized = nano::denormalized_multiplier (multiplier_receive, threshold); + ASSERT_NEAR (nano::difficulty::to_multiplier (fork_receive->difficulty (), threshold), denormalized, 1e-10); + + // Ensure a fork with updated difficulty will also update the election difficulty + fork_receive->block_work_set (*system.work.generate (fork_receive->root (), fork_receive->difficulty () + 1)); + node.process_active (fork_receive); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::election_block_conflict)); + ASSERT_EQ (3, node.stats.count (nano::stat::type::election, nano::stat::detail::election_difficulty_update)); + auto multiplier_receive_updated = node.active.roots.begin ()->multiplier; + ASSERT_GT (multiplier_receive_updated, multiplier_receive); +} + +TEST (active_transactions, confirm_new) +{ + nano::system system (1); + auto & node1 = *system.nodes[0]; + nano::genesis genesis; + auto send (std::make_shared (genesis.hash (), nano::public_key (), nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node1.process_active (send); + node1.block_processor.flush (); + ASSERT_EQ (1, node1.active.size ()); + auto & node2 = *system.add_node (); + // Add key to node2 + system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); system.deadline_set (5s); - while (node.block_processor.size () > 0) + // Let node2 know about the block + while (node2.block (send->hash ()) == nullptr) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_TRUE (node.active.empty ()); + system.deadline_set (5s); + // Wait confirmation + while (node1.ledger.cache.cemented_count < 2 || node2.ledger.cache.cemented_count < 2) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (active_transactions, restart_dropped) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (node_config); + nano::genesis genesis; + auto send (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::xrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + // Process only in ledger and simulate dropping the election + ASSERT_EQ (nano::process_result::progress, node.process (*send).code); + node.active.recently_dropped.add (send->qualified_root ()); + // Generate higher difficulty work + ASSERT_TRUE (node.work_generate_blocking (*send, send->difficulty () + 1).is_initialized ()); + // Process the same block with updated work + ASSERT_EQ (0, node.active.size ()); + node.process_active (send); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_restart)); + auto ledger_block (node.store.block_get (node.store.tx_begin_read (), send->hash ())); + ASSERT_NE (nullptr, ledger_block); + // Exact same block, including work value must have been re-written + ASSERT_EQ (*send, *ledger_block); + // Removed from the dropped elections cache + ASSERT_EQ (std::chrono::steady_clock::time_point{}, node.active.recently_dropped.find (send->qualified_root ())); + // Drop election + node.active.erase (*send); + ASSERT_EQ (0, node.active.size ()); + // Try to restart election with the same difficulty + node.process_active (send); + node.block_processor.flush (); + ASSERT_EQ (0, node.active.size ()); + ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_restart)); // Verify the block was not updated in the ledger + ASSERT_EQ (*node.store.block_get (node.store.tx_begin_read (), send->hash ()), *send); + // Generate even higher difficulty work + ASSERT_TRUE (node.work_generate_blocking (*send, send->difficulty () + 1).is_initialized ()); + // Add voting + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + // Process the same block with updated work + ASSERT_EQ (0, node.active.size ()); + node.process_active (send); + node.block_processor.flush (); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (1, node.ledger.cache.cemented_count); + ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::election_restart)); + // Wait for the election to complete + ASSERT_TIMELY (5s, node.ledger.cache.cemented_count == 2); +} + +// Ensures votes are tallied on election::publish even if no vote is inserted through inactive_votes_cache +TEST (active_transactions, conflicting_block_vote_existing_election) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_flags); + nano::genesis genesis; + nano::keypair key; + auto send (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 100, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto fork (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 200, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto vote_fork (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, fork)); + + ASSERT_EQ (nano::process_result::progress, node.process_local (send).code); + ASSERT_EQ (1, node.active.size ()); + + // Vote for conflicting block, but the block does not yet exist in the ledger + node.active.vote (vote_fork); + + // Block now gets processed + ASSERT_EQ (nano::process_result::fork, node.process_local (fork).code); + + // Election must be confirmed + auto election (node.active.election (fork->qualified_root ())); + ASSERT_NE (nullptr, election); + ASSERT_TRUE (election->confirmed ()); +} + +TEST (active_transactions, activate_account_chain) +{ + nano::system system; + nano::node_flags flags; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (config, flags); + + nano::keypair key; + nano::state_block_builder builder; + auto send = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + auto send2 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (send->hash ()) + .representative (nano::test_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - 2) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send->hash ())) + .build (); + auto send3 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (send2->hash ()) + .representative (nano::test_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - 3) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send2->hash ())) + .build (); + auto open = builder.make_block () + .account (key.pub) + .previous (0) + .representative (key.pub) + .link (send2->hash ()) + .balance (1) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + auto receive = builder.make_block () + .account (key.pub) + .previous (open->hash ()) + .representative (key.pub) + .link (send3->hash ()) + .balance (2) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + ASSERT_EQ (nano::process_result::progress, node.process (*send).code); + ASSERT_EQ (nano::process_result::progress, node.process (*send2).code); + ASSERT_EQ (nano::process_result::progress, node.process (*send3).code); + ASSERT_EQ (nano::process_result::progress, node.process (*open).code); + ASSERT_EQ (nano::process_result::progress, node.process (*receive).code); + + auto result = node.active.activate (nano::test_genesis_key.pub); + ASSERT_TRUE (result.inserted); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (1, result.election->blocks.count (send->hash ())); + auto result2 = node.active.activate (nano::test_genesis_key.pub); + ASSERT_FALSE (result2.inserted); + ASSERT_EQ (result2.election, result.election); { - auto block (node.store.block_get (node.store.tx_begin_read (), send1->hash ())); - ASSERT_EQ (work2, block->block_work ()); + nano::lock_guard guard (node.active.mutex); + result.election->confirm_once (); + } + ASSERT_TIMELY (3s, node.block_confirmed (send->hash ())); + // On cementing, the next election is started + ASSERT_TIMELY (3s, node.active.active (send2->qualified_root ())); + auto result3 = node.active.activate (nano::test_genesis_key.pub); + ASSERT_FALSE (result3.inserted); + ASSERT_NE (nullptr, result3.election); + ASSERT_EQ (1, result3.election->blocks.count (send2->hash ())); + { + nano::lock_guard guard (node.active.mutex); + result3.election->confirm_once (); + } + ASSERT_TIMELY (3s, node.block_confirmed (send2->hash ())); + // On cementing, the next election is started + ASSERT_TIMELY (3s, node.active.active (open->qualified_root ())); + ASSERT_TIMELY (3s, node.active.active (send3->qualified_root ())); + auto result4 = node.active.activate (nano::test_genesis_key.pub); + ASSERT_FALSE (result4.inserted); + ASSERT_NE (nullptr, result4.election); + ASSERT_EQ (1, result4.election->blocks.count (send3->hash ())); + auto result5 = node.active.activate (key.pub); + ASSERT_FALSE (result5.inserted); + ASSERT_NE (nullptr, result5.election); + ASSERT_EQ (1, result5.election->blocks.count (open->hash ())); + { + nano::lock_guard guard (node.active.mutex); + result5.election->confirm_once (); + } + ASSERT_TIMELY (3s, node.block_confirmed (open->hash ())); + // Until send3 is also confirmed, the receive block should not activate + std::this_thread::sleep_for (200ms); + auto result6 = node.active.activate (key.pub); + ASSERT_FALSE (result6.inserted); + ASSERT_EQ (nullptr, result6.election); + { + nano::lock_guard guard (node.active.mutex); + result4.election->confirm_once (); } + ASSERT_TIMELY (3s, node.block_confirmed (send3->hash ())); + ASSERT_TIMELY (3s, node.active.active (receive->qualified_root ())); } diff --git a/nano/core_test/block.cpp b/nano/core_test/block.cpp index 03aaaa1b23..e6360be079 100644 --- a/nano/core_test/block.cpp +++ b/nano/core_test/block.cpp @@ -1,13 +1,11 @@ #include #include -#include +#include #include #include -#include - #include TEST (ed25519, signing) @@ -279,7 +277,7 @@ TEST (frontier_req, serialization) std::vector bytes; { nano::vectorstream stream (bytes); - request1.serialize (stream); + request1.serialize (stream, false); } auto error (false); nano::bufferstream stream (bytes.data (), bytes.size ()); @@ -299,7 +297,7 @@ TEST (block, publish_req_serialization) std::vector bytes; { nano::vectorstream stream (bytes); - req.serialize (stream); + req.serialize (stream, false); } auto error (false); nano::bufferstream stream2 (bytes.data (), bytes.size ()); @@ -311,6 +309,12 @@ TEST (block, publish_req_serialization) ASSERT_EQ (*req.block, *req2.block); } +TEST (block, difficulty) +{ + nano::send_block block (0, 1, 2, nano::keypair ().prv, 4, 5); + ASSERT_EQ (block.difficulty (), nano::work_difficulty (block.work_version (), block.root (), block.block_work ())); +} + TEST (state_block, serialization) { nano::keypair key1; @@ -368,28 +372,48 @@ TEST (state_block, hashing) nano::keypair key; nano::state_block block (key.pub, 0, key.pub, 0, 0, key.prv, key.pub, 0); auto hash (block.hash ()); + ASSERT_EQ (hash, block.hash ()); // check cache works block.hashables.account.bytes[0] ^= 0x1; + block.refresh (); ASSERT_NE (hash, block.hash ()); block.hashables.account.bytes[0] ^= 0x1; + block.refresh (); ASSERT_EQ (hash, block.hash ()); block.hashables.previous.bytes[0] ^= 0x1; + block.refresh (); ASSERT_NE (hash, block.hash ()); block.hashables.previous.bytes[0] ^= 0x1; + block.refresh (); ASSERT_EQ (hash, block.hash ()); block.hashables.representative.bytes[0] ^= 0x1; + block.refresh (); ASSERT_NE (hash, block.hash ()); block.hashables.representative.bytes[0] ^= 0x1; + block.refresh (); ASSERT_EQ (hash, block.hash ()); block.hashables.balance.bytes[0] ^= 0x1; + block.refresh (); ASSERT_NE (hash, block.hash ()); block.hashables.balance.bytes[0] ^= 0x1; + block.refresh (); ASSERT_EQ (hash, block.hash ()); block.hashables.link.bytes[0] ^= 0x1; + block.refresh (); ASSERT_NE (hash, block.hash ()); block.hashables.link.bytes[0] ^= 0x1; + block.refresh (); ASSERT_EQ (hash, block.hash ()); } +TEST (blocks, work_version) +{ + ASSERT_EQ (nano::work_version::work_1, nano::send_block ().work_version ()); + ASSERT_EQ (nano::work_version::work_1, nano::receive_block ().work_version ()); + ASSERT_EQ (nano::work_version::work_1, nano::change_block ().work_version ()); + ASSERT_EQ (nano::work_version::work_1, nano::open_block ().work_version ()); + ASSERT_EQ (nano::work_version::work_1, nano::state_block ().work_version ()); +} + TEST (block_uniquer, null) { nano::block_uniquer uniquer; diff --git a/nano/core_test/block_store.cpp b/nano/core_test/block_store.cpp index 89fc6b30e1..4e88ef970b 100644 --- a/nano/core_test/block_store.cpp +++ b/nano/core_test/block_store.cpp @@ -1,17 +1,27 @@ #include #include +#include +#include #include +#include #include -#include +#include +#include #include +#include + #if NANO_ROCKSDB #include #endif +#include +#include + #include #include +#include #include @@ -20,8 +30,10 @@ namespace void modify_account_info_to_v13 (nano::mdb_store & store, nano::transaction const & transaction_a, nano::account const & account_a, nano::block_hash const & rep_block); void modify_account_info_to_v14 (nano::mdb_store & store, nano::transaction const & transaction_a, nano::account const & account_a, uint64_t confirmation_height, nano::block_hash const & rep_block); void modify_genesis_account_info_to_v5 (nano::mdb_store & store, nano::transaction const & transaction_a); +void modify_confirmation_height_to_v15 (nano::mdb_store & store, nano::transaction const & transaction, nano::account const & account, uint64_t confirmation_height); void write_sideband_v12 (nano::mdb_store & store_a, nano::transaction & transaction_a, nano::block & block_a, nano::block_hash const & successor_a, MDB_dbi db_a); void write_sideband_v14 (nano::mdb_store & store_a, nano::transaction & transaction_a, nano::block const & block_a, MDB_dbi db_a); +void write_sideband_v15 (nano::mdb_store & store_a, nano::transaction & transaction_a, nano::block const & block_a); } TEST (block_store, construction) @@ -31,10 +43,54 @@ TEST (block_store, construction) ASSERT_TRUE (!store->init_error ()); } +TEST (block_store, block_details) +{ + nano::block_details details_send (nano::epoch::epoch_0, true, false, false); + ASSERT_TRUE (details_send.is_send); + ASSERT_FALSE (details_send.is_receive); + ASSERT_FALSE (details_send.is_epoch); + ASSERT_EQ (nano::epoch::epoch_0, details_send.epoch); + + nano::block_details details_receive (nano::epoch::epoch_1, false, true, false); + ASSERT_FALSE (details_receive.is_send); + ASSERT_TRUE (details_receive.is_receive); + ASSERT_FALSE (details_receive.is_epoch); + ASSERT_EQ (nano::epoch::epoch_1, details_receive.epoch); + + nano::block_details details_epoch (nano::epoch::epoch_2, false, false, true); + ASSERT_FALSE (details_epoch.is_send); + ASSERT_FALSE (details_epoch.is_receive); + ASSERT_TRUE (details_epoch.is_epoch); + ASSERT_EQ (nano::epoch::epoch_2, details_epoch.epoch); + + nano::block_details details_none (nano::epoch::unspecified, false, false, false); + ASSERT_FALSE (details_none.is_send); + ASSERT_FALSE (details_none.is_receive); + ASSERT_FALSE (details_none.is_epoch); + ASSERT_EQ (nano::epoch::unspecified, details_none.epoch); +} + +TEST (block_store, block_details_serialization) +{ + nano::block_details details1; + details1.epoch = nano::epoch::epoch_2; + details1.is_epoch = false; + details1.is_receive = true; + details1.is_send = false; + std::vector vector; + { + nano::vectorstream stream1 (vector); + details1.serialize (stream1); + } + nano::bufferstream stream2 (vector.data (), vector.size ()); + nano::block_details details2; + ASSERT_FALSE (details2.deserialize (stream2)); + ASSERT_EQ (details1, details2); +} + TEST (block_store, sideband_serialization) { nano::block_sideband sideband1; - sideband1.type = nano::block_type::receive; sideband1.account = 1; sideband1.balance = 2; sideband1.height = 3; @@ -43,12 +99,11 @@ TEST (block_store, sideband_serialization) std::vector vector; { nano::vectorstream stream1 (vector); - sideband1.serialize (stream1); + sideband1.serialize (stream1, nano::block_type::receive); } nano::bufferstream stream2 (vector.data (), vector.size ()); nano::block_sideband sideband2; - sideband2.type = nano::block_type::receive; - ASSERT_FALSE (sideband2.deserialize (stream2)); + ASSERT_FALSE (sideband2.deserialize (stream2, nano::block_type::receive)); ASSERT_EQ (sideband1.account, sideband2.account); ASSERT_EQ (sideband1.balance, sideband2.balance); ASSERT_EQ (sideband1.height, sideband2.height); @@ -62,19 +117,19 @@ TEST (block_store, add_item) auto store = nano::make_store (logger, nano::unique_path ()); ASSERT_TRUE (!store->init_error ()); nano::open_block block (0, 1, 0, nano::keypair ().prv, 0, 0); + block.sideband_set ({}); auto hash1 (block.hash ()); auto transaction (store->tx_begin_write ()); auto latest1 (store->block_get (transaction, hash1)); ASSERT_EQ (nullptr, latest1); ASSERT_FALSE (store->block_exists (transaction, hash1)); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash1, block, sideband); + store->block_put (transaction, hash1, block); auto latest2 (store->block_get (transaction, hash1)); ASSERT_NE (nullptr, latest2); ASSERT_EQ (block, *latest2); ASSERT_TRUE (store->block_exists (transaction, hash1)); ASSERT_FALSE (store->block_exists (transaction, hash1.number () - 1)); - store->block_del (transaction, hash1); + store->block_del (transaction, hash1, block.type ()); auto latest3 (store->block_get (transaction, hash1)); ASSERT_EQ (nullptr, latest3); } @@ -85,20 +140,30 @@ TEST (block_store, clear_successor) auto store = nano::make_store (logger, nano::unique_path ()); ASSERT_TRUE (!store->init_error ()); nano::open_block block1 (0, 1, 0, nano::keypair ().prv, 0, 0); + block1.sideband_set ({}); auto transaction (store->tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, block1.hash (), block1, sideband); + store->block_put (transaction, block1.hash (), block1); nano::open_block block2 (0, 2, 0, nano::keypair ().prv, 0, 0); - store->block_put (transaction, block2.hash (), block2, sideband); - ASSERT_NE (nullptr, store->block_get (transaction, block1.hash (), &sideband)); - ASSERT_EQ (0, sideband.successor.number ()); - sideband.successor = block2.hash (); - store->block_put (transaction, block1.hash (), block1, sideband); - ASSERT_NE (nullptr, store->block_get (transaction, block1.hash (), &sideband)); - ASSERT_EQ (block2.hash (), sideband.successor); + block2.sideband_set ({}); + store->block_put (transaction, block2.hash (), block2); + auto block2_store (store->block_get (transaction, block1.hash ())); + ASSERT_NE (nullptr, block2_store); + ASSERT_EQ (0, block2_store->sideband ().successor.number ()); + auto modified_sideband = block2_store->sideband (); + modified_sideband.successor = block2.hash (); + block1.sideband_set (modified_sideband); + store->block_put (transaction, block1.hash (), block1); + { + auto block1_store (store->block_get (transaction, block1.hash ())); + ASSERT_NE (nullptr, block1_store); + ASSERT_EQ (block2.hash (), block1_store->sideband ().successor); + } store->block_successor_clear (transaction, block1.hash ()); - ASSERT_NE (nullptr, store->block_get (transaction, block1.hash (), &sideband)); - ASSERT_EQ (0, sideband.successor.number ()); + { + auto block1_store (store->block_get (transaction, block1.hash ())); + ASSERT_NE (nullptr, block1_store); + ASSERT_EQ (0, block1_store->sideband ().successor.number ()); + } } TEST (block_store, add_nonempty_block) @@ -108,13 +173,13 @@ TEST (block_store, add_nonempty_block) ASSERT_TRUE (!store->init_error ()); nano::keypair key1; nano::open_block block (0, 1, 0, nano::keypair ().prv, 0, 0); + block.sideband_set ({}); auto hash1 (block.hash ()); block.signature = nano::sign_message (key1.prv, key1.pub, hash1); auto transaction (store->tx_begin_write ()); auto latest1 (store->block_get (transaction, hash1)); ASSERT_EQ (nullptr, latest1); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash1, block, sideband); + store->block_put (transaction, hash1, block); auto latest2 (store->block_get (transaction, hash1)); ASSERT_NE (nullptr, latest2); ASSERT_EQ (block, *latest2); @@ -127,21 +192,21 @@ TEST (block_store, add_two_items) ASSERT_TRUE (!store->init_error ()); nano::keypair key1; nano::open_block block (0, 1, 1, nano::keypair ().prv, 0, 0); + block.sideband_set ({}); auto hash1 (block.hash ()); block.signature = nano::sign_message (key1.prv, key1.pub, hash1); auto transaction (store->tx_begin_write ()); auto latest1 (store->block_get (transaction, hash1)); ASSERT_EQ (nullptr, latest1); nano::open_block block2 (0, 1, 3, nano::keypair ().prv, 0, 0); + block2.sideband_set ({}); block2.hashables.account = 3; auto hash2 (block2.hash ()); block2.signature = nano::sign_message (key1.prv, key1.pub, hash2); auto latest2 (store->block_get (transaction, hash2)); ASSERT_EQ (nullptr, latest2); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash1, block, sideband); - nano::block_sideband sideband2 (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash2, block2, sideband2); + store->block_put (transaction, hash1, block); + store->block_put (transaction, hash2, block2); auto latest3 (store->block_get (transaction, hash1)); ASSERT_NE (nullptr, latest3); ASSERT_EQ (block, *latest3); @@ -159,15 +224,15 @@ TEST (block_store, add_receive) nano::keypair key1; nano::keypair key2; nano::open_block block1 (0, 1, 0, nano::keypair ().prv, 0, 0); + block1.sideband_set ({}); auto transaction (store->tx_begin_write ()); - nano::block_sideband sideband1 (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, block1.hash (), block1, sideband1); + store->block_put (transaction, block1.hash (), block1); nano::receive_block block (block1.hash (), 1, nano::keypair ().prv, 2, 3); + block.sideband_set ({}); nano::block_hash hash1 (block.hash ()); auto latest1 (store->block_get (transaction, hash1)); ASSERT_EQ (nullptr, latest1); - nano::block_sideband sideband (nano::block_type::receive, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash1, block, sideband); + store->block_put (transaction, hash1, block); auto latest2 (store->block_get (transaction, hash1)); ASSERT_NE (nullptr, latest2); ASSERT_EQ (block, *latest2); @@ -266,11 +331,9 @@ TEST (block_store, genesis) ASSERT_TRUE (!store->init_error ()); nano::genesis genesis; auto hash (genesis.hash ()); - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; + nano::ledger_cache ledger_cache; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + store->initialize (transaction, genesis, ledger_cache); nano::account_info info; ASSERT_FALSE (store->account_get (transaction, nano::genesis_account, info)); ASSERT_EQ (hash, info.head); @@ -281,9 +344,10 @@ TEST (block_store, genesis) ASSERT_LE (info.modified, nano::seconds_since_epoch ()); ASSERT_EQ (info.block_count, 1); // Genesis block should be confirmed by default - uint64_t confirmation_height; - ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 1); + ASSERT_EQ (confirmation_height_info.frontier, hash); auto test_pub_text (nano::test_genesis_key.pub.to_string ()); auto test_pub_account (nano::test_genesis_key.pub.to_account ()); auto test_prv_text (nano::test_genesis_key.prv.data.to_string ()); @@ -408,9 +472,9 @@ TEST (block_store, one_block) auto store = nano::make_store (logger, nano::unique_path ()); ASSERT_TRUE (!store->init_error ()); nano::open_block block1 (0, 1, 0, nano::keypair ().prv, 0, 0); + block1.sideband_set ({}); auto transaction (store->tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, block1.hash (), block1, sideband); + store->block_put (transaction, block1.hash (), block1); ASSERT_TRUE (store->block_exists (transaction, block1.hash ())); } @@ -465,7 +529,7 @@ TEST (block_store, frontier_retrieval) nano::account account1 (0); nano::account_info info1 (0, 0, 0, 0, 0, 0, nano::epoch::epoch_0); auto transaction (store->tx_begin_write ()); - store->confirmation_height_put (transaction, account1, 0); + store->confirmation_height_put (transaction, account1, { 0, nano::block_hash (0) }); store->account_put (transaction, account1, info1); nano::account_info info2; store->account_get (transaction, account1, info2); @@ -480,7 +544,7 @@ TEST (block_store, one_account) nano::account account (0); nano::block_hash hash (0); auto transaction (store->tx_begin_write ()); - store->confirmation_height_put (transaction, account, 20); + store->confirmation_height_put (transaction, account, { 20, nano::block_hash (15) }); store->account_put (transaction, account, { hash, account, hash, 42, 100, 200, nano::epoch::epoch_0 }); auto begin (store->latest_begin (transaction)); auto end (store->latest_end ()); @@ -491,9 +555,10 @@ TEST (block_store, one_account) ASSERT_EQ (42, info.balance.number ()); ASSERT_EQ (100, info.modified); ASSERT_EQ (200, info.block_count); - uint64_t confirmation_height; - ASSERT_FALSE (store->confirmation_height_get (transaction, account, confirmation_height)); - ASSERT_EQ (20, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store->confirmation_height_get (transaction, account, confirmation_height_info)); + ASSERT_EQ (20, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (15), confirmation_height_info.frontier); ++begin; ASSERT_EQ (end, begin); } @@ -504,19 +569,19 @@ TEST (block_store, two_block) auto store = nano::make_store (logger, nano::unique_path ()); ASSERT_TRUE (!store->init_error ()); nano::open_block block1 (0, 1, 1, nano::keypair ().prv, 0, 0); + block1.sideband_set ({}); block1.hashables.account = 1; std::vector hashes; std::vector blocks; hashes.push_back (block1.hash ()); blocks.push_back (block1); auto transaction (store->tx_begin_write ()); - nano::block_sideband sideband1 (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hashes[0], block1, sideband1); + store->block_put (transaction, hashes[0], block1); nano::open_block block2 (0, 1, 2, nano::keypair ().prv, 0, 0); + block2.sideband_set ({}); hashes.push_back (block2.hash ()); blocks.push_back (block2); - nano::block_sideband sideband2 (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hashes[1], block2, sideband2); + store->block_put (transaction, hashes[1], block2); ASSERT_TRUE (store->block_exists (transaction, block1.hash ())); ASSERT_TRUE (store->block_exists (transaction, block2.hash ())); } @@ -531,9 +596,9 @@ TEST (block_store, two_account) nano::account account2 (3); nano::block_hash hash2 (4); auto transaction (store->tx_begin_write ()); - store->confirmation_height_put (transaction, account1, 20); + store->confirmation_height_put (transaction, account1, { 20, nano::block_hash (10) }); store->account_put (transaction, account1, { hash1, account1, hash1, 42, 100, 300, nano::epoch::epoch_0 }); - store->confirmation_height_put (transaction, account2, 30); + store->confirmation_height_put (transaction, account2, { 30, nano::block_hash (20) }); store->account_put (transaction, account2, { hash2, account2, hash2, 84, 200, 400, nano::epoch::epoch_0 }); auto begin (store->latest_begin (transaction)); auto end (store->latest_end ()); @@ -544,9 +609,10 @@ TEST (block_store, two_account) ASSERT_EQ (42, info1.balance.number ()); ASSERT_EQ (100, info1.modified); ASSERT_EQ (300, info1.block_count); - uint64_t confirmation_height; - ASSERT_FALSE (store->confirmation_height_get (transaction, account1, confirmation_height)); - ASSERT_EQ (20, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store->confirmation_height_get (transaction, account1, confirmation_height_info)); + ASSERT_EQ (20, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (10), confirmation_height_info.frontier); ++begin; ASSERT_NE (end, begin); ASSERT_EQ (account2, nano::account (begin->first)); @@ -555,8 +621,9 @@ TEST (block_store, two_account) ASSERT_EQ (84, info2.balance.number ()); ASSERT_EQ (200, info2.modified); ASSERT_EQ (400, info2.block_count); - ASSERT_FALSE (store->confirmation_height_get (transaction, account2, confirmation_height)); - ASSERT_EQ (30, confirmation_height); + ASSERT_FALSE (store->confirmation_height_get (transaction, account2, confirmation_height_info)); + ASSERT_EQ (30, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (20), confirmation_height_info.frontier); ++begin; ASSERT_EQ (end, begin); } @@ -571,9 +638,9 @@ TEST (block_store, latest_find) nano::account account2 (3); nano::block_hash hash2 (4); auto transaction (store->tx_begin_write ()); - store->confirmation_height_put (transaction, account1, 0); + store->confirmation_height_put (transaction, account1, { 0, nano::block_hash (0) }); store->account_put (transaction, account1, { hash1, account1, hash1, 100, 0, 300, nano::epoch::epoch_0 }); - store->confirmation_height_put (transaction, account2, 0); + store->confirmation_height_put (transaction, account2, { 0, nano::block_hash (0) }); store->account_put (transaction, account2, { hash2, account2, hash2, 200, 0, 400, nano::epoch::epoch_0 }); auto first (store->latest_begin (transaction)); auto second (store->latest_begin (transaction)); @@ -642,7 +709,7 @@ TEST (block_store, latest_exists) nano::account two (2); nano::account_info info; auto transaction (store->tx_begin_write ()); - store->confirmation_height_put (transaction, two, 0); + store->confirmation_height_put (transaction, two, { 0, nano::block_hash (0) }); store->account_put (transaction, two, info); nano::account one (1); ASSERT_FALSE (store->account_exists (transaction, one)); @@ -660,7 +727,7 @@ TEST (block_store, large_iteration) nano::account account; nano::random_pool::generate_block (account.bytes.data (), account.bytes.size ()); accounts1.insert (account); - store->confirmation_height_put (transaction, account, 0); + store->confirmation_height_put (transaction, account, { 0, nano::block_hash (0) }); store->account_put (transaction, account, nano::account_info ()); } std::unordered_set accounts2; @@ -669,7 +736,7 @@ TEST (block_store, large_iteration) for (auto i (store->latest_begin (transaction, 0)), n (store->latest_end ()); i != n; ++i) { nano::account current (i->first); - assert (current.number () > previous.number ()); + ASSERT_GT (current.number (), previous.number ()); accounts2.insert (current); previous = current; } @@ -697,12 +764,12 @@ TEST (block_store, block_replace) auto store = nano::make_store (logger, nano::unique_path ()); ASSERT_TRUE (!store->init_error ()); nano::send_block send1 (0, 0, 0, nano::keypair ().prv, 0, 1); + send1.sideband_set ({}); nano::send_block send2 (0, 0, 0, nano::keypair ().prv, 0, 2); + send2.sideband_set ({}); auto transaction (store->tx_begin_write ()); - nano::block_sideband sideband1 (nano::block_type::send, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, 0, send1, sideband1); - nano::block_sideband sideband2 (nano::block_type::send, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, 0, send2, sideband2); + store->block_put (transaction, 0, send1); + store->block_put (transaction, 0, send2); auto block3 (store->block_get (transaction, 0)); ASSERT_NE (nullptr, block3); ASSERT_EQ (2, block3->block_work ()); @@ -717,9 +784,9 @@ TEST (block_store, block_count) auto transaction (store->tx_begin_write ()); ASSERT_EQ (0, store->block_count (transaction).sum ()); nano::open_block block (0, 1, 0, nano::keypair ().prv, 0, 0); + block.sideband_set ({}); auto hash1 (block.hash ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, hash1, block, sideband); + store->block_put (transaction, hash1, block); } auto transaction (store->tx_begin_read ()); ASSERT_EQ (1, store->block_count (transaction).sum ()); @@ -734,7 +801,7 @@ TEST (block_store, account_count) auto transaction (store->tx_begin_write ()); ASSERT_EQ (0, store->account_count (transaction)); nano::account account (200); - store->confirmation_height_put (transaction, account, 0); + store->confirmation_height_put (transaction, account, { 0, nano::block_hash (0) }); store->account_put (transaction, account, nano::account_info ()); } auto transaction (store->tx_begin_read ()); @@ -748,11 +815,9 @@ TEST (block_store, cemented_count_cache) ASSERT_TRUE (!store->init_error ()); auto transaction (store->tx_begin_write ()); nano::genesis genesis; - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store->initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); - ASSERT_EQ (1, cemented_count); + nano::ledger_cache ledger_cache; + store->initialize (transaction, genesis, ledger_cache); + ASSERT_EQ (1, ledger_cache.cemented_count); } TEST (block_store, sequence_increment) @@ -799,7 +864,7 @@ TEST (mdb_block_store, upgrade_v2_v3) auto hash (genesis.hash ()); nano::stat stats; nano::ledger ledger (store, stats); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::change_block change (hash, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (hash)); change_hash = change.hash (); @@ -807,18 +872,18 @@ TEST (mdb_block_store, upgrade_v2_v3) ASSERT_EQ (0, ledger.weight (nano::test_genesis_key.pub)); ASSERT_EQ (nano::genesis_amount, ledger.weight (key1.pub)); store.version_put (transaction, 2); - ledger.rep_weights.representation_put (key1.pub, 7); + ledger.cache.rep_weights.representation_put (key1.pub, 7); ASSERT_EQ (7, ledger.weight (key1.pub)); ASSERT_EQ (2, store.version_get (transaction)); - ledger.rep_weights.representation_put (key2.pub, 6); + ledger.cache.rep_weights.representation_put (key2.pub, 6); ASSERT_EQ (6, ledger.weight (key2.pub)); nano::account_info info; ASSERT_FALSE (store.account_get (transaction, nano::test_genesis_key.pub, info)); auto rep_block = ledger.representative (transaction, ledger.latest (transaction, nano::test_genesis_key.pub)); nano::account_info_v5 info_old (info.head, rep_block, info.open_block, info.balance, info.modified); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (nano::test_genesis_key.pub), nano::mdb_val (sizeof (info_old), &info_old), 0)); - (void)status; - assert (status == 0); + ASSERT_EQ (status, 0); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); @@ -879,7 +944,7 @@ TEST (mdb_block_store, upgrade_v4_v5) nano::genesis genesis; nano::stat stats; nano::ledger ledger (store, stats); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); store.version_put (transaction, 4); nano::account_info info; ASSERT_FALSE (store.account_get (transaction, nano::test_genesis_key.pub, info)); @@ -896,6 +961,7 @@ TEST (mdb_block_store, upgrade_v4_v5) // The pending send needs to be the correct version auto status (mdb_put (store.env.tx (transaction), store.pending_v0, nano::mdb_val (nano::pending_key (key0.pub, block0.hash ())), nano::mdb_val (nano::pending_info_v14 (nano::genesis_account, nano::Gxrb_ratio, nano::epoch::epoch_0)), 0)); ASSERT_EQ (status, MDB_SUCCESS); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); @@ -911,11 +977,9 @@ TEST (block_store, block_random) ASSERT_TRUE (!store->init_error ()); nano::genesis genesis; { - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; + nano::ledger_cache ledger_cache; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + store->initialize (transaction, genesis, ledger_cache); } auto transaction (store->tx_begin_read ()); auto block (store->block_random (transaction)); @@ -932,12 +996,11 @@ TEST (mdb_block_store, upgrade_v5_v6) ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); nano::genesis genesis; - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store.initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + nano::ledger_cache ledger_cache; + store.initialize (transaction, genesis, ledger_cache); store.version_put (transaction, 5); modify_genesis_account_info_to_v5 (store, transaction); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); @@ -957,16 +1020,15 @@ TEST (mdb_block_store, upgrade_v6_v7) ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); nano::genesis genesis; - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store.initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + nano::ledger_cache ledger_cache; + store.initialize (transaction, genesis, ledger_cache); store.version_put (transaction, 6); - modify_account_info_to_v13 (store, transaction, nano::genesis_account, genesis.open->hash ()); + modify_account_info_to_v13 (store, transaction, nano::genesis_account, nano::genesis_hash); auto send1 (std::make_shared (0, 0, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); store.unchecked_put (transaction, send1->hash (), send1); store.flush (transaction); ASSERT_NE (store.unchecked_end (), store.unchecked_begin (transaction)); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); @@ -1075,7 +1137,7 @@ TEST (block_store, sequence_flush_by_hash) auto transaction (store->tx_begin_write ()); nano::keypair key1; std::vector blocks1; - blocks1.push_back (nano::genesis ().hash ()); + blocks1.push_back (nano::genesis_hash); blocks1.push_back (1234); blocks1.push_back (5678); auto vote1 (store->vote_generate (transaction, key1.pub, key1.prv, blocks1)); @@ -1119,15 +1181,13 @@ TEST (block_store, state_block) nano::genesis genesis; nano::keypair key1; nano::state_block block1 (1, genesis.hash (), 3, 4, 6, key1.prv, key1.pub, 7); + block1.sideband_set ({}); { + nano::ledger_cache ledger_cache; auto transaction (store->tx_begin_write ()); - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store->initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + store->initialize (transaction, genesis, ledger_cache); ASSERT_EQ (nano::block_type::state, block1.type ()); - nano::block_sideband sideband1 (nano::block_type::state, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (transaction, block1.hash (), block1, sideband1); + store->block_put (transaction, block1.hash (), block1); ASSERT_TRUE (store->block_exists (transaction, block1.hash ())); auto block2 (store->block_get (transaction, block1.hash ())); ASSERT_NE (nullptr, block2); @@ -1137,7 +1197,7 @@ TEST (block_store, state_block) auto transaction (store->tx_begin_write ()); auto count (store->block_count (transaction)); ASSERT_EQ (1, count.state); - store->block_del (transaction, block1.hash ()); + store->block_del (transaction, block1.hash (), block1.type ()); ASSERT_FALSE (store->block_exists (transaction, block1.hash ())); } auto transaction (store->tx_begin_read ()); @@ -1155,31 +1215,28 @@ TEST (mdb_block_store, upgrade_sideband_genesis) ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 11); - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store.initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); - modify_account_info_to_v13 (store, transaction, nano::genesis_account, genesis.open->hash ()); - nano::block_sideband sideband; - auto genesis_block (store.block_get (transaction, genesis.hash (), &sideband)); + nano::ledger_cache ledger_cache; + store.initialize (transaction, genesis, ledger_cache); + modify_account_info_to_v13 (store, transaction, nano::genesis_account, nano::genesis_hash); + auto genesis_block (store.block_get (transaction, genesis.hash ())); ASSERT_NE (nullptr, genesis_block); - ASSERT_EQ (1, sideband.height); + ASSERT_EQ (1, genesis_block->sideband ().height); ASSERT_FALSE (mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1)); write_sideband_v12 (store, transaction, *genesis_block, 0, store.open_blocks); nano::block_sideband_v14 sideband1; auto genesis_block2 (store.block_get_v14 (transaction, genesis.hash (), &sideband1)); ASSERT_NE (nullptr, genesis_block); ASSERT_EQ (0, sideband1.height); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_read ()); ASSERT_TRUE (store.full_sideband (transaction)); - nano::block_sideband sideband; - auto genesis_block (store.block_get (transaction, genesis.hash (), &sideband)); + auto genesis_block (store.block_get (transaction, genesis.hash ())); ASSERT_NE (nullptr, genesis_block); - ASSERT_EQ (1, sideband.height); + ASSERT_EQ (1, genesis_block->sideband ().height); } TEST (mdb_block_store, upgrade_sideband_two_blocks) @@ -1195,12 +1252,12 @@ TEST (mdb_block_store, upgrade_sideband_two_blocks) nano::ledger ledger (store, stat); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 11); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block block (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); hash2 = block.hash (); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block).code); - store.block_del (transaction, hash2); + store.block_del (transaction, hash2, block.type ()); mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1); mdb_dbi_open (store.env.tx (transaction), "state", MDB_CREATE, &store.state_blocks_v0); write_sideband_v12 (store, transaction, *genesis.open, hash2, store.open_blocks); @@ -1208,20 +1265,19 @@ TEST (mdb_block_store, upgrade_sideband_two_blocks) modify_account_info_to_v13 (store, transaction, nano::genesis_account, hash2); auto status (mdb_put (store.env.tx (transaction), store.pending_v0, nano::mdb_val (nano::pending_key (nano::test_genesis_key.pub, block.hash ())), nano::mdb_val (nano::pending_info_v14 (nano::genesis_account, nano::Gxrb_ratio, nano::epoch::epoch_0)), 0)); ASSERT_EQ (status, MDB_SUCCESS); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_read ()); ASSERT_TRUE (store.full_sideband (transaction)); - nano::block_sideband sideband; - auto genesis_block (store.block_get (transaction, genesis.hash (), &sideband)); + auto genesis_block (store.block_get (transaction, genesis.hash ())); ASSERT_NE (nullptr, genesis_block); - ASSERT_EQ (1, sideband.height); - nano::block_sideband sideband2; - auto block2 (store.block_get (transaction, hash2, &sideband2)); + ASSERT_EQ (1, genesis_block->sideband ().height); + auto block2 (store.block_get (transaction, hash2)); ASSERT_NE (nullptr, block2); - ASSERT_EQ (2, sideband2.height); + ASSERT_EQ (2, block2->sideband ().height); } TEST (mdb_block_store, upgrade_sideband_two_accounts) @@ -1238,7 +1294,7 @@ TEST (mdb_block_store, upgrade_sideband_two_accounts) nano::ledger ledger (store, stat); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 11); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block block1 (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); hash2 = block1.hash (); @@ -1246,8 +1302,8 @@ TEST (mdb_block_store, upgrade_sideband_two_accounts) nano::state_block block2 (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, hash2, key.prv, key.pub, *pool.generate (key.pub)); hash3 = block2.hash (); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block2).code); - store.block_del (transaction, hash2); - store.block_del (transaction, hash3); + store.block_del (transaction, hash2, block1.type ()); + store.block_del (transaction, hash3, block2.type ()); mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1); mdb_dbi_open (store.env.tx (transaction), "state", MDB_CREATE, &store.state_blocks_v0); write_sideband_v12 (store, transaction, *genesis.open, hash2, store.open_blocks); @@ -1255,24 +1311,23 @@ TEST (mdb_block_store, upgrade_sideband_two_accounts) write_sideband_v12 (store, transaction, block2, 0, store.state_blocks_v0); modify_account_info_to_v13 (store, transaction, nano::genesis_account, hash2); modify_account_info_to_v13 (store, transaction, block2.account (), hash3); + store.confirmation_height_del (transaction, nano::genesis_account); + store.confirmation_height_del (transaction, key.pub); } nano::logger_mt logger; nano::mdb_store store (logger, path); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_read ()); ASSERT_TRUE (store.full_sideband (transaction)); - nano::block_sideband sideband; - auto genesis_block (store.block_get (transaction, genesis.hash (), &sideband)); + auto genesis_block (store.block_get (transaction, genesis.hash ())); ASSERT_NE (nullptr, genesis_block); - ASSERT_EQ (1, sideband.height); - nano::block_sideband sideband2; - auto block2 (store.block_get (transaction, hash2, &sideband2)); + ASSERT_EQ (1, genesis_block->sideband ().height); + auto block2 (store.block_get (transaction, hash2)); ASSERT_NE (nullptr, block2); - ASSERT_EQ (2, sideband2.height); - nano::block_sideband sideband3; - auto block3 (store.block_get (transaction, hash3, &sideband3)); + ASSERT_EQ (2, block2->sideband ().height); + auto block3 (store.block_get (transaction, hash3)); ASSERT_NE (nullptr, block3); - ASSERT_EQ (1, sideband3.height); + ASSERT_EQ (1, block3->sideband ().height); } TEST (mdb_block_store, insert_after_legacy) @@ -1285,7 +1340,7 @@ TEST (mdb_block_store, insert_after_legacy) nano::ledger ledger (store, stat); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 11); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1); write_sideband_v12 (store, transaction, *genesis.open, 0, store.open_blocks); nano::work_pool pool (std::numeric_limits::max ()); @@ -1303,7 +1358,7 @@ TEST (mdb_block_store, legacy_account_computed) nano::ledger ledger (store, stats); nano::genesis genesis; auto transaction (store.tx_begin_write ()); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); store.version_put (transaction, 11); mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1); write_sideband_v12 (store, transaction, *genesis.open, 0, store.open_blocks); @@ -1325,24 +1380,25 @@ TEST (mdb_block_store, upgrade_sideband_epoch) nano::ledger ledger (store, stat); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 11); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::state_block block1 (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); hash2 = block1.hash (); ASSERT_FALSE (mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block1).code); ASSERT_EQ (nano::epoch::epoch_1, store.block_version (transaction, hash2)); - store.block_del (transaction, hash2); - store.block_del (transaction, genesis.open->hash ()); + store.block_del (transaction, hash2, block1.type ()); + store.block_del (transaction, genesis.open->hash (), genesis.open->type ()); write_sideband_v12 (store, transaction, *genesis.open, hash2, store.open_blocks); write_sideband_v12 (store, transaction, block1, 0, store.state_blocks_v1); nano::mdb_val value; ASSERT_FALSE (mdb_get (store.env.tx (transaction), store.state_blocks_v1, nano::mdb_val (hash2), value)); - ASSERT_FALSE (mdb_get (store.env.tx (transaction), store.open_blocks, nano::mdb_val (genesis.open->hash ()), value)); + ASSERT_FALSE (mdb_get (store.env.tx (transaction), store.open_blocks, nano::mdb_val (nano::genesis_hash), value)); ASSERT_FALSE (mdb_dbi_open (store.env.tx (transaction), "accounts_v1", MDB_CREATE, &store.accounts_v1)); modify_account_info_to_v13 (store, transaction, nano::genesis_account, hash2); store.account_del (transaction, nano::genesis_account); + store.confirmation_height_del (transaction, nano::genesis_account); } nano::logger_mt logger; nano::mdb_store store (logger, path); @@ -1352,9 +1408,8 @@ TEST (mdb_block_store, upgrade_sideband_epoch) auto transaction (store.tx_begin_write ()); ASSERT_TRUE (store.full_sideband (transaction)); ASSERT_EQ (nano::epoch::epoch_1, store.block_version (transaction, hash2)); - nano::block_sideband sideband; - auto block1 (store.block_get (transaction, hash2, &sideband)); - ASSERT_NE (0, sideband.height); + auto block1 (store.block_get (transaction, hash2)); + ASSERT_NE (0, block1->sideband ().height); nano::state_block block2 (nano::test_genesis_key.pub, hash2, nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (hash2)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block2).code); ASSERT_EQ (nano::epoch::epoch_1, store.block_version (transaction, block2.hash ())); @@ -1372,7 +1427,7 @@ TEST (mdb_block_store, sideband_height) nano::stat stat; nano::ledger ledger (store, stat); auto transaction (store.tx_begin_write ()); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send (genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); @@ -1398,42 +1453,30 @@ TEST (mdb_block_store, sideband_height) ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_receive).code); nano::open_block open (state_send3.hash (), nano::test_genesis_key.pub, key3.pub, key3.prv, key3.pub, *pool.generate (key3.pub)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open).code); - nano::block_sideband sideband1; - auto block1 (store.block_get (transaction, genesis.hash (), &sideband1)); - ASSERT_EQ (sideband1.height, 1); - nano::block_sideband sideband2; - auto block2 (store.block_get (transaction, send.hash (), &sideband2)); - ASSERT_EQ (sideband2.height, 2); - nano::block_sideband sideband3; - auto block3 (store.block_get (transaction, receive.hash (), &sideband3)); - ASSERT_EQ (sideband3.height, 3); - nano::block_sideband sideband4; - auto block4 (store.block_get (transaction, change.hash (), &sideband4)); - ASSERT_EQ (sideband4.height, 4); - nano::block_sideband sideband5; - auto block5 (store.block_get (transaction, state_send1.hash (), &sideband5)); - ASSERT_EQ (sideband5.height, 5); - nano::block_sideband sideband6; - auto block6 (store.block_get (transaction, state_send2.hash (), &sideband6)); - ASSERT_EQ (sideband6.height, 6); - nano::block_sideband sideband7; - auto block7 (store.block_get (transaction, state_send3.hash (), &sideband7)); - ASSERT_EQ (sideband7.height, 7); - nano::block_sideband sideband8; - auto block8 (store.block_get (transaction, state_open.hash (), &sideband8)); - ASSERT_EQ (sideband8.height, 1); - nano::block_sideband sideband9; - auto block9 (store.block_get (transaction, epoch.hash (), &sideband9)); - ASSERT_EQ (sideband9.height, 2); - nano::block_sideband sideband10; - auto block10 (store.block_get (transaction, epoch_open.hash (), &sideband10)); - ASSERT_EQ (sideband10.height, 1); - nano::block_sideband sideband11; - auto block11 (store.block_get (transaction, state_receive.hash (), &sideband11)); - ASSERT_EQ (sideband11.height, 2); - nano::block_sideband sideband12; - auto block12 (store.block_get (transaction, open.hash (), &sideband12)); - ASSERT_EQ (sideband12.height, 1); + auto block1 (store.block_get (transaction, genesis.hash ())); + ASSERT_EQ (block1->sideband ().height, 1); + auto block2 (store.block_get (transaction, send.hash ())); + ASSERT_EQ (block2->sideband ().height, 2); + auto block3 (store.block_get (transaction, receive.hash ())); + ASSERT_EQ (block3->sideband ().height, 3); + auto block4 (store.block_get (transaction, change.hash ())); + ASSERT_EQ (block4->sideband ().height, 4); + auto block5 (store.block_get (transaction, state_send1.hash ())); + ASSERT_EQ (block5->sideband ().height, 5); + auto block6 (store.block_get (transaction, state_send2.hash ())); + ASSERT_EQ (block6->sideband ().height, 6); + auto block7 (store.block_get (transaction, state_send3.hash ())); + ASSERT_EQ (block7->sideband ().height, 7); + auto block8 (store.block_get (transaction, state_open.hash ())); + ASSERT_EQ (block8->sideband ().height, 1); + auto block9 (store.block_get (transaction, epoch.hash ())); + ASSERT_EQ (block9->sideband ().height, 2); + auto block10 (store.block_get (transaction, epoch_open.hash ())); + ASSERT_EQ (block10->sideband ().height, 1); + auto block11 (store.block_get (transaction, state_receive.hash ())); + ASSERT_EQ (block11->sideband ().height, 2); + auto block12 (store.block_get (transaction, open.hash ())); + ASSERT_EQ (block12->sideband ().height, 1); } TEST (block_store, peers) @@ -1503,7 +1546,7 @@ TEST (block_store, peers) TEST (block_store, endpoint_key_byte_order) { - boost::asio::ip::address_v6 address (boost::asio::ip::address_v6::from_string ("::ffff:127.0.0.1")); + boost::asio::ip::address_v6 address (boost::asio::ip::make_address_v6 ("::ffff:127.0.0.1")); uint16_t port = 100; nano::endpoint_key endpoint_key (address.to_bytes (), port); @@ -1563,28 +1606,24 @@ TEST (block_store, online_weight) TEST (mdb_block_store, upgrade_v13_v14) { auto path (nano::unique_path ()); + nano::genesis genesis; { nano::logger_mt logger; - nano::genesis genesis; nano::mdb_store store (logger, path); auto transaction (store.tx_begin_write ()); - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store.initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + nano::ledger_cache ledger_cache; + store.initialize (transaction, genesis, ledger_cache); nano::account_info account_info; ASSERT_FALSE (store.account_get (transaction, nano::genesis_account, account_info)); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); store.version_put (transaction, 13); - modify_account_info_to_v13 (store, transaction, nano::genesis_account, genesis.open->hash ()); + modify_account_info_to_v13 (store, transaction, nano::genesis_account, nano::genesis_hash); // This should fail as sizes are no longer correct for account_info_v14 nano::mdb_val value; ASSERT_FALSE (mdb_get (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (nano::genesis_account), value)); nano::account_info_v14 info; ASSERT_NE (value.size (), info.db_size ()); + store.confirmation_height_del (transaction, nano::genesis_account); } // Now do the upgrade @@ -1601,9 +1640,10 @@ TEST (mdb_block_store, upgrade_v13_v14) ASSERT_EQ (value.size (), info.db_size ()); // Confirmation height should exist and be correct - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 1); + ASSERT_EQ (confirmation_height_info.frontier, genesis.hash ()); // Test deleting node ID nano::uint256_union node_id_mdb_key (3); @@ -1629,12 +1669,13 @@ TEST (mdb_block_store, upgrade_v14_v15) nano::stat stats; nano::ledger ledger (store, stats); auto transaction (store.tx_begin_write ()); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::account_info account_info; ASSERT_FALSE (store.account_get (transaction, nano::genesis_account, account_info)); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 1); + ASSERT_EQ (confirmation_height_info.frontier, genesis.hash ()); // These databases get remove after an upgrade, so readd them ASSERT_FALSE (mdb_dbi_open (store.env.tx (transaction), "state_v1", MDB_CREATE, &store.state_blocks_v1)); ASSERT_FALSE (mdb_dbi_open (store.env.tx (transaction), "accounts_v1", MDB_CREATE, &store.accounts_v1)); @@ -1645,7 +1686,7 @@ TEST (mdb_block_store, upgrade_v14_v15) // Lower the database to the previous version store.version_put (transaction, 14); store.confirmation_height_del (transaction, nano::genesis_account); - modify_account_info_to_v14 (store, transaction, nano::genesis_account, confirmation_height, state_send.hash ()); + modify_account_info_to_v14 (store, transaction, nano::genesis_account, confirmation_height_info.height, state_send.hash ()); store.pending_del (transaction, nano::pending_key (nano::genesis_account, state_send.hash ())); @@ -1653,8 +1694,8 @@ TEST (mdb_block_store, upgrade_v14_v15) write_sideband_v14 (store, transaction, epoch, store.state_blocks_v1); // Remove from state table - store.block_del (transaction, state_send.hash ()); - store.block_del (transaction, epoch.hash ()); + store.block_del (transaction, state_send.hash (), state_send.type ()); + store.block_del (transaction, epoch.hash (), epoch.type ()); // Turn pending into v14 ASSERT_FALSE (mdb_put (store.env.tx (transaction), store.pending_v0, nano::mdb_val (nano::pending_key (nano::test_genesis_key.pub, send.hash ())), nano::mdb_val (nano::pending_info_v14 (nano::genesis_account, nano::Gxrb_ratio, nano::epoch::epoch_0)), 0)); @@ -1668,7 +1709,7 @@ TEST (mdb_block_store, upgrade_v14_v15) store.account_del (transaction, nano::genesis_account); // Confirmation height for the account should be deleted - ASSERT_TRUE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); + ASSERT_TRUE (mdb_get (store.env.tx (transaction), store.confirmation_height, nano::mdb_val (nano::genesis_account), value)); } // Now do the upgrade @@ -1685,14 +1726,10 @@ TEST (mdb_block_store, upgrade_v14_v15) ASSERT_EQ (value.size (), info.db_size ()); // Confirmation height should exist - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); - - // The representation table should be deleted - auto error_get_representation (mdb_get (store.env.tx (transaction), store.representation, nano::mdb_val (nano::genesis_account), value)); - ASSERT_NE (error_get_representation, MDB_SUCCESS); - ASSERT_EQ (store.representation, 0); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 1); + ASSERT_EQ (confirmation_height_info.frontier, genesis.hash ()); // accounts_v1, state_blocks_v1 & pending_v1 tables should be deleted auto error_get_accounts_v1 (mdb_get (store.env.tx (transaction), store.accounts_v1, nano::mdb_val (nano::genesis_account), value)); @@ -1703,14 +1740,12 @@ TEST (mdb_block_store, upgrade_v14_v15) ASSERT_NE (error_get_state_v1, MDB_SUCCESS); // Check that the epochs are set correctly for the sideband, accounts and pending entries - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_send.hash (), &sideband); + auto block = store.block_get (transaction, state_send.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.epoch, nano::epoch::epoch_1); - block = store.block_get (transaction, send.hash (), &sideband); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + block = store.block_get (transaction, send.hash ()); ASSERT_NE (block, nullptr); - nano::block_sideband sideband1; - ASSERT_EQ (sideband1.epoch, nano::epoch::epoch_0); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_0); ASSERT_EQ (info.epoch (), nano::epoch::epoch_1); nano::pending_info pending_info; store.pending_get (transaction, nano::pending_key (nano::test_genesis_key.pub, send.hash ()), pending_info); @@ -1722,6 +1757,284 @@ TEST (mdb_block_store, upgrade_v14_v15) ASSERT_LT (14, store.version_get (transaction)); } +TEST (mdb_block_store, upgrade_v15_v16) +{ + auto path (nano::unique_path ()); + nano::mdb_val value; + { + nano::genesis genesis; + nano::logger_mt logger; + nano::mdb_store store (logger, path); + nano::stat stats; + nano::ledger ledger (store, stats); + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + // The representation table should get removed after, so readd it so that we can later confirm this actually happens + auto txn = store.env.tx (transaction); + ASSERT_FALSE (mdb_dbi_open (txn, "representation", MDB_CREATE, &store.representation)); + auto weight = ledger.cache.rep_weights.representation_get (nano::genesis_account); + ASSERT_EQ (MDB_SUCCESS, mdb_put (txn, store.representation, nano::mdb_val (nano::genesis_account), nano::mdb_val (nano::uint128_union (weight)), 0)); + // Lower the database to the previous version + store.version_put (transaction, 15); + // Confirm the rep weight exists in the database + ASSERT_EQ (MDB_SUCCESS, mdb_get (store.env.tx (transaction), store.representation, nano::mdb_val (nano::genesis_account), value)); + store.confirmation_height_del (transaction, nano::genesis_account); + } + + // Now do the upgrade + nano::logger_mt logger; + auto error (false); + nano::mdb_store store (logger, path); + ASSERT_FALSE (error); + auto transaction (store.tx_begin_read ()); + + // The representation table should now be deleted + auto error_get_representation (mdb_get (store.env.tx (transaction), store.representation, nano::mdb_val (nano::genesis_account), value)); + ASSERT_NE (MDB_SUCCESS, error_get_representation); + ASSERT_EQ (store.representation, 0); + + // Version should be correct + ASSERT_LT (15, store.version_get (transaction)); +} + +TEST (mdb_block_store, upgrade_v16_v17) +{ + nano::genesis genesis; + nano::work_pool pool (std::numeric_limits::max ()); + nano::state_block block1 (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); + nano::state_block block2 (nano::test_genesis_key.pub, block1.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio - 1, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (block1.hash ())); + nano::state_block block3 (nano::test_genesis_key.pub, block2.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio - 2, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (block2.hash ())); + + auto code = [&block1, &block2, &block3](auto confirmation_height, nano::block_hash const & expected_cemented_frontier) { + auto path (nano::unique_path ()); + nano::mdb_val value; + { + nano::genesis genesis; + nano::logger_mt logger; + nano::mdb_store store (logger, path); + nano::stat stats; + nano::ledger ledger (store, stats); + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block1).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block2).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block3).code); + modify_confirmation_height_to_v15 (store, transaction, nano::genesis_account, confirmation_height); + + // Lower the database to the previous version + store.version_put (transaction, 16); + } + + // Now do the upgrade + nano::logger_mt logger; + auto error (false); + nano::mdb_store store (logger, path); + ASSERT_FALSE (error); + auto transaction (store.tx_begin_read ()); + + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, confirmation_height); + + // Check confirmation height frontier is correct + ASSERT_EQ (confirmation_height_info.frontier, expected_cemented_frontier); + + // Version should be correct + ASSERT_LT (16, store.version_get (transaction)); + }; + + code (0, nano::block_hash (0)); + code (1, genesis.hash ()); + code (2, block1.hash ()); + code (3, block2.hash ()); + code (4, block3.hash ()); +} + +TEST (mdb_block_store, upgrade_v17_v18) +{ + auto path (nano::unique_path ()); + nano::genesis genesis; + nano::keypair key1; + nano::keypair key2; + nano::keypair key3; + nano::network_params network_params; + nano::work_pool pool (std::numeric_limits::max ()); + nano::send_block send_zero (genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); + nano::state_block state_receive_zero (nano::test_genesis_key.pub, send_zero.hash (), nano::test_genesis_key.pub, nano::genesis_amount, send_zero.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (send_zero.hash ())); + nano::state_block epoch (nano::test_genesis_key.pub, state_receive_zero.hash (), nano::test_genesis_key.pub, nano::genesis_amount, network_params.ledger.epochs.link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (state_receive_zero.hash ())); + nano::state_block state_send (nano::test_genesis_key.pub, epoch.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (epoch.hash ())); + nano::state_block state_receive (nano::test_genesis_key.pub, state_send.hash (), nano::test_genesis_key.pub, nano::genesis_amount, state_send.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (state_send.hash ())); + nano::state_block state_change (nano::test_genesis_key.pub, state_receive.hash (), nano::test_genesis_key.pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (state_receive.hash ())); + nano::state_block state_send_change (nano::test_genesis_key.pub, state_change.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (state_change.hash ())); + nano::state_block epoch_first (key1.pub, 0, 0, 0, network_params.ledger.epochs.link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (key1.pub)); + nano::state_block state_receive2 (key1.pub, epoch_first.hash (), key1.pub, nano::Gxrb_ratio, state_send_change.hash (), key1.prv, key1.pub, *pool.generate (epoch_first.hash ())); + nano::state_block state_send2 (nano::test_genesis_key.pub, state_send_change.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (state_send_change.hash ())); + nano::state_block state_open (key2.pub, 0, key2.pub, nano::Gxrb_ratio, state_send2.hash (), key2.prv, key2.pub, *pool.generate (key2.pub)); + nano::state_block state_send_epoch_link (key2.pub, state_open.hash (), key2.pub, 0, network_params.ledger.epochs.link (nano::epoch::epoch_2), key2.prv, key2.pub, *pool.generate (state_open.hash ())); + { + nano::logger_mt logger; + nano::mdb_store store (logger, path); + auto transaction (store.tx_begin_write ()); + nano::stat stats; + nano::ledger ledger (store, stats); + store.initialize (transaction, genesis, ledger.cache); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send_zero).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_receive_zero).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_send).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_receive).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_change).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_send_change).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch_first).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_receive2).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_send2).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_open).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, state_send_epoch_link).code); + + // Downgrade the store + store.version_put (transaction, 17); + + // Replace with the previous sideband version for state blocks + // The upgrade can resume after upgrading some blocks, test this by only downgrading some of them + write_sideband_v15 (store, transaction, state_receive_zero); + write_sideband_v15 (store, transaction, epoch); + write_sideband_v15 (store, transaction, state_send); + // DISABLED write_sideband_v15 (store, transaction, state_receive); + write_sideband_v15 (store, transaction, state_change); + write_sideband_v15 (store, transaction, state_send_change); + // DISABLED write_sideband_v15 (store, transaction, epoch_first); + write_sideband_v15 (store, transaction, state_receive2); + // DISABLED write_sideband_v15 (store, transaction, state_send2); + write_sideband_v15 (store, transaction, state_open); + // DISABLED write_sideband_v15 (store, transaction, state_send_epoch_link); + } + + // Now do the upgrade + nano::logger_mt logger; + auto error (false); + nano::mdb_store store (logger, path); + ASSERT_FALSE (error); + auto transaction (store.tx_begin_read ()); + + // Size of state block should equal that set in db (no change) + nano::mdb_val value; + ASSERT_FALSE (mdb_get (store.env.tx (transaction), store.state_blocks, nano::mdb_val (state_send.hash ()), value)); + ASSERT_EQ (value.size (), nano::state_block::size + nano::block_sideband::size (nano::block_type::state)); + + // Check that sidebands are correctly populated + { + // Non-state unaffected + auto block = store.block_get (transaction, send_zero.hash ()); + ASSERT_NE (block, nullptr); + // All defaults + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_0); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State receive from old zero send + auto block = store.block_get (transaction, state_receive_zero.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_0); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_TRUE (block->sideband ().details.is_receive); + } + { + // Epoch + auto block = store.block_get (transaction, epoch.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_TRUE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State send + auto block = store.block_get (transaction, state_send.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_TRUE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State receive + auto block = store.block_get (transaction, state_receive.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_TRUE (block->sideband ().details.is_receive); + } + { + // State change + auto block = store.block_get (transaction, state_change.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State send + change + auto block = store.block_get (transaction, state_send_change.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_TRUE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // Epoch on unopened account + auto block = store.block_get (transaction, epoch_first.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_2); + ASSERT_TRUE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State open following epoch + auto block = store.block_get (transaction, state_receive2.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_2); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_TRUE (block->sideband ().details.is_receive); + } + { + // Another state send + auto block = store.block_get (transaction, state_send2.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_TRUE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + { + // State open + auto block = store.block_get (transaction, state_open.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_FALSE (block->sideband ().details.is_send); + ASSERT_TRUE (block->sideband ().details.is_receive); + } + { + // State send to an epoch link + auto block = store.block_get (transaction, state_send_epoch_link.hash ()); + ASSERT_NE (block, nullptr); + ASSERT_EQ (block->sideband ().details.epoch, nano::epoch::epoch_1); + ASSERT_FALSE (block->sideband ().details.is_epoch); + ASSERT_TRUE (block->sideband ().details.is_send); + ASSERT_FALSE (block->sideband ().details.is_receive); + } + // Version should be correct + ASSERT_LT (17, store.version_get (transaction)); +} + TEST (mdb_block_store, upgrade_backup) { auto dir (nano::unique_path ()); @@ -1729,7 +2042,6 @@ TEST (mdb_block_store, upgrade_backup) fs::create_directory (dir); auto path = dir / "data.ldb"; /** Returns 'dir' if backup file cannot be found */ - // clang-format off auto get_backup_path = [&dir]() { for (fs::directory_iterator itr (dir); itr != fs::directory_iterator (); ++itr) { @@ -1740,7 +2052,6 @@ TEST (mdb_block_store, upgrade_backup) } return dir; }; - // clang-format on { nano::logger_mt logger; @@ -1753,7 +2064,7 @@ TEST (mdb_block_store, upgrade_backup) // Now do the upgrade and confirm that backup is saved nano::logger_mt logger; - nano::mdb_store store (logger, path, nano::txn_tracking_config{}, std::chrono::seconds (5), 128, 512, true); + nano::mdb_store store (logger, path, nano::txn_tracking_config{}, std::chrono::seconds (5), nano::lmdb_config{}, 512, true); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_read ()); ASSERT_LT (14, store.version_get (transaction)); @@ -1770,32 +2081,41 @@ TEST (block_store, confirmation_height) nano::account account1 (0); nano::account account2 (1); nano::account account3 (2); + nano::block_hash cemented_frontier1 (3); + nano::block_hash cemented_frontier2 (4); + nano::block_hash cemented_frontier3 (5); { auto transaction (store.tx_begin_write ()); - store.confirmation_height_put (transaction, account1, 500); - store.confirmation_height_put (transaction, account2, std::numeric_limits::max ()); - store.confirmation_height_put (transaction, account3, 10); - - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, account1, confirmation_height)); - ASSERT_EQ (confirmation_height, 500); - ASSERT_FALSE (store.confirmation_height_get (transaction, account2, confirmation_height)); - ASSERT_EQ (confirmation_height, std::numeric_limits::max ()); - ASSERT_FALSE (store.confirmation_height_get (transaction, account3, confirmation_height)); - ASSERT_EQ (confirmation_height, 10); + store.confirmation_height_put (transaction, account1, { 500, cemented_frontier1 }); + store.confirmation_height_put (transaction, account2, { std::numeric_limits::max (), cemented_frontier2 }); + store.confirmation_height_put (transaction, account3, { 10, cemented_frontier3 }); + + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, account1, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 500); + ASSERT_EQ (confirmation_height_info.frontier, cemented_frontier1); + ASSERT_FALSE (store.confirmation_height_get (transaction, account2, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, std::numeric_limits::max ()); + ASSERT_EQ (confirmation_height_info.frontier, cemented_frontier2); + ASSERT_FALSE (store.confirmation_height_get (transaction, account3, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 10); + ASSERT_EQ (confirmation_height_info.frontier, cemented_frontier3); // Check cleaning of confirmation heights store.confirmation_height_clear (transaction); } auto transaction (store.tx_begin_read ()); ASSERT_EQ (store.confirmation_height_count (transaction), 3); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, account1, confirmation_height)); - ASSERT_EQ (confirmation_height, 0); - ASSERT_FALSE (store.confirmation_height_get (transaction, account2, confirmation_height)); - ASSERT_EQ (confirmation_height, 0); - ASSERT_FALSE (store.confirmation_height_get (transaction, account3, confirmation_height)); - ASSERT_EQ (confirmation_height, 0); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, account1, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 0); + ASSERT_EQ (confirmation_height_info.frontier, nano::block_hash (0)); + ASSERT_FALSE (store.confirmation_height_get (transaction, account2, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 0); + ASSERT_EQ (confirmation_height_info.frontier, nano::block_hash (0)); + ASSERT_FALSE (store.confirmation_height_get (transaction, account3, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 0); + ASSERT_EQ (confirmation_height_info.frontier, nano::block_hash (0)); } // Upgrade many accounts and check they all have a confirmation height of 0 (except genesis which should have 1) @@ -1812,23 +2132,22 @@ TEST (mdb_block_store, upgrade_confirmation_height_many) ASSERT_FALSE (error); auto transaction (store.tx_begin_write ()); store.version_put (transaction, 13); - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - store.initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); - modify_account_info_to_v13 (store, transaction, nano::genesis_account, genesis.open->hash ()); + nano::ledger_cache ledger_cache; + store.initialize (transaction, genesis, ledger_cache); + modify_account_info_to_v13 (store, transaction, nano::genesis_account, nano::genesis_hash); // Add many accounts for (auto i = 0; i < total_num_accounts - 1; ++i) { nano::account account (i); nano::open_block open (1, nano::genesis_account, 3, nullptr); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store.block_put (transaction, open.hash (), open, sideband); + open.sideband_set ({}); + store.block_put (transaction, open.hash (), open); nano::account_info_v13 account_info_v13 (open.hash (), open.hash (), open.hash (), 3, 4, 1, nano::epoch::epoch_0); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (account), nano::mdb_val (account_info_v13), 0)); ASSERT_EQ (status, 0); } + store.confirmation_height_del (transaction, nano::genesis_account); ASSERT_EQ (store.count (transaction, store.accounts_v0), total_num_accounts); } @@ -1842,7 +2161,8 @@ TEST (mdb_block_store, upgrade_confirmation_height_many) for (auto i (store.confirmation_height_begin (transaction)), n (store.confirmation_height_end ()); i != n; ++i) { - ASSERT_EQ (i->second, (i->first == nano::genesis_account) ? 1 : 0); + ASSERT_EQ (i->second.height, (i->first == nano::genesis_account) ? 1 : 0); + ASSERT_EQ (i->second.frontier, (i->first == nano::genesis_account) ? genesis.hash () : nano::block_hash (0)); } } @@ -1879,6 +2199,7 @@ TEST (block_store, reset_renew_existing_transaction) nano::keypair key1; nano::open_block block (0, 1, 1, nano::keypair ().prv, 0, 0); + block.sideband_set ({}); auto hash1 (block.hash ()); auto read_transaction = store->tx_begin_read (); @@ -1892,8 +2213,7 @@ TEST (block_store, reset_renew_existing_transaction) // Write the block { auto write_transaction (store->tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store->block_put (write_transaction, hash1, block, sideband); + store->block_put (write_transaction, hash1, block); } read_transaction.renew (); @@ -1952,11 +2272,10 @@ void write_sideband_v12 (nano::mdb_store & store_a, nano::transaction & transact void write_sideband_v14 (nano::mdb_store & store_a, nano::transaction & transaction_a, nano::block const & block_a, MDB_dbi db_a) { - nano::block_sideband sideband; - auto block = store_a.block_get (transaction_a, block_a.hash (), &sideband); + auto block = store_a.block_get (transaction_a, block_a.hash ()); ASSERT_NE (block, nullptr); - nano::block_sideband_v14 sideband_v14 (sideband.type, sideband.account, sideband.successor, sideband.balance, sideband.timestamp, sideband.height); + nano::block_sideband_v14 sideband_v14 (block->type (), block->sideband ().account, block->sideband ().successor, block->sideband ().balance, block->sideband ().timestamp, block->sideband ().height); std::vector data; { nano::vectorstream stream (data); @@ -1965,7 +2284,26 @@ void write_sideband_v14 (nano::mdb_store & store_a, nano::transaction & transact } MDB_val val{ data.size (), data.data () }; - ASSERT_FALSE (mdb_put (store_a.env.tx (transaction_a), sideband.epoch == nano::epoch::epoch_0 ? store_a.state_blocks_v0 : store_a.state_blocks_v1, nano::mdb_val (block_a.hash ()), &val, 0)); + ASSERT_FALSE (mdb_put (store_a.env.tx (transaction_a), block->sideband ().details.epoch == nano::epoch::epoch_0 ? store_a.state_blocks_v0 : store_a.state_blocks_v1, nano::mdb_val (block_a.hash ()), &val, 0)); +} + +void write_sideband_v15 (nano::mdb_store & store_a, nano::transaction & transaction_a, nano::block const & block_a) +{ + auto block = store_a.block_get (transaction_a, block_a.hash ()); + ASSERT_NE (block, nullptr); + + ASSERT_LE (block->sideband ().details.epoch, nano::epoch::max); + // Simulated by writing 0 on every of the most significant bits, leaving out epoch only, as if pre-upgrade + nano::block_sideband sideband_v15 (block->sideband ().account, block->sideband ().successor, block->sideband ().balance, block->sideband ().timestamp, block->sideband ().height, block->sideband ().details.epoch, false, false, false); + std::vector data; + { + nano::vectorstream stream (data); + block_a.serialize (stream); + sideband_v15.serialize (stream, block_a.type ()); + } + + MDB_val val{ data.size (), data.data () }; + ASSERT_FALSE (mdb_put (store_a.env.tx (transaction_a), store_a.state_blocks, nano::mdb_val (block_a.hash ()), &val, 0)); } // These functions take the latest account_info and create a legacy one so that upgrade tests can be emulated more easily. @@ -1975,8 +2313,7 @@ void modify_account_info_to_v13 (nano::mdb_store & store, nano::transaction cons ASSERT_FALSE (store.account_get (transaction, account, info)); nano::account_info_v13 account_info_v13 (info.head, rep_block, info.open_block, info.balance, info.modified, info.block_count, info.epoch ()); auto status (mdb_put (store.env.tx (transaction), (info.epoch () == nano::epoch::epoch_0) ? store.accounts_v0 : store.accounts_v1, nano::mdb_val (account), nano::mdb_val (account_info_v13), 0)); - (void)status; - assert (status == 0); + ASSERT_EQ (status, 0); } void modify_account_info_to_v14 (nano::mdb_store & store, nano::transaction const & transaction, nano::account const & account, uint64_t confirmation_height, nano::block_hash const & rep_block) @@ -1985,8 +2322,13 @@ void modify_account_info_to_v14 (nano::mdb_store & store, nano::transaction cons ASSERT_FALSE (store.account_get (transaction, account, info)); nano::account_info_v14 account_info_v14 (info.head, rep_block, info.open_block, info.balance, info.modified, info.block_count, confirmation_height, info.epoch ()); auto status (mdb_put (store.env.tx (transaction), info.epoch () == nano::epoch::epoch_0 ? store.accounts_v0 : store.accounts_v1, nano::mdb_val (account), nano::mdb_val (account_info_v14), 0)); - (void)status; - assert (status == 0); + ASSERT_EQ (status, 0); +} + +void modify_confirmation_height_to_v15 (nano::mdb_store & store, nano::transaction const & transaction, nano::account const & account, uint64_t confirmation_height) +{ + auto status (mdb_put (store.env.tx (transaction), store.confirmation_height, nano::mdb_val (account), nano::mdb_val (confirmation_height), 0)); + ASSERT_EQ (status, 0); } void modify_genesis_account_info_to_v5 (nano::mdb_store & store, nano::transaction const & transaction) @@ -1997,7 +2339,6 @@ void modify_genesis_account_info_to_v5 (nano::mdb_store & store, nano::transacti visitor.compute (info.head); nano::account_info_v5 info_old (info.head, visitor.result, info.open_block, info.balance, info.modified); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (nano::test_genesis_key.pub), nano::mdb_val (sizeof (info_old), &info_old), 0)); - (void)status; - assert (status == 0); + ASSERT_EQ (status, 0); } } diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp index b1da599b5e..30acac54cf 100644 --- a/nano/core_test/bootstrap.cpp +++ b/nano/core_test/bootstrap.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -8,9 +10,9 @@ using namespace std::chrono_literals; // If the account doesn't exist, current == end so there's no iteration TEST (bulk_pull, no_address) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::bulk_pull); + auto req = std::make_unique (); req->start = 1; req->end = 2; connection->requests.push (std::unique_ptr{}); @@ -21,9 +23,9 @@ TEST (bulk_pull, no_address) TEST (bulk_pull, genesis_to_end) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = nano::test_genesis_key.pub; req->end.clear (); connection->requests.push (std::unique_ptr{}); @@ -35,9 +37,9 @@ TEST (bulk_pull, genesis_to_end) // If we can't find the end block, send everything TEST (bulk_pull, no_end) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = nano::test_genesis_key.pub; req->end = 1; connection->requests.push (std::unique_ptr{}); @@ -48,7 +50,7 @@ TEST (bulk_pull, no_end) TEST (bulk_pull, end_not_owned) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, 100)); @@ -57,12 +59,13 @@ TEST (bulk_pull, end_not_owned) open.hashables.account = key2.pub; open.hashables.representative = key2.pub; open.hashables.source = latest; + open.refresh (); open.signature = nano::sign_message (key2.prv, key2.pub, open.hash ()); system.nodes[0]->work_generate_blocking (open); ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); auto connection (std::make_shared (nullptr, system.nodes[0])); nano::genesis genesis; - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = key2.pub; req->end = genesis.hash (); connection->requests.push (std::unique_ptr{}); @@ -72,10 +75,10 @@ TEST (bulk_pull, end_not_owned) TEST (bulk_pull, none) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); nano::genesis genesis; - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = nano::test_genesis_key.pub; req->end = genesis.hash (); connection->requests.push (std::unique_ptr{}); @@ -86,9 +89,9 @@ TEST (bulk_pull, none) TEST (bulk_pull, get_next_on_open) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = nano::test_genesis_key.pub; req->end.clear (); connection->requests.push (std::unique_ptr{}); @@ -102,10 +105,10 @@ TEST (bulk_pull, get_next_on_open) TEST (bulk_pull, by_block) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); nano::genesis genesis; - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = genesis.hash (); req->end.clear (); connection->requests.push (std::unique_ptr{}); @@ -120,10 +123,10 @@ TEST (bulk_pull, by_block) TEST (bulk_pull, by_block_single) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); nano::genesis genesis; - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = genesis.hash (); req->end = genesis.hash (); connection->requests.push (std::unique_ptr{}); @@ -138,7 +141,7 @@ TEST (bulk_pull, by_block_single) TEST (bulk_pull, count_limit) { - nano::system system (24000, 1); + nano::system system (1); nano::genesis genesis; auto send1 (std::make_shared (system.nodes[0]->latest (nano::test_genesis_key.pub), nano::test_genesis_key.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (system.nodes[0]->latest (nano::test_genesis_key.pub)))); @@ -147,7 +150,7 @@ TEST (bulk_pull, count_limit) ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*receive1).code); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::bulk_pull{}); + auto req = std::make_unique (); req->start = receive1->hash (); req->set_count_present (true); req->count = 2; @@ -169,8 +172,8 @@ TEST (bulk_pull, count_limit) TEST (bootstrap_processor, DISABLED_process_none) { - nano::system system (24000, 1); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system (1); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto done (false); node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); @@ -185,16 +188,16 @@ TEST (bootstrap_processor, DISABLED_process_none) TEST (bootstrap_processor, process_one) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; node_config.enable_voting = false; - auto node0 = system.add_node (node_config); + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 = system.add_node (node_config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 100)); - node_config.peering_port = 24001; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; + node_config.peering_port = nano::get_available_port (); node_flags.disable_rep_crawler = true; auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, node_config, system.work, node_flags)); nano::block_hash hash1 (node0->latest (nano::test_genesis_key.pub)); @@ -213,22 +216,27 @@ TEST (bootstrap_processor, process_one) TEST (bootstrap_processor, process_two) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash hash1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash1 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 50)); - nano::block_hash hash2 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash2 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 50)); - nano::block_hash hash3 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash3 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (hash1, hash2); ASSERT_NE (hash1, hash3); ASSERT_NE (hash2, hash3); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); - ASSERT_NE (node1->latest (nano::test_genesis_key.pub), system.nodes[0]->latest (nano::test_genesis_key.pub)); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); + ASSERT_NE (node1->latest (nano::test_genesis_key.pub), node0->latest (nano::test_genesis_key.pub)); system.deadline_set (10s); - while (node1->latest (nano::test_genesis_key.pub) != system.nodes[0]->latest (nano::test_genesis_key.pub)) + while (node1->latest (nano::test_genesis_key.pub) != node0->latest (nano::test_genesis_key.pub)) { ASSERT_NO_ERROR (system.poll ()); } @@ -238,17 +246,21 @@ TEST (bootstrap_processor, process_two) // Bootstrap can pull universal blocks TEST (bootstrap_processor, process_state) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node0 (system.nodes[0]); auto block1 (std::make_shared (nano::test_genesis_key.pub, node0->latest (nano::test_genesis_key.pub), nano::test_genesis_key.pub, nano::genesis_amount - 100, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); auto block2 (std::make_shared (nano::test_genesis_key.pub, block1->hash (), nano::test_genesis_key.pub, nano::genesis_amount, block1->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node0->work_generate_blocking (*block1); node0->work_generate_blocking (*block2); node0->process (*block1); node0->process (*block2); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_EQ (node0->latest (nano::test_genesis_key.pub), block2->hash ()); ASSERT_NE (node1->latest (nano::test_genesis_key.pub), block2->hash ()); node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); @@ -264,45 +276,57 @@ TEST (bootstrap_processor, process_state) TEST (bootstrap_processor, process_new) { - nano::system system (24000, 2); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node1 (system.add_node (config, node_flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, node_flags)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node1->config.receive_minimum.number ())); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub).is_zero ()) + while (node1->balance (key2.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } - nano::uint128_t balance1 (system.nodes[0]->balance (nano::test_genesis_key.pub)); - nano::uint128_t balance2 (system.nodes[0]->balance (key2.pub)); - auto node1 (std::make_shared (system.io_ctx, 24002, nano::unique_path (), system.alarm, system.logging, system.work)); - ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + nano::uint128_t balance1 (node1->balance (nano::test_genesis_key.pub)); + nano::uint128_t balance2 (node1->balance (key2.pub)); + auto node3 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + ASSERT_FALSE (node3->init_error ()); + node3->bootstrap_initiator.bootstrap (node1->network.endpoint ()); system.deadline_set (10s); - while (node1->balance (key2.pub) != balance2) + while (node3->balance (key2.pub) != balance2) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (balance1, node1->balance (nano::test_genesis_key.pub)); - node1->stop (); + ASSERT_EQ (balance1, node3->balance (nano::test_genesis_key.pub)); + node3->stop (); } TEST (bootstrap_processor, pull_diamond) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::keypair key; - auto send1 (std::make_shared (system.nodes[0]->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (system.nodes[0]->latest (nano::test_genesis_key.pub)))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*send1).code); + auto send1 (std::make_shared (node0->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (node0->latest (nano::test_genesis_key.pub)))); + ASSERT_EQ (nano::process_result::progress, node0->process (*send1).code); auto open (std::make_shared (send1->hash (), 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*open).code); + ASSERT_EQ (nano::process_result::progress, node0->process (*open).code); auto send2 (std::make_shared (open->hash (), nano::test_genesis_key.pub, std::numeric_limits::max () - 100, key.prv, key.pub, *system.work.generate (open->hash ()))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*send2).code); + ASSERT_EQ (nano::process_result::progress, node0->process (*send2).code); auto receive (std::make_shared (send1->hash (), send2->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*receive).code); - auto node1 (std::make_shared (system.io_ctx, 24002, nano::unique_path (), system.alarm, system.logging, system.work)); + ASSERT_EQ (nano::process_result::progress, node0->process (*receive).code); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); while (node1->balance (nano::test_genesis_key.pub) != 100) { @@ -312,11 +336,17 @@ TEST (bootstrap_processor, pull_diamond) node1->stop (); } -TEST (bootstrap_processor, pull_requeue_network_error) +TEST (bootstrap_processor, DISABLED_pull_requeue_network_error) { - nano::system system (24000, 2); - auto node1 = system.nodes[0]; - auto node2 = system.nodes[1]; + // Bootstrap attempt stopped before requeue & then cannot be found in attempts list + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node1 (system.add_node (config, node_flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); @@ -331,10 +361,11 @@ TEST (bootstrap_processor, pull_requeue_network_error) } // Add non-existing pull & stop remote peer { - nano::unique_lock lock (attempt->mutex); + nano::unique_lock lock (node1->bootstrap_initiator.connections->mutex); ASSERT_FALSE (attempt->stopped); - attempt->pulls.push_back (nano::pull_info (nano::test_genesis_key.pub, send1->hash (), genesis.hash ())); - attempt->request_pull (lock); + ++attempt->pulling; + node1->bootstrap_initiator.connections->pulls.push_back (nano::pull_info (nano::test_genesis_key.pub, send1->hash (), genesis.hash (), attempt->incremental_id)); + node1->bootstrap_initiator.connections->request_pull (lock); node2->stop (); } system.deadline_set (5s); @@ -348,7 +379,7 @@ TEST (bootstrap_processor, pull_requeue_network_error) TEST (bootstrap_processor, frontiers_unconfirmed) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; node_config.tcp_io_timeout = std::chrono::seconds (2); nano::node_flags node_flags; @@ -371,7 +402,7 @@ TEST (bootstrap_processor, frontiers_unconfirmed) auto open2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*open2).code); - node_config.peering_port = 24001; + node_config.peering_port = nano::get_available_port (); node_flags.disable_bootstrap_bulk_pull_server = false; node_flags.disable_rep_crawler = false; auto node2 = system.add_node (node_config, node_flags); @@ -382,8 +413,12 @@ TEST (bootstrap_processor, frontiers_unconfirmed) ASSERT_EQ (nano::process_result::progress, node2->process (*open3).code); system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + // Ensure node2 can generate votes + node2->block_confirm (send3); + ASSERT_TIMELY (5s, node2->ledger.cache.cemented_count == 3); + // Test node to restart bootstrap - node_config.peering_port = 24002; + node_config.peering_port = nano::get_available_port (); node_flags.disable_legacy_bootstrap = false; auto node3 = system.add_node (node_config, node_flags); system.deadline_set (5s); @@ -392,8 +427,8 @@ TEST (bootstrap_processor, frontiers_unconfirmed) ASSERT_NO_ERROR (system.poll ()); } //Add single excluded peers record (2 records are required to drop peer) - node3->bootstrap_initiator.excluded_peers.add (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()), 0); - ASSERT_FALSE (node3->bootstrap_initiator.excluded_peers.check (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()))); + node3->network.excluded_peers.add (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()), 0); + ASSERT_FALSE (node3->network.excluded_peers.check (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()))); node3->bootstrap_initiator.bootstrap (node1->network.endpoint ()); system.deadline_set (15s); while (node3->bootstrap_initiator.in_progress ()) @@ -403,13 +438,13 @@ TEST (bootstrap_processor, frontiers_unconfirmed) ASSERT_FALSE (node3->ledger.block_exists (send1->hash ())); ASSERT_FALSE (node3->ledger.block_exists (open1->hash ())); ASSERT_EQ (1, node3->stats.count (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in)); // failed request from node1 - ASSERT_TRUE (node3->bootstrap_initiator.excluded_peers.check (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()))); + ASSERT_TRUE (node3->network.excluded_peers.check (nano::transport::map_endpoint_to_tcp (node1->network.endpoint ()))); } TEST (bootstrap_processor, frontiers_confirmed) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; node_config.tcp_io_timeout = std::chrono::seconds (2); nano::node_flags node_flags; @@ -422,7 +457,7 @@ TEST (bootstrap_processor, frontiers_confirmed) auto node1 = system.add_node (node_config, node_flags); nano::genesis genesis; nano::keypair key1, key2; - // Generating invalid chain + // Generating valid chain auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); @@ -433,8 +468,12 @@ TEST (bootstrap_processor, frontiers_confirmed) ASSERT_EQ (nano::process_result::progress, node1->process (*open2).code); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + // Confirm all blocks so node1 is free to generate votes + node1->block_confirm (send1); + ASSERT_TIMELY (5s, node1->ledger.cache.cemented_count == 5); + // Test node to bootstrap - node_config.peering_port = 24001; + node_config.peering_port = nano::get_available_port (); node_flags.disable_legacy_bootstrap = false; node_flags.disable_rep_crawler = false; auto node2 = system.add_node (node_config, node_flags); @@ -453,16 +492,78 @@ TEST (bootstrap_processor, frontiers_confirmed) ASSERT_EQ (0, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in)); } +TEST (bootstrap_processor, frontiers_unconfirmed_threshold) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.tcp_io_timeout = std::chrono::seconds (2); + node_config.bootstrap_fraction_numerator = 4; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_pull_server = true; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_legacy_bootstrap = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_wallet_bootstrap = true; + node_flags.disable_rep_crawler = true; + auto node1 = system.add_node (node_config, node_flags); + nano::genesis genesis; + nano::keypair key1, key2; + // Generating invalid chain + auto threshold (node1->gap_cache.bootstrap_threshold () + 1); + ASSERT_LT (threshold, node1->config.online_weight_minimum.number ()); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - threshold, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - threshold - nano::Gxrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); + ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); + auto open1 (std::make_shared (key1.pub, 0, key1.pub, threshold, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); + ASSERT_EQ (nano::process_result::progress, node1->process (*open1).code); + auto open2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); + ASSERT_EQ (nano::process_result::progress, node1->process (*open2).code); + system.wallet (0)->insert_adhoc (key1.prv); // Small representative + + // Test node with large representative + node_config.peering_port = nano::get_available_port (); + auto node2 = system.add_node (node_config, node_flags); + system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + + // Test node to bootstrap + node_config.peering_port = nano::get_available_port (); + node_flags.disable_legacy_bootstrap = false; + node_flags.disable_rep_crawler = false; + auto node3 = system.add_node (node_config, node_flags); + ASSERT_EQ (nano::process_result::progress, node3->process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node3->process (*open1).code); // Change known representative weight + system.deadline_set (5s); + while (node3->rep_crawler.representative_count () < 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + node3->bootstrap_initiator.bootstrap (node1->network.endpoint ()); + system.deadline_set (15s); + while (node3->stats.count (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in) < 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_FALSE (node3->ledger.block_exists (send2->hash ())); + ASSERT_FALSE (node3->ledger.block_exists (open2->hash ())); + ASSERT_EQ (1, node3->stats.count (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in)); // failed confirmation + ASSERT_EQ (0, node3->stats.count (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_successful, nano::stat::dir::in)); +} + TEST (bootstrap_processor, push_diamond) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system.add_node (config)); nano::keypair key; - auto node1 (std::make_shared (system.io_ctx, 24002, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto wallet1 (node1->wallets.create (100)); wallet1->insert_adhoc (nano::test_genesis_key.prv); wallet1->insert_adhoc (key.prv); - auto send1 (std::make_shared (system.nodes[0]->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (system.nodes[0]->latest (nano::test_genesis_key.pub)))); + auto send1 (std::make_shared (node0->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (node0->latest (nano::test_genesis_key.pub)))); ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); auto open (std::make_shared (send1->hash (), 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); @@ -470,30 +571,33 @@ TEST (bootstrap_processor, push_diamond) ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); auto receive (std::make_shared (send1->hash (), send2->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); ASSERT_EQ (nano::process_result::progress, node1->process (*receive).code); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) != 100) + while (node0->balance (nano::test_genesis_key.pub) != 100) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (100, system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (100, node0->balance (nano::test_genesis_key.pub)); node1->stop (); } TEST (bootstrap_processor, push_one) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system.add_node (config)); nano::keypair key1; - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); auto wallet (node1->wallets.create (nano::random_wallet_id ())); ASSERT_NE (nullptr, wallet); wallet->insert_adhoc (nano::test_genesis_key.prv); nano::uint128_t balance1 (node1->balance (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, wallet->send_action (nano::test_genesis_key.pub, key1.pub, 100)); ASSERT_NE (balance1, node1->balance (nano::test_genesis_key.pub)); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) == balance1) + while (node0->balance (nano::test_genesis_key.pub) == balance1) { ASSERT_NO_ERROR (system.poll ()); } @@ -502,25 +606,75 @@ TEST (bootstrap_processor, push_one) TEST (bootstrap_processor, lazy_hash) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); + nano::genesis genesis; + nano::keypair key1; + nano::keypair key2; + // Generating test chain + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); + // Processing test chain + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.flush (); + // Start lazy bootstrap with last block in chain known + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true); + { + auto lazy_attempt (node1->bootstrap_initiator.current_lazy_attempt ()); + ASSERT_NE (nullptr, lazy_attempt); + ASSERT_EQ (receive2->hash ().to_string (), lazy_attempt->id); + } + // Check processed blocks + system.deadline_set (10s); + while (node1->balance (key2.pub) == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + node1->stop (); +} + +TEST (bootstrap_processor, lazy_hash_bootstrap_id) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.flush (); // Start lazy bootstrap with last block in chain known - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); - node1->bootstrap_initiator.bootstrap_lazy (receive2->hash ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true, true, "123456"); + { + auto lazy_attempt (node1->bootstrap_initiator.current_lazy_attempt ()); + ASSERT_NE (nullptr, lazy_attempt); + ASSERT_EQ ("123456", lazy_attempt->id); + } // Check processed blocks system.deadline_set (10s); while (node1->balance (key2.pub) == 0) @@ -532,30 +686,35 @@ TEST (bootstrap_processor, lazy_hash) TEST (bootstrap_processor, lazy_max_pull_count) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); - auto change1 (std::make_shared (key2.pub, receive2->hash (), key1.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (receive2->hash ()))); - auto change2 (std::make_shared (key2.pub, change1->hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (change1->hash ()))); - auto change3 (std::make_shared (key2.pub, change2->hash (), key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (change2->hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); + auto change1 (std::make_shared (key2.pub, receive2->hash (), key1.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (receive2->hash ()))); + auto change2 (std::make_shared (key2.pub, change1->hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (change1->hash ()))); + auto change3 (std::make_shared (key2.pub, change2->hash (), key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (change2->hash ()))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.add (change1); - system.nodes[0]->block_processor.add (change2); - system.nodes[0]->block_processor.add (change3); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.add (change1); + node0->block_processor.add (change2); + node0->block_processor.add (change3); + node0->block_processor.flush (); // Start lazy bootstrap with last block in chain known - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); node1->bootstrap_initiator.bootstrap_lazy (change3->hash ()); // Check processed blocks system.deadline_set (10s); @@ -563,15 +722,21 @@ TEST (bootstrap_processor, lazy_max_pull_count) { ASSERT_NO_ERROR (system.poll ()); } + + auto transaction = node1->store.tx_begin_read (); + ASSERT_EQ (node1->ledger.cache.unchecked_count, node1->store.unchecked_count (transaction)); node1->stop (); } TEST (bootstrap_processor, lazy_unclear_state_link) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (24000, system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key; // Generating test chain @@ -584,7 +749,7 @@ TEST (bootstrap_processor, lazy_unclear_state_link) auto receive (std::make_shared (key.pub, open->hash (), key.pub, 2 * nano::Gxrb_ratio, send2->hash (), key.prv, key.pub, *system.work.generate (open->hash ()))); // It is not possible to define this block send/receive status based on previous block (legacy open) ASSERT_EQ (nano::process_result::progress, node1->process (*receive).code); // Start lazy bootstrap with last block in chain known - auto node2 = system.add_node (nano::node_config (24001, system.logging), node_flags); + auto node2 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); node2->network.udp_channels.insert (node1->network.endpoint (), node1->network_params.protocol.protocol_version); node2->bootstrap_initiator.bootstrap_lazy (receive->hash ()); // Check processed blocks @@ -604,24 +769,25 @@ TEST (bootstrap_processor, lazy_unclear_state_link) TEST (bootstrap_processor, lazy_unclear_state_link_not_existing) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (24000, system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key, key2; // Generating test chain auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); auto open (std::make_shared (send1->hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); - auto send3 (std::make_shared (key.pub, open->hash (), key.pub, 0, key2.pub, key.prv, key.pub, *system.work.generate (open->hash ()))); // It is not possible to define this block send/receive status based on previous block (legacy open) - ASSERT_EQ (nano::process_result::progress, node1->process (*send3).code); + auto send2 (std::make_shared (key.pub, open->hash (), key.pub, 0, key2.pub, key.prv, key.pub, *system.work.generate (open->hash ()))); // It is not possible to define this block send/receive status based on previous block (legacy open) + ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); // Start lazy bootstrap with last block in chain known - auto node2 = system.add_node (nano::node_config (24001, system.logging), node_flags); + auto node2 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); node2->network.udp_channels.insert (node1->network.endpoint (), node1->network_params.protocol.protocol_version); - node2->bootstrap_initiator.bootstrap_lazy (send3->hash ()); + node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); // Check processed blocks system.deadline_set (15s); while (node2->bootstrap_initiator.in_progress ()) @@ -630,18 +796,20 @@ TEST (bootstrap_processor, lazy_unclear_state_link_not_existing) } node2->block_processor.flush (); ASSERT_TRUE (node2->ledger.block_exists (send1->hash ())); - ASSERT_TRUE (node2->ledger.block_exists (send2->hash ())); ASSERT_TRUE (node2->ledger.block_exists (open->hash ())); - ASSERT_TRUE (node2->ledger.block_exists (send3->hash ())); + ASSERT_TRUE (node2->ledger.block_exists (send2->hash ())); ASSERT_EQ (1, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in)); } TEST (bootstrap_processor, lazy_destinations) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (24000, system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1, key2; // Generating test chain @@ -654,7 +822,7 @@ TEST (bootstrap_processor, lazy_destinations) auto state_open (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*state_open).code); // Start lazy bootstrap with last block in sender chain - auto node2 = system.add_node (nano::node_config (24001, system.logging), node_flags); + auto node2 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); node2->network.udp_channels.insert (node1->network.endpoint (), node1->network_params.protocol.protocol_version); node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); // Check processed blocks @@ -672,28 +840,39 @@ TEST (bootstrap_processor, lazy_destinations) TEST (bootstrap_processor, wallet_lazy_frontier) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_legacy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.flush (); // Start wallet lazy bootstrap - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); auto wallet (node1->wallets.create (nano::random_wallet_id ())); ASSERT_NE (nullptr, wallet); wallet->insert_adhoc (key2.prv); node1->bootstrap_wallet (); + { + auto wallet_attempt (node1->bootstrap_initiator.current_wallet_attempt ()); + ASSERT_NE (nullptr, wallet_attempt); + ASSERT_EQ (key2.pub.to_account (), wallet_attempt->id); + } // Check processed blocks system.deadline_set (10s); while (!node1->ledger.block_exists (receive2->hash ())) @@ -705,22 +884,28 @@ TEST (bootstrap_processor, wallet_lazy_frontier) TEST (bootstrap_processor, wallet_lazy_pending) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_legacy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.flush (); // Start wallet lazy bootstrap - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); auto wallet (node1->wallets.create (nano::random_wallet_id ())); ASSERT_NE (nullptr, wallet); wallet->insert_adhoc (key2.prv); @@ -734,14 +919,69 @@ TEST (bootstrap_processor, wallet_lazy_pending) node1->stop (); } +TEST (bootstrap_processor, multiple_attempts) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node1 = system.add_node (config, node_flags); + nano::genesis genesis; + nano::keypair key1; + nano::keypair key2; + // Generating test chain + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node1->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node1->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node1->work_generate_blocking (key2.pub))); + // Processing test chain + node1->block_processor.add (send1); + node1->block_processor.add (receive1); + node1->block_processor.add (send2); + node1->block_processor.add (receive2); + node1->block_processor.flush (); + // Start 2 concurrent bootstrap attempts + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.bootstrap_initiator_threads = 3; + auto node2 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, node_config, system.work)); + node2->network.udp_channels.insert (node1->network.endpoint (), node2->network_params.protocol.protocol_version); + node2->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true); + node2->bootstrap_initiator.bootstrap (); + auto lazy_attempt (node2->bootstrap_initiator.current_lazy_attempt ()); + auto legacy_attempt (node2->bootstrap_initiator.current_attempt ()); + system.deadline_set (5s); + while (!lazy_attempt->started || !legacy_attempt->started) + { + ASSERT_NO_ERROR (system.poll ()); + } + // Check that both bootstrap attempts are running & not finished + ASSERT_FALSE (lazy_attempt->stopped); + ASSERT_FALSE (legacy_attempt->stopped); + ASSERT_GE (node2->bootstrap_initiator.attempts.size (), 2); + // Check processed blocks + system.deadline_set (10s); + while (node2->balance (key2.pub) == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + // Check attempts finish + system.deadline_set (5s); + while (node2->bootstrap_initiator.attempts.size () != 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + node2->stop (); +} + TEST (frontier_req_response, DISABLED_destruction) { { std::shared_ptr hold; // Destructing tcp acceptor on non-existent io_context { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + auto req = std::make_unique (); req->start.clear (); req->age = std::numeric_limitsage)>::max (); req->count = std::numeric_limitscount)>::max (); @@ -754,9 +994,9 @@ TEST (frontier_req_response, DISABLED_destruction) TEST (frontier_req, begin) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + auto req = std::make_unique (); req->start.clear (); req->age = std::numeric_limitsage)>::max (); req->count = std::numeric_limitscount)>::max (); @@ -769,9 +1009,9 @@ TEST (frontier_req, begin) TEST (frontier_req, end) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + auto req = std::make_unique (); req->start = nano::test_genesis_key.pub.number () + 1; req->age = std::numeric_limitsage)>::max (); req->count = std::numeric_limitscount)>::max (); @@ -782,19 +1022,19 @@ TEST (frontier_req, end) TEST (frontier_req, count) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system (1); + auto node1 = system.nodes[0]; nano::genesis genesis; // Public key FB93... after genesis in accounts table nano::keypair key1 ("ED5AE0A6505B14B67435C29FD9FEEBC26F597D147BC92F6D795FFAD7AFD3D967"); nano::state_block send1 (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); - node1.work_generate_blocking (send1); - ASSERT_EQ (nano::process_result::progress, node1.process (send1).code); + node1->work_generate_blocking (send1); + ASSERT_EQ (nano::process_result::progress, node1->process (send1).code); nano::state_block receive1 (key1.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send1.hash (), key1.prv, key1.pub, 0); - node1.work_generate_blocking (receive1); - ASSERT_EQ (nano::process_result::progress, node1.process (receive1).code); - auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + node1->work_generate_blocking (receive1); + ASSERT_EQ (nano::process_result::progress, node1->process (receive1).code); + auto connection (std::make_shared (nullptr, node1)); + auto req = std::make_unique (); req->start.clear (); req->age = std::numeric_limitsage)>::max (); req->count = 1; @@ -806,9 +1046,9 @@ TEST (frontier_req, count) TEST (frontier_req, time_bound) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + auto req = std::make_unique (); req->start.clear (); req->age = 1; req->count = std::numeric_limitscount)>::max (); @@ -817,7 +1057,7 @@ TEST (frontier_req, time_bound) ASSERT_EQ (nano::test_genesis_key.pub, request->current); // Wait 2 seconds until age of account will be > 1 seconds std::this_thread::sleep_for (std::chrono::milliseconds (2100)); - std::unique_ptr req2 (new nano::frontier_req); + auto req2 (std::make_unique ()); req2->start.clear (); req2->age = 1; req2->count = std::numeric_limitscount)>::max (); @@ -829,9 +1069,9 @@ TEST (frontier_req, time_bound) TEST (frontier_req, time_cutoff) { - nano::system system (24000, 1); + nano::system system (1); auto connection (std::make_shared (nullptr, system.nodes[0])); - std::unique_ptr req (new nano::frontier_req); + auto req = std::make_unique (); req->start.clear (); req->age = 3; req->count = std::numeric_limitscount)>::max (); @@ -842,7 +1082,7 @@ TEST (frontier_req, time_cutoff) ASSERT_EQ (genesis.hash (), request->frontier); // Wait 4 seconds until age of account will be > 3 seconds std::this_thread::sleep_for (std::chrono::milliseconds (4100)); - std::unique_ptr req2 (new nano::frontier_req); + auto req2 (std::make_unique ()); req2->start.clear (); req2->age = 3; req2->count = std::numeric_limitscount)>::max (); @@ -854,70 +1094,82 @@ TEST (frontier_req, time_cutoff) TEST (bulk, genesis) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node1 = system.add_node (config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - ASSERT_FALSE (node1->init_error ()); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - nano::block_hash latest2 (node1->latest (nano::test_genesis_key.pub)); + auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + ASSERT_FALSE (node2->init_error ()); + nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); + nano::block_hash latest2 (node2->latest (nano::test_genesis_key.pub)); ASSERT_EQ (latest1, latest2); nano::keypair key2; ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, 100)); - nano::block_hash latest3 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest3 (node1->latest (nano::test_genesis_key.pub)); ASSERT_NE (latest1, latest3); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node2->bootstrap_initiator.bootstrap (node1->network.endpoint ()); system.deadline_set (10s); - while (node1->latest (nano::test_genesis_key.pub) != system.nodes[0]->latest (nano::test_genesis_key.pub)) + while (node2->latest (nano::test_genesis_key.pub) != node1->latest (nano::test_genesis_key.pub)) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (node1->latest (nano::test_genesis_key.pub), system.nodes[0]->latest (nano::test_genesis_key.pub)); - node1->stop (); + ASSERT_EQ (node2->latest (nano::test_genesis_key.pub), node1->latest (nano::test_genesis_key.pub)); + node2->stop (); } TEST (bulk, offline_send) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node1 = system.add_node (config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - ASSERT_FALSE (node1->init_error ()); - node1->start (); - system.nodes.push_back (node1); + auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + ASSERT_FALSE (node2->init_error ()); + node2->start (); + system.nodes.push_back (node2); nano::keypair key2; - auto wallet (node1->wallets.create (nano::random_wallet_id ())); + auto wallet (node2->wallets.create (nano::random_wallet_id ())); wallet->insert_adhoc (key2.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); - ASSERT_NE (std::numeric_limits::max (), system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node1->config.receive_minimum.number ())); + ASSERT_NE (std::numeric_limits::max (), node1->balance (nano::test_genesis_key.pub)); // Wait to finish election background tasks system.deadline_set (10s); - while (!system.nodes[0]->active.empty ()) + while (!node1->active.empty ()) { ASSERT_NO_ERROR (system.poll ()); } // Initiate bootstrap - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node2->bootstrap_initiator.bootstrap (node1->network.endpoint ()); // Nodes should find each other do { ASSERT_NO_ERROR (system.poll ()); - } while (system.nodes[0]->network.empty () || node1->network.empty ()); + } while (node1->network.empty () || node2->network.empty ()); // Send block arrival via bootstrap - while (node1->balance (nano::test_genesis_key.pub) == std::numeric_limits::max ()) + while (node2->balance (nano::test_genesis_key.pub) == std::numeric_limits::max ()) { ASSERT_NO_ERROR (system.poll ()); } // Receiving send block system.deadline_set (20s); - while (node1->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number ()) + while (node2->balance (key2.pub) != node1->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } - node1->stop (); + node2->stop (); } TEST (bulk_pull_account, basics) { - nano::system system (24000, 1); + nano::system system (1); system.nodes[0]->config.receive_minimum = 20; nano::keypair key1; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -933,7 +1185,7 @@ TEST (bulk_pull_account, basics) auto connection (std::make_shared (nullptr, system.nodes[0])); { - std::unique_ptr req (new nano::bulk_pull_account{}); + auto req = std::make_unique (); req->account = key1.pub; req->minimum_amount = 5; req->flags = nano::bulk_pull_account_flags (); @@ -952,7 +1204,7 @@ TEST (bulk_pull_account, basics) } { - std::unique_ptr req (new nano::bulk_pull_account{}); + auto req = std::make_unique (); req->account = key1.pub; req->minimum_amount = 0; req->flags = nano::bulk_pull_account_flags::pending_address_only; diff --git a/nano/core_test/cli.cpp b/nano/core_test/cli.cpp new file mode 100644 index 0000000000..eda13af6ad --- /dev/null +++ b/nano/core_test/cli.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +#include + +#include + +#include + +using namespace std::chrono_literals; + +namespace +{ +std::string call_cli_command (boost::program_options::variables_map const & vm); +} + +TEST (cli, key_create) +{ + boost::program_options::variables_map vm; + vm.emplace ("key_create", boost::program_options::variable_value ()); + auto output = call_cli_command (vm); + + // Extract the private, public and account values. The regular expression extracts anything between the semi-colon and new line. + std::regex regexpr (": (\\w+)"); + std::smatch matches; + std::vector vals; + std::string::const_iterator search_start (output.cbegin ()); + while (std::regex_search (search_start, output.cend (), matches, regexpr)) + { + ASSERT_NE (matches[1].str (), ""); + vals.push_back (matches[1].str ()); + search_start = matches.suffix ().first; + } + + // Get the contents of the private key and check that the public key and account are successfully derived from the private key + auto private_key_str = vals.front (); + nano::private_key private_key; + private_key.decode_hex (private_key_str); + + auto public_key = nano::pub_key (private_key); + ASSERT_EQ (vals[1], public_key.to_string ()); + ASSERT_EQ (vals[2], public_key.to_account ()); +} + +namespace +{ +std::string call_cli_command (boost::program_options::variables_map const & vm) +{ + std::stringstream ss; + nano::cout_redirect redirect (ss.rdbuf ()); + + // Execute CLI command. This populates the stringstream with a string like: "Private: 123\n Public: 456\n Account: nano_123" + auto ec = nano::handle_node_options (vm); + release_assert (!static_cast (ec)); + return ss.str (); +} +} \ No newline at end of file diff --git a/nano/core_test/common.hpp b/nano/core_test/common.hpp new file mode 100644 index 0000000000..a4d3328289 --- /dev/null +++ b/nano/core_test/common.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include + +#include + +using namespace std::chrono_literals; + +namespace nano +{ +inline void wait_peer_connections (nano::system & system_a) +{ + auto wait_peer_count = [&system_a](bool in_memory) { + auto num_nodes = system_a.nodes.size (); + system_a.deadline_set (20s); + size_t peer_count = 0; + while (peer_count != num_nodes * (num_nodes - 1)) + { + ASSERT_NO_ERROR (system_a.poll ()); + peer_count = std::accumulate (system_a.nodes.cbegin (), system_a.nodes.cend (), std::size_t{ 0 }, [in_memory](auto total, auto const & node) { + if (in_memory) + { + return total += node->network.size (); + } + else + { + auto transaction = node->store.tx_begin_read (); + return total += node->store.peer_count (transaction); + } + }); + } + }; + + // Do a pre-pass with in-memory containers to reduce IO if still in the process of connecting to peers + wait_peer_count (true); + wait_peer_count (false); +} +} diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index 7851e58624..f42996393d 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -1,176 +1,197 @@ #include +#include #include #include +#include + using namespace std::chrono_literals; namespace { -void add_callback_stats (nano::node & node) +void add_callback_stats (nano::node & node, std::vector * observer_order = nullptr, std::mutex * mutex = nullptr) { - node.observers.blocks.add ([& stats = node.stats](nano::election_status const & status_a, nano::account const &, nano::amount const &, bool) { + node.observers.blocks.add ([& stats = node.stats, observer_order, mutex](nano::election_status const & status_a, nano::account const &, nano::amount const &, bool) { stats.inc (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out); + if (mutex) + { + nano::lock_guard guard (*mutex); + debug_assert (observer_order); + observer_order->push_back (status_a.winner->hash ()); + } }); } +nano::stat::detail get_stats_detail (nano::confirmation_height_mode mode_a) +{ + debug_assert (mode_a == nano::confirmation_height_mode::bounded || mode_a == nano::confirmation_height_mode::unbounded); + return (mode_a == nano::confirmation_height_mode::bounded) ? nano::stat::detail::blocks_confirmed_bounded : nano::stat::detail::blocks_confirmed_unbounded; +} } TEST (confirmation_height, single) { - auto amount (std::numeric_limits::max ()); - nano::system system (24000, 2); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - system.wallet (1)->insert_adhoc (key1.prv); - auto send1 (std::make_shared (latest1, key1.pub, amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); - - // Check confirmation heights before, should be uninitialized (1 for genesis). - uint64_t confirmation_height; - for (auto & node : system.nodes) - { + auto test_mode = [](nano::confirmation_height_mode mode_a) { + auto amount (std::numeric_limits::max ()); + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + auto node = system.add_node (node_flags); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest1 (node->latest (nano::test_genesis_key.pub)); + auto send1 (std::make_shared (nano::test_genesis_key.pub, latest1, nano::test_genesis_key.pub, amount - 100, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); + + // Check confirmation heights before, should be uninitialized (1 for genesis). + nano::confirmation_height_info confirmation_height_info; add_callback_stats (*node); auto transaction = node->store.tx_begin_read (); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - } + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (nano::genesis_hash, confirmation_height_info.frontier); - for (auto & node : system.nodes) - { node->process_active (send1); node->block_processor.flush (); system.deadline_set (10s); - while (true) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 1) { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, send1->hash ())) - { - break; - } - ASSERT_NO_ERROR (system.poll ()); } - auto transaction = node->store.tx_begin_write (); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, send1->hash ())); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (2, confirmation_height_info.height); + ASSERT_EQ (send1->hash (), confirmation_height_info.frontier); + + // Rollbacks should fail as these blocks have been cemented + ASSERT_TRUE (node->ledger.rollback (transaction, latest1)); + ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ())); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (2, node->ledger.cache.cemented_count); + + ASSERT_EQ (0, node->active.election_winner_details_size ()); + } + }; - // Rollbacks should fail as these blocks have been cemented - ASSERT_TRUE (node->ledger.rollback (transaction, latest1)); - ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ())); - ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, multiple_accounts) { - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - system.add_node (node_config); - node_config.peering_port = 24002; - system.add_node (node_config); - nano::keypair key1; - nano::keypair key2; - nano::keypair key3; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - system.wallet (1)->insert_adhoc (key1.prv); - system.wallet (0)->insert_adhoc (key2.prv); - system.wallet (1)->insert_adhoc (key3.prv); - - // Send to all accounts - nano::send_block send1 (latest1, key1.pub, system.nodes.front ()->config.online_weight_minimum.number () + 300, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); - nano::send_block send2 (send1.hash (), key2.pub, system.nodes.front ()->config.online_weight_minimum.number () + 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - nano::send_block send3 (send2.hash (), key3.pub, system.nodes.front ()->config.online_weight_minimum.number () + 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); - - // Open all accounts - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); - nano::open_block open2 (send2.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); - nano::open_block open3 (send3.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, *system.work.generate (key3.pub)); - - // Send and recieve various blocks to these accounts - nano::send_block send4 (open1.hash (), key2.pub, 50, key1.prv, key1.pub, *system.work.generate (open1.hash ())); - nano::send_block send5 (send4.hash (), key2.pub, 10, key1.prv, key1.pub, *system.work.generate (send4.hash ())); - - nano::receive_block receive1 (open2.hash (), send4.hash (), key2.prv, key2.pub, *system.work.generate (open2.hash ())); - nano::send_block send6 (receive1.hash (), key3.pub, 10, key2.prv, key2.pub, *system.work.generate (receive1.hash ())); - nano::receive_block receive2 (send6.hash (), send5.hash (), key2.prv, key2.pub, *system.work.generate (send6.hash ())); - - for (auto & node : system.nodes) - { - add_callback_stats (*node); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + nano::keypair key1; + nano::keypair key2; + nano::keypair key3; + nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + + // Send to all accounts + nano::send_block send1 (latest1, key1.pub, system.nodes.front ()->config.online_weight_minimum.number () + 300, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); + nano::send_block send2 (send1.hash (), key2.pub, system.nodes.front ()->config.online_weight_minimum.number () + 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::send_block send3 (send2.hash (), key3.pub, system.nodes.front ()->config.online_weight_minimum.number () + 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); + + // Open all accounts + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::open_block open2 (send2.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); + nano::open_block open3 (send3.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, *system.work.generate (key3.pub)); + + // Send and receive various blocks to these accounts + nano::send_block send4 (open1.hash (), key2.pub, 50, key1.prv, key1.pub, *system.work.generate (open1.hash ())); + nano::send_block send5 (send4.hash (), key2.pub, 10, key1.prv, key1.pub, *system.work.generate (send4.hash ())); + + nano::receive_block receive1 (open2.hash (), send4.hash (), key2.prv, key2.pub, *system.work.generate (open2.hash ())); + nano::send_block send6 (receive1.hash (), key3.pub, 10, key2.prv, key2.pub, *system.work.generate (receive1.hash ())); + nano::receive_block receive2 (send6.hash (), send5.hash (), key2.prv, key2.pub, *system.work.generate (send6.hash ())); - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - - // Check confirmation heights of all the accounts are uninitialized (0), - // as we have any just added them to the ledger and not processed any live transactions yet. - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - } + add_callback_stats (*node); - // The nodes process a live receive which propagates across to all accounts - auto receive3 = std::make_shared (open3.hash (), send6.hash (), key3.prv, key3.pub, *system.work.generate (open3.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + + // Check confirmation heights of all the accounts are uninitialized (0), + // as we have any just added them to the ledger and not processed any live transactions yet. + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (nano::genesis_hash, confirmation_height_info.frontier); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier); + } - for (auto & node : system.nodes) - { + // The nodes process a live receive which propagates across to all accounts + auto receive3 = std::make_shared (open3.hash (), send6.hash (), key3.prv, key3.pub, *system.work.generate (open3.hash ())); node->process_active (receive3); node->block_processor.flush (); + node->block_confirm (receive3); + { + auto election = node->active.election (receive3->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } system.deadline_set (10s); - while (true) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 10) { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive3->hash ())) - { - break; - } - ASSERT_NO_ERROR (system.poll ()); } nano::account_info account_info; - uint64_t confirmation_height; + nano::confirmation_height_info confirmation_height_info; auto & store = node->store; auto transaction = node->store.tx_begin_read (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ())); ASSERT_FALSE (store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (4, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (4, confirmation_height_info.height); + ASSERT_EQ (send3.hash (), confirmation_height_info.frontier); ASSERT_EQ (4, account_info.block_count); ASSERT_FALSE (store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height_info)); + ASSERT_EQ (2, confirmation_height_info.height); + ASSERT_EQ (send4.hash (), confirmation_height_info.frontier); ASSERT_EQ (3, account_info.block_count); ASSERT_FALSE (store.account_get (transaction, key2.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height_info)); + ASSERT_EQ (3, confirmation_height_info.height); + ASSERT_EQ (send6.hash (), confirmation_height_info.frontier); ASSERT_EQ (4, account_info.block_count); ASSERT_FALSE (store.account_get (transaction, key3.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height_info)); + ASSERT_EQ (2, confirmation_height_info.height); + ASSERT_EQ (receive3->hash (), confirmation_height_info.frontier); ASSERT_EQ (2, account_info.block_count); // The accounts for key1 and key2 have 1 more block in the chain than is confirmed. @@ -195,103 +216,125 @@ TEST (confirmation_height, multiple_accounts) ASSERT_TRUE (node->ledger.rollback (transaction, send2.hash ())); } ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } + ASSERT_EQ (11, node->ledger.cache.cemented_count); + + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, gap_bootstrap) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); - nano::genesis genesis; - nano::keypair destination; - auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send1); - auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send2); - auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send3); - auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*open1); - - // Receive - auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*receive1); - auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*receive2); - - node1.block_processor.add (send1); - node1.block_processor.add (send2); - node1.block_processor.add (send3); - node1.block_processor.add (receive1); - node1.block_processor.flush (); - - add_callback_stats (node1); - - // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked - node1.process_active (receive2); - node1.block_processor.flush (); - - // Confirmation heights should not be updated - { - auto transaction (node1.store.tx_begin_read ()); - auto unchecked_count (node1.store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 2); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + auto & node1 = *system.add_node (node_flags); + nano::genesis genesis; + nano::keypair destination; + auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send1); + auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send2); + auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send3); + auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*open1); + + // Receive + auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*receive1); + auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*receive2); + + node1.block_processor.add (send1); + node1.block_processor.add (send2); + node1.block_processor.add (send3); + node1.block_processor.add (receive1); + node1.block_processor.flush (); + + add_callback_stats (node1); - uint64_t confirmation_height; - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - } + // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked + node1.process_active (receive2); + node1.block_processor.flush (); - // Now complete the chain where the block comes in on the bootstrap network. - node1.block_processor.add (open1); - node1.block_processor.flush (); + // Confirmation heights should not be updated + { + auto transaction (node1.store.tx_begin_read ()); + auto unchecked_count (node1.store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 2); + + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (genesis.hash (), confirmation_height_info.frontier); + } - // Confirmation height should be unchanged and unchecked should now be 0 - { - auto transaction (node1.store.tx_begin_read ()); - auto unchecked_count (node1.store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 0); + // Now complete the chain where the block comes in on the bootstrap network. + node1.block_processor.add (open1); + node1.block_processor.flush (); - uint64_t confirmation_height; - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, destination.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - } - ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + // Confirmation height should be unchanged and unchecked should now be 0 + { + auto transaction (node1.store.tx_begin_read ()); + auto unchecked_count (node1.store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 0); + + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (genesis.hash (), confirmation_height_info.frontier); + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, destination.pub, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier); + } + ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (1, node1.ledger.cache.cemented_count); + + ASSERT_EQ (0, node1.active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, gap_live) { - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - system.add_node (node_config); - node_config.peering_port = 24002; - system.add_node (node_config); - nano::keypair destination; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (1)->insert_adhoc (destination.prv); - - nano::genesis genesis; - auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send1); - auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send2); - auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send3); - - auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*open1); - auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*receive1); - auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*receive2); - - for (auto & node : system.nodes) - { + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + node_config.peering_port = nano::get_available_port (); + system.add_node (node_config, node_flags); + nano::keypair destination; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (1)->insert_adhoc (destination.prv); + + nano::genesis genesis; + auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node->work_generate_blocking (*send1); + auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node->work_generate_blocking (*send2); + auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node->work_generate_blocking (*send3); + + auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); + node->work_generate_blocking (*open1); + auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); + node->work_generate_blocking (*receive1); + auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); + node->work_generate_blocking (*receive2); + node->block_processor.add (send1); node->block_processor.add (send2); node->block_processor.add (send3); @@ -307,24 +350,26 @@ TEST (confirmation_height, gap_live) // Confirmation heights should not be updated { auto transaction = node->store.tx_begin_read (); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (nano::genesis_hash, confirmation_height_info.frontier); } // Now complete the chain where the block comes in on the live network node->process_active (open1); node->block_processor.flush (); + node->block_confirm (open1); + { + auto election = node->active.election (open1->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } system.deadline_set (10s); - while (true) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 6) { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive2->hash ())) - { - break; - } - ASSERT_NO_ERROR (system.poll ()); } @@ -333,852 +378,1293 @@ TEST (confirmation_height, gap_live) auto unchecked_count (node->store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (4, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, destination.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive2->hash ())); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (4, confirmation_height_info.height); + ASSERT_EQ (send3->hash (), confirmation_height_info.frontier); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, destination.pub, confirmation_height_info)); + ASSERT_EQ (3, confirmation_height_info.height); + ASSERT_EQ (receive2->hash (), confirmation_height_info.frontier); ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } + ASSERT_EQ (7, node->ledger.cache.cemented_count); + + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, send_receive_between_2_accounts) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - system.wallet (0)->insert_adhoc (key1.prv); - - nano::send_block send1 (latest, key1.pub, node->config.online_weight_minimum.number () + 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); - - nano::send_block send2 (open1.hash (), nano::genesis_account, 1000, key1.prv, key1.pub, *system.work.generate (open1.hash ())); - nano::send_block send3 (send2.hash (), nano::genesis_account, 900, key1.prv, key1.pub, *system.work.generate (send2.hash ())); - nano::send_block send4 (send3.hash (), nano::genesis_account, 500, key1.prv, key1.pub, *system.work.generate (send3.hash ())); - - nano::receive_block receive1 (send1.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - nano::receive_block receive2 (receive1.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive1.hash ())); - nano::receive_block receive3 (receive2.hash (), send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); - - nano::send_block send5 (receive3.hash (), key1.pub, node->config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive3.hash ())); - auto receive4 = std::make_shared (send4.hash (), send5.hash (), key1.prv, key1.pub, *system.work.generate (send4.hash ())); - // Unpocketed send - nano::keypair key2; - nano::send_block send6 (send5.hash (), key2.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + nano::keypair key1; + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::send_block send1 (latest, key1.pub, node->config.online_weight_minimum.number () + 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::send_block send2 (open1.hash (), nano::genesis_account, 1000, key1.prv, key1.pub, *system.work.generate (open1.hash ())); + nano::send_block send3 (send2.hash (), nano::genesis_account, 900, key1.prv, key1.pub, *system.work.generate (send2.hash ())); + nano::send_block send4 (send3.hash (), nano::genesis_account, 500, key1.prv, key1.pub, *system.work.generate (send3.hash ())); + + nano::receive_block receive1 (send1.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::receive_block receive2 (receive1.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive1.hash ())); + nano::receive_block receive3 (receive2.hash (), send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); + + nano::send_block send5 (receive3.hash (), key1.pub, node->config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive3.hash ())); + auto receive4 = std::make_shared (send4.hash (), send5.hash (), key1.prv, key1.pub, *system.work.generate (send4.hash ())); + // Unpocketed send + nano::keypair key2; + nano::send_block send6 (send5.hash (), key2.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - } - - add_callback_stats (*node); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + } - node->process_active (receive4); - node->block_processor.flush (); + add_callback_stats (*node); - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive4->hash ())) + node->process_active (receive4); + node->block_processor.flush (); + node->block_confirm (receive4); { - break; + auto election = node->active.election (receive4->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); } - ASSERT_NO_ERROR (system.poll ()); - } + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 10) + { + ASSERT_NO_ERROR (system.poll ()); + } - auto transaction (node->store.tx_begin_read ()); + auto transaction (node->store.tx_begin_read ()); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive4->hash ())); + nano::account_info account_info; + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (6, confirmation_height_info.height); + ASSERT_EQ (send5.hash (), confirmation_height_info.frontier); + ASSERT_EQ (7, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height_info)); + ASSERT_EQ (5, confirmation_height_info.height); + ASSERT_EQ (receive4->hash (), confirmation_height_info.frontier); + ASSERT_EQ (5, account_info.block_count); - nano::account_info account_info; - uint64_t confirmation_height; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (6, confirmation_height); - ASSERT_EQ (7, account_info.block_count); + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (11, node->ledger.cache.cemented_count); - ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (5, confirmation_height); - ASSERT_EQ (5, account_info.block_count); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; - ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (11, node->ledger.cemented_count); + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, send_receive_self) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - - nano::send_block send1 (latest, nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - nano::receive_block receive1 (send1.hash (), send1.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive1.hash ())); - nano::send_block send3 (send2.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); - - nano::receive_block receive2 (send3.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); - auto receive3 = std::make_shared (receive2.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); - - // Send to another account to prevent automatic receiving on the genesis account - nano::keypair key1; - nano::send_block send4 (receive3->hash (), key1.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive3->hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - } + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - add_callback_stats (*node); + nano::send_block send1 (latest, nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + nano::receive_block receive1 (send1.hash (), send1.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive1.hash ())); + nano::send_block send3 (send2.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); - node->block_confirm (receive3); + nano::receive_block receive2 (send3.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); + auto receive3 = std::make_shared (receive2.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive3->hash ())) + // Send to another account to prevent automatic receiving on the genesis account + nano::keypair key1; + nano::send_block send4 (receive3->hash (), key1.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive3->hash ())); { - break; + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); } - ASSERT_NO_ERROR (system.poll ()); - } + add_callback_stats (*node); + + node->block_confirm (receive3); + { + auto election = node->active.election (receive3->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } - auto transaction (node->store.tx_begin_read ()); - nano::account_info account_info; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (7, confirmation_height); - ASSERT_EQ (8, account_info.block_count); - ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (confirmation_height, node->ledger.cemented_count); + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 6) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction (node->store.tx_begin_read ()); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ())); + nano::account_info account_info; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (7, confirmation_height_info.height); + ASSERT_EQ (receive3->hash (), confirmation_height_info.frontier); + ASSERT_EQ (8, account_info.block_count); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (confirmation_height_info.height, node->ledger.cache.cemented_count); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, all_block_types) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - nano::keypair key1; - nano::keypair key2; - auto & store = node->store; - nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - nano::send_block send1 (send.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + nano::keypair key1; + nano::keypair key2; + auto & store = node->store; + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + nano::send_block send1 (send.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); - nano::open_block open (send.hash (), nano::test_genesis_key.pub, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); - nano::state_block state_open (key2.pub, 0, 0, nano::Gxrb_ratio, send1.hash (), key2.prv, key2.pub, *system.work.generate (key2.pub)); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::state_block state_open (key2.pub, 0, 0, nano::Gxrb_ratio, send1.hash (), key2.prv, key2.pub, *system.work.generate (key2.pub)); - nano::send_block send2 (open.hash (), key2.pub, 0, key1.prv, key1.pub, *system.work.generate (open.hash ())); - nano::state_block state_receive (key2.pub, state_open.hash (), 0, nano::Gxrb_ratio * 2, send2.hash (), key2.prv, key2.pub, *system.work.generate (state_open.hash ())); + nano::send_block send2 (open.hash (), key2.pub, 0, key1.prv, key1.pub, *system.work.generate (open.hash ())); + nano::state_block state_receive (key2.pub, state_open.hash (), 0, nano::Gxrb_ratio * 2, send2.hash (), key2.prv, key2.pub, *system.work.generate (state_open.hash ())); - nano::state_block state_send (key2.pub, state_receive.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, *system.work.generate (state_receive.hash ())); - nano::receive_block receive (send2.hash (), state_send.hash (), key1.prv, key1.pub, *system.work.generate (send2.hash ())); + nano::state_block state_send (key2.pub, state_receive.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, *system.work.generate (state_receive.hash ())); + nano::receive_block receive (send2.hash (), state_send.hash (), key1.prv, key1.pub, *system.work.generate (send2.hash ())); - nano::change_block change (receive.hash (), key2.pub, key1.prv, key1.pub, *system.work.generate (receive.hash ())); + nano::change_block change (receive.hash (), key2.pub, key1.prv, key1.pub, *system.work.generate (receive.hash ())); - nano::state_block state_change (key2.pub, state_send.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.work.generate (state_send.hash ())); + nano::state_block state_change (key2.pub, state_send.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.work.generate (state_send.hash ())); - nano::state_block epoch (key2.pub, state_change.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, node->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (state_change.hash ())); + nano::state_block epoch (key2.pub, state_change.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, node->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (state_change.hash ())); - nano::state_block epoch1 (key1.pub, change.hash (), key2.pub, nano::Gxrb_ratio, node->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (change.hash ())); - nano::state_block state_send1 (key1.pub, epoch1.hash (), 0, nano::Gxrb_ratio - 1, key2.pub, key1.prv, key1.pub, *system.work.generate (epoch1.hash ())); - nano::state_block state_receive2 (key2.pub, epoch.hash (), 0, nano::Gxrb_ratio + 1, state_send1.hash (), key2.prv, key2.pub, *system.work.generate (epoch.hash ())); + nano::state_block epoch1 (key1.pub, change.hash (), key2.pub, nano::Gxrb_ratio, node->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (change.hash ())); + nano::state_block state_send1 (key1.pub, epoch1.hash (), 0, nano::Gxrb_ratio - 1, key2.pub, key1.prv, key1.pub, *system.work.generate (epoch1.hash ())); + nano::state_block state_receive2 (key2.pub, epoch.hash (), 0, nano::Gxrb_ratio + 1, state_send1.hash (), key2.prv, key2.pub, *system.work.generate (epoch.hash ())); - auto state_send2 = std::make_shared (key2.pub, state_receive2.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, *system.work.generate (state_receive2.hash ())); - nano::state_block state_send3 (key2.pub, state_send2->hash (), 0, nano::Gxrb_ratio - 1, key1.pub, key2.prv, key2.pub, *system.work.generate (state_send2->hash ())); + auto state_send2 = std::make_shared (key2.pub, state_receive2.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, *system.work.generate (state_receive2.hash ())); + nano::state_block state_send3 (key2.pub, state_send2->hash (), 0, nano::Gxrb_ratio - 1, key1.pub, key2.prv, key2.pub, *system.work.generate (state_send2->hash ())); - nano::state_block state_send4 (key1.pub, state_send1.hash (), 0, nano::Gxrb_ratio - 2, nano::test_genesis_key.pub, key1.prv, key1.pub, *system.work.generate (state_send1.hash ())); - nano::state_block state_receive3 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2 + 1, state_send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::state_block state_send4 (key1.pub, state_send1.hash (), 0, nano::Gxrb_ratio - 2, nano::test_genesis_key.pub, key1.prv, key1.pub, *system.work.generate (state_send1.hash ())); + nano::state_block state_receive3 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2 + 1, state_send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - { - auto transaction (store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_open).code); + { + auto transaction (store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_open).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, change).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_change).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, change).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_change).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive3).code); - } + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive3).code); + } - add_callback_stats (*node); - node->block_confirm (state_send2); + add_callback_stats (*node); + node->block_confirm (state_send2); + { + auto election = node->active.election (state_send2->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, state_send2->hash ())) + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 15) { - break; + ASSERT_NO_ERROR (system.poll ()); } - ASSERT_NO_ERROR (system.poll ()); - } + auto transaction (node->store.tx_begin_read ()); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, state_send2->hash ())); + nano::account_info account_info; + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (3, confirmation_height_info.height); + ASSERT_EQ (send1.hash (), confirmation_height_info.frontier); + ASSERT_LE (4, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height_info)); + ASSERT_EQ (state_send1.hash (), confirmation_height_info.frontier); + ASSERT_EQ (6, confirmation_height_info.height); + ASSERT_LE (7, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key2.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height_info)); + ASSERT_EQ (7, confirmation_height_info.height); + ASSERT_EQ (state_send2->hash (), confirmation_height_info.frontier); + ASSERT_LE (8, account_info.block_count); + + ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (15, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (16, node->ledger.cache.cemented_count); + + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; - auto transaction (node->store.tx_begin_read ()); - nano::account_info account_info; - uint64_t confirmation_height; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); - ASSERT_LE (4, account_info.block_count); - - ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (6, confirmation_height); - ASSERT_LE (7, account_info.block_count); - - ASSERT_FALSE (node->store.account_get (transaction, key2.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (7, confirmation_height); - ASSERT_LE (8, account_info.block_count); - - ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (15, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (16, node->ledger.cemented_count); + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } /* Bulk of the this test was taken from the node.fork_flip test */ TEST (confirmation_height, conflict_rollback_cemented) { - boost::iostreams::stream_buffer sb; - sb.open (nano::stringstream_mt_sink{}); - nano::boost_log_cerr_redirect redirect_cerr (&sb); - nano::system system (24000, 2); - auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); - ASSERT_EQ (1, node1.network.size ()); - nano::keypair key1; - nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - nano::publish publish1 (send1); - nano::keypair key2; - auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - nano::publish publish2 (send2); - auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); - node1.network.process_message (publish1, channel1); - node1.block_processor.flush (); - auto channel2 (node2.network.udp_channels.create (node1.network.endpoint ())); - node2.network.process_message (publish2, channel2); - node2.block_processor.flush (); - ASSERT_EQ (1, node1.active.size ()); - ASSERT_EQ (1, node2.active.size ()); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - node1.network.process_message (publish2, channel1); - node1.block_processor.flush (); - node2.network.process_message (publish1, channel2); - node2.block_processor.flush (); - nano::unique_lock lock (node2.active.mutex); - auto conflict (node2.active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); - ASSERT_NE (node2.active.roots.end (), conflict); - auto votes1 (conflict->election); - ASSERT_NE (nullptr, votes1); - ASSERT_EQ (1, votes1->last_votes.size ()); - lock.unlock (); - // Force blocks to be cemented on both nodes - { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); - node1.store.confirmation_height_put (transaction, nano::genesis_account, 2); - } - { - auto transaction (system.nodes[1]->store.tx_begin_write ()); - ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); - node2.store.confirmation_height_put (transaction, nano::genesis_account, 2); - } + auto test_mode = [](nano::confirmation_height_mode mode_a) { + boost::iostreams::stream_buffer sb; + sb.open (nano::stringstream_mt_sink{}); + nano::boost_log_cerr_redirect redirect_cerr (&sb); + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + auto node1 = system.add_node (node_flags); + auto node2 = system.add_node (node_flags); + ASSERT_EQ (1, node1->network.size ()); + nano::keypair key1; + nano::genesis genesis; + auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + nano::publish publish1 (send1); + nano::keypair key2; + auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + nano::publish publish2 (send2); + auto channel1 (node1->network.udp_channels.create (node1->network.endpoint ())); + node1->network.process_message (publish1, channel1); + node1->block_processor.flush (); + auto channel2 (node2->network.udp_channels.create (node1->network.endpoint ())); + node2->network.process_message (publish2, channel2); + node2->block_processor.flush (); + ASSERT_EQ (1, node1->active.size ()); + ASSERT_EQ (1, node2->active.size ()); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node1->network.process_message (publish2, channel1); + node1->block_processor.flush (); + node2->network.process_message (publish1, channel2); + node2->block_processor.flush (); + nano::unique_lock lock (node2->active.mutex); + auto conflict (node2->active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); + ASSERT_NE (node2->active.roots.end (), conflict); + auto votes1 (conflict->election); + ASSERT_NE (nullptr, votes1); + ASSERT_EQ (1, votes1->last_votes.size ()); + lock.unlock (); + // Force blocks to be cemented on both nodes + { + auto transaction (node1->store.tx_begin_write ()); + ASSERT_TRUE (node1->store.block_exists (transaction, publish1.block->hash ())); + node1->store.confirmation_height_put (transaction, nano::genesis_account, nano::confirmation_height_info{ 2, send2->hash () }); + } + { + auto transaction (node2->store.tx_begin_write ()); + ASSERT_TRUE (node2->store.block_exists (transaction, publish2.block->hash ())); + node2->store.confirmation_height_put (transaction, nano::genesis_account, nano::confirmation_height_info{ 2, send2->hash () }); + } + + auto rollback_log_entry = boost::str (boost::format ("Failed to roll back %1%") % send2->hash ().to_string ()); + system.deadline_set (20s); + auto done (false); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + done = (sb.component ()->str ().find (rollback_log_entry) != std::string::npos); + } + auto transaction1 (node1->store.tx_begin_read ()); + auto transaction2 (node2->store.tx_begin_read ()); + lock.lock (); + auto winner (*votes1->tally ().begin ()); + ASSERT_EQ (*publish1.block, *winner.second); + ASSERT_EQ (nano::genesis_amount - 100, winner.first); + ASSERT_TRUE (node1->store.block_exists (transaction1, publish1.block->hash ())); + ASSERT_TRUE (node2->store.block_exists (transaction2, publish2.block->hash ())); + ASSERT_FALSE (node2->store.block_exists (transaction2, publish1.block->hash ())); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); +} + +TEST (confirmation_heightDeathTest, rollback_added_block) +{ + // For ASSERT_DEATH_IF_SUPPORTED + testing::FLAGS_gtest_death_test_style = "threadsafe"; - auto rollback_log_entry = boost::str (boost::format ("Failed to roll back %1%") % send2->hash ().to_string ()); - system.deadline_set (20s); - auto done (false); - while (!done) + // valgrind can be noisy with death tests + if (!nano::running_within_valgrind ()) { - ASSERT_NO_ERROR (system.poll ()); - done = (sb.component ()->str ().find (rollback_log_entry) != std::string::npos); + nano::logger_mt logger; + auto path (nano::unique_path ()); + nano::mdb_store store (logger, path); + ASSERT_TRUE (!store.init_error ()); + nano::genesis genesis; + nano::stat stats; + nano::ledger ledger (store, stats); + nano::write_database_queue write_database_queue; + nano::work_pool pool (std::numeric_limits::max ()); + nano::keypair key1; + auto send = std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); + { + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + } + + auto block_hash_being_processed (send->hash ()); + uint64_t batch_write_size = 2048; + std::atomic stopped{ false }; + nano::confirmation_height_unbounded unbounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); + + // Processing a block which doesn't exist should bail + ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.process (), ""); + + nano::confirmation_height_bounded bounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); + // Processing a block which doesn't exist should bail + ASSERT_DEATH_IF_SUPPORTED (bounded_processor.process (), ""); } - auto transaction1 (system.nodes[0]->store.tx_begin_read ()); - auto transaction2 (system.nodes[1]->store.tx_begin_read ()); - lock.lock (); - auto winner (*votes1->tally ().begin ()); - ASSERT_EQ (*publish1.block, *winner.second); - ASSERT_EQ (nano::genesis_amount - 100, winner.first); - ASSERT_TRUE (node1.store.block_exists (transaction1, publish1.block->hash ())); - ASSERT_TRUE (node2.store.block_exists (transaction2, publish2.block->hash ())); - ASSERT_FALSE (node2.store.block_exists (transaction2, publish1.block->hash ())); } TEST (confirmation_height, observers) { - auto amount (std::numeric_limits::max ()); - nano::system system (24000, 1); - auto node1 (system.nodes[0]); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); - auto send1 (std::make_shared (latest1, key1.pub, amount - node1->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); - - add_callback_stats (*node1); - - node1->process_active (send1); - node1->block_processor.flush (); - bool confirmed (false); - system.deadline_set (10s); - while (!confirmed) - { + auto test_mode = [](nano::confirmation_height_mode mode_a) { + auto amount (std::numeric_limits::max ()); + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + auto node1 = system.add_node (node_flags); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); + auto send1 (std::make_shared (latest1, key1.pub, amount - node1->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); + + add_callback_stats (*node1); + + node1->process_active (send1); + node1->block_processor.flush (); + system.deadline_set (10s); + while (node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } auto transaction = node1->store.tx_begin_read (); - confirmed = node1->ledger.block_confirmed (transaction, send1->hash ()); - ASSERT_NO_ERROR (system.poll ()); - } - ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_TRUE (node1->ledger.block_confirmed (transaction, send1->hash ())); + ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (2, node1->ledger.cache.cemented_count); + ASSERT_EQ (0, node1->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } -// This tests when a read has been done and the block no longer exists by the time a write is done -TEST (confirmation_height, modified_chain) +// This tests when a read has been done, but the block no longer exists by the time a write is done +TEST (confirmation_heightDeathTest, modified_chain) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); + // For ASSERT_DEATH_IF_SUPPORTED + testing::FLAGS_gtest_death_test_style = "threadsafe"; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + // valgrind can be noisy with death tests + if (!nano::running_within_valgrind ()) + { + nano::logger_mt logger; + auto path (nano::unique_path ()); + nano::mdb_store store (logger, path); + ASSERT_TRUE (!store.init_error ()); + nano::genesis genesis; + nano::stat stats; + nano::ledger ledger (store, stats); + nano::write_database_queue write_database_queue; + nano::work_pool pool (std::numeric_limits::max ()); + nano::keypair key1; + auto send = std::make_shared (nano::genesis_hash, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (nano::genesis_hash)); + { + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send).code); + } - nano::keypair key1; - auto & store = node->store; - auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + auto block_hash_being_processed (send->hash ()); + uint64_t batch_write_size = 2048; + std::atomic stopped{ false }; + nano::confirmation_height_bounded bounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + { + // This reads the blocks in the account, but prevents any writes from occuring yet + auto scoped_write_guard = write_database_queue.wait (nano::writer::testing); + bounded_processor.process (); + } + + // Rollback the block and now try to write, the block no longer exists so should bail + ledger.rollback (store.tx_begin_write (), send->hash ()); + { + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + ASSERT_DEATH_IF_SUPPORTED (bounded_processor.cement_blocks (scoped_write_guard), ""); + } + + ASSERT_EQ (nano::process_result::progress, ledger.process (store.tx_begin_write (), *send).code); + store.confirmation_height_put (store.tx_begin_write (), nano::genesis_account, { 1, nano::genesis_hash }); + + nano::confirmation_height_unbounded unbounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); + + { + // This reads the blocks in the account, but prevents any writes from occuring yet + auto scoped_write_guard = write_database_queue.wait (nano::writer::testing); + unbounded_processor.process (); + } + + // Rollback the block and now try to write, the block no longer exists so should bail + ledger.rollback (store.tx_begin_write (), send->hash ()); + { + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.cement_blocks (scoped_write_guard), ""); + } } +} - node->confirmation_height_processor.add (send->hash ()); +// This tests when a read has been done, but the account no longer exists by the time a write is done +TEST (confirmation_heightDeathTest, modified_chain_account_removed) +{ + // For ASSERT_DEATH_IF_SUPPORTED + testing::FLAGS_gtest_death_test_style = "threadsafe"; + // valgrind can be noisy with death tests + if (!nano::running_within_valgrind ()) { - // The write guard prevents the confirmation height processor doing any writes - auto write_guard = node->write_database_queue.wait (nano::writer::testing); - while (!node->write_database_queue.contains (nano::writer::confirmation_height)) - ; + nano::logger_mt logger; + auto path (nano::unique_path ()); + nano::mdb_store store (logger, path); + ASSERT_TRUE (!store.init_error ()); + nano::genesis genesis; + nano::stat stats; + nano::ledger ledger (store, stats); + nano::write_database_queue write_database_queue; + nano::work_pool pool (std::numeric_limits::max ()); + nano::keypair key1; + auto send = std::make_shared (nano::genesis_hash, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (nano::genesis_hash)); + auto open = std::make_shared (key1.pub, 0, 0, nano::Gxrb_ratio, send->hash (), key1.prv, key1.pub, *pool.generate (key1.pub)); + { + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *open).code); + } - store.block_del (store.tx_begin_write (), send->hash ()); - } + auto block_hash_being_processed (open->hash ()); + uint64_t batch_write_size = 2048; + std::atomic stopped{ false }; + nano::confirmation_height_unbounded unbounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); + + { + // This reads the blocks in the account, but prevents any writes from occuring yet + auto scoped_write_guard = write_database_queue.wait (nano::writer::testing); + unbounded_processor.process (); + } - while (node->write_database_queue.contains (nano::writer::confirmation_height)) - ; + // Rollback the block and now try to write, the send should be cemented but the account which the open block belongs no longer exists so should bail + ledger.rollback (store.tx_begin_write (), open->hash ()); + { + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.cement_blocks (scoped_write_guard), ""); + } - ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); + // Reset conditions and test with the bounded processor + ASSERT_EQ (nano::process_result::progress, ledger.process (store.tx_begin_write (), *open).code); + store.confirmation_height_put (store.tx_begin_write (), nano::genesis_account, { 1, nano::genesis_hash }); + + nano::confirmation_height_bounded bounded_processor ( + ledger, write_database_queue, 10ms, logger, stopped, block_hash_being_processed, batch_write_size, [](auto const &) {}, [](auto const &) {}, []() { return 0; }); + + { + // This reads the blocks in the account, but prevents any writes from occuring yet + auto scoped_write_guard = write_database_queue.wait (nano::writer::testing); + bounded_processor.process (); + } + + // Rollback the block and now try to write, the send should be cemented but the account which the open block belongs no longer exists so should bail + ledger.rollback (store.tx_begin_write (), open->hash ()); + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + ASSERT_DEATH_IF_SUPPORTED (bounded_processor.cement_blocks (scoped_write_guard), ""); + } } namespace nano { TEST (confirmation_height, pending_observer_callbacks) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - nano::keypair key1; - nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - auto send1 = std::make_shared (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); + nano::keypair key1; + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + auto send1 = std::make_shared (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); - } + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + } - add_callback_stats (*node); + add_callback_stats (*node); - node->confirmation_height_processor.add (send1->hash ()); + node->confirmation_height_processor.add (send1->hash ()); - while (true) - { - if (node->pending_confirmation_height.size () == 0) - { - break; - } - } - // Can have timing issues. - node->confirmation_height_processor.add (send.hash ()); - { - nano::unique_lock lk (node->pending_confirmation_height.mutex); - while (!node->pending_confirmation_height.current_hash.is_zero ()) + system.deadline_set (10s); + // Confirm the callback is not called under this circumstance because there is no election information + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 1 || node->ledger.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out) != 1) { - lk.unlock (); - std::this_thread::yield (); - lk.lock (); + ASSERT_NO_ERROR (system.poll ()); } - } - // Confirm the callback is not called under this circumstance - ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (0, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (3, node->ledger.cemented_count); + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (3, node->ledger.cache.cemented_count); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } TEST (confirmation_height, prioritize_frontiers) { - nano::system system; - // Prevent frontiers being confirmed as it will affect the priorization checking - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - - nano::keypair key1; - nano::keypair key2; - nano::keypair key3; - nano::keypair key4; - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - - // Send different numbers of blocks all accounts - nano::send_block send1 (latest1, key1.pub, node->config.online_weight_minimum.number () + 10000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); - nano::send_block send2 (send1.hash (), key1.pub, node->config.online_weight_minimum.number () + 8500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - nano::send_block send3 (send2.hash (), key1.pub, node->config.online_weight_minimum.number () + 8000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); - nano::send_block send4 (send3.hash (), key2.pub, node->config.online_weight_minimum.number () + 7500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); - nano::send_block send5 (send4.hash (), key3.pub, node->config.online_weight_minimum.number () + 6500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send4.hash ())); - nano::send_block send6 (send5.hash (), key4.pub, node->config.online_weight_minimum.number () + 6000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5.hash ())); - - // Open all accounts and add other sends to get different uncemented counts (as well as some which are the same) - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); - nano::send_block send7 (open1.hash (), nano::test_genesis_key.pub, 500, key1.prv, key1.pub, *system.work.generate (open1.hash ())); - - nano::open_block open2 (send4.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); - - nano::open_block open3 (send5.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, *system.work.generate (key3.pub)); - nano::send_block send8 (open3.hash (), nano::test_genesis_key.pub, 500, key3.prv, key3.pub, *system.work.generate (open3.hash ())); - nano::send_block send9 (send8.hash (), nano::test_genesis_key.pub, 200, key3.prv, key3.pub, *system.work.generate (send8.hash ())); - - nano::open_block open4 (send6.hash (), nano::genesis_account, key4.pub, key4.prv, key4.pub, *system.work.generate (key4.pub)); - nano::send_block send10 (open4.hash (), nano::test_genesis_key.pub, 500, key4.prv, key4.pub, *system.work.generate (open4.hash ())); - nano::send_block send11 (send10.hash (), nano::test_genesis_key.pub, 200, key4.prv, key4.pub, *system.work.generate (send10.hash ())); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + // Prevent frontiers being confirmed as it will affect the priorization checking + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send7).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send8).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send9).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send10).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send11).code); - } + nano::keypair key1; + nano::keypair key2; + nano::keypair key3; + nano::keypair key4; + nano::block_hash latest1 (node->latest (nano::test_genesis_key.pub)); - auto transaction = node->store.tx_begin_read (); - constexpr auto num_accounts = 5; - // clang-format off - auto priority_orders_match = [](auto const & cementable_frontiers, auto const & desired_order) { - return std::equal (desired_order.begin (), desired_order.end (), cementable_frontiers.template get<1> ().begin (), cementable_frontiers.template get<1> ().end (), [](nano::account const & account, nano::cementable_account const & cementable_account) { - return (account == cementable_account.account); - }); - }; - // clang-format on - { - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts); - // Check the order of accounts is as expected (greatest number of uncemented blocks at the front). key3 and key4 have the same value, the order is unspecified so check both - std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; - std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); - } + // Send different numbers of blocks all accounts + nano::send_block send1 (latest1, key1.pub, node->config.online_weight_minimum.number () + 10000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); + nano::send_block send2 (send1.hash (), key1.pub, node->config.online_weight_minimum.number () + 8500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::send_block send3 (send2.hash (), key1.pub, node->config.online_weight_minimum.number () + 8000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); + nano::send_block send4 (send3.hash (), key2.pub, node->config.online_weight_minimum.number () + 7500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); + nano::send_block send5 (send4.hash (), key3.pub, node->config.online_weight_minimum.number () + 6500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send4.hash ())); + nano::send_block send6 (send5.hash (), key4.pub, node->config.online_weight_minimum.number () + 6000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5.hash ())); - { - // Add some to the local node wallets and check ordering of both containers - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (0)->insert_adhoc (key1.prv); - system.wallet (0)->insert_adhoc (key2.prv); - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts - 3); - ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts - 2); - std::array local_desired_order{ nano::genesis_account, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, local_desired_order)); - std::array desired_order_1{ key3.pub, key4.pub }; - std::array desired_order_2{ key4.pub, key3.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); - } + // Open all accounts and add other sends to get different uncemented counts (as well as some which are the same) + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::send_block send7 (open1.hash (), nano::test_genesis_key.pub, 500, key1.prv, key1.pub, *system.work.generate (open1.hash ())); - { - // Add the remainder of accounts to node wallets and check size/ordering is correct - system.wallet (0)->insert_adhoc (key3.prv); - system.wallet (0)->insert_adhoc (key4.prv); + nano::open_block open2 (send4.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); + + nano::open_block open3 (send5.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, *system.work.generate (key3.pub)); + nano::send_block send8 (open3.hash (), nano::test_genesis_key.pub, 500, key3.prv, key3.pub, *system.work.generate (open3.hash ())); + nano::send_block send9 (send8.hash (), nano::test_genesis_key.pub, 200, key3.prv, key3.pub, *system.work.generate (send8.hash ())); + + nano::open_block open4 (send6.hash (), nano::genesis_account, key4.pub, key4.prv, key4.pub, *system.work.generate (key4.pub)); + nano::send_block send10 (open4.hash (), nano::test_genesis_key.pub, 500, key4.prv, key4.pub, *system.work.generate (open4.hash ())); + nano::send_block send11 (send10.hash (), nano::test_genesis_key.pub, 200, key4.prv, key4.pub, *system.work.generate (send10.hash ())); + + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send7).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send8).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send9).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send10).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send11).code); + } + + auto transaction = node->store.tx_begin_read (); + constexpr auto num_accounts = 5; + auto priority_orders_match = [](auto const & cementable_frontiers, auto const & desired_order) { + return std::equal (desired_order.begin (), desired_order.end (), cementable_frontiers.template get<1> ().begin (), cementable_frontiers.template get<1> ().end (), [](nano::account const & account, nano::cementable_account const & cementable_account) { + return (account == cementable_account.account); + }); + }; + { + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts); + // Check the order of accounts is as expected (greatest number of uncemented blocks at the front). key3 and key4 have the same value, the order is unspecified so check both + std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; + std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); + } + + { + // Add some to the local node wallets and check ordering of both containers + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key1.prv); + system.wallet (0)->insert_adhoc (key2.prv); + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts - 3); + ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts - 2); + std::array local_desired_order{ nano::genesis_account, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, local_desired_order)); + std::array desired_order_1{ key3.pub, key4.pub }; + std::array desired_order_2{ key4.pub, key3.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); + } + + { + // Add the remainder of accounts to node wallets and check size/ordering is correct + system.wallet (0)->insert_adhoc (key3.prv); + system.wallet (0)->insert_adhoc (key4.prv); + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), 0); + ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts); + std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; + std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_2)); + } + + // Check that accounts which already exist have their order modified when the uncemented count changes. + nano::send_block send12 (send9.hash (), nano::test_genesis_key.pub, 100, key3.prv, key3.pub, *system.work.generate (send9.hash ())); + nano::send_block send13 (send12.hash (), nano::test_genesis_key.pub, 90, key3.prv, key3.pub, *system.work.generate (send12.hash ())); + nano::send_block send14 (send13.hash (), nano::test_genesis_key.pub, 80, key3.prv, key3.pub, *system.work.generate (send13.hash ())); + nano::send_block send15 (send14.hash (), nano::test_genesis_key.pub, 70, key3.prv, key3.pub, *system.work.generate (send14.hash ())); + nano::send_block send16 (send15.hash (), nano::test_genesis_key.pub, 60, key3.prv, key3.pub, *system.work.generate (send15.hash ())); + nano::send_block send17 (send16.hash (), nano::test_genesis_key.pub, 50, key3.prv, key3.pub, *system.work.generate (send16.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send12).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send13).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send14).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send15).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send16).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send17).code); + } + transaction.refresh (); node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), 0); - ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts); - std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; - std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_2)); - } + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub })); + node->active.confirm_prioritized_frontiers (transaction); - // Check that accounts which already exist have their order modified when the uncemented count changes. - nano::send_block send12 (send9.hash (), nano::test_genesis_key.pub, 100, key3.prv, key3.pub, *system.work.generate (send9.hash ())); - nano::send_block send13 (send12.hash (), nano::test_genesis_key.pub, 90, key3.prv, key3.pub, *system.work.generate (send12.hash ())); - nano::send_block send14 (send13.hash (), nano::test_genesis_key.pub, 80, key3.prv, key3.pub, *system.work.generate (send13.hash ())); - nano::send_block send15 (send14.hash (), nano::test_genesis_key.pub, 70, key3.prv, key3.pub, *system.work.generate (send14.hash ())); - nano::send_block send16 (send15.hash (), nano::test_genesis_key.pub, 60, key3.prv, key3.pub, *system.work.generate (send15.hash ())); - nano::send_block send17 (send16.hash (), nano::test_genesis_key.pub, 50, key3.prv, key3.pub, *system.work.generate (send16.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send12).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send13).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send14).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send15).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send16).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send17).code); - } - transaction.refresh (); - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub })); - node->active.search_frontiers (transaction); - - // Check that the active transactions roots contains the frontiers - system.deadline_set (std::chrono::seconds (10)); - while (node->active.size () != num_accounts) - { - ASSERT_NO_ERROR (system.poll ()); - } + // Check that the active transactions roots contains the frontiers + system.deadline_set (std::chrono::seconds (10)); + while (node->active.size () != num_accounts) + { + ASSERT_NO_ERROR (system.poll ()); + } - std::array frontiers{ send17.qualified_root (), send6.qualified_root (), send7.qualified_root (), open2.qualified_root (), send11.qualified_root () }; - for (auto & frontier : frontiers) - { - ASSERT_NE (node->active.roots.find (frontier), node->active.roots.end ()); - } + std::array frontiers{ send17.qualified_root (), send6.qualified_root (), send7.qualified_root (), open2.qualified_root (), send11.qualified_root () }; + for (auto & frontier : frontiers) + { + nano::lock_guard guard (node->active.mutex); + ASSERT_NE (node->active.roots.find (frontier), node->active.roots.end ()); + } + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } } TEST (confirmation_height, frontiers_confirmation_mode) { - nano::genesis genesis; - nano::keypair key; - // Always mode - { + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::genesis genesis; + nano::keypair key; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + // Always mode + { + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::always; + auto node = system.add_node (node_config, node_flags); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.deadline_set (5s); + while (node->active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + // Auto mode + { + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::automatic; + auto node = system.add_node (node_config, node_flags); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.deadline_set (5s); + while (node->active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + // Disabled mode + { + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + std::this_thread::sleep_for (std::chrono::seconds (1)); + ASSERT_EQ (0, node->active.size ()); + } + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); +} + +// The callback and confirmation history should only be updated after confirmation height is set (and not just after voting) +TEST (confirmation_height, callback_confirmed_history) +{ + auto test_mode = [](nano::confirmation_height_mode mode_a) { nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::always; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); + + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); { auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); } - system.deadline_set (5s); - while (node->active.size () != 1) + + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); + + add_callback_stats (*node); + + node->process_active (send1); + node->block_processor.flush (); + node->block_confirm (send1); + { + node->process_active (send); + node->block_processor.flush (); + + // The write guard prevents the confirmation height processor doing any writes + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + + // Confirm send1 + { + auto election = node->active.election (send1->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + system.deadline_set (10s); + while (node->active.size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (0, node->active.list_recently_cemented ().size ()); + { + nano::lock_guard guard (node->active.mutex); + ASSERT_EQ (0, node->active.blocks.size ()); + } + + auto transaction = node->store.tx_begin_read (); + ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); + + system.deadline_set (10s); + while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Confirm that no inactive callbacks have been called when the confirmation height processor has already iterated over it, waiting to write + ASSERT_EQ (0, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + } + + system.deadline_set (10s); + while (node->write_database_queue.contains (nano::writer::confirmation_height)) { ASSERT_NO_ERROR (system.poll ()); } - } - // Auto mode - { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::automatic; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + + auto transaction = node->store.tx_begin_read (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, send->hash ())); + + system.deadline_set (10s); + while (node->active.size () > 0) { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_NO_ERROR (system.poll ()); } - system.deadline_set (5s); - while (node->active.size () != 1) + + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out) != 1) { ASSERT_NO_ERROR (system.poll ()); } - } - // Disabled mode - { + + ASSERT_EQ (1, node->active.list_recently_cemented ().size ()); + ASSERT_EQ (0, node->active.blocks.size ()); + + // Confirm the callback is not called under this circumstance + ASSERT_EQ (2, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (3, node->ledger.cache.cemented_count); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); +} + +namespace nano +{ +TEST (confirmation_height, dependent_election) +{ + auto test_mode = [](nano::confirmation_height_mode mode_a) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + auto node = system.add_node (node_config, node_flags); + + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); + auto send2 = std::make_shared (send1->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ())); { auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code); } - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - std::this_thread::sleep_for (std::chrono::seconds (1)); - ASSERT_EQ (0, node->active.size ()); - } + + add_callback_stats (*node); + + // This election should be confirmed as active_conf_height + node->block_confirm (send1); + // Start an election and confirm it + node->block_confirm (send2); + { + auto election = node->active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 3) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (4, node->ledger.cache.cemented_count); + + ASSERT_EQ (0, node->active.election_winner_details_size ()); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } -// The callback and confirmation history should only be updated after confirmation height is set (and not just after voting) -TEST (confirmation_height, callback_confirmed_history) +// This test checks that a receive block with uncemented blocks below cements them too. +TEST (confirmation_height, cemented_gap_below_receive) { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - nano::keypair key1; - auto & store = node->store; - auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); - } + nano::keypair key1; + system.wallet (0)->insert_adhoc (key1.prv); - auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + nano::send_block send1 (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); + nano::keypair dummy_key; + nano::send_block dummy_send (send1.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - add_callback_stats (*node); + nano::open_block open (send.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::receive_block receive1 (open.hash (), send1.hash (), key1.prv, key1.pub, *system.work.generate (open.hash ())); + nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, key1.prv, key1.pub, *system.work.generate (receive1.hash ())); - node->process_active (send1); - node->block_processor.flush (); + nano::receive_block receive2 (dummy_send.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (dummy_send.hash ())); + nano::send_block dummy_send1 (receive2.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); + + nano::keypair key2; + system.wallet (0)->insert_adhoc (key2.prv); + nano::send_block send3 (dummy_send1.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 4, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (dummy_send1.hash ())); + nano::send_block dummy_send2 (send3.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 5, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); + + auto open1 = std::make_shared (send3.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); - { - node->process_active (send); - node->block_processor.flush (); - // The write guard prevents the confirmation height processor doing any writes - auto write_guard = node->write_database_queue.wait (nano::writer::testing); - system.deadline_set (10s); - while (node->active.size () > 0) { - ASSERT_NO_ERROR (system.poll ()); + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send1).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code); } - ASSERT_EQ (0, node->active.list_confirmed ().size ()); + std::vector observer_order; + std::mutex mutex; + add_callback_stats (*node, &observer_order, &mutex); + + node->block_confirm (open1); { + auto election = node->active.election (open1->qualified_root ()); + ASSERT_NE (nullptr, election); nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.blocks.size ()); + election->confirm_once (); } - - auto transaction = node->store.tx_begin_read (); - ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); - system.deadline_set (10s); - while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 10) { ASSERT_NO_ERROR (system.poll ()); } - // Confirm that no inactive callbacks have been called when the confirmation height processor has already iterated over it, waiting to write - ASSERT_EQ (0, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); - } + auto transaction = node->store.tx_begin_read (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, open1->hash ())); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out)); + ASSERT_EQ (9, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (11, node->ledger.cache.cemented_count); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + + // Check that the order of callbacks is correct + std::vector expected_order = { send.hash (), open.hash (), send1.hash (), receive1.hash (), send2.hash (), dummy_send.hash (), receive2.hash (), dummy_send1.hash (), send3.hash (), open1->hash () }; + nano::lock_guard guard (mutex); + ASSERT_EQ (observer_order, expected_order); + }; - system.deadline_set (10s); - while (node->write_database_queue.contains (nano::writer::confirmation_height)) - { - ASSERT_NO_ERROR (system.poll ()); - } + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); +} - auto transaction = node->store.tx_begin_read (); - ASSERT_TRUE (node->ledger.block_confirmed (transaction, send->hash ())); +// This test checks that a receive block with uncemented blocks below cements them too, compared with the test above, this +// is the first write in this chain. +TEST (confirmation_height, cemented_gap_below_no_cache) +{ + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); - ASSERT_EQ (1, node->active.list_confirmed ().size ()); - ASSERT_EQ (0, node->active.blocks.size ()); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - // Confirm the callback is not called under this circumstance - ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (2, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); + nano::keypair key1; + system.wallet (0)->insert_adhoc (key1.prv); - nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.pending_conf_height.size ()); -} + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + nano::send_block send1 (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send.hash ())); + nano::keypair dummy_key; + nano::send_block dummy_send (send1.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); -namespace nano -{ -TEST (confirmation_height, dependent_election) -{ - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - - nano::keypair key1; - auto & store = node->store; - auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); - auto send2 = std::make_shared (send1->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code); - } + nano::open_block open (send.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub)); + nano::receive_block receive1 (open.hash (), send1.hash (), key1.prv, key1.pub, *system.work.generate (open.hash ())); + nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, key1.prv, key1.pub, *system.work.generate (receive1.hash ())); - add_callback_stats (*node); + nano::receive_block receive2 (dummy_send.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (dummy_send.hash ())); + nano::send_block dummy_send1 (receive2.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (receive2.hash ())); - // Prevent the confirmation height processor from doing any processing - node->confirmation_height_processor.pause (); + nano::keypair key2; + system.wallet (0)->insert_adhoc (key2.prv); + nano::send_block send3 (dummy_send1.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 4, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (dummy_send1.hash ())); + nano::send_block dummy_send2 (send3.hash (), dummy_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 5, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3.hash ())); - // Wait until it has been processed - node->block_confirm (send2); - system.deadline_set (10s); - while (node->active.size () > 0) - { - ASSERT_NO_ERROR (system.poll ()); - } + auto open1 = std::make_shared (send3.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, *system.work.generate (key2.pub)); - system.deadline_set (10s); - while (node->pending_confirmation_height.size () != 1) - { - ASSERT_NO_ERROR (system.poll ()); - } + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send).code); - { - nano::lock_guard guard (node->pending_confirmation_height.mutex); - ASSERT_EQ (*node->pending_confirmation_height.pending.begin (), send2->hash ()); - } + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - // Now put the other block in active so it can be confirmed as a dependent election - node->block_confirm (send1); - node->confirmation_height_processor.unpause (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send1).code); - system.deadline_set (10s); - while (node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out) != 1 && node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_conf_height, nano::stat::dir::out) != 1) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, dummy_send2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code); + } - ASSERT_EQ (3, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); - ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + // Force some blocks to be cemented so that the cached confirmed info variable is empty + { + auto transaction (node->store.tx_begin_write ()); + node->store.confirmation_height_put (transaction, nano::genesis_account, nano::confirmation_height_info{ 3, send1.hash () }); + node->store.confirmation_height_put (transaction, key1.pub, nano::confirmation_height_info{ 2, receive1.hash () }); + } + + add_callback_stats (*node); - nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.pending_conf_height.size ()); + node->block_confirm (open1); + { + auto election = node->active.election (open1->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 6) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction = node->store.tx_begin_read (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, open1->hash ())); + ASSERT_EQ (node->active.election_winner_details_size (), 0); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out)); + ASSERT_EQ (5, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (7, node->ledger.cache.cemented_count); + }; + + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } -TEST (confirmation_height, dependent_election_after_already_cemented) +TEST (confirmation_height, election_winner_details_clearing) { - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); + auto test_mode = [](nano::confirmation_height_mode mode_a) { + nano::system system; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = mode_a; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config, node_flags); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - nano::keypair key1; - auto & store = node->store; - auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); - auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); + nano::keypair key1; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ())); + auto send2 = std::make_shared (send1->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); - } + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code); + } - add_callback_stats (*node); + add_callback_stats (*node); - { node->block_confirm (send1); - auto write_guard = node->write_database_queue.wait (nano::writer::testing); + { + auto election = node->active.election (send1->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + system.deadline_set (10s); - while (node->active.size () > 0) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 2) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (0, node->active.list_confirmed ().size ()); + ASSERT_EQ (0, node->active.election_winner_details_size ()); + node->block_confirm (send); { + auto election = node->active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.blocks.size ()); + election->confirm_once (); } - auto transaction = node->store.tx_begin_read (); - ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); + // Wait until this block is confirmed + system.deadline_set (10s); + while (node->active.election_winner_details_size () != 1 && !node->confirmation_height_processor.current ().is_zero ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + + node->block_confirm (send2); + { + auto election = node->active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } system.deadline_set (10s); - while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 3) { ASSERT_NO_ERROR (system.poll ()); } - node->block_confirm (send); + // Add an already cemented block with fake election details. It should get removed + node->active.add_election_winner_details (send2->hash (), nullptr); + node->confirmation_height_processor.add (send2->hash ()); + system.deadline_set (10s); - while (node->active.size () > 0) + while (node->active.election_winner_details_size () > 0) { ASSERT_NO_ERROR (system.poll ()); } - } - system.deadline_set (10s); - while (node->pending_confirmation_height.size () != 0) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (4, node->ledger.cache.cemented_count); + }; - system.deadline_set (10s); - nano::unique_lock lk (node->active.mutex); - while (node->active.pending_conf_height.size () > 0) - { - lk.unlock (); - ASSERT_NO_ERROR (system.poll ()); - lk.lock (); - } + test_mode (nano::confirmation_height_mode::bounded); + test_mode (nano::confirmation_height_mode::unbounded); } } + +TEST (confirmation_height, election_winner_details_clearing_node_process_confirmed) +{ + // Make sure election_winner_details is also cleared if the block never enters the confirmation height processor from node::process_confirmed + nano::system system (1); + auto node = system.nodes.front (); + + auto send = std::make_shared (nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash)); + // Add to election_winner_details. Use an unrealistic iteration so that it should fall into the else case and do a cleanup + node->active.add_election_winner_details (send->hash (), nullptr); + nano::election_status election; + election.winner = send; + node->process_confirmed (election, 1000000); + ASSERT_EQ (0, node->active.election_winner_details_size ()); +} diff --git a/nano/core_test/confirmation_solicitor.cpp b/nano/core_test/confirmation_solicitor.cpp new file mode 100644 index 0000000000..1d108712fc --- /dev/null +++ b/nano/core_test/confirmation_solicitor.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +TEST (confirmation_solicitor, batches) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + node_flags.disable_rep_crawler = true; + node_flags.disable_udp = false; + auto & node1 = *system.add_node (node_flags); + node_flags.disable_request_loop = true; + auto & node2 = *system.add_node (node_flags); + auto channel1 (node2.network.udp_channels.create (node1.network.endpoint ())); + // Solicitor will only solicit from this representative + nano::representative representative (nano::test_genesis_key.pub, nano::genesis_amount, channel1); + std::vector representatives{ representative }; + nano::confirmation_solicitor solicitor (node2.network, node2.network_params.network); + solicitor.prepare (representatives); + // Ensure the representatives are correct + ASSERT_EQ (1, representatives.size ()); + ASSERT_EQ (channel1, representatives.front ().channel); + ASSERT_EQ (nano::test_genesis_key.pub, representatives.front ().account); + ASSERT_TIMELY (3s, node2.network.size () == 1); + auto send (std::make_shared (nano::genesis_hash, nano::keypair ().pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); + send->sideband_set ({}); + { + nano::lock_guard guard (node2.active.mutex); + for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i) + { + auto election (std::make_shared (node2, send, nullptr, false)); + ASSERT_FALSE (solicitor.add (*election)); + } + ASSERT_EQ (1, solicitor.max_confirm_req_batches); + // Reached the maximum amount of requests for the channel + auto election (std::make_shared (node2, send, nullptr, false)); + ASSERT_TRUE (solicitor.add (*election)); + // Broadcasting should be immediate + ASSERT_EQ (0, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); + ASSERT_FALSE (solicitor.broadcast (*election)); + } + // One publish through directed broadcasting and another through random flooding + ASSERT_EQ (2, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); + solicitor.flush (); + ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out)); +} + +TEST (confirmation_solicitor, different_hash) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + node_flags.disable_rep_crawler = true; + node_flags.disable_udp = false; + auto & node1 = *system.add_node (node_flags); + auto & node2 = *system.add_node (node_flags); + auto channel1 (node2.network.udp_channels.create (node1.network.endpoint ())); + // Solicitor will only solicit from this representative + nano::representative representative (nano::test_genesis_key.pub, nano::genesis_amount, channel1); + std::vector representatives{ representative }; + nano::confirmation_solicitor solicitor (node2.network, node2.network_params.network); + solicitor.prepare (representatives); + // Ensure the representatives are correct + ASSERT_EQ (1, representatives.size ()); + ASSERT_EQ (channel1, representatives.front ().channel); + ASSERT_EQ (nano::test_genesis_key.pub, representatives.front ().account); + ASSERT_TIMELY (3s, node2.network.size () == 1); + auto send (std::make_shared (nano::genesis_hash, nano::keypair ().pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); + send->sideband_set ({}); + { + nano::lock_guard guard (node2.active.mutex); + auto election (std::make_shared (node2, send, nullptr, false)); + // Add a vote for something else, not the winner + election->last_votes[representative.account] = { std::chrono::steady_clock::now (), 1, 1 }; + // Ensure the request and broadcast goes through + ASSERT_FALSE (solicitor.add (*election)); + ASSERT_FALSE (solicitor.broadcast (*election)); + } + // One publish through directed broadcasting and another through random flooding + ASSERT_EQ (2, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); + solicitor.flush (); + ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out)); +} diff --git a/nano/core_test/conflicts.cpp b/nano/core_test/conflicts.cpp index c170bc24d5..522e5df765 100644 --- a/nano/core_test/conflicts.cpp +++ b/nano/core_test/conflicts.cpp @@ -1,13 +1,16 @@ #include +#include #include #include +#include + using namespace std::chrono_literals; TEST (conflicts, start_stop) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -15,59 +18,56 @@ TEST (conflicts, start_stop) node1.work_generate_blocking (*send1); ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code); ASSERT_EQ (0, node1.active.size ()); - node1.active.start (send1); + auto election1 = node1.active.insert (send1); ASSERT_EQ (1, node1.active.size ()); { nano::lock_guard guard (node1.active.mutex); - auto existing1 (node1.active.roots.find (send1->qualified_root ())); - ASSERT_NE (node1.active.roots.end (), existing1); - auto votes1 (existing1->election); - ASSERT_NE (nullptr, votes1); - ASSERT_EQ (1, votes1->last_votes.size ()); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (1, election1.election->last_votes.size ()); } } TEST (conflicts, add_existing) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; auto send1 (std::make_shared (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send1); ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code); - node1.active.start (send1); + node1.active.insert (send1); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.active.start (send2); + send2->sideband_set ({}); + auto election1 = node1.active.insert (send2); ASSERT_EQ (1, node1.active.size ()); auto vote1 (std::make_shared (key2.pub, key2.prv, 0, send2)); node1.active.vote (vote1); ASSERT_EQ (1, node1.active.size ()); { nano::lock_guard guard (node1.active.mutex); - auto votes1 (node1.active.roots.find (send2->qualified_root ())->election); - ASSERT_NE (nullptr, votes1); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (key2.pub)); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (key2.pub)); } } TEST (conflicts, add_two) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; auto send1 (std::make_shared (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send1); ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code); - node1.active.start (send1); + node1.active.insert (send1); nano::keypair key2; auto send2 (std::make_shared (send1->hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); ASSERT_EQ (nano::process_result::progress, node1.process (*send2).code); - node1.active.start (send2); + node1.active.insert (send2); ASSERT_EQ (2, node1.active.size ()); } @@ -160,14 +160,14 @@ TEST (vote_uniquer, cleanup) TEST (conflicts, reprioritize) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; auto send1 (std::make_shared (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send1); - uint64_t difficulty1; - nano::work_validate (*send1, &difficulty1); + auto difficulty1 (send1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (send1->work_version (), nano::block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */))), node1.network_params.network.publish_thresholds.epoch_1)); nano::send_block send1_copy (*send1); node1.process_active (send1); node1.block_processor.flush (); @@ -175,25 +175,25 @@ TEST (conflicts, reprioritize) nano::lock_guard guard (node1.active.mutex); auto existing1 (node1.active.roots.find (send1->qualified_root ())); ASSERT_NE (node1.active.roots.end (), existing1); - ASSERT_EQ (difficulty1, existing1->difficulty); + ASSERT_EQ (multiplier1, existing1->multiplier); } node1.work_generate_blocking (send1_copy, difficulty1); - uint64_t difficulty2; - nano::work_validate (send1_copy, &difficulty2); + auto difficulty2 (send1_copy.difficulty ()); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (send1_copy.work_version (), nano::block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (std::make_shared (send1_copy)); node1.block_processor.flush (); { nano::lock_guard guard (node1.active.mutex); auto existing2 (node1.active.roots.find (send1->qualified_root ())); ASSERT_NE (node1.active.roots.end (), existing2); - ASSERT_EQ (difficulty2, existing2->difficulty); + ASSERT_EQ (multiplier2, existing2->multiplier); } } TEST (conflicts, dependency) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node1 = system.add_node (node_config); nano::genesis genesis; @@ -205,25 +205,24 @@ TEST (conflicts, dependency) ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); ASSERT_EQ (nano::process_result::progress, node1->process (*state_open1).code); ASSERT_EQ (0, node1->active.size ()); - node1->active.start (send1); - node1->active.start (state_open1); + auto election1 = node1->active.insert (send1); + node1->active.insert (state_open1); ASSERT_EQ (2, node1->active.size ()); // Check dependency for send block { nano::lock_guard guard (node1->active.mutex); - auto existing1 (node1->active.roots.find (send1->qualified_root ())); - ASSERT_NE (node1->active.roots.end (), existing1); - auto election1 (existing1->election); - ASSERT_NE (nullptr, election1); - ASSERT_EQ (1, election1->dependent_blocks.size ()); - ASSERT_NE (election1->dependent_blocks.end (), election1->dependent_blocks.find (state_open1->hash ())); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (1, election1.election->dependent_blocks.size ()); + ASSERT_NE (election1.election->dependent_blocks.end (), election1.election->dependent_blocks.find (state_open1->hash ())); } } -TEST (conflicts, adjusted_difficulty) +TEST (conflicts, adjusted_multiplier) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node1 (*system.add_node (flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; @@ -250,49 +249,54 @@ TEST (conflicts, adjusted_difficulty) node1.process_active (open2); auto change1 (std::make_shared (key3.pub, open2->hash (), nano::test_genesis_key.pub, nano::xrb_ratio, 0, key3.prv, key3.pub, *system.work.generate (open2->hash ()))); node1.process_active (change1); - node1.block_processor.flush (); + nano::keypair key4; + auto send5 (std::make_shared (key3.pub, change1->hash (), nano::test_genesis_key.pub, 0, key4.pub, key3.prv, key3.pub, *system.work.generate (change1->hash ()))); // Pending for open epoch block + node1.process_active (send5); + nano::blocks_confirm (node1, { send1, send2, receive1, open1, send3, send4, open_epoch1, receive2, open2, change1, send5 }); system.deadline_set (3s); - while (node1.active.size () != 10) + while (node1.active.size () != 11) { ASSERT_NO_ERROR (system.poll ()); } - std::unordered_map adjusted_difficulties; + std::unordered_map adjusted_multipliers; { nano::lock_guard guard (node1.active.mutex); + node1.active.update_adjusted_multiplier (); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i) { - adjusted_difficulties.insert (std::make_pair (i->election->status.winner->hash (), i->adjusted_difficulty)); + adjusted_multipliers.insert (std::make_pair (i->election->status.winner->hash (), i->adjusted_multiplier)); } } // genesis - ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (send2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (send2->hash ())->second, adjusted_difficulties.find (receive1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send1->hash ())->second, adjusted_multipliers.find (send2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send2->hash ())->second, adjusted_multipliers.find (receive1->hash ())->second); // key1 - ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (open1->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open1->hash ())->second, adjusted_difficulties.find (send3->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (send4->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send1->hash ())->second, adjusted_multipliers.find (open1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open1->hash ())->second, adjusted_multipliers.find (send3->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send3->hash ())->second, adjusted_multipliers.find (send4->hash ())->second); //key2 - ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open_epoch1->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send3->hash ())->second, adjusted_multipliers.find (receive2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open_epoch1->hash ())->second, adjusted_multipliers.find (receive2->hash ())->second); // key3 - ASSERT_GT (adjusted_difficulties.find (send4->hash ())->second, adjusted_difficulties.find (open2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open2->hash ())->second, adjusted_difficulties.find (change1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send4->hash ())->second, adjusted_multipliers.find (open2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open2->hash ())->second, adjusted_multipliers.find (change1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (change1->hash ())->second, adjusted_multipliers.find (send5->hash ())->second); // Independent elections can have higher difficulty than adjusted tree - nano::keypair key4; - auto open_epoch2 (std::make_shared (key4.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key4.pub, adjusted_difficulties.find (send1->hash ())->second))); - uint64_t difficulty; - ASSERT_FALSE (nano::work_validate (*open_epoch2, &difficulty)); - ASSERT_GT (difficulty, adjusted_difficulties.find (send1->hash ())->second); + auto open_epoch2 (std::make_shared (key4.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key4.pub, nano::difficulty::from_multiplier ((adjusted_multipliers.find (send1->hash ())->second), node1.network_params.network.publish_thresholds.base)))); + ASSERT_GT (open_epoch2->difficulty (), nano::difficulty::from_multiplier ((adjusted_multipliers.find (send1->hash ())->second), node1.network_params.network.publish_thresholds.base)); node1.process_active (open_epoch2); node1.block_processor.flush (); + node1.block_confirm (open_epoch2); system.deadline_set (3s); - while (node1.active.size () != 11) + while (node1.active.size () != 12) { ASSERT_NO_ERROR (system.poll ()); } { nano::lock_guard guard (node1.active.mutex); + node1.active.update_adjusted_multiplier (); + ASSERT_EQ (node1.active.roots.size (), 12); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), open_epoch2->hash ()); } } diff --git a/nano/core_test/core_test_main.cc b/nano/core_test/core_test_main.cc index d9c606b40f..291bb63e39 100644 --- a/nano/core_test/core_test_main.cc +++ b/nano/core_test/core_test_main.cc @@ -3,10 +3,13 @@ #include #include +#include + namespace nano { void cleanup_test_directories_on_exit (); void force_nano_test_network (); +boost::filesystem::path unique_path (); } GTEST_API_ int main (int argc, char ** argv) diff --git a/nano/core_test/difficulty.cpp b/nano/core_test/difficulty.cpp index 52521dcbdc..1a987ec37e 100644 --- a/nano/core_test/difficulty.cpp +++ b/nano/core_test/difficulty.cpp @@ -1,9 +1,29 @@ +#include #include +#include #include +#include +#include #include -TEST (difficulty, multipliers) +TEST (system, work_generate_limited) +{ + nano::system system; + nano::block_hash key (1); + nano::network_constants constants; + auto min = constants.publish_thresholds.entry; + auto max = constants.publish_thresholds.base; + for (int i = 0; i < 5; ++i) + { + auto work = system.work_generate_limited (key, min, max); + auto difficulty = nano::work_difficulty (nano::work_version::work_1, key, work); + ASSERT_GE (difficulty, min); + ASSERT_LT (difficulty, max); + } +} + +TEST (difficultyDeathTest, multipliers) { // For ASSERT_DEATH_IF_SUPPORTED testing::FLAGS_gtest_death_test_style = "threadsafe"; @@ -44,25 +64,19 @@ TEST (difficulty, multipliers) ASSERT_EQ (difficulty, nano::difficulty::from_multiplier (expected_multiplier, base)); } - { + // The death checks don't fail on a release config, so guard against them #ifndef NDEBUG - // Causes valgrind to be noisy - if (!nano::running_within_valgrind ()) - { - uint64_t base = 0xffffffc000000000; - uint64_t difficulty_nil = 0; - double multiplier_nil = 0.; - - ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::to_multiplier (difficulty_nil, base), ""); - ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::from_multiplier (multiplier_nil, base), ""); - } -#endif - } -} + // Causes valgrind to be noisy + if (!nano::running_within_valgrind ()) + { + uint64_t base = 0xffffffc000000000; + uint64_t difficulty_nil = 0; + double multiplier_nil = 0.; -TEST (difficulty, network_constants) -{ - ASSERT_NEAR (16., nano::difficulty::to_multiplier (nano::network_constants::publish_full_threshold, nano::network_constants::publish_beta_threshold), 1e-10); + ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::to_multiplier (difficulty_nil, base), ""); + ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::from_multiplier (multiplier_nil, base), ""); + } +#endif } TEST (difficulty, overflow) @@ -105,3 +119,44 @@ TEST (difficulty, zero) ASSERT_EQ (difficulty, nano::difficulty::from_multiplier (multiplier, base)); } } + +TEST (difficulty, network_constants) +{ + nano::network_constants constants; + auto & full_thresholds = constants.publish_full; + auto & beta_thresholds = constants.publish_beta; + auto & test_thresholds = constants.publish_test; + + ASSERT_NEAR (8., nano::difficulty::to_multiplier (full_thresholds.epoch_2, full_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1 / 8., nano::difficulty::to_multiplier (full_thresholds.epoch_2_receive, full_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (full_thresholds.epoch_2_receive, full_thresholds.entry), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (full_thresholds.epoch_2, full_thresholds.base), 1e-10); + + ASSERT_NEAR (1 / 64., nano::difficulty::to_multiplier (beta_thresholds.epoch_1, full_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (2., nano::difficulty::to_multiplier (beta_thresholds.epoch_2, beta_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1 / 2., nano::difficulty::to_multiplier (beta_thresholds.epoch_2_receive, beta_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (beta_thresholds.epoch_2_receive, beta_thresholds.entry), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (beta_thresholds.epoch_2, beta_thresholds.base), 1e-10); + + ASSERT_NEAR (8., nano::difficulty::to_multiplier (test_thresholds.epoch_2, test_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1 / 8., nano::difficulty::to_multiplier (test_thresholds.epoch_2_receive, test_thresholds.epoch_1), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (test_thresholds.epoch_2_receive, test_thresholds.entry), 1e-10); + ASSERT_NEAR (1., nano::difficulty::to_multiplier (test_thresholds.epoch_2, test_thresholds.base), 1e-10); + + nano::work_version version{ nano::work_version::work_1 }; + ASSERT_EQ (constants.publish_thresholds.base, constants.publish_thresholds.epoch_2); + ASSERT_EQ (constants.publish_thresholds.base, nano::work_threshold_base (version)); + ASSERT_EQ (constants.publish_thresholds.entry, nano::work_threshold_entry (version)); + ASSERT_EQ (constants.publish_thresholds.epoch_1, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_0, false, false, false))); + ASSERT_EQ (constants.publish_thresholds.epoch_1, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_1, false, false, false))); + ASSERT_EQ (constants.publish_thresholds.epoch_1, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_1, false, false, false))); + + // Send [+ change] + ASSERT_EQ (constants.publish_thresholds.epoch_2, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_2, true, false, false))); + // Change + ASSERT_EQ (constants.publish_thresholds.epoch_2, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_2, false, false, false))); + // Receive [+ change] / Open + ASSERT_EQ (constants.publish_thresholds.epoch_2_receive, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_2, false, true, false))); + // Epoch + ASSERT_EQ (constants.publish_thresholds.epoch_2_receive, nano::work_threshold (version, nano::block_details (nano::epoch::epoch_2, false, false, true))); +} diff --git a/nano/core_test/distributed_work.cpp b/nano/core_test/distributed_work.cpp index af52c8289b..acd14bcdf5 100644 --- a/nano/core_test/distributed_work.cpp +++ b/nano/core_test/distributed_work.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -7,14 +8,14 @@ using namespace std::chrono_literals; TEST (distributed_work, stopped) { - nano::system system (24000, 1); + nano::system system (1); system.nodes[0]->distributed_work.stop (); - ASSERT_TRUE (system.nodes[0]->distributed_work.make (nano::block_hash (), {}, {}, nano::network_constants::publish_test_threshold)); + ASSERT_TRUE (system.nodes[0]->distributed_work.make (nano::work_version::work_1, nano::block_hash (), {}, nano::network_constants ().publish_thresholds.base, {})); } TEST (distributed_work, no_peers) { - nano::system system (24000, 1); + nano::system system (1); auto node (system.nodes[0]); nano::block_hash hash{ 1 }; boost::optional work; @@ -24,13 +25,13 @@ TEST (distributed_work, no_peers) work = work_a; done = true; }; - ASSERT_FALSE (node->distributed_work.make (hash, node->config.work_peers, callback, node->network_params.network.publish_threshold, nano::account ())); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, node->config.work_peers, node->network_params.network.publish_thresholds.base, callback, nano::account ())); system.deadline_set (5s); while (!done) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_FALSE (nano::work_validate (hash, *work)); + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, hash, *work), node->network_params.network.publish_thresholds.base); // should only be removed after cleanup ASSERT_EQ (1, node->distributed_work.items.size ()); while (!node->distributed_work.items.empty ()) @@ -42,19 +43,18 @@ TEST (distributed_work, no_peers) TEST (distributed_work, no_peers_disabled) { - nano::system system (24000, 0); - nano::node_config node_config (24000, system.logging); + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.work_threads = 0; auto & node = *system.add_node (node_config); - ASSERT_TRUE (node.distributed_work.make (nano::block_hash (), node.config.work_peers, {}, nano::network_constants::publish_test_threshold)); + ASSERT_TRUE (node.distributed_work.make (nano::work_version::work_1, nano::block_hash (), node.config.work_peers, nano::network_constants ().publish_thresholds.base, {})); } TEST (distributed_work, no_peers_cancel) { - nano::system system (24000, 0); - nano::node_config node_config (24000, system.logging); + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.max_work_generate_multiplier = 1e6; - node_config.max_work_generate_difficulty = nano::difficulty::from_multiplier (node_config.max_work_generate_multiplier, nano::network_constants::publish_test_threshold); auto & node = *system.add_node (node_config); nano::block_hash hash{ 1 }; bool done{ false }; @@ -62,7 +62,7 @@ TEST (distributed_work, no_peers_cancel) ASSERT_FALSE (work_a.is_initialized ()); done = true; }; - ASSERT_FALSE (node.distributed_work.make (hash, node.config.work_peers, callback_to_cancel, nano::difficulty::from_multiplier (1e6, node.network_params.network.publish_threshold))); + ASSERT_FALSE (node.distributed_work.make (nano::work_version::work_1, hash, node.config.work_peers, nano::difficulty::from_multiplier (1e6, node.network_params.network.publish_thresholds.base), callback_to_cancel)); ASSERT_EQ (1, node.distributed_work.items.size ()); // cleanup should not cancel or remove an ongoing work node.distributed_work.cleanup_finished (); @@ -78,7 +78,7 @@ TEST (distributed_work, no_peers_cancel) // now using observer done = false; - ASSERT_FALSE (node.distributed_work.make (hash, node.config.work_peers, callback_to_cancel, nano::difficulty::from_multiplier (1e6, node.network_params.network.publish_threshold))); + ASSERT_FALSE (node.distributed_work.make (nano::work_version::work_1, hash, node.config.work_peers, nano::difficulty::from_multiplier (1e6, node.network_params.network.publish_thresholds.base), callback_to_cancel)); ASSERT_EQ (1, node.distributed_work.items.size ()); node.observers.work_cancel.notify (hash); system.deadline_set (20s); @@ -90,7 +90,7 @@ TEST (distributed_work, no_peers_cancel) TEST (distributed_work, no_peers_multi) { - nano::system system (24000, 1); + nano::system system (1); auto node (system.nodes[0]); nano::block_hash hash{ 1 }; unsigned total{ 10 }; @@ -102,7 +102,7 @@ TEST (distributed_work, no_peers_multi) // Test many works for the same root for (unsigned i{ 0 }; i < total; ++i) { - ASSERT_FALSE (node->distributed_work.make (hash, node->config.work_peers, callback, nano::difficulty::from_multiplier (10, node->network_params.network.publish_threshold))); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, node->config.work_peers, nano::difficulty::from_multiplier (10, node->network_params.network.publish_thresholds.base), callback)); } // 1 root, and _total_ requests for that root are expected, but some may have already finished ASSERT_EQ (1, node->distributed_work.items.size ()); @@ -127,7 +127,7 @@ TEST (distributed_work, no_peers_multi) for (unsigned i{ 0 }; i < total; ++i) { nano::block_hash hash_i (i + 1); - ASSERT_FALSE (node->distributed_work.make (hash_i, node->config.work_peers, callback, node->network_params.network.publish_threshold)); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash_i, node->config.work_peers, node->network_params.network.publish_thresholds.base, callback)); } // 10 roots expected with 1 work each, but some may have completed so test for some ASSERT_GT (node->distributed_work.items.size (), 5); @@ -148,3 +148,160 @@ TEST (distributed_work, no_peers_multi) } count = 0; } + +TEST (distributed_work, peer) +{ + nano::system system; + nano::node_config node_config; + node_config.peering_port = nano::get_available_port (); + // Disable local work generation + node_config.work_threads = 0; + auto node (system.add_node (node_config)); + ASSERT_FALSE (node->local_work_generation_enabled ()); + nano::block_hash hash{ 1 }; + boost::optional work; + std::atomic done{ false }; + auto callback = [&work, &done](boost::optional work_a) { + ASSERT_TRUE (work_a.is_initialized ()); + work = work_a; + done = true; + }; + auto work_peer (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::good)); + work_peer->start (); + decltype (node->config.work_peers) peers; + peers.emplace_back ("::ffff:127.0.0.1", work_peer->port ()); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, peers, node->network_params.network.publish_thresholds.base, callback, nano::account ())); + system.deadline_set (5s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, hash, *work), node->network_params.network.publish_thresholds.base); + ASSERT_EQ (1, work_peer->generations_good); + ASSERT_EQ (0, work_peer->generations_bad); + ASSERT_NO_ERROR (system.poll ()); + ASSERT_EQ (0, work_peer->cancels); +} + +TEST (distributed_work, peer_malicious) +{ + nano::system system (1); + auto node (system.nodes[0]); + ASSERT_TRUE (node->local_work_generation_enabled ()); + nano::block_hash hash{ 1 }; + boost::optional work; + std::atomic done{ false }; + auto callback = [&work, &done](boost::optional work_a) { + ASSERT_TRUE (work_a.is_initialized ()); + work = work_a; + done = true; + }; + auto malicious_peer (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::malicious)); + malicious_peer->start (); + decltype (node->config.work_peers) peers; + peers.emplace_back ("::ffff:127.0.0.1", malicious_peer->port ()); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, peers, node->network_params.network.publish_thresholds.base, callback, nano::account ())); + system.deadline_set (5s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, hash, *work), node->network_params.network.publish_thresholds.base); + system.deadline_set (5s); + while (malicious_peer->generations_bad < 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + // make sure it was *not* the malicious peer that replied + ASSERT_EQ (0, malicious_peer->generations_good); + // initial generation + the second time when it also starts doing local generation + // it is possible local work generation finishes before the second request is sent, only 1 failure can be required to pass + ASSERT_GE (malicious_peer->generations_bad, 1); + // this peer should not receive a cancel + ASSERT_EQ (0, malicious_peer->cancels); + // Test again with no local work generation enabled to make sure the malicious peer is sent more than one request + node->config.work_threads = 0; + ASSERT_FALSE (node->local_work_generation_enabled ()); + auto malicious_peer2 (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::malicious)); + malicious_peer2->start (); + peers[0].second = malicious_peer2->port (); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, peers, node->network_params.network.publish_thresholds.base, {}, nano::account ())); + system.deadline_set (5s); + while (malicious_peer2->generations_bad < 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + node->distributed_work.cancel (hash); + ASSERT_EQ (0, malicious_peer2->cancels); +} + +TEST (distributed_work, peer_multi) +{ + nano::system system (1); + auto node (system.nodes[0]); + ASSERT_TRUE (node->local_work_generation_enabled ()); + nano::block_hash hash{ 1 }; + boost::optional work; + std::atomic done{ false }; + auto callback = [&work, &done](boost::optional work_a) { + ASSERT_TRUE (work_a.is_initialized ()); + work = work_a; + done = true; + }; + auto good_peer (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::good)); + auto malicious_peer (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::malicious)); + auto slow_peer (std::make_shared (node->work, node->io_ctx, nano::get_available_port (), work_peer_type::slow)); + good_peer->start (); + malicious_peer->start (); + slow_peer->start (); + decltype (node->config.work_peers) peers; + peers.emplace_back ("localhost", malicious_peer->port ()); + peers.emplace_back ("localhost", slow_peer->port ()); + peers.emplace_back ("localhost", good_peer->port ()); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, peers, node->network_params.network.publish_thresholds.base, callback, nano::account ())); + system.deadline_set (5s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, hash, *work), node->network_params.network.publish_thresholds.base); + system.deadline_set (5s); + while (slow_peer->cancels < 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (0, malicious_peer->generations_good); + ASSERT_EQ (1, malicious_peer->generations_bad); + ASSERT_EQ (0, malicious_peer->cancels); + + ASSERT_EQ (0, slow_peer->generations_good); + ASSERT_EQ (0, slow_peer->generations_bad); + ASSERT_EQ (1, slow_peer->cancels); + + ASSERT_EQ (1, good_peer->generations_good); + ASSERT_EQ (0, good_peer->generations_bad); + ASSERT_EQ (0, good_peer->cancels); +} + +TEST (distributed_work, fail_resolve) +{ + nano::system system (1); + auto node (system.nodes[0]); + nano::block_hash hash{ 1 }; + boost::optional work; + std::atomic done{ false }; + auto callback = [&work, &done](boost::optional work_a) { + ASSERT_TRUE (work_a.is_initialized ()); + work = work_a; + done = true; + }; + decltype (node->config.work_peers) peers; + peers.emplace_back ("beeb.boop.123z", 0); + ASSERT_FALSE (node->distributed_work.make (nano::work_version::work_1, hash, peers, node->network_params.network.publish_thresholds.base, callback, nano::account ())); + system.deadline_set (5s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, hash, *work), node->network_params.network.publish_thresholds.base); +} diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp new file mode 100644 index 0000000000..b112aab5ba --- /dev/null +++ b/nano/core_test/election.cpp @@ -0,0 +1,177 @@ +#include +#include +#include + +#include + +TEST (election, construction) +{ + nano::system system (1); + nano::genesis genesis; + auto & node = *system.nodes[0]; + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); + auto election = node.active.insert (genesis.open).election; + ASSERT_TRUE (election->idle ()); + election->transition_active (); + ASSERT_FALSE (election->idle ()); + election->transition_passive (); + ASSERT_FALSE (election->idle ()); +} + +namespace nano +{ +TEST (election, bisect_dependencies) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node = *system.add_node (flags); + nano::genesis genesis; + nano::confirmation_height_info conf_info; + ASSERT_FALSE (node.store.confirmation_height_get (node.store.tx_begin_read (), nano::test_genesis_key.pub, conf_info)); + ASSERT_EQ (1, conf_info.height); + std::vector> blocks; + blocks.push_back (nullptr); // idx == height + blocks.push_back (genesis.open); + nano::block_builder builder; + auto amount = nano::genesis_amount; + for (int i = 0; i < 299; ++i) + { + auto latest = blocks.back (); + blocks.push_back (builder.state () + .previous (latest->hash ()) + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .balance (--amount) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (latest->hash ())) + .build ()); + ASSERT_EQ (nano::process_result::progress, node.process (*blocks.back ()).code); + } + ASSERT_EQ (301, blocks.size ()); + ASSERT_TRUE (node.active.empty ()); + { + auto election = node.active.insert (blocks.back ()).election; + ASSERT_NE (nullptr, election); + ASSERT_EQ (300, election->blocks.begin ()->second->sideband ().height); + nano::unique_lock lock (node.active.mutex); + election->activate_dependencies (); + node.active.activate_dependencies (lock); + } + // The first dependency activation also starts an election for the first unconfirmed block + ASSERT_EQ (3, node.active.size ()); + { + auto election = node.active.election (blocks[2]->qualified_root ()); + ASSERT_NE (nullptr, election); + ASSERT_EQ (2, election->blocks.begin ()->second->sideband ().height); + } + + auto check_height_and_activate_next = [&node, &blocks](uint64_t height_a) { + auto election = node.active.election (blocks[height_a]->qualified_root ()); + ASSERT_NE (nullptr, election); + ASSERT_EQ (height_a, election->blocks.begin ()->second->sideband ().height); + nano::unique_lock lock (node.active.mutex); + election->activate_dependencies (); + node.active.activate_dependencies (lock); + }; + check_height_and_activate_next (300 - 128); // ensure limited by 128 jumps + ASSERT_EQ (4, node.active.size ()); + check_height_and_activate_next (87); + ASSERT_EQ (5, node.active.size ()); + check_height_and_activate_next (44); + ASSERT_EQ (6, node.active.size ()); + check_height_and_activate_next (23); + ASSERT_EQ (7, node.active.size ()); + check_height_and_activate_next (12); + ASSERT_EQ (8, node.active.size ()); + check_height_and_activate_next (7); + ASSERT_EQ (9, node.active.size ()); + check_height_and_activate_next (4); + ASSERT_EQ (10, node.active.size ()); + check_height_and_activate_next (3); + ASSERT_EQ (10, node.active.size ()); // height 2 already inserted initially, no more blocks to activate + check_height_and_activate_next (2); + ASSERT_EQ (10, node.active.size ()); // conf height is 1, no more blocks to activate + ASSERT_EQ (node.active.blocks.size (), node.active.roots.size ()); +} + +// Tests successful dependency activation of the open block of an account, and its corresponding source +TEST (election, dependencies_open_link) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node = *system.add_node (flags); + + nano::state_block_builder builder; + nano::keypair key; + + // Send to key + auto gen_send = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + // Receive from genesis + auto key_open = builder.make_block () + .account (key.pub) + .previous (0) + .representative (key.pub) + .link (gen_send->hash ()) + .balance (1) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + // Send to self + std::shared_ptr key_send = builder.make_block () + .account (key.pub) + .previous (key_open->hash ()) + .representative (key.pub) + .link (key.pub) + .balance (0) + .sign (key.prv, key.pub) + .work (*system.work.generate (key_open->hash ())) + .build (); + + node.process (*gen_send); + node.process (*key_open); + node.process (*key_send); + + // Insert frontier + node.block_confirm (key_send); + ASSERT_EQ (1, node.active.size ()); + { + auto election = node.active.election (key_send->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::unique_lock lock (node.active.mutex); + election->activate_dependencies (); + node.active.activate_dependencies (lock); + } + // Must have activated the open block + ASSERT_EQ (2, node.active.size ()); + { + auto election = node.active.election (key_open->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::unique_lock lock (node.active.mutex); + election->activate_dependencies (); + node.active.activate_dependencies (lock); + } + // Must have activated the open's source block + ASSERT_EQ (3, node.active.size ()); + { + auto election = node.active.election (gen_send->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::unique_lock lock (node.active.mutex); + election->activate_dependencies (); + node.active.activate_dependencies (lock); + } + // Nothing else to activate + ASSERT_EQ (3, node.active.size ()); +} +} diff --git a/nano/core_test/epochs.cpp b/nano/core_test/epochs.cpp index 96a9c3292d..4355885ab4 100644 --- a/nano/core_test/epochs.cpp +++ b/nano/core_test/epochs.cpp @@ -1,5 +1,5 @@ +#include #include -#include #include diff --git a/nano/core_test/fakes/websocket_client.hpp b/nano/core_test/fakes/websocket_client.hpp new file mode 100644 index 0000000000..895b8bcd4b --- /dev/null +++ b/nano/core_test/fakes/websocket_client.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +namespace +{ +// Creates its own io context +class fake_websocket_client +{ +public: + fake_websocket_client (unsigned port) : + socket (std::make_shared> (ioc)) + { + std::string const host = "::1"; + boost::asio::ip::tcp::resolver resolver{ ioc }; + auto const results = resolver.resolve (host, std::to_string (port)); + boost::asio::connect (socket->next_layer (), results.begin (), results.end ()); + socket->handshake (host, "/"); + socket->text (true); + } + + ~fake_websocket_client () + { + if (socket->is_open ()) + { + socket->async_close (boost::beast::websocket::close_code::normal, [socket = this->socket](boost::beast::error_code const & ec) { + // A synchronous close usually hangs in tests when the server's io_context stops looping + // An async_close solves this problem + }); + } + } + + void send_message (std::string const & message_a) + { + socket->write (boost::asio::buffer (message_a)); + } + + void await_ack () + { + debug_assert (socket->is_open ()); + boost::beast::flat_buffer buffer; + socket->read (buffer); + } + + boost::optional get_response (std::chrono::seconds const deadline = 5s) + { + debug_assert (deadline > 0s); + boost::optional result; + auto buffer (std::make_shared ()); + socket->async_read (*buffer, [&result, &buffer, socket = this->socket](boost::beast::error_code const & ec, std::size_t const /*n*/) { + if (!ec) + { + std::ostringstream res; + res << beast_buffers (buffer->data ()); + result = res.str (); + } + }); + ioc.run_one_for (deadline); + return result; + } + +private: + boost::asio::io_context ioc; + std::shared_ptr> socket; +}; +} diff --git a/nano/core_test/fakes/work_peer.hpp b/nano/core_test/fakes/work_peer.hpp new file mode 100644 index 0000000000..2f54c09dd0 --- /dev/null +++ b/nano/core_test/fakes/work_peer.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace ptree = boost::property_tree; +namespace asio = boost::asio; +using tcp = boost::asio::ip::tcp; + +namespace +{ +enum class work_peer_type +{ + good, + malicious, + slow +}; + +class work_peer_connection : public std::enable_shared_from_this +{ + const std::string generic_error = "Unable to parse JSON"; + const std::string empty_response = "Empty response"; + +public: + work_peer_connection (asio::io_context & ioc_a, work_peer_type const type_a, nano::work_version const version_a, nano::work_pool & pool_a, std::function on_generation_a, std::function on_cancel_a) : + socket (ioc_a), + type (type_a), + version (version_a), + work_pool (pool_a), + on_generation (on_generation_a), + on_cancel (on_cancel_a), + timer (ioc_a) + { + } + void start () + { + read_request (); + } + tcp::socket socket; + +private: + work_peer_type type; + nano::work_version version; + nano::work_pool & work_pool; + beast::flat_buffer buffer{ 8192 }; + http::request request; + http::response response; + std::function on_generation; + std::function on_cancel; + asio::deadline_timer timer; + + void read_request () + { + auto this_l = shared_from_this (); + http::async_read (socket, buffer, request, [this_l](beast::error_code ec, std::size_t const /*size_a*/) { + if (!ec) + { + this_l->process_request (); + } + }); + } + + void process_request () + { + switch (request.method ()) + { + case http::verb::post: + response.result (http::status::ok); + create_response (); + break; + + default: + response.result (http::status::bad_request); + break; + } + } + + void create_response () + { + std::stringstream istream (request.body ()); + try + { + ptree::ptree result; + ptree::read_json (istream, result); + handle (result); + } + catch (...) + { + error (generic_error); + write_response (); + } + response.version (request.version ()); + response.keep_alive (false); + } + + void write_response () + { + auto this_l = shared_from_this (); + response.set (http::field::content_length, response.body ().size ()); + http::async_write (socket, response, [this_l](beast::error_code ec, std::size_t /*size_a*/) { + this_l->socket.shutdown (tcp::socket::shutdown_send, ec); + this_l->socket.close (); + }); + } + + void error (std::string const & message_a) + { + ptree::ptree error_l; + error_l.put ("error", message_a); + std::stringstream ostream; + ptree::write_json (ostream, error_l); + beast::ostream (response.body ()) << ostream.str (); + } + + void handle_generate (nano::block_hash const & hash_a) + { + if (type == work_peer_type::good) + { + auto hash = hash_a; + auto request_difficulty = nano::work_threshold_base (version); + auto this_l (shared_from_this ()); + work_pool.generate (version, hash, request_difficulty, [this_l, hash](boost::optional work_a) { + auto result = work_a.value_or (0); + auto result_difficulty (nano::work_difficulty (this_l->version, hash, result)); + static nano::network_params params; + ptree::ptree message_l; + message_l.put ("work", nano::to_string_hex (result)); + message_l.put ("difficulty", nano::to_string_hex (result_difficulty)); + message_l.put ("multiplier", nano::to_string (nano::difficulty::to_multiplier (result_difficulty, nano::work_threshold_base (this_l->version)))); + message_l.put ("hash", hash.to_string ()); + std::stringstream ostream; + ptree::write_json (ostream, message_l); + beast::ostream (this_l->response.body ()) << ostream.str (); + // Delay response by 500ms as a slow peer, immediate async call for a good peer + this_l->timer.expires_from_now (boost::posix_time::milliseconds (this_l->type == work_peer_type::slow ? 500 : 0)); + this_l->timer.async_wait ([this_l, result](const boost::system::error_code & ec) { + if (this_l->on_generation) + { + this_l->on_generation (result != 0); + } + this_l->write_response (); + }); + }); + } + else if (type == work_peer_type::malicious) + { + // Respond immediately with no work + on_generation (false); + write_response (); + } + } + + void handle (ptree::ptree const & tree_a) + { + auto action_text (tree_a.get ("action")); + auto hash_text (tree_a.get ("hash")); + nano::block_hash hash; + hash.decode_hex (hash_text); + if (action_text == "work_generate") + { + handle_generate (hash); + } + else if (action_text == "work_cancel") + { + error (empty_response); + on_cancel (); + write_response (); + } + else + { + throw; + } + } +}; + +class fake_work_peer : public std::enable_shared_from_this +{ +public: + fake_work_peer () = delete; + fake_work_peer (nano::work_pool & pool_a, asio::io_context & ioc_a, unsigned short port_a, work_peer_type const type_a, nano::work_version const version_a = nano::work_version::work_1) : + pool (pool_a), + endpoint (tcp::v4 (), port_a), + ioc (ioc_a), + acceptor (ioc_a, endpoint), + type (type_a), + version (version_a) + { + } + void start () + { + listen (); + } + unsigned short port () const + { + return endpoint.port (); + } + std::atomic generations_good{ 0 }; + std::atomic generations_bad{ 0 }; + std::atomic cancels{ 0 }; + +private: + void listen () + { + std::weak_ptr this_w (shared_from_this ()); + auto connection (std::make_shared ( + ioc, type, version, pool, + [this_w](bool const good_generation) { + if (auto this_l = this_w.lock ()) + { + if (good_generation) + { + ++this_l->generations_good; + } + else + { + ++this_l->generations_bad; + } + }; + }, + [this_w]() { + if (auto this_l = this_w.lock ()) + { + ++this_l->cancels; + } + })); + acceptor.async_accept (connection->socket, [connection, this_w](beast::error_code ec) { + if (!ec) + { + if (auto this_l = this_w.lock ()) + { + connection->start (); + this_l->listen (); + } + } + }); + } + nano::work_pool & pool; + tcp::endpoint endpoint; + asio::io_context & ioc; + tcp::acceptor acceptor; + work_peer_type const type; + nano::work_version version; +}; +} diff --git a/nano/core_test/gap_cache.cpp b/nano/core_test/gap_cache.cpp index 0c4adc599c..766c993d25 100644 --- a/nano/core_test/gap_cache.cpp +++ b/nano/core_test/gap_cache.cpp @@ -7,7 +7,7 @@ using namespace std::chrono_literals; TEST (gap_cache, add_new) { - nano::system system (24000, 1); + nano::system system (1); nano::gap_cache cache (*system.nodes[0]); auto block1 (std::make_shared (0, 1, 2, nano::keypair ().prv, 4, 5)); cache.add (block1->hash ()); @@ -15,7 +15,7 @@ TEST (gap_cache, add_new) TEST (gap_cache, add_existing) { - nano::system system (24000, 1); + nano::system system (1); nano::gap_cache cache (*system.nodes[0]); auto block1 (std::make_shared (0, 1, 2, nano::keypair ().prv, 4, 5)); cache.add (block1->hash ()); @@ -39,7 +39,7 @@ TEST (gap_cache, add_existing) TEST (gap_cache, comparison) { - nano::system system (24000, 1); + nano::system system (1); nano::gap_cache cache (*system.nodes[0]); auto block1 (std::make_shared (1, 0, 2, nano::keypair ().prv, 4, 5)); cache.add (block1->hash ()); @@ -63,57 +63,61 @@ TEST (gap_cache, comparison) ASSERT_EQ (arrival, cache.blocks.get<1> ().begin ()->arrival); } +// Upon receiving enough votes for a gapped block, a lazy bootstrap should be initiated TEST (gap_cache, gap_bootstrap) { - nano::system system (24000, 2); - nano::block_hash latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::node_flags node_flags; + node_flags.disable_legacy_bootstrap = true; + node_flags.disable_request_loop = true; // to avoid fallback behavior of broadcasting blocks + nano::system system (2, nano::transport::transport_type::tcp, node_flags); + + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + nano::block_hash latest (node1.latest (nano::test_genesis_key.pub)); nano::keypair key; auto send (std::make_shared (latest, key.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); + node1.process (*send); + ASSERT_EQ (nano::genesis_amount - 100, node1.balance (nano::genesis_account)); + ASSERT_EQ (nano::genesis_amount, node2.balance (nano::genesis_account)); + // Confirm send block, allowing voting on the upcoming block + node1.block_confirm (send); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->block_processor.process_one (transaction, send).code); + auto election = node1.active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node1.active.mutex); + election->confirm_once (); } - ASSERT_EQ (nano::genesis_amount - 100, system.nodes[0]->balance (nano::genesis_account)); - ASSERT_EQ (nano::genesis_amount, system.nodes[1]->balance (nano::genesis_account)); + ASSERT_TIMELY (2s, node1.block_confirmed (send->hash ())); + node1.active.erase (*send); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (0)->insert_adhoc (key.prv); auto latest_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 100)); ASSERT_NE (nullptr, latest_block); - ASSERT_EQ (nano::genesis_amount - 200, system.nodes[0]->balance (nano::genesis_account)); - ASSERT_EQ (nano::genesis_amount, system.nodes[1]->balance (nano::genesis_account)); - system.deadline_set (10s); - { - // The separate publish and vote system doesn't work very well here because it's instantly confirmed. - // We help it get the block and vote out here. - auto transaction (system.nodes[0]->store.tx_begin_read ()); - system.nodes[0]->network.flood_block (latest_block); - } - while (system.nodes[1]->balance (nano::genesis_account) != nano::genesis_amount - 200) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_EQ (nano::genesis_amount - 200, node1.balance (nano::genesis_account)); + ASSERT_EQ (nano::genesis_amount, node2.balance (nano::genesis_account)); + ASSERT_TIMELY (10s, node2.balance (nano::genesis_account) == nano::genesis_amount - 200); } TEST (gap_cache, two_dependencies) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::keypair key; nano::genesis genesis; auto send1 (std::make_shared (genesis.hash (), key.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); auto send2 (std::make_shared (send1->hash (), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); auto open (std::make_shared (send1->hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); - ASSERT_EQ (0, system.nodes[0]->gap_cache.size ()); - system.nodes[0]->block_processor.add (send2, nano::seconds_since_epoch ()); - system.nodes[0]->block_processor.flush (); - ASSERT_EQ (1, system.nodes[0]->gap_cache.size ()); - system.nodes[0]->block_processor.add (open, nano::seconds_since_epoch ()); - system.nodes[0]->block_processor.flush (); - ASSERT_EQ (2, system.nodes[0]->gap_cache.size ()); - system.nodes[0]->block_processor.add (send1, nano::seconds_since_epoch ()); - system.nodes[0]->block_processor.flush (); - ASSERT_EQ (0, system.nodes[0]->gap_cache.size ()); - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction, send1->hash ())); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction, send2->hash ())); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction, open->hash ())); + ASSERT_EQ (0, node1.gap_cache.size ()); + node1.block_processor.add (send2, nano::seconds_since_epoch ()); + node1.block_processor.flush (); + ASSERT_EQ (1, node1.gap_cache.size ()); + node1.block_processor.add (open, nano::seconds_since_epoch ()); + node1.block_processor.flush (); + ASSERT_EQ (2, node1.gap_cache.size ()); + node1.block_processor.add (send1, nano::seconds_since_epoch ()); + node1.block_processor.flush (); + ASSERT_EQ (0, node1.gap_cache.size ()); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_TRUE (node1.store.block_exists (transaction, send1->hash ())); + ASSERT_TRUE (node1.store.block_exists (transaction, send2->hash ())); + ASSERT_TRUE (node1.store.block_exists (transaction, open->hash ())); } diff --git a/nano/core_test/ipc.cpp b/nano/core_test/ipc.cpp index 2040495911..f789fc9f16 100644 --- a/nano/core_test/ipc.cpp +++ b/nano/core_test/ipc.cpp @@ -1,6 +1,8 @@ #include #include -#include +#include +#include +#include #include #include @@ -17,14 +19,14 @@ using namespace std::chrono_literals; TEST (ipc, asynchronous) { - nano::system system (24000, 1); + nano::system system (1); system.nodes[0]->config.ipc_config.transport_tcp.enabled = true; system.nodes[0]->config.ipc_config.transport_tcp.port = 24077; nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc (*system.nodes[0], node_rpc_config); nano::ipc::ipc_client client (system.nodes[0]->io_ctx); - auto req (nano::ipc::prepare_request (nano::ipc::payload_encoding::json_legacy, std::string (R"({"action": "block_count"})"))); + auto req (nano::ipc::prepare_request (nano::ipc::payload_encoding::json_v1, std::string (R"({"action": "block_count"})"))); auto res (std::make_shared> ()); std::atomic call_completed{ false }; client.async_connect ("::1", 24077, [&client, &req, &res, &call_completed](nano::error err) { @@ -56,11 +58,12 @@ TEST (ipc, asynchronous) { ASSERT_NO_ERROR (system.poll ()); } + ipc.stop (); } TEST (ipc, synchronous) { - nano::system system (24000, 1); + nano::system system (1); system.nodes[0]->config.ipc_config.transport_tcp.enabled = true; system.nodes[0]->config.ipc_config.transport_tcp.port = 24077; nano::node_rpc_config node_rpc_config; @@ -71,7 +74,7 @@ TEST (ipc, synchronous) std::atomic call_completed{ false }; std::thread client_thread ([&client, &call_completed]() { client.connect ("::1", 24077); - std::string response (nano::ipc::request (client, std::string (R"({"action": "block_count"})"))); + std::string response (nano::ipc::request (nano::ipc::payload_encoding::json_v1, client, std::string (R"({"action": "block_count"})"))); std::stringstream ss; ss << response; // Make sure the response is valid json @@ -88,6 +91,7 @@ TEST (ipc, synchronous) { ASSERT_NO_ERROR (system.poll ()); } + ipc.stop (); } TEST (ipc, config_upgrade_v0_v1) @@ -108,3 +112,107 @@ TEST (ipc, config_upgrade_v0_v1) ASSERT_LE (1, local2.get ("version")); ASSERT_FALSE (local2.get ("allow_unsafe")); } + +TEST (ipc, permissions_default_user) +{ + // Test empty/nonexistant access config. The default user still exists with default permissions. + std::stringstream ss; + ss << R"toml( + )toml"; + + nano::tomlconfig toml; + toml.read (ss); + + nano::ipc::access access; + access.deserialize_toml (toml); + ASSERT_TRUE (access.has_access ("", nano::ipc::access_permission::api_account_weight)); +} + +TEST (ipc, permissions_deny_default) +{ + // All users have api_account_weight permissions by default. This removes the permission for a specific user. + std::stringstream ss; + ss << R"toml( + [[user]] + id = "user1" + deny = "api_account_weight" + )toml"; + + nano::tomlconfig toml; + toml.read (ss); + + nano::ipc::access access; + access.deserialize_toml (toml); + ASSERT_FALSE (access.has_access ("user1", nano::ipc::access_permission::api_account_weight)); +} + +TEST (ipc, permissions_groups) +{ + // Make sure role permissions are adopted by user + std::stringstream ss; + ss << R"toml( + [[role]] + id = "mywalletadmin" + allow = "wallet_read, wallet_write" + + [[user]] + id = "user1" + roles = "mywalletadmin" + deny = "api_account_weight" + )toml"; + + nano::tomlconfig toml; + toml.read (ss); + + nano::ipc::access access; + access.deserialize_toml (toml); + ASSERT_FALSE (access.has_access ("user1", nano::ipc::access_permission::api_account_weight)); + ASSERT_TRUE (access.has_access_to_all ("user1", { nano::ipc::access_permission::wallet_read, nano::ipc::access_permission::wallet_write })); +} + +TEST (ipc, permissions_oneof) +{ + // Test one of two permissions + std::stringstream ss; + ss << R"toml( + [[user]] + id = "user1" + allow = "api_account_weight" + [[user]] + id = "user2" + allow = "api_account_weight, account_query" + [[user]] + id = "user3" + deny = "api_account_weight, account_query" + )toml"; + + nano::tomlconfig toml; + toml.read (ss); + + nano::ipc::access access; + access.deserialize_toml (toml); + ASSERT_TRUE (access.has_access ("user1", nano::ipc::access_permission::api_account_weight)); + ASSERT_TRUE (access.has_access ("user2", nano::ipc::access_permission::api_account_weight)); + ASSERT_FALSE (access.has_access ("user3", nano::ipc::access_permission::api_account_weight)); + ASSERT_TRUE (access.has_access_to_oneof ("user1", { nano::ipc::access_permission::account_query, nano::ipc::access_permission::api_account_weight })); + ASSERT_TRUE (access.has_access_to_oneof ("user2", { nano::ipc::access_permission::account_query, nano::ipc::access_permission::api_account_weight })); + ASSERT_FALSE (access.has_access_to_oneof ("user3", { nano::ipc::access_permission::account_query, nano::ipc::access_permission::api_account_weight })); +} + +TEST (ipc, permissions_default_user_order) +{ + // If changing the default user, it must come first + std::stringstream ss; + ss << R"toml( + [[user]] + id = "user1" + [[user]] + id = "" + )toml"; + + nano::tomlconfig toml; + toml.read (ss); + + nano::ipc::access access; + ASSERT_TRUE (access.deserialize_toml (toml)); +} diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 0a7cc401a9..5de3c44c84 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -1,9 +1,9 @@ #include #include +#include +#include #include -#include -#include #include using namespace std::chrono_literals; @@ -42,26 +42,28 @@ TEST (ledger, genesis_balance) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); auto balance (ledger.account_balance (transaction, nano::genesis_account)); ASSERT_EQ (nano::genesis_amount, balance); auto amount (ledger.amount (transaction, nano::genesis_account)); ASSERT_EQ (nano::genesis_amount, amount); nano::account_info info; ASSERT_FALSE (store->account_get (transaction, nano::genesis_account, info)); + ASSERT_EQ (1, ledger.cache.account_count); // Frontier time should have been updated when genesis balance was added ASSERT_GE (nano::seconds_since_epoch (), info.modified); ASSERT_LT (nano::seconds_since_epoch () - info.modified, 10); // Genesis block should be confirmed by default - uint64_t confirmation_height; - ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (confirmation_height, 1); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 1); + ASSERT_EQ (confirmation_height_info.frontier, genesis.hash ()); } // All nodes in the system should agree on the genesis balance TEST (system, system_genesis) { - nano::system system (24000, 2); + nano::system system (2); for (auto & i : system.nodes) { auto transaction (i->store.tx_begin_read ()); @@ -69,6 +71,21 @@ TEST (system, system_genesis) } } +TEST (ledger, process_modifies_sideband) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + nano::genesis genesis; + store->initialize (store->tx_begin_write (), genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), send1).code); + ASSERT_EQ (send1.sideband ().timestamp, store->block_get (store->tx_begin_read (), send1.hash ())->sideband ().timestamp); +} + // Create a send block and publish it. TEST (ledger, process_send) { @@ -79,7 +96,7 @@ TEST (ledger, process_send) nano::ledger ledger (*store, stats); auto transaction (store->tx_begin_write ()); nano::genesis genesis; - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -90,6 +107,8 @@ TEST (ledger, process_send) ASSERT_EQ (1, info1.block_count); // This was a valid block, it should progress. auto return1 (ledger.process (transaction, send)); + ASSERT_EQ (nano::test_genesis_key.pub, send.sideband ().account); + ASSERT_EQ (2, send.sideband ().height); ASSERT_EQ (nano::genesis_amount - 50, ledger.amount (transaction, hash1)); ASSERT_TRUE (store->frontier_get (transaction, info1.head).is_zero ()); ASSERT_EQ (nano::test_genesis_key.pub, store->frontier_get (transaction, hash1)); @@ -111,6 +130,10 @@ TEST (ledger, process_send) nano::block_hash hash2 (open.hash ()); // This was a valid block, it should progress. auto return2 (ledger.process (transaction, open)); + ASSERT_EQ (nano::process_result::progress, return2.code); + ASSERT_EQ (key2.pub, open.sideband ().account); + ASSERT_EQ (nano::genesis_amount - 50, open.sideband ().balance.number ()); + ASSERT_EQ (1, open.sideband ().height); ASSERT_EQ (nano::genesis_amount - 50, ledger.amount (transaction, hash2)); ASSERT_EQ (nano::process_result::progress, return2.code); ASSERT_EQ (key2.pub, return2.account); @@ -162,6 +185,7 @@ TEST (ledger, process_send) ASSERT_TRUE (ledger.store.pending_get (transaction, nano::pending_key (key2.pub, hash1), pending2)); ASSERT_EQ (nano::genesis_amount, ledger.account_balance (transaction, nano::test_genesis_key.pub)); ASSERT_EQ (0, ledger.account_pending (transaction, key2.pub)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, process_receive) @@ -173,7 +197,7 @@ TEST (ledger, process_receive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -187,6 +211,9 @@ TEST (ledger, process_receive) auto return1 (ledger.process (transaction, open)); ASSERT_EQ (nano::process_result::progress, return1.code); ASSERT_EQ (key2.pub, return1.account); + ASSERT_EQ (key2.pub, open.sideband ().account); + ASSERT_EQ (nano::genesis_amount - 50, open.sideband ().balance.number ()); + ASSERT_EQ (1, open.sideband ().height); ASSERT_EQ (nano::genesis_amount - 50, return1.amount.number ()); ASSERT_EQ (nano::genesis_amount - 50, ledger.weight (key3.pub)); nano::send_block send2 (hash1, key2.pub, 25, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (hash1)); @@ -196,6 +223,9 @@ TEST (ledger, process_receive) auto hash4 (receive.hash ()); ASSERT_EQ (key2.pub, store->frontier_get (transaction, hash2)); auto return2 (ledger.process (transaction, receive)); + ASSERT_EQ (key2.pub, receive.sideband ().account); + ASSERT_EQ (nano::genesis_amount - 25, receive.sideband ().balance.number ()); + ASSERT_EQ (2, receive.sideband ().height); ASSERT_EQ (25, ledger.amount (transaction, hash4)); ASSERT_TRUE (store->frontier_get (transaction, hash2).is_zero ()); ASSERT_EQ (key2.pub, store->frontier_get (transaction, hash4)); @@ -220,6 +250,7 @@ TEST (ledger, process_receive) ASSERT_FALSE (ledger.store.pending_get (transaction, nano::pending_key (key2.pub, hash3), pending1)); ASSERT_EQ (nano::test_genesis_key.pub, pending1.source); ASSERT_EQ (25, pending1.amount.number ()); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, rollback_receiver) @@ -231,7 +262,7 @@ TEST (ledger, rollback_receiver) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -257,6 +288,7 @@ TEST (ledger, rollback_receiver) ASSERT_EQ (0, ledger.weight (key3.pub)); nano::account_info info2; ASSERT_TRUE (ledger.store.account_get (transaction, key2.pub, info2)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); nano::pending_info pending1; ASSERT_TRUE (ledger.store.pending_get (transaction, nano::pending_key (key2.pub, info2.head), pending1)); } @@ -270,7 +302,7 @@ TEST (ledger, rollback_representation) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key5; nano::change_block change1 (genesis.hash (), key5.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -324,7 +356,7 @@ TEST (ledger, receive_rollback) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send (genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); @@ -342,7 +374,7 @@ TEST (ledger, process_duplicate) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -365,7 +397,7 @@ TEST (ledger, representative_genesis) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); auto latest (ledger.latest (transaction, nano::test_genesis_key.pub)); ASSERT_FALSE (latest.is_zero ()); ASSERT_EQ (genesis.open->hash (), ledger.representative (transaction, latest)); @@ -380,7 +412,7 @@ TEST (ledger, weight) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); } @@ -394,7 +426,7 @@ TEST (ledger, representative_change) nano::keypair key2; nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::test_genesis_key.pub)); ASSERT_EQ (0, ledger.weight (key2.pub)); @@ -434,7 +466,7 @@ TEST (ledger, send_fork) nano::keypair key3; nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -455,7 +487,7 @@ TEST (ledger, receive_fork) nano::keypair key3; nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -482,7 +514,7 @@ TEST (ledger, open_fork) nano::keypair key3; nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -496,45 +528,46 @@ TEST (ledger, open_fork) TEST (system, DISABLED_generate_send_existing) { - nano::system system (24000, 1); - nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::thread_runner runner (system.io_ctx, node1.config.io_threads); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair stake_preserver; auto send_block (system.wallet (0)->send_action (nano::genesis_account, stake_preserver.pub, nano::genesis_amount / 3 * 2, true)); nano::account_info info1; { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_FALSE (system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info1)); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_FALSE (node1.store.account_get (transaction, nano::test_genesis_key.pub, info1)); } std::vector accounts; accounts.push_back (nano::test_genesis_key.pub); - system.generate_send_existing (*system.nodes[0], accounts); + system.generate_send_existing (node1, accounts); // Have stake_preserver receive funds after generate_send_existing so it isn't chosen as the destination { - auto transaction (system.nodes[0]->store.tx_begin_write ()); + auto transaction (node1.store.tx_begin_write ()); auto open_block (std::make_shared (send_block->hash (), nano::genesis_account, stake_preserver.pub, stake_preserver.prv, stake_preserver.pub, 0)); - system.nodes[0]->work_generate_blocking (*open_block); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *open_block).code); + node1.work_generate_blocking (*open_block); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *open_block).code); } - ASSERT_GT (system.nodes[0]->balance (stake_preserver.pub), system.nodes[0]->balance (nano::genesis_account)); + ASSERT_GT (node1.balance (stake_preserver.pub), node1.balance (nano::genesis_account)); nano::account_info info2; { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_FALSE (system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info2)); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_FALSE (node1.store.account_get (transaction, nano::test_genesis_key.pub, info2)); } ASSERT_NE (info1.head, info2.head); system.deadline_set (15s); while (info2.block_count < info1.block_count + 2) { ASSERT_NO_ERROR (system.poll ()); - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_FALSE (system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info2)); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_FALSE (node1.store.account_get (transaction, nano::test_genesis_key.pub, info2)); } ASSERT_EQ (info1.block_count + 2, info2.block_count); ASSERT_EQ (info2.balance, nano::genesis_amount / 3); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_NE (system.nodes[0]->ledger.amount (transaction, info2.head), 0); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_NE (node1.ledger.amount (transaction, info2.head), 0); } system.stop (); runner.join (); @@ -542,31 +575,34 @@ TEST (system, DISABLED_generate_send_existing) TEST (system, generate_send_new) { - nano::system system (24000, 1); - nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::thread_runner runner (system.io_ctx, node1.config.io_threads); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - auto iterator1 (system.nodes[0]->store.latest_begin (transaction)); - ASSERT_NE (system.nodes[0]->store.latest_end (), iterator1); + auto transaction (node1.store.tx_begin_read ()); + auto iterator1 (node1.store.latest_begin (transaction)); + ASSERT_NE (node1.store.latest_end (), iterator1); ++iterator1; - ASSERT_EQ (system.nodes[0]->store.latest_end (), iterator1); + ASSERT_EQ (node1.store.latest_end (), iterator1); } nano::keypair stake_preserver; auto send_block (system.wallet (0)->send_action (nano::genesis_account, stake_preserver.pub, nano::genesis_amount / 3 * 2, true)); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); + auto transaction (node1.store.tx_begin_write ()); auto open_block (std::make_shared (send_block->hash (), nano::genesis_account, stake_preserver.pub, stake_preserver.prv, stake_preserver.pub, 0)); - system.nodes[0]->work_generate_blocking (*open_block); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *open_block).code); + node1.work_generate_blocking (*open_block); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *open_block).code); } - ASSERT_GT (system.nodes[0]->balance (stake_preserver.pub), system.nodes[0]->balance (nano::genesis_account)); + ASSERT_GT (node1.balance (stake_preserver.pub), node1.balance (nano::genesis_account)); std::vector accounts; accounts.push_back (nano::test_genesis_key.pub); - system.generate_send_new (*system.nodes[0], accounts); + // This indirectly waits for online weight to stabilize, required to prevent intermittent failures + ASSERT_TIMELY (5s, node1.wallets.reps ().voting > 0); + system.generate_send_new (node1, accounts); nano::account new_account (0); { - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node1.wallets.tx_begin_read ()); auto iterator2 (system.wallet (0)->store.begin (transaction)); if (iterator2->first != nano::test_genesis_key.pub) { @@ -583,7 +619,7 @@ TEST (system, generate_send_new) ASSERT_FALSE (new_account.is_zero ()); } system.deadline_set (10s); - while (system.nodes[0]->balance (new_account) == 0) + while (node1.balance (new_account) == 0) { ASSERT_NO_ERROR (system.poll ()); } @@ -609,10 +645,10 @@ TEST (ledger, representation) ASSERT_TRUE (!store->init_error ()); nano::stat stats; nano::ledger ledger (*store, stats); - auto & rep_weights = ledger.rep_weights; + auto & rep_weights = ledger.cache.rep_weights; nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); ASSERT_EQ (nano::genesis_amount, rep_weights.representation_get (nano::test_genesis_key.pub)); nano::keypair key2; @@ -686,7 +722,7 @@ TEST (ledger, double_open) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key2; nano::send_block send1 (genesis.hash (), key2.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -706,7 +742,7 @@ TEST (ledger, double_receive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key2; nano::send_block send1 (genesis.hash (), key2.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -720,7 +756,7 @@ TEST (ledger, double_receive) TEST (votes, check_signature) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.online_weight_minimum = std::numeric_limits::max (); auto & node1 = *system.add_node (node_config); nano::genesis genesis; @@ -731,22 +767,22 @@ TEST (votes, check_signature) auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); } - node1.active.start (send1); - nano::lock_guard lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); - ASSERT_EQ (1, votes1->last_votes.size ()); + auto election1 = node1.active.insert (send1); + { + nano::lock_guard lock (node1.active.mutex); + ASSERT_EQ (1, election1.election->last_votes.size ()); + } auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); vote1->signature.bytes[0] ^= 1; - auto transaction (node1.store.tx_begin_read ()); - ASSERT_EQ (nano::vote_code::invalid, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::invalid, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); vote1->signature.bytes[0] ^= 1; - ASSERT_EQ (nano::vote_code::vote, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); - ASSERT_EQ (nano::vote_code::replay, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::vote, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::replay, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); } TEST (votes, add_one) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -754,28 +790,27 @@ TEST (votes, add_one) node1.work_generate_blocking (*send1); auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - node1.active.start (send1); + auto election1 = node1.active.insert (send1); nano::unique_lock lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); - ASSERT_EQ (1, votes1->last_votes.size ()); + ASSERT_EQ (1, election1.election->last_votes.size ()); lock.unlock (); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); - ASSERT_FALSE (node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); - ASSERT_FALSE (node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); lock.lock (); - ASSERT_EQ (2, votes1->last_votes.size ()); - auto existing1 (votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_NE (votes1->last_votes.end (), existing1); + ASSERT_EQ (2, election1.election->last_votes.size ()); + auto existing1 (election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election1.election->last_votes.end (), existing1); ASSERT_EQ (send1->hash (), existing1->second.hash); - auto winner (*votes1->tally ().begin ()); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); ASSERT_EQ (nano::genesis_amount - 100, winner.first); } TEST (votes, add_two) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -783,23 +818,22 @@ TEST (votes, add_two) node1.work_generate_blocking (*send1); auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - node1.active.start (send1); + auto election1 = node1.active.insert (send1); nano::unique_lock lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); lock.unlock (); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); auto vote2 (std::make_shared (key2.pub, key2.prv, 1, send2)); - ASSERT_FALSE (node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); - ASSERT_FALSE (node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); lock.lock (); - ASSERT_EQ (3, votes1->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (key2.pub)); - ASSERT_EQ (send2->hash (), votes1->last_votes[key2.pub].hash); - auto winner (*votes1->tally ().begin ()); + ASSERT_EQ (3, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (key2.pub)); + ASSERT_EQ (send2->hash (), election1.election->last_votes[key2.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } @@ -807,7 +841,7 @@ TEST (votes, add_two) TEST (votes, add_existing) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.online_weight_minimum = std::numeric_limits::max (); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node1 = *system.add_node (node_config); @@ -819,37 +853,36 @@ TEST (votes, add_existing) auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); } - node1.active.start (send1); + auto election1 = node1.active.insert (send1); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); - ASSERT_FALSE (node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); // Block is already processed from vote ASSERT_TRUE (node1.active.publish (send1)); nano::unique_lock lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); - ASSERT_EQ (1, votes1->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (1, election1.election->last_votes[nano::test_genesis_key.pub].sequence); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send2)); // Pretend we've waited the timeout - votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); lock.unlock (); - ASSERT_FALSE (node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); ASSERT_FALSE (node1.active.publish (send2)); lock.lock (); - ASSERT_EQ (2, votes1->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (2, election1.election->last_votes[nano::test_genesis_key.pub].sequence); // Also resend the old vote, and see if we respect the sequence number - votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); lock.unlock (); - ASSERT_TRUE (node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::replay, node1.active.vote (vote1)); lock.lock (); - ASSERT_EQ (2, votes1->last_votes[nano::test_genesis_key.pub].sequence); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send2->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_EQ (2, election1.election->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send2->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); { auto transaction (node1.store.tx_begin_read ()); - auto winner (*votes1->tally ().begin ()); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send2, *winner.second); } } @@ -857,7 +890,7 @@ TEST (votes, add_existing) // Lower sequence numbers are ignored TEST (votes, add_old) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -865,29 +898,31 @@ TEST (votes, add_old) node1.work_generate_blocking (*send1); auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - node1.active.start (send1); + auto election1 = node1.active.insert (send1); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); - nano::lock_guard lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - node1.vote_processor.vote_blocking (transaction, vote1, channel); + node1.vote_processor.vote_blocking (vote1, channel); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); - votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); - node1.vote_processor.vote_blocking (transaction, vote2, channel); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); - auto winner (*votes1->tally ().begin ()); + { + nano::lock_guard lock (node1.active.mutex); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + } + node1.vote_processor.vote_blocking (vote2, channel); + ASSERT_EQ (2, election1.election->last_votes_size ()); + nano::lock_guard lock (node1.active.mutex); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } // Lower sequence numbers are accepted for different accounts TEST (votes, add_old_different_account) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -895,41 +930,41 @@ TEST (votes, add_old_different_account) node1.work_generate_blocking (*send1); auto send2 (std::make_shared (send1->hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); - auto transaction (node1.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send2).code); - node1.active.start (send1); - node1.active.start (send2); - nano::unique_lock lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); - auto votes2 (node1.active.roots.find (send2->qualified_root ())->election); - ASSERT_EQ (1, votes1->last_votes.size ()); - ASSERT_EQ (1, votes2->last_votes.size ()); + ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node1.process (*send2).code); + nano::blocks_confirm (node1, { send1, send2 }); + auto election1 = node1.active.election (send1->qualified_root ()); + ASSERT_NE (nullptr, election1); + auto election2 = node1.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election2); + ASSERT_EQ (1, election1->last_votes_size ()); + ASSERT_EQ (1, election2->last_votes_size ()); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - auto vote_result1 (node1.vote_processor.vote_blocking (transaction, vote1, channel)); + auto vote_result1 (node1.vote_processor.vote_blocking (vote1, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result1); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_EQ (1, votes2->last_votes.size ()); + ASSERT_EQ (2, election1->last_votes_size ()); + ASSERT_EQ (1, election2->last_votes_size ()); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); - auto vote_result2 (node1.vote_processor.vote_blocking (transaction, vote2, channel)); + auto vote_result2 (node1.vote_processor.vote_blocking (vote2, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result2); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_EQ (2, votes2->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_NE (votes2->last_votes.end (), votes2->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); - ASSERT_EQ (send2->hash (), votes2->last_votes[nano::test_genesis_key.pub].hash); - auto winner1 (*votes1->tally ().begin ()); + ASSERT_EQ (2, election1->last_votes_size ()); + ASSERT_EQ (2, election2->last_votes_size ()); + nano::unique_lock lock (node1.active.mutex); + ASSERT_NE (election1->last_votes.end (), election1->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election2->last_votes.end (), election2->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_EQ (send2->hash (), election2->last_votes[nano::test_genesis_key.pub].hash); + auto winner1 (*election1->tally ().begin ()); ASSERT_EQ (*send1, *winner1.second); - auto winner2 (*votes2->tally ().begin ()); + auto winner2 (*election2->tally ().begin ()); ASSERT_EQ (*send2, *winner2.second); } // The voting cooldown is respected TEST (votes, add_cooldown) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -937,37 +972,37 @@ TEST (votes, add_cooldown) node1.work_generate_blocking (*send1); auto transaction (node1.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - node1.active.start (send1); - nano::unique_lock lock (node1.active.mutex); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); + auto election1 = node1.active.insert (send1); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - node1.vote_processor.vote_blocking (transaction, vote1, channel); + node1.vote_processor.vote_blocking (vote1, channel); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send2)); - node1.vote_processor.vote_blocking (transaction, vote2, channel); - ASSERT_EQ (2, votes1->last_votes.size ()); - ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); - auto winner (*votes1->tally ().begin ()); + node1.vote_processor.vote_blocking (vote2, channel); + nano::unique_lock lock (node1.active.mutex); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } // Query for block successor TEST (ledger, successor) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::keypair key1; nano::genesis genesis; nano::send_block send1 (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); - system.nodes[0]->work_generate_blocking (send1); - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, send1).code); - ASSERT_EQ (send1, *system.nodes[0]->ledger.successor (transaction, nano::qualified_root (genesis.hash (), nano::root (0)))); - ASSERT_EQ (*genesis.open, *system.nodes[0]->ledger.successor (transaction, genesis.open->qualified_root ())); - ASSERT_EQ (nullptr, system.nodes[0]->ledger.successor (transaction, nano::qualified_root (0))); + node1.work_generate_blocking (send1); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send1).code); + ASSERT_EQ (send1, *node1.ledger.successor (transaction, nano::qualified_root (genesis.hash (), nano::root (0)))); + ASSERT_EQ (*genesis.open, *node1.ledger.successor (transaction, genesis.open->qualified_root ())); + ASSERT_EQ (nullptr, node1.ledger.successor (transaction, nano::qualified_root (0))); } TEST (ledger, fail_change_old) @@ -979,7 +1014,7 @@ TEST (ledger, fail_change_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::change_block block (genesis.hash (), key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -998,7 +1033,7 @@ TEST (ledger, fail_change_gap_previous) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::change_block block (1, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (nano::root (1))); @@ -1015,7 +1050,7 @@ TEST (ledger, fail_change_bad_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::change_block block (genesis.hash (), key1.pub, nano::keypair ().prv, 0, *pool.generate (genesis.hash ())); @@ -1032,7 +1067,7 @@ TEST (ledger, fail_change_fork) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::change_block block1 (genesis.hash (), key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1053,7 +1088,7 @@ TEST (ledger, fail_send_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1072,7 +1107,7 @@ TEST (ledger, fail_send_gap_previous) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block (1, key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (nano::root (1))); @@ -1089,7 +1124,7 @@ TEST (ledger, fail_send_bad_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block (genesis.hash (), key1.pub, 1, nano::keypair ().prv, 0, *pool.generate (genesis.hash ())); @@ -1106,7 +1141,7 @@ TEST (ledger, fail_send_negative_spend) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1125,7 +1160,7 @@ TEST (ledger, fail_send_fork) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1144,7 +1179,7 @@ TEST (ledger, fail_open_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1163,7 +1198,7 @@ TEST (ledger, fail_open_gap_source) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::open_block block2 (1, 1, key1.pub, key1.prv, key1.pub, *pool.generate (key1.pub)); @@ -1180,7 +1215,7 @@ TEST (ledger, fail_open_bad_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1199,7 +1234,7 @@ TEST (ledger, fail_open_fork_previous) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1210,6 +1245,7 @@ TEST (ledger, fail_open_fork_previous) ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, block3).code); nano::open_block block4 (block2.hash (), 1, key1.pub, key1.prv, key1.pub, *pool.generate (key1.pub)); ASSERT_EQ (nano::process_result::fork, ledger.process (transaction, block4).code); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, fail_open_account_mismatch) @@ -1221,7 +1257,7 @@ TEST (ledger, fail_open_account_mismatch) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1229,6 +1265,7 @@ TEST (ledger, fail_open_account_mismatch) nano::keypair badkey; nano::open_block block2 (block1.hash (), 1, badkey.pub, badkey.prv, badkey.pub, *pool.generate (badkey.pub)); ASSERT_NE (nano::process_result::progress, ledger.process (transaction, block2).code); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, fail_receive_old) @@ -1240,7 +1277,7 @@ TEST (ledger, fail_receive_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1263,7 +1300,7 @@ TEST (ledger, fail_receive_gap_source) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1289,7 +1326,7 @@ TEST (ledger, fail_receive_overreceive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1312,7 +1349,7 @@ TEST (ledger, fail_receive_bad_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1338,7 +1375,7 @@ TEST (ledger, fail_receive_gap_previous_opened) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1364,7 +1401,7 @@ TEST (ledger, fail_receive_gap_previous_unopened) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1387,7 +1424,7 @@ TEST (ledger, fail_receive_fork_previous) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1417,7 +1454,7 @@ TEST (ledger, fail_receive_received_source) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; nano::send_block block1 (genesis.hash (), key1.pub, 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1466,7 +1503,7 @@ TEST (ledger, latest_root) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key; ASSERT_EQ (key.pub, ledger.latest_root (transaction, key.pub)); @@ -1486,7 +1523,7 @@ TEST (ledger, change_representative_move_representation) nano::keypair key1; auto transaction (store->tx_begin_write ()); nano::genesis genesis; - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); auto hash1 (genesis.hash ()); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::test_genesis_key.pub)); @@ -1511,7 +1548,7 @@ TEST (ledger, send_open_receive_rollback) nano::ledger ledger (*store, stats); auto transaction (store->tx_begin_write ()); nano::genesis genesis; - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -1574,12 +1611,12 @@ TEST (ledger, bootstrap_rep_weight) nano::work_pool pool (std::numeric_limits::max ()); { auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); nano::send_block send (info1.head, key2.pub, std::numeric_limits::max () - 50, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (info1.head)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); } - ASSERT_EQ (2, ledger.block_count_cache); + ASSERT_EQ (2, ledger.cache.block_count); { ledger.bootstrap_weight_max_blocks = 3; ledger.bootstrap_weights[key2.pub] = 1000; @@ -1591,7 +1628,7 @@ TEST (ledger, bootstrap_rep_weight) nano::send_block send (info1.head, key2.pub, std::numeric_limits::max () - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (info1.head)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); } - ASSERT_EQ (3, ledger.block_count_cache); + ASSERT_EQ (3, ledger.cache.block_count); { auto transaction (store->tx_begin_read ()); ASSERT_EQ (0, ledger.weight (key2.pub)); @@ -1607,7 +1644,7 @@ TEST (ledger, block_destination_source) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair dest; nano::uint128_t balance (nano::genesis_amount); @@ -1653,7 +1690,7 @@ TEST (ledger, state_account) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1669,7 +1706,7 @@ TEST (ledger, state_send_receive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1681,6 +1718,10 @@ TEST (ledger, state_send_receive) ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.weight (nano::genesis_account)); ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (2, send2->sideband ().height); + ASSERT_TRUE (send2->sideband ().details.is_send); + ASSERT_FALSE (send2->sideband ().details.is_receive); + ASSERT_FALSE (send2->sideband ().details.is_epoch); nano::state_block receive1 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount, send1.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (send1.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); ASSERT_TRUE (store->block_exists (transaction, receive1.hash ())); @@ -1691,6 +1732,11 @@ TEST (ledger, state_send_receive) ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); + ASSERT_EQ (3, receive2->sideband ().height); + ASSERT_FALSE (receive2->sideband ().details.is_send); + ASSERT_TRUE (receive2->sideband ().details.is_receive); + ASSERT_FALSE (receive2->sideband ().details.is_epoch); } TEST (ledger, state_receive) @@ -1702,7 +1748,7 @@ TEST (ledger, state_receive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send1 (genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1722,6 +1768,10 @@ TEST (ledger, state_receive) ASSERT_EQ (nano::genesis_amount, ledger.balance (transaction, receive1.hash ())); ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); + ASSERT_EQ (3, receive2->sideband ().height); + ASSERT_FALSE (receive2->sideband ().details.is_send); + ASSERT_TRUE (receive2->sideband ().details.is_receive); + ASSERT_FALSE (receive2->sideband ().details.is_epoch); } TEST (ledger, state_rep_change) @@ -1733,7 +1783,7 @@ TEST (ledger, state_rep_change) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair rep; nano::state_block change1 (nano::genesis_account, genesis.hash (), rep.pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1746,6 +1796,10 @@ TEST (ledger, state_rep_change) ASSERT_EQ (0, ledger.amount (transaction, change1.hash ())); ASSERT_EQ (0, ledger.weight (nano::genesis_account)); ASSERT_EQ (nano::genesis_amount, ledger.weight (rep.pub)); + ASSERT_EQ (2, change2->sideband ().height); + ASSERT_FALSE (change2->sideband ().details.is_send); + ASSERT_FALSE (change2->sideband ().details.is_receive); + ASSERT_FALSE (change2->sideband ().details.is_epoch); } TEST (ledger, state_open) @@ -1757,7 +1811,7 @@ TEST (ledger, state_open) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1780,6 +1834,11 @@ TEST (ledger, state_open) ASSERT_EQ (nano::Gxrb_ratio, ledger.balance (transaction, open1.hash ())); ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, open1.hash ())); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); + ASSERT_EQ (ledger.cache.account_count, store->account_count (transaction)); + ASSERT_EQ (1, open2->sideband ().height); + ASSERT_FALSE (open2->sideband ().details.is_send); + ASSERT_TRUE (open2->sideband ().details.is_receive); + ASSERT_FALSE (open2->sideband ().details.is_epoch); } // Make sure old block types can't be inserted after a state block. @@ -1792,7 +1851,7 @@ TEST (ledger, send_after_state_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1810,7 +1869,7 @@ TEST (ledger, receive_after_state_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1828,7 +1887,7 @@ TEST (ledger, change_after_state_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1846,7 +1905,7 @@ TEST (ledger, state_unreceivable_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send1 (genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1870,7 +1929,7 @@ TEST (ledger, state_receive_bad_amount_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send1 (genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1894,7 +1953,7 @@ TEST (ledger, state_no_link_amount_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1912,7 +1971,7 @@ TEST (ledger, state_receive_wrong_account_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -1937,7 +1996,7 @@ TEST (ledger, state_open_state_fork) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1958,7 +2017,7 @@ TEST (ledger, state_state_open_fork) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1968,6 +2027,7 @@ TEST (ledger, state_state_open_fork) nano::state_block open2 (destination.pub, 0, nano::genesis_account, nano::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, *pool.generate (destination.pub)); ASSERT_EQ (nano::process_result::fork, ledger.process (transaction, open2).code); ASSERT_EQ (open1.root (), open2.root ()); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_open_previous_fail) @@ -1979,7 +2039,7 @@ TEST (ledger, state_open_previous_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -1997,7 +2057,7 @@ TEST (ledger, state_open_source_fail) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2015,7 +2075,7 @@ TEST (ledger, state_send_change) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair rep; nano::state_block send1 (nano::genesis_account, genesis.hash (), rep.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2028,6 +2088,10 @@ TEST (ledger, state_send_change) ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); ASSERT_EQ (0, ledger.weight (nano::genesis_account)); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.weight (rep.pub)); + ASSERT_EQ (2, send2->sideband ().height); + ASSERT_TRUE (send2->sideband ().details.is_send); + ASSERT_FALSE (send2->sideband ().details.is_receive); + ASSERT_FALSE (send2->sideband ().details.is_epoch); } TEST (ledger, state_receive_change) @@ -2039,7 +2103,7 @@ TEST (ledger, state_receive_change) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -2061,6 +2125,10 @@ TEST (ledger, state_receive_change) ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); ASSERT_EQ (0, ledger.weight (nano::genesis_account)); ASSERT_EQ (nano::genesis_amount, ledger.weight (rep.pub)); + ASSERT_EQ (3, receive2->sideband ().height); + ASSERT_FALSE (receive2->sideband ().details.is_send); + ASSERT_TRUE (receive2->sideband ().details.is_receive); + ASSERT_FALSE (receive2->sideband ().details.is_epoch); } TEST (ledger, state_open_old) @@ -2072,7 +2140,7 @@ TEST (ledger, state_open_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2093,7 +2161,7 @@ TEST (ledger, state_receive_old) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2118,7 +2186,7 @@ TEST (ledger, state_rollback_send) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -2138,6 +2206,7 @@ TEST (ledger, state_rollback_send) ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); ASSERT_TRUE (store->block_successor (transaction, genesis.hash ()).is_zero ()); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_rollback_receive) @@ -2149,7 +2218,7 @@ TEST (ledger, state_rollback_receive) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -2164,6 +2233,7 @@ TEST (ledger, state_rollback_receive) ASSERT_FALSE (store->block_exists (transaction, receive1.hash ())); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.account_balance (transaction, nano::genesis_account)); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.weight (nano::genesis_account)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_rollback_received_send) @@ -2175,7 +2245,7 @@ TEST (ledger, state_rollback_received_send) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2191,6 +2261,7 @@ TEST (ledger, state_rollback_received_send) ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); ASSERT_EQ (0, ledger.account_balance (transaction, key.pub)); ASSERT_EQ (0, ledger.weight (key.pub)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_rep_change_rollback) @@ -2202,7 +2273,7 @@ TEST (ledger, state_rep_change_rollback) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair rep; nano::state_block change1 (nano::genesis_account, genesis.hash (), rep.pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2223,7 +2294,7 @@ TEST (ledger, state_open_rollback) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2238,6 +2309,7 @@ TEST (ledger, state_open_rollback) ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (destination.pub, send1.hash ()), info)); ASSERT_EQ (nano::genesis_account, info.source); ASSERT_EQ (nano::Gxrb_ratio, info.amount.number ()); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_send_change_rollback) @@ -2249,7 +2321,7 @@ TEST (ledger, state_send_change_rollback) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair rep; nano::state_block send1 (nano::genesis_account, genesis.hash (), rep.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2259,6 +2331,7 @@ TEST (ledger, state_send_change_rollback) ASSERT_EQ (nano::genesis_amount, ledger.account_balance (transaction, nano::genesis_account)); ASSERT_EQ (nano::genesis_amount, ledger.weight (nano::genesis_account)); ASSERT_EQ (0, ledger.weight (rep.pub)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, state_receive_change_rollback) @@ -2270,7 +2343,7 @@ TEST (ledger, state_receive_change_rollback) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); @@ -2282,6 +2355,7 @@ TEST (ledger, state_receive_change_rollback) ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.account_balance (transaction, nano::genesis_account)); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.weight (nano::genesis_account)); ASSERT_EQ (0, ledger.weight (rep.pub)); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, epoch_blocks_v1_general) @@ -2293,11 +2367,14 @@ TEST (ledger, epoch_blocks_v1_general) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block epoch1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch1).code); + ASSERT_FALSE (epoch1.sideband ().details.is_send); + ASSERT_FALSE (epoch1.sideband ().details.is_receive); + ASSERT_TRUE (epoch1.sideband ().details.is_epoch); nano::state_block epoch2 (nano::genesis_account, epoch1.hash (), nano::genesis_account, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (epoch1.hash ())); ASSERT_EQ (nano::process_result::block_position, ledger.process (transaction, epoch2).code); nano::account_info genesis_info; @@ -2309,16 +2386,25 @@ TEST (ledger, epoch_blocks_v1_general) ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch1).code); ASSERT_FALSE (ledger.store.account_get (transaction, nano::genesis_account, genesis_info)); ASSERT_EQ (genesis_info.epoch (), nano::epoch::epoch_1); + ASSERT_FALSE (epoch1.sideband ().details.is_send); + ASSERT_FALSE (epoch1.sideband ().details.is_receive); + ASSERT_TRUE (epoch1.sideband ().details.is_epoch); nano::change_block change1 (epoch1.hash (), nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (epoch1.hash ())); ASSERT_EQ (nano::process_result::block_position, ledger.process (transaction, change1).code); nano::state_block send1 (nano::genesis_account, epoch1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (epoch1.hash ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (send1.sideband ().details.is_send); + ASSERT_FALSE (send1.sideband ().details.is_receive); + ASSERT_FALSE (send1.sideband ().details.is_epoch); nano::open_block open1 (send1.hash (), nano::genesis_account, destination.pub, destination.prv, destination.pub, *pool.generate (destination.pub)); ASSERT_EQ (nano::process_result::unreceivable, ledger.process (transaction, open1).code); nano::state_block epoch3 (destination.pub, 0, nano::genesis_account, 0, ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (destination.pub)); ASSERT_EQ (nano::process_result::representative_mismatch, ledger.process (transaction, epoch3).code); nano::state_block epoch4 (destination.pub, 0, 0, 0, ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (destination.pub)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch4).code); + ASSERT_FALSE (epoch4.sideband ().details.is_send); + ASSERT_FALSE (epoch4.sideband ().details.is_receive); + ASSERT_TRUE (epoch4.sideband ().details.is_epoch); nano::receive_block receive1 (epoch4.hash (), send1.hash (), destination.prv, destination.pub, *pool.generate (epoch4.hash ())); ASSERT_EQ (nano::process_result::block_position, ledger.process (transaction, receive1).code); nano::state_block receive2 (destination.pub, epoch4.hash (), destination.pub, nano::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, *pool.generate (epoch4.hash ())); @@ -2328,6 +2414,9 @@ TEST (ledger, epoch_blocks_v1_general) ASSERT_EQ (nano::Gxrb_ratio, ledger.amount (transaction, receive2.hash ())); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.weight (nano::genesis_account)); ASSERT_EQ (nano::Gxrb_ratio, ledger.weight (destination.pub)); + ASSERT_FALSE (receive2.sideband ().details.is_send); + ASSERT_TRUE (receive2.sideband ().details.is_receive); + ASSERT_FALSE (receive2.sideband ().details.is_epoch); } TEST (ledger, epoch_blocks_v2_general) @@ -2339,7 +2428,7 @@ TEST (ledger, epoch_blocks_v2_general) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block epoch1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2393,7 +2482,7 @@ TEST (ledger, epoch_blocks_receive_upgrade) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2445,6 +2534,7 @@ TEST (ledger, epoch_blocks_receive_upgrade) ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send6).code); nano::state_block epoch4 (destination4.pub, 0, 0, 0, ledger.epoch_link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (destination4.pub)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch4).code); + ASSERT_EQ (store->account_count (transaction), ledger.cache.account_count); } TEST (ledger, epoch_blocks_fork) @@ -2456,7 +2546,7 @@ TEST (ledger, epoch_blocks_fork) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; nano::send_block send1 (genesis.hash (), nano::account (0), nano::genesis_amount, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ())); @@ -2473,7 +2563,8 @@ TEST (ledger, epoch_blocks_fork) TEST (ledger, successor_epoch) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::keypair key1; nano::genesis genesis; nano::work_pool pool (std::numeric_limits::max ()); @@ -2481,20 +2572,33 @@ TEST (ledger, successor_epoch) nano::state_block open (key1.pub, 0, key1.pub, 1, send1.hash (), key1.prv, key1.pub, *pool.generate (key1.pub)); nano::state_block change (key1.pub, open.hash (), key1.pub, 1, 0, key1.prv, key1.pub, *pool.generate (open.hash ())); auto open_hash = open.hash (); - nano::state_block epoch_open (reinterpret_cast (open_hash), 0, 0, 0, system.nodes[0]->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (open.hash ())); - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, open).code); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, change).code); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, epoch_open).code); - ASSERT_EQ (change, *system.nodes[0]->ledger.successor (transaction, change.qualified_root ())); - ASSERT_EQ (epoch_open, *system.nodes[0]->ledger.successor (transaction, epoch_open.qualified_root ())); + nano::send_block send2 (send1.hash (), reinterpret_cast (open_hash), nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (send1.hash ())); + nano::state_block epoch_open (reinterpret_cast (open_hash), 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (open.hash ())); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, open).code); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, change).code); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, epoch_open).code); + ASSERT_EQ (change, *node1.ledger.successor (transaction, change.qualified_root ())); + ASSERT_EQ (epoch_open, *node1.ledger.successor (transaction, epoch_open.qualified_root ())); +} + +TEST (ledger, epoch_open_pending) +{ + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::work_pool pool (std::numeric_limits::max ()); + nano::keypair key1; + nano::state_block epoch_open (key1.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (key1.pub)); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::block_position, node1.ledger.process (transaction, epoch_open).code); } TEST (ledger, block_hash_account_conflict) { nano::block_builder builder; - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair key1; @@ -2559,23 +2663,24 @@ TEST (ledger, block_hash_account_conflict) node1.work_generate_blocking (*receive1); node1.work_generate_blocking (*send2); node1.work_generate_blocking (*open_epoch1); - auto transaction (node1.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *receive1).code); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send2).code); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *open_epoch1).code); - node1.active.start (send1); - node1.active.start (receive1); - node1.active.start (send2); - node1.active.start (open_epoch1); - auto votes1 (node1.active.roots.find (send1->qualified_root ())->election); - auto votes2 (node1.active.roots.find (receive1->qualified_root ())->election); - auto votes3 (node1.active.roots.find (send2->qualified_root ())->election); - auto votes4 (node1.active.roots.find (open_epoch1->qualified_root ())->election); - auto winner1 (*votes1->tally ().begin ()); - auto winner2 (*votes2->tally ().begin ()); - auto winner3 (*votes3->tally ().begin ()); - auto winner4 (*votes4->tally ().begin ()); + ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node1.process (*receive1).code); + ASSERT_EQ (nano::process_result::progress, node1.process (*send2).code); + ASSERT_EQ (nano::process_result::progress, node1.process (*open_epoch1).code); + nano::blocks_confirm (node1, { send1, receive1, send2, open_epoch1 }); + auto election1 = node1.active.election (send1->qualified_root ()); + ASSERT_NE (nullptr, election1); + auto election2 = node1.active.election (receive1->qualified_root ()); + ASSERT_NE (nullptr, election2); + auto election3 = node1.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election3); + auto election4 = node1.active.election (open_epoch1->qualified_root ()); + ASSERT_NE (nullptr, election4); + nano::lock_guard lock (node1.active.mutex); + auto winner1 (*election1->tally ().begin ()); + auto winner2 (*election2->tally ().begin ()); + auto winner3 (*election3->tally ().begin ()); + auto winner4 (*election4->tally ().begin ()); ASSERT_EQ (*send1, *winner1.second); ASSERT_EQ (*receive1, *winner2.second); ASSERT_EQ (*send2, *winner3.second); @@ -2591,7 +2696,7 @@ TEST (ledger, could_fit) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair destination; // Test legacy and state change blocks could_fit @@ -2646,7 +2751,7 @@ TEST (ledger, could_fit) TEST (ledger, unchecked_epoch) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair destination; @@ -2662,6 +2767,7 @@ TEST (ledger, unchecked_epoch) auto transaction (node1.store.tx_begin_read ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); auto blocks (node1.store.unchecked_get (transaction, epoch1->previous ())); ASSERT_EQ (blocks.size (), 1); ASSERT_EQ (blocks[0].verified, nano::signature_verification::valid_epoch); @@ -2674,6 +2780,7 @@ TEST (ledger, unchecked_epoch) ASSERT_TRUE (node1.store.block_exists (transaction, epoch1->hash ())); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); nano::account_info info; ASSERT_FALSE (node1.store.account_get (transaction, destination.pub, info)); ASSERT_EQ (info.epoch (), nano::epoch::epoch_1); @@ -2683,7 +2790,7 @@ TEST (ledger, unchecked_epoch) TEST (ledger, unchecked_epoch_invalid) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node1 (*system.add_node (node_config)); nano::genesis genesis; @@ -2705,6 +2812,7 @@ TEST (ledger, unchecked_epoch_invalid) auto transaction (node1.store.tx_begin_read ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 2); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); auto blocks (node1.store.unchecked_get (transaction, epoch1->previous ())); ASSERT_EQ (blocks.size (), 2); ASSERT_EQ (blocks[0].verified, nano::signature_verification::valid); @@ -2720,15 +2828,22 @@ TEST (ledger, unchecked_epoch_invalid) ASSERT_TRUE (node1.active.empty ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); nano::account_info info; ASSERT_FALSE (node1.store.account_get (transaction, destination.pub, info)); ASSERT_NE (info.epoch (), nano::epoch::epoch_1); + auto epoch2_store (node1.store.block_get (transaction, epoch2->hash ())); + ASSERT_NE (nullptr, epoch2_store); + ASSERT_EQ (nano::epoch::epoch_0, epoch2_store->sideband ().details.epoch); + ASSERT_TRUE (epoch2_store->sideband ().details.is_send); + ASSERT_FALSE (epoch2_store->sideband ().details.is_epoch); + ASSERT_FALSE (epoch2_store->sideband ().details.is_receive); } } TEST (ledger, unchecked_open) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair destination; @@ -2747,6 +2862,7 @@ TEST (ledger, unchecked_open) auto transaction (node1.store.tx_begin_read ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); auto blocks (node1.store.unchecked_get (transaction, open1->source ())); ASSERT_EQ (blocks.size (), 1); ASSERT_EQ (blocks[0].verified, nano::signature_verification::valid); @@ -2758,12 +2874,13 @@ TEST (ledger, unchecked_open) ASSERT_TRUE (node1.store.block_exists (transaction, open1->hash ())); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); } } TEST (ledger, unchecked_receive) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::keypair destination; @@ -2783,6 +2900,7 @@ TEST (ledger, unchecked_receive) auto transaction (node1.store.tx_begin_read ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); auto blocks (node1.store.unchecked_get (transaction, receive1->previous ())); ASSERT_EQ (blocks.size (), 1); ASSERT_EQ (blocks[0].verified, nano::signature_verification::unknown); @@ -2794,6 +2912,7 @@ TEST (ledger, unchecked_receive) auto transaction (node1.store.tx_begin_read ()); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); auto blocks (node1.store.unchecked_get (transaction, receive1->source ())); ASSERT_EQ (blocks.size (), 1); ASSERT_EQ (blocks[0].verified, nano::signature_verification::valid); @@ -2805,6 +2924,7 @@ TEST (ledger, unchecked_receive) ASSERT_TRUE (node1.store.block_exists (transaction, receive1->hash ())); auto unchecked_count (node1.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); + ASSERT_EQ (unchecked_count, node1.ledger.cache.unchecked_count); } } @@ -2817,27 +2937,31 @@ TEST (ledger, confirmation_height_not_updated) nano::ledger ledger (*store, stats); auto transaction (store->tx_begin_write ()); nano::genesis genesis; - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info account_info; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, account_info)); nano::keypair key; nano::send_block send1 (account_info.head, key.pub, 50, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (account_info.head)); - uint64_t confirmation_height; - ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (1, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (genesis.hash (), confirmation_height_info.frontier); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); - ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height)); - ASSERT_EQ (1, confirmation_height); + ASSERT_FALSE (store->confirmation_height_get (transaction, nano::genesis_account, confirmation_height_info)); + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (genesis.hash (), confirmation_height_info.frontier); nano::open_block open1 (send1.hash (), nano::genesis_account, key.pub, key.prv, key.pub, *pool.generate (key.pub)); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code); - ASSERT_FALSE (store->confirmation_height_get (transaction, key.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); + ASSERT_FALSE (store->confirmation_height_get (transaction, key.pub, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); + ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier); } TEST (ledger, zero_rep) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::genesis genesis; nano::block_builder builder; auto block1 = builder.state () @@ -2849,10 +2973,10 @@ TEST (ledger, zero_rep) .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) .work (*system.work.generate (genesis.hash ())) .build (); - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *block1).code); - ASSERT_EQ (0, system.nodes[0]->ledger.rep_weights.representation_get (nano::test_genesis_key.pub)); - ASSERT_EQ (nano::genesis_amount, system.nodes[0]->ledger.rep_weights.representation_get (0)); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *block1).code); + ASSERT_EQ (0, node1.ledger.cache.rep_weights.representation_get (nano::test_genesis_key.pub)); + ASSERT_EQ (nano::genesis_amount, node1.ledger.cache.rep_weights.representation_get (0)); auto block2 = builder.state () .account (nano::test_genesis_key.pub) .previous (block1->hash ()) @@ -2862,7 +2986,275 @@ TEST (ledger, zero_rep) .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) .work (*system.work.generate (block1->hash ())) .build (); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *block2).code); - ASSERT_EQ (nano::genesis_amount, system.nodes[0]->ledger.rep_weights.representation_get (nano::test_genesis_key.pub)); - ASSERT_EQ (0, system.nodes[0]->ledger.rep_weights.representation_get (0)); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *block2).code); + ASSERT_EQ (nano::genesis_amount, node1.ledger.cache.rep_weights.representation_get (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node1.ledger.cache.rep_weights.representation_get (0)); +} + +TEST (ledger, work_validation) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + nano::genesis genesis; + store->initialize (store->tx_begin_write (), genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::block_builder builder; + auto gen = nano::test_genesis_key; + nano::keypair key; + + // With random work the block doesn't pass, then modifies the block with sufficient work and ensures a correct result + auto process_block = [&store, &ledger, &pool](nano::block & block_a, nano::block_details const details_a) { + auto threshold = nano::work_threshold (block_a.work_version (), details_a); + // Rarely failed with random work, so modify until it doesn't have enough difficulty + while (block_a.difficulty () >= threshold) + { + block_a.block_work_set (block_a.block_work () + 1); + } + EXPECT_EQ (nano::process_result::insufficient_work, ledger.process (store->tx_begin_write (), block_a).code); + block_a.block_work_set (*pool.generate (block_a.root (), threshold)); + EXPECT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), block_a).code); + }; + + std::error_code ec; + + auto send = *builder.send () + .previous (nano::genesis_hash) + .destination (gen.pub) + .balance (nano::genesis_amount - 1) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto receive = *builder.receive () + .previous (send.hash ()) + .source (send.hash ()) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto change = *builder.change () + .previous (receive.hash ()) + .representative (key.pub) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto state = *builder.state () + .account (gen.pub) + .previous (change.hash ()) + .representative (gen.pub) + .balance (nano::genesis_amount - 1) + .link (key.pub) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto open = *builder.open () + .account (key.pub) + .source (state.hash ()) + .representative (key.pub) + .sign (key.prv, key.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto epoch = *builder.state () + .account (key.pub) + .previous (open.hash ()) + .balance (1) + .representative (key.pub) + .link (ledger.epoch_link (nano::epoch::epoch_1)) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + process_block (send, {}); + process_block (receive, {}); + process_block (change, {}); + process_block (state, nano::block_details (nano::epoch::epoch_0, true, false, false)); + process_block (open, {}); + process_block (epoch, nano::block_details (nano::epoch::epoch_1, false, false, true)); +} + +TEST (ledger, epoch_2_started_flag) +{ + nano::system system (2); + + auto & node1 = *system.nodes[0]; + ASSERT_FALSE (node1.ledger.cache.epoch_2_started.load ()); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node1, nano::epoch::epoch_1)); + ASSERT_FALSE (node1.ledger.cache.epoch_2_started.load ()); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node1, nano::epoch::epoch_2)); + ASSERT_TRUE (node1.ledger.cache.epoch_2_started.load ()); + + auto & node2 = *system.nodes[1]; + nano::keypair key; + auto epoch1 = system.upgrade_genesis_epoch (node2, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + ASSERT_FALSE (node2.ledger.cache.epoch_2_started.load ()); + nano::state_block send (nano::test_genesis_key.pub, epoch1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (epoch1->hash ())); + ASSERT_EQ (nano::process_result::progress, node2.process (send).code); + ASSERT_FALSE (node2.ledger.cache.epoch_2_started.load ()); + nano::state_block epoch2 (key.pub, 0, 0, 0, node2.ledger.epoch_link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, node2.process (epoch2).code); + ASSERT_TRUE (node2.ledger.cache.epoch_2_started.load ()); + + // Ensure state is kept on ledger initialization + nano::stat stats; + nano::ledger ledger (node1.store, stats); + ASSERT_TRUE (ledger.cache.epoch_2_started.load ()); +} + +TEST (ledger, epoch_2_upgrade_callback) +{ + nano::genesis genesis; + nano::stat stats; + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + bool cb_hit = false; + nano::ledger ledger (*store, stats, nano::generate_cache (), [&cb_hit]() { + cb_hit = true; + }); + { + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + } + nano::work_pool pool (std::numeric_limits::max ()); + upgrade_epoch (pool, ledger, nano::epoch::epoch_1); + ASSERT_FALSE (cb_hit); + auto latest = upgrade_epoch (pool, ledger, nano::epoch::epoch_2); + ASSERT_TRUE (cb_hit); +} + +TEST (ledger, can_vote) +{ + nano::block_builder builder; + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_FALSE (store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + auto transaction (store->tx_begin_write ()); + nano::genesis genesis; + store->initialize (transaction, genesis, ledger.cache); + ASSERT_TRUE (ledger.can_vote (transaction, *genesis.open)); + nano::work_pool pool (std::numeric_limits::max ()); + nano::keypair key1; + std::shared_ptr send1 = builder.state () + .account (nano::genesis_account) + .previous (genesis.hash ()) + .representative (nano::genesis_account) + .balance (nano::genesis_amount - 100) + .link (key1.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*pool.generate (genesis.hash ())) + .build (); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send1).code); + ASSERT_TRUE (ledger.can_vote (transaction, *send1)); + std::shared_ptr send2 = builder.state () + .account (nano::genesis_account) + .previous (send1->hash ()) + .representative (nano::genesis_account) + .balance (nano::genesis_amount - 200) + .link (key1.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*pool.generate (send1->hash ())) + .build (); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send2).code); + ASSERT_FALSE (ledger.can_vote (transaction, *send2)); + std::shared_ptr receive1 = builder.state () + .account (key1.pub) + .previous (0) + .representative (nano::genesis_account) + .balance (100) + .link (send1->hash ()) + .sign (key1.prv, key1.pub) + .work (*pool.generate (key1.pub)) + .build (); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *receive1).code); + ASSERT_FALSE (ledger.can_vote (transaction, *receive1)); + nano::confirmation_height_info height; + ASSERT_FALSE (ledger.store.confirmation_height_get (transaction, nano::genesis_account, height)); + height.height += 1; + ledger.store.confirmation_height_put (transaction, nano::genesis_account, height); + ASSERT_TRUE (ledger.can_vote (transaction, *receive1)); + std::shared_ptr receive2 = builder.state () + .account (key1.pub) + .previous (receive1->hash ()) + .representative (nano::genesis_account) + .balance (200) + .link (send2->hash ()) + .sign (key1.prv, key1.pub) + .work (*pool.generate (receive1->hash ())) + .build (); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *receive2).code); + ASSERT_FALSE (ledger.can_vote (transaction, *receive2)); + ASSERT_FALSE (ledger.store.confirmation_height_get (transaction, key1.pub, height)); + height.height += 1; + ledger.store.confirmation_height_put (transaction, key1.pub, height); + ASSERT_FALSE (ledger.can_vote (transaction, *receive2)); + ASSERT_FALSE (ledger.store.confirmation_height_get (transaction, nano::genesis_account, height)); + height.height += 1; + ledger.store.confirmation_height_put (transaction, nano::genesis_account, height); + ASSERT_TRUE (ledger.can_vote (transaction, *receive2)); +} + +TEST (ledger, backtrack) +{ + nano::genesis genesis; + nano::stat stats; + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + bool cb_hit = false; + nano::ledger ledger (*store, stats, nano::generate_cache (), [&cb_hit]() { + cb_hit = true; + }); + { + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + } + nano::work_pool pool (std::numeric_limits::max ()); + std::vector> blocks; + blocks.push_back (nullptr); // idx == height + blocks.push_back (genesis.open); + auto amount = nano::genesis_amount; + for (auto i = 0; i < 300; ++i) + { + nano::block_builder builder; + std::error_code ec; + auto latest = blocks.back (); + blocks.push_back (builder.state () + .previous (latest->hash ()) + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .balance (--amount) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*pool.generate (latest->hash ())) + .build (ec)); + ASSERT_FALSE (ec); + ASSERT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), *blocks.back ()).code); + } + ASSERT_EQ (302, blocks.size ()); + ASSERT_EQ (301, blocks[301]->sideband ().height); + auto transaction (store->tx_begin_read ()); + auto block_100 = ledger.backtrack (transaction, blocks[300], 200); + ASSERT_NE (nullptr, block_100); + ASSERT_EQ (*block_100, *blocks[100]); + ASSERT_NE (nullptr, ledger.backtrack (transaction, blocks[10], 10)); + ASSERT_NE (ledger.backtrack (transaction, blocks[10], 1), ledger.backtrack (transaction, blocks[11], 2)); + ASSERT_EQ (ledger.backtrack (transaction, blocks[1], 0), ledger.backtrack (transaction, blocks[1], 1)); + ASSERT_NE (ledger.backtrack (transaction, blocks[2], 0), ledger.backtrack (transaction, blocks[2], 1)); + ASSERT_EQ (nullptr, ledger.backtrack (transaction, nullptr, 0)); + ASSERT_EQ (nullptr, ledger.backtrack (transaction, nullptr, 10)); } diff --git a/nano/core_test/locks.cpp b/nano/core_test/locks.cpp index b52b66b4d5..bebb2dc25f 100644 --- a/nano/core_test/locks.cpp +++ b/nano/core_test/locks.cpp @@ -3,6 +3,7 @@ #include +#include #include #if NANO_TIMED_LOCKS > 0 @@ -41,6 +42,9 @@ TEST (locks, no_conflicts) TEST (locks, lock_guard) { + // This test can end up taking a long time, as it sleeps for the NANO_TIMED_LOCKS amount + ASSERT_LE (NANO_TIMED_LOCKS, 10000); + std::stringstream ss; nano::cout_redirect redirect (ss.rdbuf ()); @@ -71,6 +75,9 @@ TEST (locks, lock_guard) TEST (locks, unique_lock) { + // This test can end up taking a long time, as it sleeps for the NANO_TIMED_LOCKS amount + ASSERT_LE (NANO_TIMED_LOCKS, 10000); + std::stringstream ss; nano::cout_redirect redirect (ss.rdbuf ()); diff --git a/nano/core_test/logger.cpp b/nano/core_test/logger.cpp index ba390f12b7..e5172d259c 100644 --- a/nano/core_test/logger.cpp +++ b/nano/core_test/logger.cpp @@ -1,13 +1,14 @@ #include +#include +#include #include #include #include -#include - #include #include +#include using namespace std::chrono_literals; diff --git a/nano/core_test/memory_pool.cpp b/nano/core_test/memory_pool.cpp index e9e5ae8e2b..176d2b0e64 100644 --- a/nano/core_test/memory_pool.cpp +++ b/nano/core_test/memory_pool.cpp @@ -49,8 +49,8 @@ size_t get_allocated_size () { std::vector allocated; record_allocations_new_delete_allocator alloc (&allocated); - std::allocate_shared> (alloc); - assert (allocated.size () == 1); + (void)std::allocate_shared> (alloc); + debug_assert (allocated.size () == 1); return allocated.front (); } } diff --git a/nano/core_test/message.cpp b/nano/core_test/message.cpp index 33c24fcd4c..16ac42eee0 100644 --- a/nano/core_test/message.cpp +++ b/nano/core_test/message.cpp @@ -1,14 +1,18 @@ #include +#include +#include #include +#include + TEST (message, keepalive_serialization) { nano::keepalive request1; std::vector bytes; { nano::vectorstream stream (bytes); - request1.serialize (stream); + request1.serialize (stream, false); } auto error (false); nano::bufferstream stream (bytes.data (), bytes.size ()); @@ -26,7 +30,7 @@ TEST (message, keepalive_deserialize) std::vector bytes; { nano::vectorstream stream (bytes); - message1.serialize (stream); + message1.serialize (stream, false); } nano::bufferstream stream (bytes.data (), bytes.size ()); auto error (false); @@ -46,14 +50,14 @@ TEST (message, publish_serialization) std::vector bytes; { nano::vectorstream stream (bytes); - publish.header.serialize (stream); + publish.header.serialize (stream, false); } ASSERT_EQ (8, bytes.size ()); ASSERT_EQ (0x52, bytes[0]); ASSERT_EQ (0x41, bytes[1]); ASSERT_EQ (params.protocol.protocol_version, bytes[2]); ASSERT_EQ (params.protocol.protocol_version, bytes[3]); - ASSERT_EQ (params.protocol.protocol_version_min, bytes[4]); + ASSERT_EQ (params.protocol.protocol_version_min (false), bytes[4]); ASSERT_EQ (static_cast (nano::message_type::publish), bytes[5]); ASSERT_EQ (0x00, bytes[6]); // extensions ASSERT_EQ (static_cast (nano::block_type::send), bytes[7]); @@ -61,7 +65,7 @@ TEST (message, publish_serialization) auto error (false); nano::message_header header (error, stream); ASSERT_FALSE (error); - ASSERT_EQ (params.protocol.protocol_version_min, header.version_min); + ASSERT_EQ (params.protocol.protocol_version_min (false), header.version_min ()); ASSERT_EQ (params.protocol.protocol_version, header.version_using); ASSERT_EQ (params.protocol.protocol_version, header.version_max); ASSERT_EQ (nano::message_type::publish, header.type); @@ -75,7 +79,7 @@ TEST (message, confirm_ack_serialization) std::vector bytes; { nano::vectorstream stream1 (bytes); - con1.serialize (stream1); + con1.serialize (stream1, false); } nano::bufferstream stream2 (bytes.data (), bytes.size ()); bool error (false); @@ -89,7 +93,7 @@ TEST (message, confirm_ack_serialization) TEST (message, confirm_ack_hash_serialization) { std::vector hashes; - for (auto i (hashes.size ()); i < 12; i++) + for (auto i (hashes.size ()); i < nano::network::confirm_ack_hashes_max; i++) { nano::keypair key1; nano::block_hash previous; @@ -103,7 +107,7 @@ TEST (message, confirm_ack_hash_serialization) std::vector bytes; { nano::vectorstream stream1 (bytes); - con1.serialize (stream1); + con1.serialize (stream1, false); } nano::bufferstream stream2 (bytes.data (), bytes.size ()); bool error (false); @@ -117,7 +121,7 @@ TEST (message, confirm_ack_hash_serialization) vote_blocks.push_back (boost::get (block)); } ASSERT_EQ (hashes, vote_blocks); - // Check overflow with 12 hashes + // Check overflow with max hashes ASSERT_EQ (header.count_get (), hashes.size ()); ASSERT_EQ (header.block_type (), nano::block_type::not_a_block); } @@ -131,7 +135,7 @@ TEST (message, confirm_req_serialization) std::vector bytes; { nano::vectorstream stream (bytes); - req.serialize (stream); + req.serialize (stream, false); } auto error (false); nano::bufferstream stream2 (bytes.data (), bytes.size ()); @@ -151,7 +155,7 @@ TEST (message, confirm_req_hash_serialization) std::vector bytes; { nano::vectorstream stream (bytes); - req.serialize (stream); + req.serialize (stream, false); } auto error (false); nano::bufferstream stream2 (bytes.data (), bytes.size ()); @@ -184,7 +188,7 @@ TEST (message, confirm_req_hash_batch_serialization) std::vector bytes; { nano::vectorstream stream (bytes); - req.serialize (stream); + req.serialize (stream, false); } auto error (false); nano::bufferstream stream2 (bytes.data (), bytes.size ()); diff --git a/nano/core_test/message_parser.cpp b/nano/core_test/message_parser.cpp index 3d48b1a6c0..a0649af803 100644 --- a/nano/core_test/message_parser.cpp +++ b/nano/core_test/message_parser.cpp @@ -25,50 +25,55 @@ class test_visitor : public nano::message_visitor } void bulk_pull (nano::bulk_pull const &) override { - ++bulk_pull_count; + ASSERT_FALSE (true); } void bulk_pull_account (nano::bulk_pull_account const &) override { - ++bulk_pull_account_count; + ASSERT_FALSE (true); } void bulk_push (nano::bulk_push const &) override { - ++bulk_push_count; + ASSERT_FALSE (true); } void frontier_req (nano::frontier_req const &) override { - ++frontier_req_count; + ASSERT_FALSE (true); } void node_id_handshake (nano::node_id_handshake const &) override { - ++node_id_handshake_count; + ASSERT_FALSE (true); } + void telemetry_req (nano::telemetry_req const &) override + { + ASSERT_FALSE (true); + } + void telemetry_ack (nano::telemetry_ack const &) override + { + ASSERT_FALSE (true); + } + uint64_t keepalive_count{ 0 }; uint64_t publish_count{ 0 }; uint64_t confirm_req_count{ 0 }; uint64_t confirm_ack_count{ 0 }; - uint64_t bulk_pull_count{ 0 }; - uint64_t bulk_pull_account_count{ 0 }; - uint64_t bulk_push_count{ 0 }; - uint64_t frontier_req_count{ 0 }; - uint64_t node_id_handshake_count{ 0 }; }; } TEST (message_parser, exact_confirm_ack_size) { - nano::system system (24000, 1); + nano::system system (1); test_visitor visitor; + nano::network_filter filter (1); nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer (block_uniquer); - nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::message_parser parser (filter, block_uniquer, vote_uniquer, visitor, system.work, false); auto block (std::make_shared (1, 1, 2, nano::keypair ().prv, 4, *system.work.generate (nano::root (1)))); auto vote (std::make_shared (0, nano::keypair ().prv, 0, std::move (block))); nano::confirm_ack message (vote); std::vector bytes; { nano::vectorstream stream (bytes); - message.serialize (stream); + message.serialize (stream, true); } ASSERT_EQ (0, visitor.confirm_ack_count); ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); @@ -90,17 +95,18 @@ TEST (message_parser, exact_confirm_ack_size) TEST (message_parser, exact_confirm_req_size) { - nano::system system (24000, 1); + nano::system system (1); test_visitor visitor; + nano::network_filter filter (1); nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer (block_uniquer); - nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::message_parser parser (filter, block_uniquer, vote_uniquer, visitor, system.work, false); auto block (std::make_shared (1, 1, 2, nano::keypair ().prv, 4, *system.work.generate (nano::root (1)))); nano::confirm_req message (std::move (block)); std::vector bytes; { nano::vectorstream stream (bytes); - message.serialize (stream); + message.serialize (stream, false); } ASSERT_EQ (0, visitor.confirm_req_count); ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); @@ -122,17 +128,18 @@ TEST (message_parser, exact_confirm_req_size) TEST (message_parser, exact_confirm_req_hash_size) { - nano::system system (24000, 1); + nano::system system (1); test_visitor visitor; + nano::network_filter filter (1); nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer (block_uniquer); - nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::message_parser parser (filter, block_uniquer, vote_uniquer, visitor, system.work, true); nano::send_block block (1, 1, 2, nano::keypair ().prv, 4, *system.work.generate (nano::root (1))); nano::confirm_req message (block.hash (), block.root ()); std::vector bytes; { nano::vectorstream stream (bytes); - message.serialize (stream); + message.serialize (stream, false); } ASSERT_EQ (0, visitor.confirm_req_count); ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); @@ -154,17 +161,18 @@ TEST (message_parser, exact_confirm_req_hash_size) TEST (message_parser, exact_publish_size) { - nano::system system (24000, 1); + nano::system system (1); test_visitor visitor; + nano::network_filter filter (1); nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer (block_uniquer); - nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::message_parser parser (filter, block_uniquer, vote_uniquer, visitor, system.work, true); auto block (std::make_shared (1, 1, 2, nano::keypair ().prv, 4, *system.work.generate (nano::root (1)))); nano::publish message (std::move (block)); std::vector bytes; { nano::vectorstream stream (bytes); - message.serialize (stream); + message.serialize (stream, false); } ASSERT_EQ (0, visitor.publish_count); ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); @@ -186,16 +194,17 @@ TEST (message_parser, exact_publish_size) TEST (message_parser, exact_keepalive_size) { - nano::system system (24000, 1); + nano::system system (1); test_visitor visitor; + nano::network_filter filter (1); nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer (block_uniquer); - nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::message_parser parser (filter, block_uniquer, vote_uniquer, visitor, system.work, true); nano::keepalive message; std::vector bytes; { nano::vectorstream stream (bytes); - message.serialize (stream); + message.serialize (stream, true); } ASSERT_EQ (0, visitor.keepalive_count); ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index 9476629f55..de66157331 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -13,7 +13,8 @@ TEST (network, tcp_connection) { boost::asio::io_context io_ctx; boost::asio::ip::tcp::acceptor acceptor (io_ctx); - boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::any (), 24000); + auto port = nano::get_available_port (); + boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::any (), port); acceptor.open (endpoint.protocol ()); acceptor.set_option (boost::asio::ip::tcp::acceptor::reuse_address (true)); acceptor.bind (endpoint); @@ -32,7 +33,7 @@ TEST (network, tcp_connection) boost::asio::ip::tcp::socket connector (io_ctx); std::atomic done2 (false); std::string message2; - connector.async_connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v4::loopback (), 24000), + connector.async_connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v4::loopback (), port), [&done2, &message2](boost::system::error_code const & ec_a) { if (ec_a) { @@ -51,14 +52,18 @@ TEST (network, tcp_connection) TEST (network, construction) { - nano::system system (24000, 1); + auto port = nano::get_available_port (); + nano::system system; + system.add_node (nano::node_config (port, system.logging)); ASSERT_EQ (1, system.nodes.size ()); - ASSERT_EQ (24000, system.nodes[0]->network.endpoint ().port ()); + ASSERT_EQ (port, system.nodes[0]->network.endpoint ().port ()); } TEST (network, self_discard) { - nano::system system (24000, 1); + nano::node_flags node_flags; + node_flags.disable_udp = false; + nano::system system (1, nano::transport::transport_type::tcp, node_flags); nano::message_buffer data; data.endpoint = system.nodes[0]->network.endpoint (); ASSERT_EQ (0, system.nodes[0]->stats.count (nano::stat::type::error, nano::stat::detail::bad_sender)); @@ -68,59 +73,69 @@ TEST (network, self_discard) TEST (network, send_node_id_handshake) { - nano::system system (24000, 1); - ASSERT_EQ (0, system.nodes[0]->network.size ()); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::node_flags node_flags; + node_flags.disable_udp = false; + nano::system system; + auto node0 = system.add_node (node_flags); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); node1->start (); system.nodes.push_back (node1); - auto initial (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); + auto initial (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); auto initial_node1 (node1->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); - auto channel (std::make_shared (system.nodes[0]->network.udp_channels, node1->network.endpoint (), node1->network_params.protocol.protocol_version)); - system.nodes[0]->network.send_keepalive (channel); - ASSERT_EQ (0, system.nodes[0]->network.size ()); + auto channel (std::make_shared (node0->network.udp_channels, node1->network.endpoint (), node1->network_params.protocol.protocol_version)); + node0->network.send_keepalive (channel); + ASSERT_EQ (0, node0->network.size ()); ASSERT_EQ (0, node1->network.size ()); system.deadline_set (10s); while (node1->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in) == initial_node1) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (0, system.nodes[0]->network.size ()); - ASSERT_EQ (1, node1->network.size ()); system.deadline_set (10s); - while (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in) < initial + 2) + while (node0->network.size () != 0 && node1->network.size () != 1) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[0]->network.size ()); - ASSERT_EQ (1, node1->network.size ()); - auto list1 (system.nodes[0]->network.list (1)); + system.deadline_set (10s); + while (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in) < initial + 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + system.deadline_set (10s); + while (node0->network.size () != 1 && node1->network.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto list1 (node0->network.list (1)); ASSERT_EQ (node1->network.endpoint (), list1[0]->get_endpoint ()); auto list2 (node1->network.list (1)); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (node0->network.endpoint (), list2[0]->get_endpoint ()); node1->stop (); } TEST (network, send_node_id_handshake_tcp) { - nano::system system (24000, 1); - ASSERT_EQ (0, system.nodes[0]->network.size ()); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system (1); + auto node0 (system.nodes[0]); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); node1->start (); system.nodes.push_back (node1); - auto initial (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); + auto initial (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); auto initial_node1 (node1->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in)); - auto initial_keepalive (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in)); - std::weak_ptr node_w (system.nodes[0]); - system.nodes[0]->network.tcp_channels.start_tcp (node1->network.endpoint (), [node_w](std::shared_ptr channel_a) { + auto initial_keepalive (node0->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in)); + std::weak_ptr node_w (node0); + node0->network.tcp_channels.start_tcp (node1->network.endpoint (), [node_w](std::shared_ptr channel_a) { if (auto node_l = node_w.lock ()) { node_l->network.send_keepalive (channel_a); } }); - ASSERT_EQ (0, system.nodes[0]->network.size ()); + ASSERT_EQ (0, node0->network.size ()); ASSERT_EQ (0, node1->network.size ()); system.deadline_set (10s); - while (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in) < initial + 2) + while (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in) < initial + 2) { ASSERT_NO_ERROR (system.poll ()); } @@ -130,7 +145,7 @@ TEST (network, send_node_id_handshake_tcp) ASSERT_NO_ERROR (system.poll ()); } system.deadline_set (5s); - while (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in) < initial_keepalive + 2) + while (node0->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in) < initial_keepalive + 2) { ASSERT_NO_ERROR (system.poll ()); } @@ -139,45 +154,48 @@ TEST (network, send_node_id_handshake_tcp) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[0]->network.size ()); + ASSERT_EQ (1, node0->network.size ()); ASSERT_EQ (1, node1->network.size ()); - auto list1 (system.nodes[0]->network.list (1)); + auto list1 (node0->network.list (1)); ASSERT_EQ (nano::transport::transport_type::tcp, list1[0]->get_type ()); ASSERT_EQ (node1->network.endpoint (), list1[0]->get_endpoint ()); auto list2 (node1->network.list (1)); ASSERT_EQ (nano::transport::transport_type::tcp, list2[0]->get_type ()); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (node0->network.endpoint (), list2[0]->get_endpoint ()); node1->stop (); } TEST (network, last_contacted) { - nano::system system (24000, 1); - ASSERT_EQ (0, system.nodes[0]->network.size ()); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto node0 = system.add_node (node_flags); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); node1->start (); system.nodes.push_back (node1); - auto channel1 (std::make_shared (node1->network.udp_channels, nano::endpoint (boost::asio::ip::address_v6::loopback (), 24000), node1->network_params.protocol.protocol_version)); + auto channel1 (std::make_shared (node1->network.udp_channels, nano::endpoint (boost::asio::ip::address_v6::loopback (), system.nodes.front ()->network.endpoint ().port ()), node1->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel1); system.deadline_set (10s); // Wait until the handshake is complete - while (system.nodes[0]->network.size () < 1) + while (node0->network.size () < 1) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (system.nodes[0]->network.size (), 1); + ASSERT_EQ (node0->network.size (), 1); - auto channel2 (system.nodes[0]->network.udp_channels.channel (nano::endpoint (boost::asio::ip::address_v6::loopback (), 24001))); + auto channel2 (node0->network.udp_channels.channel (nano::endpoint (boost::asio::ip::address_v6::loopback (), node1->network.endpoint ().port ()))); ASSERT_NE (nullptr, channel2); // Make sure last_contact gets updated on receiving a non-handshake message auto timestamp_before_keepalive = channel2->get_last_packet_received (); node1->network.send_keepalive (channel1); - while (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in) < 2) + while (node0->stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in) < 2) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (system.nodes[0]->network.size (), 1); + ASSERT_EQ (node0->network.size (), 1); auto timestamp_after_keepalive = channel2->get_last_packet_received (); ASSERT_GT (timestamp_after_keepalive, timestamp_before_keepalive); @@ -186,30 +204,31 @@ TEST (network, last_contacted) TEST (network, multi_keepalive) { - nano::system system (24000, 1); - ASSERT_EQ (0, system.nodes[0]->network.size ()); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto node0 = system.add_node (node_flags); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); ASSERT_FALSE (node1->init_error ()); node1->start (); system.nodes.push_back (node1); ASSERT_EQ (0, node1->network.size ()); - auto channel1 (std::make_shared (node1->network.udp_channels, system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version)); + auto channel1 (std::make_shared (node1->network.udp_channels, node0->network.endpoint (), node1->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel1); ASSERT_EQ (0, node1->network.size ()); - ASSERT_EQ (0, system.nodes[0]->network.size ()); + ASSERT_EQ (0, node0->network.size ()); system.deadline_set (10s); - while (system.nodes[0]->network.size () != 1) + while (node0->network.size () != 1) { ASSERT_NO_ERROR (system.poll ()); } - auto node2 (std::make_shared (system.io_ctx, 24002, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node2 = system.add_node (node_flags); ASSERT_FALSE (node2->init_error ()); - node2->start (); - system.nodes.push_back (node2); - auto channel2 (std::make_shared (node2->network.udp_channels, system.nodes[0]->network.endpoint (), node2->network_params.protocol.protocol_version)); + auto channel2 (std::make_shared (node2->network.udp_channels, node0->network.endpoint (), node2->network_params.protocol.protocol_version)); node2->network.send_keepalive (channel2); system.deadline_set (10s); - while (node1->network.size () != 2 || system.nodes[0]->network.size () != 2 || node2->network.size () != 2) + while (node1->network.size () != 2 || node0->network.size () != 2 || node2->network.size () != 2) { ASSERT_NO_ERROR (system.poll ()); } @@ -219,44 +238,48 @@ TEST (network, multi_keepalive) TEST (network, send_discarded_publish) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); auto block (std::make_shared (1, 1, 2, nano::keypair ().prv, 4, *system.work.generate (nano::root (1)))); nano::genesis genesis; { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - system.nodes[0]->network.flood_block (block); - ASSERT_EQ (genesis.hash (), system.nodes[0]->ledger.latest (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (genesis.hash (), system.nodes[1]->latest (nano::test_genesis_key.pub)); + auto transaction (node1.store.tx_begin_read ()); + node1.network.flood_block (block); + ASSERT_EQ (genesis.hash (), node1.ledger.latest (transaction, nano::test_genesis_key.pub)); + ASSERT_EQ (genesis.hash (), node2.latest (nano::test_genesis_key.pub)); } system.deadline_set (10s); - while (system.nodes[1]->stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) + while (node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) { ASSERT_NO_ERROR (system.poll ()); } - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_EQ (genesis.hash (), system.nodes[0]->ledger.latest (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (genesis.hash (), system.nodes[1]->latest (nano::test_genesis_key.pub)); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_EQ (genesis.hash (), node1.ledger.latest (transaction, nano::test_genesis_key.pub)); + ASSERT_EQ (genesis.hash (), node2.latest (nano::test_genesis_key.pub)); } TEST (network, send_invalid_publish) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::genesis genesis; auto block (std::make_shared (1, 1, 20, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::root (1)))); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - system.nodes[0]->network.flood_block (block); - ASSERT_EQ (genesis.hash (), system.nodes[0]->ledger.latest (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (genesis.hash (), system.nodes[1]->latest (nano::test_genesis_key.pub)); + auto transaction (node1.store.tx_begin_read ()); + node1.network.flood_block (block); + ASSERT_EQ (genesis.hash (), node1.ledger.latest (transaction, nano::test_genesis_key.pub)); + ASSERT_EQ (genesis.hash (), node2.latest (nano::test_genesis_key.pub)); } system.deadline_set (10s); - while (system.nodes[1]->stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) + while (node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) { ASSERT_NO_ERROR (system.poll ()); } - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_EQ (genesis.hash (), system.nodes[0]->ledger.latest (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (genesis.hash (), system.nodes[1]->latest (nano::test_genesis_key.pub)); + auto transaction (node1.store.tx_begin_read ()); + ASSERT_EQ (genesis.hash (), node1.ledger.latest (transaction, nano::test_genesis_key.pub)); + ASSERT_EQ (genesis.hash (), node2.latest (nano::test_genesis_key.pub)); } TEST (network, send_valid_confirm_ack) @@ -264,22 +287,31 @@ TEST (network, send_valid_confirm_ack) std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 2, type); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system (2, type, node_flags); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (1)->insert_adhoc (key2.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest1 (node1.latest (nano::test_genesis_key.pub)); nano::send_block block2 (latest1, key2.pub, 50, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); - nano::block_hash latest2 (system.nodes[1]->latest (nano::test_genesis_key.pub)); - system.nodes[0]->process_active (std::make_shared (block2)); + nano::block_hash latest2 (node2.latest (nano::test_genesis_key.pub)); + node1.process_active (std::make_shared (block2)); system.deadline_set (10s); // Keep polling until latest block changes - while (system.nodes[1]->latest (nano::test_genesis_key.pub) == latest2) + while (node2.latest (nano::test_genesis_key.pub) == latest2) { ASSERT_NO_ERROR (system.poll ()); } // Make sure the balance has decreased after processing the block. - ASSERT_EQ (50, system.nodes[1]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (50, node2.balance (nano::test_genesis_key.pub)); } } @@ -288,59 +320,70 @@ TEST (network, send_valid_publish) std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 2, type); - system.nodes[0]->bootstrap_initiator.stop (); - system.nodes[1]->bootstrap_initiator.stop (); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system (2, type, node_flags); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + node1.bootstrap_initiator.stop (); + node2.bootstrap_initiator.stop (); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest1 (node1.latest (nano::test_genesis_key.pub)); nano::send_block block2 (latest1, key2.pub, 50, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1)); auto hash2 (block2.hash ()); - nano::block_hash latest2 (system.nodes[1]->latest (nano::test_genesis_key.pub)); - system.nodes[1]->process_active (std::make_shared (block2)); + nano::block_hash latest2 (node2.latest (nano::test_genesis_key.pub)); + node2.process_active (std::make_shared (block2)); system.deadline_set (10s); - while (system.nodes[0]->stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) + while (node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 0) { ASSERT_NO_ERROR (system.poll ()); } ASSERT_NE (hash2, latest2); system.deadline_set (10s); - while (system.nodes[1]->latest (nano::test_genesis_key.pub) == latest2) + while (node2.latest (nano::test_genesis_key.pub) == latest2) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (50, system.nodes[1]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (50, node2.balance (nano::test_genesis_key.pub)); } } TEST (network, send_insufficient_work) { - nano::system system (24000, 2); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto & node1 = *system.add_node (node_flags); + auto & node2 = *system.add_node (node_flags); auto block (std::make_shared (0, 1, 20, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); nano::publish publish (block); - auto node1 (system.nodes[1]->shared ()); - nano::transport::channel_udp channel (system.nodes[0]->network.udp_channels, system.nodes[1]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version); + nano::transport::channel_udp channel (node1.network.udp_channels, node2.network.endpoint (), node1.network_params.protocol.protocol_version); channel.send (publish, [](boost::system::error_code const & ec, size_t size) {}); - ASSERT_EQ (0, system.nodes[0]->stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); system.deadline_set (10s); - while (system.nodes[1]->stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work) == 0) + while (node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work) == 0) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, system.nodes[1]->stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); + ASSERT_EQ (1, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); } TEST (receivable_processor, confirm_insufficient_pos) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; auto block1 (std::make_shared (genesis.hash (), 0, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*block1); ASSERT_EQ (nano::process_result::progress, node1.process (*block1).code); - auto node_l (system.nodes[0]); - node1.active.start (block1); + node1.active.insert (block1); nano::keypair key1; auto vote (std::make_shared (key1.pub, key1.prv, 0, block1)); nano::confirm_ack con1 (vote); @@ -349,14 +392,13 @@ TEST (receivable_processor, confirm_insufficient_pos) TEST (receivable_processor, confirm_sufficient_pos) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::genesis genesis; auto block1 (std::make_shared (genesis.hash (), 0, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*block1); ASSERT_EQ (nano::process_result::progress, node1.process (*block1).code); - auto node_l (system.nodes[0]); - node1.active.start (block1); + node1.active.insert (block1); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, block1)); nano::confirm_ack con1 (vote); node1.network.process_message (con1, node1.network.udp_channels.create (node1.network.endpoint ())); @@ -367,40 +409,49 @@ TEST (receivable_processor, send_with_receive) std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 2, type); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system (2, type, node_flags); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); auto amount (std::numeric_limits::max ()); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest1 (node1.latest (nano::test_genesis_key.pub)); system.wallet (1)->insert_adhoc (key2.prv); - auto block1 (std::make_shared (latest1, key2.pub, amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); - ASSERT_EQ (amount, system.nodes[0]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (0, system.nodes[0]->balance (key2.pub)); - ASSERT_EQ (amount, system.nodes[1]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (0, system.nodes[1]->balance (key2.pub)); - system.nodes[0]->process_active (block1); - system.nodes[0]->block_processor.flush (); - system.nodes[1]->process_active (block1); - system.nodes[1]->block_processor.flush (); - ASSERT_EQ (amount - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (0, system.nodes[0]->balance (key2.pub)); - ASSERT_EQ (amount - system.nodes[0]->config.receive_minimum.number (), system.nodes[1]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (0, system.nodes[1]->balance (key2.pub)); + auto block1 (std::make_shared (latest1, key2.pub, amount - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest1))); + ASSERT_EQ (amount, node1.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node1.balance (key2.pub)); + ASSERT_EQ (amount, node2.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node2.balance (key2.pub)); + node1.process_active (block1); + node1.block_processor.flush (); + node2.process_active (block1); + node2.block_processor.flush (); + ASSERT_EQ (amount - node1.config.receive_minimum.number (), node1.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node1.balance (key2.pub)); + ASSERT_EQ (amount - node1.config.receive_minimum.number (), node2.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node2.balance (key2.pub)); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number () || system.nodes[1]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number ()) + while (node1.balance (key2.pub) != node1.config.receive_minimum.number () || node2.balance (key2.pub) != node1.config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (amount - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (key2.pub)); - ASSERT_EQ (amount - system.nodes[0]->config.receive_minimum.number (), system.nodes[1]->balance (nano::test_genesis_key.pub)); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.number (), system.nodes[1]->balance (key2.pub)); + ASSERT_EQ (amount - node1.config.receive_minimum.number (), node1.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (node1.config.receive_minimum.number (), node1.balance (key2.pub)); + ASSERT_EQ (amount - node1.config.receive_minimum.number (), node2.balance (nano::test_genesis_key.pub)); + ASSERT_EQ (node1.config.receive_minimum.number (), node2.balance (key2.pub)); } } TEST (network, receive_weight_change) { - nano::system system (24000, 2); + nano::system system (2); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); @@ -462,7 +513,7 @@ TEST (parse_endpoint, no_colon) TEST (network, ipv6) { - boost::asio::ip::address_v6 address (boost::asio::ip::address_v6::from_string ("::ffff:127.0.0.1")); + boost::asio::ip::address_v6 address (boost::asio::ip::make_address_v6 ("::ffff:127.0.0.1")); ASSERT_TRUE (address.is_v4_mapped ()); nano::endpoint endpoint1 (address, 16384); std::vector bytes1; @@ -496,8 +547,10 @@ TEST (network, ipv6_from_ipv4) TEST (network, ipv6_bind_send_ipv4) { boost::asio::io_context io_ctx; - nano::endpoint endpoint1 (boost::asio::ip::address_v6::any (), 24000); - nano::endpoint endpoint2 (boost::asio::ip::address_v4::any (), 24001); + auto port1 = nano::get_available_port (); + auto port2 = nano::get_available_port (); + nano::endpoint endpoint1 (boost::asio::ip::address_v6::any (), port1); + nano::endpoint endpoint2 (boost::asio::ip::address_v4::any (), port2); std::array bytes1; auto finish1 (false); nano::endpoint endpoint3; @@ -508,8 +561,8 @@ TEST (network, ipv6_bind_send_ipv4) finish1 = true; }); boost::asio::ip::udp::socket socket2 (io_ctx, endpoint2); - nano::endpoint endpoint5 (boost::asio::ip::address_v4::loopback (), 24000); - nano::endpoint endpoint6 (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4::loopback ()), 24001); + nano::endpoint endpoint5 (boost::asio::ip::address_v4::loopback (), port1); + nano::endpoint endpoint6 (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4::loopback ()), port2); socket2.async_send_to (boost::asio::buffer (std::array{}, 16), endpoint5, [](boost::system::error_code const & error, size_t size_a) { ASSERT_FALSE (error); ASSERT_EQ (16, size_a); @@ -536,7 +589,7 @@ TEST (network, ipv6_bind_send_ipv4) TEST (network, endpoint_bad_fd) { - nano::system system (24000, 1); + nano::system system (1); system.nodes[0]->stop (); auto endpoint (system.nodes[0]->network.endpoint ()); ASSERT_TRUE (endpoint.address ().is_loopback ()); @@ -550,21 +603,21 @@ TEST (network, endpoint_bad_fd) TEST (network, reserved_address) { - nano::system system (24000, 1); + nano::system system (1); // 0 port test - ASSERT_TRUE (nano::transport::reserved_address (nano::endpoint (boost::asio::ip::address_v6::from_string ("2001::"), 0))); + ASSERT_TRUE (nano::transport::reserved_address (nano::endpoint (boost::asio::ip::make_address_v6 ("2001::"), 0))); // Valid address test - ASSERT_FALSE (nano::transport::reserved_address (nano::endpoint (boost::asio::ip::address_v6::from_string ("2001::"), 1))); - nano::endpoint loopback (boost::asio::ip::address_v6::from_string ("::1"), 1); + ASSERT_FALSE (nano::transport::reserved_address (nano::endpoint (boost::asio::ip::make_address_v6 ("2001::"), 1))); + nano::endpoint loopback (boost::asio::ip::make_address_v6 ("::1"), 1); ASSERT_FALSE (nano::transport::reserved_address (loopback)); - nano::endpoint private_network_peer (boost::asio::ip::address_v6::from_string ("::ffff:10.0.0.0"), 1); + nano::endpoint private_network_peer (boost::asio::ip::make_address_v6 ("::ffff:10.0.0.0"), 1); ASSERT_TRUE (nano::transport::reserved_address (private_network_peer, false)); ASSERT_FALSE (nano::transport::reserved_address (private_network_peer, true)); } TEST (node, port_mapping) { - nano::system system (24000, 1); + nano::system system (1); auto node0 (system.nodes[0]); node0->port_mapping.refresh_devices (); node0->port_mapping.start (); @@ -726,12 +779,12 @@ TEST (message_buffer_manager, stats) TEST (tcp_listener, tcp_node_id_handshake) { - nano::system system (24000, 1); + nano::system system (1); auto socket (std::make_shared (system.nodes[0])); auto bootstrap_endpoint (system.nodes[0]->bootstrap.endpoint ()); auto cookie (system.nodes[0]->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (bootstrap_endpoint))); nano::node_id_handshake node_id_handshake (cookie, boost::none); - auto input (node_id_handshake.to_shared_const_buffer ()); + auto input (node_id_handshake.to_shared_const_buffer (false)); std::atomic write_done (false); socket->async_connect (bootstrap_endpoint, [&input, socket, &write_done](boost::system::error_code const & ec) { ASSERT_FALSE (ec); @@ -750,7 +803,7 @@ TEST (tcp_listener, tcp_node_id_handshake) boost::optional> response_zero (std::make_pair (nano::account (0), nano::signature (0))); nano::node_id_handshake node_id_handshake_response (boost::none, response_zero); - auto output (node_id_handshake_response.to_bytes ()); + auto output (node_id_handshake_response.to_bytes (false)); std::atomic done (false); socket->async_read (output, output->size (), [&output, &done](boost::system::error_code const & ec, size_t size_a) { ASSERT_FALSE (ec); @@ -766,7 +819,7 @@ TEST (tcp_listener, tcp_node_id_handshake) TEST (tcp_listener, tcp_listener_timeout_empty) { - nano::system system (24000, 1); + nano::system system (1); auto node0 (system.nodes[0]); auto socket (std::make_shared (node0)); std::atomic connected (false); @@ -793,12 +846,12 @@ TEST (tcp_listener, tcp_listener_timeout_empty) TEST (tcp_listener, tcp_listener_timeout_node_id_handshake) { - nano::system system (24000, 1); + nano::system system (1); auto node0 (system.nodes[0]); auto socket (std::make_shared (node0)); auto cookie (node0->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (node0->bootstrap.endpoint ()))); nano::node_id_handshake node_id_handshake (cookie, boost::none); - auto input (node_id_handshake.to_shared_const_buffer ()); + auto input (node_id_handshake.to_shared_const_buffer (false)); socket->async_connect (node0->bootstrap.endpoint (), [&input, socket](boost::system::error_code const & ec) { ASSERT_FALSE (ec); socket->async_write (input, [&input](boost::system::error_code const & ec, size_t size_a) { @@ -829,93 +882,254 @@ TEST (tcp_listener, tcp_listener_timeout_node_id_handshake) TEST (network, replace_port) { - nano::system system (24000, 1); - ASSERT_EQ (0, system.nodes[0]->network.size ()); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + node_flags.disable_ongoing_telemetry_requests = true; + node_flags.disable_initial_telemetry_requests = true; + auto node0 = system.add_node (node_flags); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); node1->start (); system.nodes.push_back (node1); { - auto channel (system.nodes[0]->network.udp_channels.insert (nano::endpoint (node1->network.endpoint ().address (), 23000), node1->network_params.protocol.protocol_version)); + auto channel (node0->network.udp_channels.insert (nano::endpoint (node1->network.endpoint ().address (), 23000), node1->network_params.protocol.protocol_version)); if (channel) { channel->set_node_id (node1->node_id.pub); } } - auto peers_list (system.nodes[0]->network.list (std::numeric_limits::max ())); + auto peers_list (node0->network.list (std::numeric_limits::max ())); ASSERT_EQ (peers_list[0]->get_node_id (), node1->node_id.pub); - auto channel (std::make_shared (system.nodes[0]->network.udp_channels, node1->network.endpoint (), node1->network_params.protocol.protocol_version)); - system.nodes[0]->network.send_keepalive (channel); + auto channel (std::make_shared (node0->network.udp_channels, node1->network.endpoint (), node1->network_params.protocol.protocol_version)); + node0->network.send_keepalive (channel); system.deadline_set (5s); - while (!system.nodes[0]->network.udp_channels.channel (node1->network.endpoint ())) + while (!node0->network.udp_channels.channel (node1->network.endpoint ())) { ASSERT_NO_ERROR (system.poll ()); } system.deadline_set (5s); - while (system.nodes[0]->network.udp_channels.size () > 1) + while (node0->network.udp_channels.size () > 1) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (system.nodes[0]->network.udp_channels.size (), 1); - auto list1 (system.nodes[0]->network.list (1)); + ASSERT_EQ (node0->network.udp_channels.size (), 1); + auto list1 (node0->network.list (1)); ASSERT_EQ (node1->network.endpoint (), list1[0]->get_endpoint ()); auto list2 (node1->network.list (1)); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (node0->network.endpoint (), list2[0]->get_endpoint ()); // Remove correct peer (same node ID) - system.nodes[0]->network.udp_channels.clean_node_id (nano::endpoint (node1->network.endpoint ().address (), 23000), node1->node_id.pub); - ASSERT_EQ (system.nodes[0]->network.udp_channels.size (), 0); + node0->network.udp_channels.clean_node_id (nano::endpoint (node1->network.endpoint ().address (), 23000), node1->node_id.pub); + system.deadline_set (5s); + while (node0->network.udp_channels.size () > 1) + { + ASSERT_NO_ERROR (system.poll ()); + } node1->stop (); } -TEST (bandwidth_limiter, validate) +TEST (network, peer_max_tcp_attempts) { - size_t const full_confirm_ack (488 + 8); + nano::system system (1); + auto node (system.nodes[0]); + // Add nodes that can accept TCP connection, but not node ID handshake + nano::node_flags node_flags; + node_flags.disable_tcp_realtime = true; + for (auto i (0); i < node->network_params.node.max_peers_per_ip; ++i) { - nano::bandwidth_limiter limiter_0 (0); - nano::bandwidth_limiter limiter_1 (1024); - nano::bandwidth_limiter limiter_256 (1024 * 256); - nano::bandwidth_limiter limiter_1024 (1024 * 1024); - nano::bandwidth_limiter limiter_1536 (1024 * 1536); + auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); + node2->start (); + system.nodes.push_back (node2); + // Start TCP attempt + node->network.merge_peer (node2->network.endpoint ()); + } + ASSERT_EQ (0, node->network.size ()); + ASSERT_TRUE (node->network.tcp_channels.reachout (nano::endpoint (node->network.endpoint ().address (), nano::get_available_port ()))); +} - auto now (std::chrono::steady_clock::now ()); +TEST (network, duplicate_detection) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto & node0 (*system.add_node (node_flags)); + auto & node1 (*system.add_node (node_flags)); + auto udp_channel (std::make_shared (node0.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); + nano::genesis genesis; + nano::publish publish (genesis.open); - while (now + 1s >= std::chrono::steady_clock::now ()) - { - ASSERT_FALSE (limiter_0.should_drop (full_confirm_ack)); // will never drop - ASSERT_TRUE (limiter_1.should_drop (full_confirm_ack)); // always drop as message > limit / rate_buffer.size () - limiter_256.should_drop (full_confirm_ack); - limiter_1024.should_drop (full_confirm_ack); - limiter_1536.should_drop (full_confirm_ack); - std::this_thread::sleep_for (10ms); - } - ASSERT_FALSE (limiter_0.should_drop (full_confirm_ack)); // will never drop - ASSERT_TRUE (limiter_1.should_drop (full_confirm_ack)); // always drop as message > limit / rate_buffer.size () - ASSERT_FALSE (limiter_256.should_drop (full_confirm_ack)); // as a second has passed counter is started and nothing is dropped - ASSERT_FALSE (limiter_1024.should_drop (full_confirm_ack)); // as a second has passed counter is started and nothing is dropped - ASSERT_FALSE (limiter_1536.should_drop (full_confirm_ack)); // as a second has passed counter is started and nothing is dropped + // Publish duplicate detection through UDP + ASSERT_EQ (0, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish)); + udp_channel->send (publish); + udp_channel->send (publish); + system.deadline_set (2s); + while (node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish) < 1) + { + ASSERT_NO_ERROR (system.poll ()); } + // Publish duplicate detection through TCP + auto tcp_channel (node0.network.tcp_channels.find_channel (nano::transport::map_endpoint_to_tcp (node1.network.endpoint ()))); + ASSERT_EQ (1, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish)); + tcp_channel->send (publish); + system.deadline_set (2s); + while (node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish) < 2) { - nano::bandwidth_limiter limiter_0 (0); - nano::bandwidth_limiter limiter_1 (1024); - nano::bandwidth_limiter limiter_256 (1024 * 256); - nano::bandwidth_limiter limiter_1024 (1024 * 1024); - nano::bandwidth_limiter limiter_1536 (1024 * 1536); + ASSERT_NO_ERROR (system.poll ()); + } +} - auto now (std::chrono::steady_clock::now ()); - //trend rate for 5 sec - while (now + 5s >= std::chrono::steady_clock::now ()) - { - ASSERT_FALSE (limiter_0.should_drop (full_confirm_ack)); // will never drop - ASSERT_TRUE (limiter_1.should_drop (full_confirm_ack)); // always drop as message > limit / rate_buffer.size () - limiter_256.should_drop (full_confirm_ack); - limiter_1024.should_drop (full_confirm_ack); - limiter_1536.should_drop (full_confirm_ack); - std::this_thread::sleep_for (50ms); - } - ASSERT_EQ (limiter_0.get_rate (), 0); //should be 0 as rate is not gathered if not needed - ASSERT_EQ (limiter_1.get_rate (), 0); //should be 0 since nothing is small enough to pass through is tracked - ASSERT_EQ (limiter_256.get_rate (), full_confirm_ack); //should be 0 since nothing is small enough to pass through is tracked - ASSERT_EQ (limiter_1024.get_rate (), full_confirm_ack); //should be 0 since nothing is small enough to pass through is tracked - ASSERT_EQ (limiter_1536.get_rate (), full_confirm_ack); //should be 0 since nothing is small enough to pass through is tracked +TEST (network, duplicate_revert_publish) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.block_processor_full_size = 0; + auto & node (*system.add_node (node_flags)); + ASSERT_TRUE (node.block_processor.full ()); + nano::genesis genesis; + nano::publish publish (genesis.open); + std::vector bytes; + { + nano::vectorstream stream (bytes); + publish.block->serialize (stream); + } + // Add to the blocks filter + // Should be cleared when dropping due to a full block processor, as long as the message has the optional digest attached + // Test network.duplicate_detection ensures that the digest is attached when deserializing messages + nano::uint128_t digest; + ASSERT_FALSE (node.network.publish_filter.apply (bytes.data (), bytes.size (), &digest)); + ASSERT_TRUE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + ASSERT_EQ (0, publish.digest); + node.network.process_message (publish, channel); + ASSERT_TRUE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); + publish.digest = digest; + node.network.process_message (publish, channel); + ASSERT_FALSE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); +} + +// The test must be completed in less than 1 second +TEST (network, bandwidth_limiter) +{ + nano::system system; + nano::genesis genesis; + nano::publish message (genesis.open); + auto message_size = message.to_bytes (false)->size (); + auto message_limit = 4; // must be multiple of the number of channels + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.bandwidth_limit = message_limit * message_size; + node_config.bandwidth_limit_burst_ratio = 1.0; + auto & node = *system.add_node (node_config); + auto channel1 (node.network.udp_channels.create (node.network.endpoint ())); + auto channel2 (node.network.udp_channels.create (node.network.endpoint ())); + // Send droppable messages + for (unsigned i = 0; i < message_limit; i += 2) // number of channels + { + channel1->send (message); + channel2->send (message); + } + // Only sent messages below limit, so we don't expect any drops + ASSERT_TIMELY (1s, 0 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + + // Send droppable message; drop stats should increase by one now + channel1->send (message); + ASSERT_TIMELY (1s, 1 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + + // Send non-droppable message, i.e. drop stats should not increase + channel2->send (message, nullptr, nano::buffer_drop_policy::no_limiter_drop); + ASSERT_TIMELY (1s, 1 == node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); + + node.stop (); +} + +namespace nano +{ +TEST (peer_exclusion, validate) +{ + nano::peer_exclusion excluded_peers; + size_t fake_peers_count = 10; + auto max_size = excluded_peers.limited_size (fake_peers_count); + for (auto i = 0; i < max_size + 2; ++i) + { + nano::tcp_endpoint endpoint (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (i)), 0); + ASSERT_FALSE (excluded_peers.check (endpoint)); + ASSERT_EQ (1, excluded_peers.add (endpoint, fake_peers_count)); + ASSERT_FALSE (excluded_peers.check (endpoint)); } + // The oldest one must have been removed + ASSERT_EQ (max_size + 1, excluded_peers.size ()); + auto & peers_by_endpoint (excluded_peers.peers.get ()); + nano::tcp_endpoint oldest (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x0)), 0); + ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (oldest.address ())); + + auto to_seconds = [](std::chrono::steady_clock::time_point const & timepoint) { + return std::chrono::duration_cast (timepoint.time_since_epoch ()).count (); + }; + nano::tcp_endpoint first (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x1)), 0); + ASSERT_NE (peers_by_endpoint.end (), peers_by_endpoint.find (first.address ())); + nano::tcp_endpoint second (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x2)), 0); + ASSERT_EQ (false, excluded_peers.check (second)); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); + ASSERT_EQ (2, excluded_peers.add (second, fake_peers_count)); + ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (first.address ())); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); + ASSERT_EQ (3, excluded_peers.add (second, fake_peers_count)); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours * 3 * 2), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); + ASSERT_EQ (max_size, excluded_peers.size ()); + + // Clear many entries if there are a low number of peers + ASSERT_EQ (4, excluded_peers.add (second, 0)); + ASSERT_EQ (1, excluded_peers.size ()); + + auto component (nano::collect_container_info (excluded_peers, "")); + auto composite (dynamic_cast (component.get ())); + ASSERT_NE (nullptr, component); + auto & children (composite->get_children ()); + ASSERT_EQ (1, children.size ()); + auto child_leaf (dynamic_cast (children.front ().get ())); + ASSERT_NE (nullptr, child_leaf); + auto child_info (child_leaf->get_info ()); + ASSERT_EQ ("peers", child_info.name); + ASSERT_EQ (1, child_info.count); + ASSERT_EQ (sizeof (decltype (excluded_peers.peers)::value_type), child_info.sizeof_element); +} +} + +TEST (network, tcp_no_connect_excluded_peers) +{ + nano::system system (1); + auto node0 (system.nodes[0]); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->start (); + system.nodes.push_back (node1); + auto endpoint1 (node1->network.endpoint ()); + auto endpoint1_tcp (nano::transport::map_endpoint_to_tcp (endpoint1)); + while (!node0->network.excluded_peers.check (endpoint1_tcp)) + { + node0->network.excluded_peers.add (endpoint1_tcp, 1); + } + ASSERT_EQ (0, node0->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_excluded)); + node1->network.merge_peer (node0->network.endpoint ()); + ASSERT_TIMELY (5s, node0->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_excluded) >= 1); + ASSERT_EQ (nullptr, node0->network.find_channel (endpoint1)); + + // Should not actively reachout to excluded peers + ASSERT_TRUE (node0->network.reachout (endpoint1, true)); + + // Erasing from excluded peers should allow a connection + node0->network.excluded_peers.remove (endpoint1_tcp); + ASSERT_FALSE (node0->network.excluded_peers.check (endpoint1_tcp)); + + // Wait until there is a syn_cookie + ASSERT_TIMELY (5s, node1->network.syn_cookies.cookies_size () != 0); + + // Manually cleanup previous attempt + node1->network.cleanup (std::chrono::steady_clock::now ()); + node1->network.syn_cookies.purge (std::chrono::steady_clock::now ()); + + // Ensure a successful connection + ASSERT_EQ (0, node0->network.size ()); + node1->network.merge_peer (node0->network.endpoint ()); + ASSERT_TIMELY (5s, node0->network.size () == 1); } diff --git a/nano/core_test/network_filter.cpp b/nano/core_test/network_filter.cpp new file mode 100644 index 0000000000..ad54457d6b --- /dev/null +++ b/nano/core_test/network_filter.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include + +#include + +TEST (network_filter, unit) +{ + nano::genesis genesis; + nano::network_filter filter (1); + auto one_block = [&filter](std::shared_ptr const & block_a, bool expect_duplicate_a) { + nano::publish message (block_a); + auto bytes (message.to_bytes (false)); + nano::bufferstream stream (bytes->data (), bytes->size ()); + + // First read the header + bool error{ false }; + nano::message_header header (error, stream); + ASSERT_FALSE (error); + + // This validates nano::message_header::size + ASSERT_EQ (bytes->size (), block_a->size (block_a->type ()) + header.size); + + // Now filter the rest of the stream + bool duplicate (filter.apply (bytes->data (), bytes->size () - header.size)); + ASSERT_EQ (expect_duplicate_a, duplicate); + + // Make sure the stream was rewinded correctly + auto block (nano::deserialize_block (stream, header.block_type ())); + ASSERT_NE (nullptr, block); + ASSERT_EQ (*block, *block_a); + }; + one_block (genesis.open, false); + for (int i = 0; i < 10; ++i) + { + one_block (genesis.open, true); + } + auto new_block (std::make_shared (nano::test_genesis_key.pub, genesis.open->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + one_block (new_block, false); + for (int i = 0; i < 10; ++i) + { + one_block (new_block, true); + } + for (int i = 0; i < 100; ++i) + { + one_block (genesis.open, false); + one_block (new_block, false); + } +} + +TEST (network_filter, many) +{ + nano::genesis genesis; + nano::network_filter filter (4); + nano::keypair key1; + for (int i = 0; i < 100; ++i) + { + auto block (std::make_shared (nano::test_genesis_key.pub, genesis.open->hash (), nano::test_genesis_key.pub, nano::genesis_amount - i * 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + + nano::publish message (block); + auto bytes (message.to_bytes (false)); + nano::bufferstream stream (bytes->data (), bytes->size ()); + + // First read the header + bool error{ false }; + nano::message_header header (error, stream); + ASSERT_FALSE (error); + + // This validates nano::message_header::size + ASSERT_EQ (bytes->size (), block->size + header.size); + + // Now filter the rest of the stream + // All blocks should pass through + ASSERT_FALSE (filter.apply (bytes->data (), block->size)); + ASSERT_FALSE (error); + + // Make sure the stream was rewinded correctly + auto deserialized_block (nano::deserialize_block (stream, header.block_type ())); + ASSERT_NE (nullptr, deserialized_block); + ASSERT_EQ (*block, *deserialized_block); + } +} + +TEST (network_filter, clear) +{ + nano::network_filter filter (1); + std::vector bytes1{ 1, 2, 3 }; + std::vector bytes2{ 1 }; + ASSERT_FALSE (filter.apply (bytes1.data (), bytes1.size ())); + ASSERT_TRUE (filter.apply (bytes1.data (), bytes1.size ())); + filter.clear (bytes1.data (), bytes1.size ()); + ASSERT_FALSE (filter.apply (bytes1.data (), bytes1.size ())); + ASSERT_TRUE (filter.apply (bytes1.data (), bytes1.size ())); + filter.clear (bytes2.data (), bytes2.size ()); + ASSERT_TRUE (filter.apply (bytes1.data (), bytes1.size ())); + ASSERT_FALSE (filter.apply (bytes2.data (), bytes2.size ())); +} + +TEST (network_filter, optional_digest) +{ + nano::network_filter filter (1); + std::vector bytes1{ 1, 2, 3 }; + nano::uint128_t digest{ 0 }; + ASSERT_FALSE (filter.apply (bytes1.data (), bytes1.size (), &digest)); + ASSERT_NE (0, digest); + ASSERT_TRUE (filter.apply (bytes1.data (), bytes1.size ())); + filter.clear (digest); + ASSERT_FALSE (filter.apply (bytes1.data (), bytes1.size ())); +} diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 63c52ae66d..8024e2421f 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -1,13 +1,14 @@ #include #include +#include #include #include -#include #include +#include #include -#include +#include #include @@ -20,13 +21,38 @@ void add_required_children_node_config_tree (nano::jsonconfig & tree); TEST (node, stop) { - nano::system system (24000, 1); + nano::system system (1); ASSERT_NE (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.begin ()); system.nodes[0]->stop (); system.io_ctx.run (); ASSERT_TRUE (true); } +TEST (node, work_generate) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::block_hash root{ 1 }; + nano::work_version version{ nano::work_version::work_1 }; + { + auto difficulty = nano::difficulty::from_multiplier (1.5, node.network_params.network.publish_thresholds.base); + auto work = node.work_generate_blocking (version, root, difficulty); + ASSERT_TRUE (work.is_initialized ()); + ASSERT_TRUE (nano::work_difficulty (version, root, *work) >= difficulty); + } + { + auto difficulty = nano::difficulty::from_multiplier (0.5, node.network_params.network.publish_thresholds.base); + boost::optional work; + do + { + work = node.work_generate_blocking (version, root, difficulty); + } while (nano::work_difficulty (version, root, *work) >= node.network_params.network.publish_thresholds.base); + ASSERT_TRUE (work.is_initialized ()); + ASSERT_TRUE (nano::work_difficulty (version, root, *work) >= difficulty); + ASSERT_FALSE (nano::work_difficulty (version, root, *work) >= node.network_params.network.publish_thresholds.base); + } +} + TEST (node, block_store_path_failure) { auto service (boost::make_shared ()); @@ -35,7 +61,7 @@ TEST (node, block_store_path_failure) nano::logging logging; logging.init (path); nano::work_pool work (std::numeric_limits::max ()); - auto node (std::make_shared (*service, 24000, path, alarm, logging, work)); + auto node (std::make_shared (*service, nano::get_available_port (), path, alarm, logging, work)); ASSERT_TRUE (node->wallets.items.empty ()); node->stop (); } @@ -46,7 +72,7 @@ TEST (node, password_fanout) nano::alarm alarm (*service); auto path (nano::unique_path ()); nano::node_config config; - config.peering_port = 24000; + config.peering_port = nano::get_available_port (); config.logging.init (path); nano::work_pool work (std::numeric_limits::max ()); config.password_fanout = 10; @@ -58,7 +84,7 @@ TEST (node, password_fanout) TEST (node, balance) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto transaction (system.nodes[0]->store.tx_begin_write ()); ASSERT_EQ (std::numeric_limits::max (), system.nodes[0]->ledger.account_balance (transaction, nano::test_genesis_key.pub)); @@ -66,7 +92,7 @@ TEST (node, balance) TEST (node, representative) { - nano::system system (24000, 1); + nano::system system (1); auto block1 (system.nodes[0]->rep_block (nano::test_genesis_key.pub)); { auto transaction (system.nodes[0]->store.tx_begin_read ()); @@ -78,7 +104,7 @@ TEST (node, representative) TEST (node, send_unkeyed) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->store.password.value_set (nano::keypair ().prv); @@ -87,7 +113,7 @@ TEST (node, send_unkeyed) TEST (node, send_self) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); @@ -102,7 +128,7 @@ TEST (node, send_self) TEST (node, send_single) { - nano::system system (24000, 2); + nano::system system (2); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (1)->insert_adhoc (key2.prv); @@ -118,7 +144,7 @@ TEST (node, send_single) TEST (node, send_single_observing_peer) { - nano::system system (24000, 3); + nano::system system (3); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (1)->insert_adhoc (key2.prv); @@ -134,7 +160,7 @@ TEST (node, send_single_observing_peer) TEST (node, send_single_many_peers) { - nano::system system (24000, 10); + nano::system system (10); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (1)->insert_adhoc (key2.prv); @@ -156,17 +182,18 @@ TEST (node, send_single_many_peers) TEST (node, send_out_of_order) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); nano::keypair key2; nano::genesis genesis; - nano::send_block send1 (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ())); - nano::send_block send2 (send1.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); - nano::send_block send3 (send2.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); - system.nodes[0]->process_active (std::make_shared (send3)); - system.nodes[0]->process_active (std::make_shared (send2)); - system.nodes[0]->process_active (std::make_shared (send1)); + nano::send_block send1 (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ())); + nano::send_block send2 (send1.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1.hash ())); + nano::send_block send3 (send2.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2.hash ())); + node1.process_active (std::make_shared (send3)); + node1.process_active (std::make_shared (send2)); + node1.process_active (std::make_shared (send1)); system.deadline_set (10s); - while (std::any_of (system.nodes.begin (), system.nodes.end (), [&](std::shared_ptr const & node_a) { return node_a->balance (nano::test_genesis_key.pub) != nano::genesis_amount - system.nodes[0]->config.receive_minimum.number () * 3; })) + while (std::any_of (system.nodes.begin (), system.nodes.end (), [&](std::shared_ptr const & node_a) { return node_a->balance (nano::test_genesis_key.pub) != nano::genesis_amount - node1.config.receive_minimum.number () * 3; })) { ASSERT_NO_ERROR (system.poll ()); } @@ -174,53 +201,55 @@ TEST (node, send_out_of_order) TEST (node, quick_confirm) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::keypair key; - nano::block_hash previous (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto genesis_start_balance (system.nodes[0]->balance (nano::test_genesis_key.pub)); + nano::block_hash previous (node1.latest (nano::test_genesis_key.pub)); + auto genesis_start_balance (node1.balance (nano::test_genesis_key.pub)); system.wallet (0)->insert_adhoc (key.prv); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto send (std::make_shared (previous, key.pub, system.nodes[0]->config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); - system.nodes[0]->process_active (send); + auto send (std::make_shared (previous, key.pub, node1.config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); + node1.process_active (send); system.deadline_set (10s); - while (system.nodes[0]->balance (key.pub).is_zero ()) + while (node1.balance (key.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (system.nodes[0]->balance (nano::test_genesis_key.pub), system.nodes[0]->config.online_weight_minimum.number () + 1); - ASSERT_EQ (system.nodes[0]->balance (key.pub), genesis_start_balance - (system.nodes[0]->config.online_weight_minimum.number () + 1)); + ASSERT_EQ (node1.balance (nano::test_genesis_key.pub), node1.config.online_weight_minimum.number () + 1); + ASSERT_EQ (node1.balance (key.pub), genesis_start_balance - (node1.config.online_weight_minimum.number () + 1)); } TEST (node, node_receive_quorum) { - nano::system system (24000, 1); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto & node1 = *system.add_node (node_flags); nano::keypair key; - nano::block_hash previous (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash previous (node1.latest (nano::test_genesis_key.pub)); system.wallet (0)->insert_adhoc (key.prv); auto send (std::make_shared (previous, key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); - system.nodes[0]->process_active (send); + node1.process_active (send); system.deadline_set (10s); - while (!system.nodes[0]->ledger.block_exists (send->hash ())) + while (!node1.ledger.block_exists (send->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - auto done (false); - while (!done) { - { - nano::lock_guard guard (system.nodes[0]->active.mutex); - auto info (system.nodes[0]->active.roots.find (nano::qualified_root (previous, previous))); - ASSERT_NE (system.nodes[0]->active.roots.end (), info); - done = info->election->confirmation_request_count > 2; - } - ASSERT_NO_ERROR (system.poll ()); + nano::lock_guard guard (node1.active.mutex); + auto info (node1.active.roots.find (nano::qualified_root (previous, previous))); + ASSERT_NE (node1.active.roots.end (), info); + ASSERT_FALSE (info->election->confirmed ()); + ASSERT_EQ (1, info->election->last_votes.size ()); } - nano::system system2 (24001, 1); + nano::system system2; + system2.add_node (node_flags); + system2.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_TRUE (system.nodes[0]->balance (key.pub).is_zero ()); - auto channel (std::make_shared (system.nodes[0]->network.udp_channels, system2.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - system.nodes[0]->network.send_keepalive (channel); - while (system.nodes[0]->balance (key.pub).is_zero ()) + ASSERT_TRUE (node1.balance (key.pub).is_zero ()); + auto channel (std::make_shared (node1.network.udp_channels, system2.nodes[0]->network.endpoint (), node1.network_params.protocol.protocol_version)); + node1.network.send_keepalive (channel); + while (node1.balance (key.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); ASSERT_NO_ERROR (system2.poll ()); @@ -229,20 +258,27 @@ TEST (node, node_receive_quorum) TEST (node, auto_bootstrap) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_udp = false; + auto node0 = system.add_node (config, node_flags); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); - auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, send1); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number ()) + while (node0->balance (key2.pub) != node0->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); ASSERT_FALSE (node1->init_error ()); - auto channel (std::make_shared (node1->network.udp_channels, system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version)); + auto channel (std::make_shared (node1->network.udp_channels, node0->network.endpoint (), node1->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel); node1->start (); system.nodes.push_back (node1); @@ -252,7 +288,7 @@ TEST (node, auto_bootstrap) ASSERT_NO_ERROR (system.poll ()); } system.deadline_set (10s); - while (node1->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number ()) + while (node1->balance (key2.pub) != node0->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -264,35 +300,45 @@ TEST (node, auto_bootstrap) ASSERT_TRUE (node1->ledger.block_exists (send1->hash ())); // Wait block receive system.deadline_set (5s); - while (node1->ledger.block_count_cache < 3) + while (node1->ledger.cache.block_count < 3) { ASSERT_NO_ERROR (system.poll ()); } // Confirmation for all blocks system.deadline_set (5s); - while (node1->ledger.cemented_count < 3) + while (node1->ledger.cache.cemented_count < 3) { ASSERT_NO_ERROR (system.poll ()); } + auto transaction = node1->store.tx_begin_read (); + ASSERT_EQ (node1->ledger.cache.unchecked_count, node1->store.unchecked_count (transaction)); + node1->stop (); } TEST (node, auto_bootstrap_reverse) { - nano::system system (24000, 1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_udp = false; + auto node0 = system.add_node (config, node_flags); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); ASSERT_FALSE (node1->init_error ()); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); - auto channel (std::make_shared (system.nodes[0]->network.udp_channels, node1->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - system.nodes[0]->network.send_keepalive (channel); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node0->config.receive_minimum.number ())); + auto channel (std::make_shared (node0->network.udp_channels, node1->network.endpoint (), node0->network_params.protocol.protocol_version)); + node0->network.send_keepalive (channel); node1->start (); system.nodes.push_back (node1); system.deadline_set (10s); - while (node1->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number ()) + while (node1->balance (key2.pub) != node0->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -301,7 +347,7 @@ TEST (node, auto_bootstrap_reverse) TEST (node, receive_gap) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); ASSERT_EQ (0, node1.gap_cache.size ()); auto block (std::make_shared (5, 1, 2, nano::keypair ().prv, 4, 0)); @@ -314,25 +360,25 @@ TEST (node, receive_gap) TEST (node, merge_peers) { - nano::system system (24000, 1); + nano::system system (1); std::array endpoints; - endpoints.fill (nano::endpoint (boost::asio::ip::address_v6::loopback (), 24000)); - endpoints[0] = nano::endpoint (boost::asio::ip::address_v6::loopback (), 24001); + endpoints.fill (nano::endpoint (boost::asio::ip::address_v6::loopback (), nano::get_available_port ())); + endpoints[0] = nano::endpoint (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); system.nodes[0]->network.merge_peers (endpoints); ASSERT_EQ (0, system.nodes[0]->network.size ()); } TEST (node, search_pending) { - nano::system system (24000, 1); + nano::system system (1); + auto node (system.nodes[0]); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); system.wallet (0)->insert_adhoc (key2.prv); - auto node (system.nodes[0]); ASSERT_FALSE (system.wallet (0)->search_pending ()); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub).is_zero ()) + while (node->balance (key2.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -340,16 +386,16 @@ TEST (node, search_pending) TEST (node, search_pending_same) { - nano::system system (24000, 1); + nano::system system (1); + auto node (system.nodes[0]); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); system.wallet (0)->insert_adhoc (key2.prv); - auto node (system.nodes[0]); ASSERT_FALSE (system.wallet (0)->search_pending ()); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub) != 2 * system.nodes[0]->config.receive_minimum.number ()) + while (node->balance (key2.pub) != 2 * node->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -357,24 +403,24 @@ TEST (node, search_pending_same) TEST (node, search_pending_multiple) { - nano::system system (24000, 1); + nano::system system (1); + auto node (system.nodes[0]); nano::keypair key2; nano::keypair key3; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key3.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key3.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key3.pub, node->config.receive_minimum.number ())); system.deadline_set (10s); - while (system.nodes[0]->balance (key3.pub).is_zero ()) + while (node->balance (key3.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); - ASSERT_NE (nullptr, system.wallet (0)->send_action (key3.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (key3.pub, key2.pub, node->config.receive_minimum.number ())); system.wallet (0)->insert_adhoc (key2.prv); - auto node (system.nodes[0]); ASSERT_FALSE (system.wallet (0)->search_pending ()); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub) != 2 * system.nodes[0]->config.receive_minimum.number ()) + while (node->balance (key2.pub) != 2 * node->config.receive_minimum.number ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -383,7 +429,7 @@ TEST (node, search_pending_multiple) TEST (node, search_pending_confirmed) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node = system.add_node (node_config); nano::keypair key2; @@ -427,21 +473,22 @@ TEST (node, search_pending_confirmed) TEST (node, unlock_search) { - nano::system system (24000, 1); + nano::system system (1); + auto node (system.nodes[0]); nano::keypair key2; - nano::uint128_t balance (system.nodes[0]->balance (nano::test_genesis_key.pub)); + nano::uint128_t balance (node->balance (nano::test_genesis_key.pub)); { auto transaction (system.wallet (0)->wallets.tx_begin_write ()); system.wallet (0)->store.rekey (transaction, ""); } system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) == balance) + while (node->balance (nano::test_genesis_key.pub) == balance) { ASSERT_NO_ERROR (system.poll ()); } - while (!system.nodes[0]->active.empty ()) + while (!node->active.empty ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -450,13 +497,12 @@ TEST (node, unlock_search) nano::lock_guard lock (system.wallet (0)->store.mutex); system.wallet (0)->store.password.value_set (nano::keypair ().prv); } - auto node (system.nodes[0]); { auto transaction (system.wallet (0)->wallets.tx_begin_write ()); ASSERT_FALSE (system.wallet (0)->enter_password (transaction, "")); } system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub).is_zero ()) + while (node->balance (key2.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -464,20 +510,23 @@ TEST (node, unlock_search) TEST (node, connect_after_junk) { - nano::system system (24000, 1); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto node0 = system.add_node (node_flags); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); std::vector junk_buffer; junk_buffer.push_back (0); - auto channel1 (std::make_shared (node1->network.udp_channels, system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version)); + auto channel1 (std::make_shared (node1->network.udp_channels, node0->network.endpoint (), node1->network_params.protocol.protocol_version)); channel1->send_buffer (nano::shared_const_buffer (std::move (junk_buffer)), nano::stat::detail::bulk_pull, [](boost::system::error_code const &, size_t) {}); system.deadline_set (10s); - while (system.nodes[0]->stats.count (nano::stat::type::error) == 0) + while (node0->stats.count (nano::stat::type::error) == 0) { ASSERT_NO_ERROR (system.poll ()); } node1->start (); system.nodes.push_back (node1); - auto channel2 (std::make_shared (node1->network.udp_channels, system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version)); + auto channel2 (std::make_shared (node1->network.udp_channels, node0->network.endpoint (), node1->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel2); system.deadline_set (10s); while (node1->network.empty ()) @@ -495,7 +544,7 @@ TEST (node, working) TEST (node, price) { - nano::system system (24000, 1); + nano::system system (1); auto price1 (system.nodes[0]->price (nano::Gxrb_ratio, 1)); ASSERT_EQ (nano::node::price_max * 100.0, price1); auto price2 (system.nodes[0]->price (nano::Gxrb_ratio * int(nano::node::free_cutoff + 1), 1)); @@ -508,7 +557,7 @@ TEST (node, price) TEST (node, confirm_locked) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto transaction (system.wallet (0)->wallets.tx_begin_read ()); system.wallet (0)->enter_password (transaction, "1"); @@ -531,7 +580,7 @@ TEST (node_config, serialization) config1.callback_address = "test"; config1.callback_port = 10; config1.callback_target = "test"; - config1.lmdb_max_dbs = 256; + config1.deprecated_lmdb_max_dbs = 256; nano::jsonconfig tree; config1.serialize_json (tree); nano::logging logging2; @@ -548,7 +597,7 @@ TEST (node_config, serialization) ASSERT_NE (config2.callback_address, config1.callback_address); ASSERT_NE (config2.callback_port, config1.callback_port); ASSERT_NE (config2.callback_target, config1.callback_target); - ASSERT_NE (config2.lmdb_max_dbs, config1.lmdb_max_dbs); + ASSERT_NE (config2.deprecated_lmdb_max_dbs, config1.deprecated_lmdb_max_dbs); ASSERT_FALSE (tree.get_optional ("epoch_block_link")); ASSERT_FALSE (tree.get_optional ("epoch_block_signer")); @@ -566,7 +615,7 @@ TEST (node_config, serialization) ASSERT_EQ (config2.callback_address, config1.callback_address); ASSERT_EQ (config2.callback_port, config1.callback_port); ASSERT_EQ (config2.callback_target, config1.callback_target); - ASSERT_EQ (config2.lmdb_max_dbs, config1.lmdb_max_dbs); + ASSERT_EQ (config2.deprecated_lmdb_max_dbs, config1.deprecated_lmdb_max_dbs); } TEST (node_config, v1_v2_upgrade) @@ -790,7 +839,7 @@ TEST (node_config, v17_values) tree.put ("use_memory_pools", true); tree.put ("confirmation_history_size", 2048); tree.put ("active_elections_size", 50000); - tree.put ("bandwidth_limit", 5242880); + tree.put ("bandwidth_limit", 10485760); tree.put ("conf_height_processor_batch_min_time", 0); } @@ -798,7 +847,7 @@ TEST (node_config, v17_values) ASSERT_FALSE (upgraded); ASSERT_EQ (config.tcp_io_timeout.count (), 1); ASSERT_EQ (config.pow_sleep_interval.count (), 0); - ASSERT_EQ (config.external_address, boost::asio::ip::address_v6::from_string ("::1")); + ASSERT_EQ (config.external_address, "::1"); ASSERT_EQ (config.external_port, 0); ASSERT_EQ (config.tcp_incoming_connections_max, 1); ASSERT_FALSE (config.diagnostics_config.txn_tracking.enable); @@ -808,7 +857,7 @@ TEST (node_config, v17_values) ASSERT_TRUE (config.use_memory_pools); ASSERT_EQ (config.confirmation_history_size, 2048); ASSERT_EQ (config.active_elections_size, 50000); - ASSERT_EQ (config.bandwidth_limit, 5242880); + ASSERT_EQ (config.bandwidth_limit, 10485760); ASSERT_EQ (config.conf_height_processor_batch_min_time.count (), 0); // Check config is correct with other values @@ -838,7 +887,7 @@ TEST (node_config, v17_values) ASSERT_FALSE (upgraded); ASSERT_EQ (config.tcp_io_timeout.count (), std::numeric_limits::max () - 100); ASSERT_EQ (config.pow_sleep_interval.count (), std::numeric_limits::max () - 100); - ASSERT_EQ (config.external_address, boost::asio::ip::address_v6::from_string ("::ffff:192.168.1.1")); + ASSERT_EQ (config.external_address, "::ffff:192.168.1.1"); ASSERT_EQ (config.external_port, std::numeric_limits::max () - 1); ASSERT_EQ (config.tcp_incoming_connections_max, std::numeric_limits::max ()); ASSERT_EQ (config.vote_generator_delay.count (), std::numeric_limits::max () - 100); @@ -1052,7 +1101,6 @@ TEST (json, backup) ASSERT_EQ ("created", object1.text); /** Returns 'dir' if backup file cannot be found */ - // clang-format off auto get_backup_path = [&dir]() { for (fs::directory_iterator itr (dir); itr != fs::directory_iterator (); ++itr) { @@ -1067,7 +1115,6 @@ TEST (json, backup) auto get_file_count = [&dir]() { return std::count_if (boost::filesystem::directory_iterator (dir), boost::filesystem::directory_iterator (), static_cast (boost::filesystem::is_regular_file)); }; - // clang-format on // There should only be the original file in this directory ASSERT_EQ (get_file_count (), 1); @@ -1090,11 +1137,33 @@ TEST (json, backup) TEST (node_flags, disable_tcp_realtime) { - nano::system system (24000, 1); - auto node1 = system.nodes[0]; + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto node1 = system.add_node (node_flags); + node_flags.disable_tcp_realtime = true; + auto node2 = system.add_node (node_flags); + ASSERT_EQ (1, node1->network.size ()); + auto list1 (node1->network.list (2)); + ASSERT_EQ (node2->network.endpoint (), list1[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list1[0]->get_type ()); + ASSERT_EQ (1, node2->network.size ()); + auto list2 (node2->network.list (2)); + ASSERT_EQ (node1->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list2[0]->get_type ()); +} + +TEST (node_flags, disable_tcp_realtime_and_bootstrap_listener) +{ + nano::system system; nano::node_flags node_flags; + node_flags.disable_udp = false; + auto node1 = system.add_node (node_flags); node_flags.disable_tcp_realtime = true; - auto node2 = system.add_node (nano::node_config (24001, system.logging), node_flags); + node_flags.disable_bootstrap_listener = true; + auto node2 = system.add_node (node_flags); + ASSERT_EQ (nano::tcp_endpoint (boost::asio::ip::address_v6::loopback (), 0), node2->bootstrap.endpoint ()); + ASSERT_NE (nano::endpoint (boost::asio::ip::address_v6::loopback (), 0), node2->network.endpoint ()); ASSERT_EQ (1, node1->network.size ()); auto list1 (node1->network.list (2)); ASSERT_EQ (node2->network.endpoint (), list1[0]->get_endpoint ()); @@ -1105,15 +1174,18 @@ TEST (node_flags, disable_tcp_realtime) ASSERT_EQ (nano::transport::transport_type::udp, list2[0]->get_type ()); } +// UDP is disabled by default TEST (node_flags, disable_udp) { - nano::system system (24000, 1); - auto node1 = system.nodes[0]; + nano::system system; nano::node_flags node_flags; - node_flags.disable_udp = true; - auto node2 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, nano::node_config (24001, system.logging), system.work, node_flags)); + node_flags.disable_udp = false; + auto node1 = system.add_node (node_flags); + auto node2 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, nano::node_config (nano::get_available_port (), system.logging), system.work)); system.nodes.push_back (node2); node2->start (); + ASSERT_EQ (nano::endpoint (boost::asio::ip::address_v6::loopback (), 0), node2->network.udp_channels.get_local_endpoint ()); + ASSERT_NE (nano::endpoint (boost::asio::ip::address_v6::loopback (), 0), node2->network.endpoint ()); // Send UDP message auto channel (std::make_shared (node1->network.udp_channels, node2->network.endpoint (), node2->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel); @@ -1143,7 +1215,7 @@ TEST (node, fork_publish) { std::weak_ptr node0; { - nano::system system (24000, 1); + nano::system system (1); node0 = system.nodes[0]; auto & node1 (*system.nodes[0]); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -1166,7 +1238,6 @@ TEST (node, fork_publish) // Wait until the genesis rep activated & makes vote while (election->last_votes_size () != 2) { - node1.block_processor.generator.add (send1->hash ()); node1.vote_processor.flush (); ASSERT_NO_ERROR (system.poll ()); } @@ -1184,9 +1255,33 @@ TEST (node, fork_publish) ASSERT_TRUE (node0.expired ()); } +// Tests that an election gets started correctly from a fork +TEST (node, fork_publish_inactive) +{ + nano::system system (1); + nano::genesis genesis; + nano::keypair key1; + nano::keypair key2; + auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, send1->block_work ())); + auto & node (*system.nodes[0]); + ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + ASSERT_EQ (nano::process_result::fork, node.process_local (send2).code); + auto election (node.active.election (send1->qualified_root ())); + ASSERT_NE (election, nullptr); + { + nano::lock_guard guard (node.active.mutex); + auto & blocks (election->blocks); + ASSERT_NE (blocks.end (), blocks.find (send1->hash ())); + ASSERT_NE (blocks.end (), blocks.find (send2->hash ())); + ASSERT_NE (election->status.winner, send1); + ASSERT_NE (election->status.winner, send2); + } +} + TEST (node, fork_keep) { - nano::system system (24000, 2); + nano::system system (2); auto & node1 (*system.nodes[0]); auto & node2 (*system.nodes[1]); ASSERT_EQ (1, node1.network.size ()); @@ -1215,10 +1310,10 @@ TEST (node, fork_keep) ASSERT_EQ (1, votes1->last_votes.size ()); lock.unlock (); { - auto transaction0 (system.nodes[0]->store.tx_begin_read ()); - auto transaction1 (system.nodes[1]->store.tx_begin_read ()); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction0, send1->hash ())); - ASSERT_TRUE (system.nodes[1]->store.block_exists (transaction1, send1->hash ())); + auto transaction0 (node1.store.tx_begin_read ()); + auto transaction1 (node2.store.tx_begin_read ()); + ASSERT_TRUE (node1.store.block_exists (transaction0, send1->hash ())); + ASSERT_TRUE (node2.store.block_exists (transaction1, send1->hash ())); } system.deadline_set (1.5min); // Wait until the genesis rep makes a vote @@ -1226,20 +1321,20 @@ TEST (node, fork_keep) { ASSERT_NO_ERROR (system.poll ()); } - auto transaction0 (system.nodes[0]->store.tx_begin_read ()); - auto transaction1 (system.nodes[1]->store.tx_begin_read ()); + auto transaction0 (node1.store.tx_begin_read ()); + auto transaction1 (node2.store.tx_begin_read ()); // The vote should be in agreement with what we already have. lock.lock (); auto winner (*votes1->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); ASSERT_EQ (nano::genesis_amount - 100, winner.first); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction0, send1->hash ())); - ASSERT_TRUE (system.nodes[1]->store.block_exists (transaction1, send1->hash ())); + ASSERT_TRUE (node1.store.block_exists (transaction0, send1->hash ())); + ASSERT_TRUE (node2.store.block_exists (transaction1, send1->hash ())); } TEST (node, fork_flip) { - nano::system system (24000, 2); + nano::system system (2); auto & node1 (*system.nodes[0]); auto & node2 (*system.nodes[1]); ASSERT_EQ (1, node1.network.size ()); @@ -1271,11 +1366,11 @@ TEST (node, fork_flip) ASSERT_EQ (1, votes1->last_votes.size ()); lock.unlock (); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); + auto transaction (node1.store.tx_begin_read ()); ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); } { - auto transaction (system.nodes[1]->store.tx_begin_read ()); + auto transaction (node2.store.tx_begin_read ()); ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); } system.deadline_set (10s); @@ -1285,8 +1380,8 @@ TEST (node, fork_flip) ASSERT_NO_ERROR (system.poll ()); done = node2.ledger.block_exists (publish1.block->hash ()); } - auto transaction1 (system.nodes[0]->store.tx_begin_read ()); - auto transaction2 (system.nodes[1]->store.tx_begin_read ()); + auto transaction1 (node1.store.tx_begin_read ()); + auto transaction2 (node2.store.tx_begin_read ()); lock.lock (); auto winner (*votes1->tally ().begin ()); ASSERT_EQ (*publish1.block, *winner.second); @@ -1298,77 +1393,99 @@ TEST (node, fork_flip) TEST (node, fork_multi_flip) { - nano::system system (24000, 2); - auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); - ASSERT_EQ (1, node1.network.size ()); - nano::keypair key1; - nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - nano::publish publish1 (send1); - nano::keypair key2; - auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - nano::publish publish2 (send2); - auto send3 (std::make_shared (publish2.block->hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (publish2.block->hash ()))); - nano::publish publish3 (send3); - node1.network.process_message (publish1, node1.network.udp_channels.create (node1.network.endpoint ())); - node1.block_processor.flush (); - node2.network.process_message (publish2, node2.network.udp_channels.create (node2.network.endpoint ())); - node2.network.process_message (publish3, node2.network.udp_channels.create (node2.network.endpoint ())); - node2.block_processor.flush (); - ASSERT_EQ (1, node1.active.size ()); - ASSERT_EQ (2, node2.active.size ()); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - node1.network.process_message (publish2, node1.network.udp_channels.create (node1.network.endpoint ())); - node1.network.process_message (publish3, node1.network.udp_channels.create (node1.network.endpoint ())); - node1.block_processor.flush (); - node2.network.process_message (publish1, node2.network.udp_channels.create (node2.network.endpoint ())); - node2.block_processor.flush (); - nano::unique_lock lock (node2.active.mutex); - auto conflict (node2.active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); - ASSERT_NE (node2.active.roots.end (), conflict); - auto votes1 (conflict->election); - ASSERT_NE (nullptr, votes1); - ASSERT_EQ (1, votes1->last_votes.size ()); - lock.unlock (); - { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); - } - { - auto transaction (system.nodes[1]->store.tx_begin_read ()); - ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); - ASSERT_TRUE (node2.store.block_exists (transaction, publish3.block->hash ())); - } - system.deadline_set (10s); - auto done (false); - while (!done) + std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; + for (auto & type : types) { - ASSERT_NO_ERROR (system.poll ()); - done = node2.ledger.block_exists (publish1.block->hash ()); + nano::system system; + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node1 (*system.add_node (node_config, node_flags, type)); + node_config.peering_port = nano::get_available_port (); + auto & node2 (*system.add_node (node_config, node_flags, type)); + ASSERT_EQ (1, node1.network.size ()); + nano::keypair key1; + nano::genesis genesis; + auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + nano::publish publish1 (send1); + nano::keypair key2; + auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + nano::publish publish2 (send2); + auto send3 (std::make_shared (publish2.block->hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (publish2.block->hash ()))); + nano::publish publish3 (send3); + node1.network.process_message (publish1, node1.network.udp_channels.create (node1.network.endpoint ())); + node2.network.process_message (publish2, node2.network.udp_channels.create (node2.network.endpoint ())); + node2.network.process_message (publish3, node2.network.udp_channels.create (node2.network.endpoint ())); + node1.block_processor.flush (); + node2.block_processor.flush (); + ASSERT_EQ (1, node1.active.size ()); + ASSERT_EQ (1, node2.active.size ()); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node1.network.process_message (publish2, node1.network.udp_channels.create (node1.network.endpoint ())); + node1.network.process_message (publish3, node1.network.udp_channels.create (node1.network.endpoint ())); + node1.block_processor.flush (); + node2.network.process_message (publish1, node2.network.udp_channels.create (node2.network.endpoint ())); + node2.block_processor.flush (); + nano::unique_lock lock (node2.active.mutex); + auto conflict (node2.active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); + ASSERT_NE (node2.active.roots.end (), conflict); + auto votes1 (conflict->election); + ASSERT_NE (nullptr, votes1); + ASSERT_EQ (1, votes1->last_votes.size ()); + lock.unlock (); + { + auto transaction (node1.store.tx_begin_read ()); + ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); + } + { + auto transaction (node2.store.tx_begin_read ()); + ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); + ASSERT_TRUE (node2.store.block_exists (transaction, publish3.block->hash ())); + } + system.deadline_set (10s); + auto done (false); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + done = node2.ledger.block_exists (publish1.block->hash ()); + } + auto transaction1 (node1.store.tx_begin_read ()); + auto transaction2 (node2.store.tx_begin_read ()); + lock.lock (); + auto winner (*votes1->tally ().begin ()); + ASSERT_EQ (*publish1.block, *winner.second); + ASSERT_EQ (nano::genesis_amount - 100, winner.first); + ASSERT_TRUE (node1.store.block_exists (transaction1, publish1.block->hash ())); + ASSERT_TRUE (node2.store.block_exists (transaction2, publish1.block->hash ())); + ASSERT_FALSE (node2.store.block_exists (transaction2, publish2.block->hash ())); + ASSERT_FALSE (node2.store.block_exists (transaction2, publish3.block->hash ())); } - auto transaction1 (system.nodes[0]->store.tx_begin_read ()); - auto transaction2 (system.nodes[1]->store.tx_begin_read ()); - lock.lock (); - auto winner (*votes1->tally ().begin ()); - ASSERT_EQ (*publish1.block, *winner.second); - ASSERT_EQ (nano::genesis_amount - 100, winner.first); - ASSERT_TRUE (node1.store.block_exists (transaction1, publish1.block->hash ())); - ASSERT_TRUE (node2.store.block_exists (transaction2, publish1.block->hash ())); - ASSERT_FALSE (node2.store.block_exists (transaction2, publish2.block->hash ())); - ASSERT_FALSE (node2.store.block_exists (transaction2, publish3.block->hash ())); } // Blocks that are no longer actively being voted on should be able to be evicted through bootstrapping. // This could happen if a fork wasn't resolved before the process previously shut down TEST (node, fork_bootstrap_flip) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); - auto & node1 (*system0.nodes[0]); - auto & node2 (*system1.nodes[0]); + nano::system system0; + nano::system system1; + nano::node_config config0 (nano::get_available_port (), system0.logging); + config0.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_udp = false; + auto & node1 (*system0.add_node (config0, node_flags)); + nano::node_config config1 (nano::get_available_port (), system1.logging); + config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node2 (*system1.add_node (config1, node_flags)); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (system0.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest (node1.latest (nano::test_genesis_key.pub)); nano::keypair key1; auto send1 (std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system0.work.generate (latest))); nano::keypair key2; @@ -1404,7 +1521,7 @@ TEST (node, fork_bootstrap_flip) TEST (node, fork_open) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::keypair key1; nano::genesis genesis; @@ -1413,21 +1530,36 @@ TEST (node, fork_open) auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); node1.network.process_message (publish1, channel1); node1.block_processor.flush (); + { + auto election = node1.active.election (publish1.block->qualified_root ()); + nano::lock_guard guard (node1.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (3s, node1.active.empty () && node1.block_confirmed (publish1.block->hash ())); auto open1 (std::make_shared (publish1.block->hash (), 1, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub))); nano::publish publish2 (open1); node1.network.process_message (publish2, channel1); node1.block_processor.flush (); + ASSERT_EQ (1, node1.active.size ()); auto open2 (std::make_shared (publish1.block->hash (), 2, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub))); nano::publish publish3 (open2); - ASSERT_EQ (2, node1.active.size ()); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); node1.network.process_message (publish3, channel1); node1.block_processor.flush (); + { + auto election = node1.active.election (publish3.block->qualified_root ()); + nano::lock_guard guard (node1.active.mutex); + ASSERT_EQ (2, election->blocks.size ()); + ASSERT_EQ (publish2.block->hash (), election->status.winner->hash ()); + ASSERT_FALSE (election->confirmed ()); + } + ASSERT_TRUE (node1.block (publish2.block->hash ())); + ASSERT_FALSE (node1.block (publish3.block->hash ())); } TEST (node, fork_open_flip) { - nano::system system (24000, 2); + nano::system system (2); auto & node1 (*system.nodes[0]); auto & node2 (*system.nodes[1]); ASSERT_EQ (1, node1.network.size ()); @@ -1436,8 +1568,10 @@ TEST (node, fork_open_flip) nano::keypair rep1; nano::keypair rep2; auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + // A copy is necessary to avoid data races during ledger processing, which sets the sideband + auto send1_copy (std::make_shared (*send1)); node1.process_active (send1); - node2.process_active (send1); + node2.process_active (send1_copy); // We should be keeping this block auto open1 (std::make_shared (send1->hash (), rep1.pub, key1.pub, key1.prv, key1.pub, *system.work.generate (key1.pub))); // This block should be evicted @@ -1446,9 +1580,11 @@ TEST (node, fork_open_flip) // node1 gets copy that will remain node1.process_active (open1); node1.block_processor.flush (); + node1.block_confirm (open1); // node2 gets copy that will be evicted node2.process_active (open2); node2.block_processor.flush (); + node2.block_confirm (open2); ASSERT_EQ (2, node1.active.size ()); ASSERT_EQ (2, node2.active.size ()); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -1473,8 +1609,8 @@ TEST (node, fork_open_flip) ASSERT_NO_ERROR (system.poll ()); } node2.block_processor.flush (); - auto transaction1 (system.nodes[0]->store.tx_begin_read ()); - auto transaction2 (system.nodes[1]->store.tx_begin_read ()); + auto transaction1 (node1.store.tx_begin_read ()); + auto transaction2 (node2.store.tx_begin_read ()); lock.lock (); auto winner (*votes1->tally ().begin ()); ASSERT_EQ (*open1, *winner.second); @@ -1486,7 +1622,7 @@ TEST (node, fork_open_flip) TEST (node, coherent_observer) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); node1.observers.blocks.add ([&node1](nano::election_status const & status_a, nano::account const &, nano::uint128_t const &, bool) { auto transaction (node1.store.tx_begin_read ()); @@ -1499,7 +1635,7 @@ TEST (node, coherent_observer) TEST (node, fork_no_vote_quorum) { - nano::system system (24000, 3); + nano::system system (3); auto & node1 (*system.nodes[0]); auto & node2 (*system.nodes[1]); auto & node3 (*system.nodes[2]); @@ -1535,7 +1671,7 @@ TEST (node, fork_no_vote_quorum) std::vector buffer; { nano::vectorstream stream (buffer); - confirm.serialize (stream); + confirm.serialize (stream, false); } nano::transport::channel_udp channel (node2.network.udp_channels, node3.network.endpoint (), node1.network_params.protocol.protocol_version); channel.send_buffer (nano::shared_const_buffer (std::move (buffer)), nano::stat::detail::confirm_ack); @@ -1551,7 +1687,7 @@ TEST (node, fork_no_vote_quorum) // Disabled because it sometimes takes way too long (but still eventually finishes) TEST (node, DISABLED_fork_pre_confirm) { - nano::system system (24000, 3); + nano::system system (3); auto & node0 (*system.nodes[0]); auto & node1 (*system.nodes[1]); auto & node2 (*system.nodes[2]); @@ -1605,14 +1741,15 @@ TEST (node, DISABLED_fork_pre_confirm) // Sometimes hangs on the bootstrap_initiator.bootstrap call TEST (node, DISABLED_fork_stale) { - nano::system system1 (24000, 1); + nano::system system1 (1); system1.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::system system2 (24001, 1); + nano::system system2 (1); auto & node1 (*system1.nodes[0]); auto & node2 (*system2.nodes[0]); node2.bootstrap_initiator.bootstrap (node1.network.endpoint ()); - auto channel (std::make_shared (node2.network.udp_channels, node1.network.endpoint (), node2.network_params.protocol.protocol_version)); - node2.rep_crawler.response (channel, nano::test_genesis_key.pub, nano::genesis_amount); + std::shared_ptr channel (std::make_shared (node2.network.udp_channels, node1.network.endpoint (), node2.network_params.protocol.protocol_version)); + auto vote = std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, std::vector ()); + node2.rep_crawler.response (channel, vote); nano::genesis genesis; nano::keypair key1; nano::keypair key2; @@ -1652,16 +1789,24 @@ TEST (node, broadcast_elected) std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 3, type); - auto node0 (system.nodes[0]); - auto node1 (system.nodes[1]); - auto node2 (system.nodes[2]); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 = system.add_node (node_config, node_flags, type); + node_config.peering_port = nano::get_available_port (); + auto node1 = system.add_node (node_config, node_flags, type); + node_config.peering_port = nano::get_available_port (); + auto node2 = system.add_node (node_config, node_flags, type); nano::keypair rep_big; nano::keypair rep_small; nano::keypair rep_other; - //std::cerr << "Big: " << rep_big.pub.to_account () << std::endl; - //std::cerr << "Small: " << rep_small.pub.to_account () << std::endl; - //std::cerr << "Other: " << rep_other.pub.to_account () << std::endl; { auto transaction0 (node0->store.tx_begin_write ()); auto transaction1 (node1->store.tx_begin_write ()); @@ -1697,19 +1842,34 @@ TEST (node, broadcast_elected) ASSERT_EQ (nano::process_result::progress, node1->ledger.process (transaction1, open_other).code); ASSERT_EQ (nano::process_result::progress, node2->ledger.process (transaction2, open_other).code); } + // Confirm blocks to allow voting + for (auto & node : system.nodes) + { + auto block (node->block (node->latest (nano::test_genesis_key.pub))); + ASSERT_NE (nullptr, block); + node->block_confirm (block); + auto election (node->active.election (block->qualified_root ())); + ASSERT_NE (nullptr, election); + { + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (5s, 4 == node->ledger.cache.cemented_count) + } + system.wallet (0)->insert_adhoc (rep_big.prv); system.wallet (1)->insert_adhoc (rep_small.prv); system.wallet (2)->insert_adhoc (rep_other.prv); auto fork0 (std::make_shared (node2->latest (nano::test_genesis_key.pub), rep_small.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node0->work_generate_blocking (*fork0); + // A copy is necessary to avoid data races during ledger processing, which sets the sideband + auto fork0_copy (std::make_shared (*fork0)); node0->process_active (fork0); - node1->process_active (fork0); + node1->process_active (fork0_copy); auto fork1 (std::make_shared (node2->latest (nano::test_genesis_key.pub), rep_big.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node0->work_generate_blocking (*fork1); system.wallet (2)->insert_adhoc (rep_small.prv); node2->process_active (fork1); - //std::cerr << "fork0: " << fork_hash.to_string () << std::endl; - //std::cerr << "fork1: " << fork1.hash ().to_string () << std::endl; system.deadline_set (10s); while (!node0->ledger.block_exists (fork0->hash ()) || !node1->ledger.block_exists (fork0->hash ())) { @@ -1724,7 +1884,7 @@ TEST (node, broadcast_elected) ASSERT_NO_ERROR (ec); } system.deadline_set (5s); - while (node1->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out) == 0) + while (node1->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out) == 0) { ASSERT_NO_ERROR (system.poll ()); } @@ -1734,46 +1894,42 @@ TEST (node, broadcast_elected) TEST (node, rep_self_vote) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.online_weight_minimum = std::numeric_limits::max (); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node0 = system.add_node (node_config); nano::keypair rep_big; + nano::send_block fund_big (node0->ledger.latest (node0->store.tx_begin_read (), nano::test_genesis_key.pub), rep_big.pub, nano::uint128_t ("0xb0000000000000000000000000000000"), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::open_block open_big (fund_big.hash (), rep_big.pub, rep_big.pub, rep_big.prv, rep_big.pub, 0); + node0->work_generate_blocking (fund_big); + node0->work_generate_blocking (open_big); + ASSERT_EQ (nano::process_result::progress, node0->process (fund_big).code); + ASSERT_EQ (nano::process_result::progress, node0->process (open_big).code); + // Confirm both blocks, allowing voting on the upcoming block + node0->block_confirm (node0->block (open_big.hash ())); { - auto transaction0 (node0->store.tx_begin_write ()); - nano::send_block fund_big (node0->ledger.latest (transaction0, nano::test_genesis_key.pub), rep_big.pub, nano::uint128_t ("0xb0000000000000000000000000000000"), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); - nano::open_block open_big (fund_big.hash (), rep_big.pub, rep_big.pub, rep_big.prv, rep_big.pub, 0); - node0->work_generate_blocking (fund_big); - node0->work_generate_blocking (open_big); - ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction0, fund_big).code); - ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction0, open_big).code); + auto election = node0->active.election (open_big.qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node0->active.mutex); + election->confirm_once (); } + system.wallet (0)->insert_adhoc (rep_big.prv); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_EQ (system.wallet (0)->wallets.reps_count, 2); + ASSERT_EQ (system.wallet (0)->wallets.reps ().voting, 2); auto block0 (std::make_shared (node0->latest (nano::test_genesis_key.pub), rep_big.pub, nano::uint128_t ("0x60000000000000000000000000000000"), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node0->work_generate_blocking (*block0); ASSERT_EQ (nano::process_result::progress, node0->process (*block0).code); auto & active (node0->active); - active.start (block0); - nano::unique_lock lock (active.mutex); - auto existing (active.roots.find (block0->qualified_root ())); - ASSERT_NE (active.roots.end (), existing); - auto election (existing->election); - lock.unlock (); + auto election1 = active.insert (block0); system.deadline_set (1s); // Wait until representatives are activated & make vote - while (election->last_votes_size () != 3) + while (election1.election->last_votes_size () != 3) { - lock.lock (); - auto transaction (node0->store.tx_begin_read ()); - election->compute_rep_votes (transaction); - lock.unlock (); - node0->vote_processor.flush (); ASSERT_NO_ERROR (system.poll ()); } - lock.lock (); - auto & rep_votes (election->last_votes); + nano::unique_lock lock (active.mutex); + auto & rep_votes (election1.election->last_votes); ASSERT_NE (rep_votes.end (), rep_votes.find (nano::test_genesis_key.pub)); ASSERT_NE (rep_votes.end (), rep_votes.find (rep_big.pub)); } @@ -1781,16 +1937,16 @@ TEST (node, rep_self_vote) // Bootstrapping shouldn't republish the blocks to the network. TEST (node, DISABLED_bootstrap_no_publish) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); + nano::system system0 (1); + nano::system system1 (1); auto node0 (system0.nodes[0]); auto node1 (system1.nodes[0]); nano::keypair key0; // node0 knows about send0 but node1 doesn't. - nano::send_block send0 (system0.nodes[0]->latest (nano::test_genesis_key.pub), key0.pub, 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::send_block send0 (node0->latest (nano::test_genesis_key.pub), key0.pub, 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); { auto transaction (node0->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system0.nodes[0]->ledger.process (transaction, send0).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, send0).code); } ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); @@ -1810,17 +1966,21 @@ TEST (node, DISABLED_bootstrap_no_publish) // Check that an outgoing bootstrap request can push blocks TEST (node, bootstrap_bulk_push) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); - auto node0 (system0.nodes[0]); - auto node1 (system1.nodes[0]); + nano::system system0; + nano::system system1; + nano::node_config config0 (nano::get_available_port (), system0.logging); + config0.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system0.add_node (config0)); + nano::node_config config1 (nano::get_available_port (), system1.logging); + config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node1 (system1.add_node (config1)); nano::keypair key0; // node0 knows about send0 but node1 doesn't. - nano::send_block send0 (system0.nodes[0]->latest (nano::test_genesis_key.pub), key0.pub, 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::send_block send0 (node0->latest (nano::test_genesis_key.pub), key0.pub, 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); node0->work_generate_blocking (send0); { auto transaction (node0->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system0.nodes[0]->ledger.process (transaction, send0).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, send0).code); } ASSERT_FALSE (node0->bootstrap_initiator.in_progress ()); ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); @@ -1839,46 +1999,51 @@ TEST (node, bootstrap_bulk_push) // Bootstrapping a forked open block should succeed. TEST (node, bootstrap_fork_open) { - nano::system system0; - nano::node_config node_config (24000, system0.logging); + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node0 = system0.add_node (node_config); - node_config.peering_port = 24001; - auto node1 = system0.add_node (node_config); - system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto node0 = system.add_node (node_config); + node_config.peering_port = nano::get_available_port (); + auto node1 = system.add_node (node_config); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key0; - nano::send_block send0 (system0.nodes[0]->latest (nano::test_genesis_key.pub), key0.pub, nano::genesis_amount - 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::send_block send0 (node0->latest (nano::test_genesis_key.pub), key0.pub, nano::genesis_amount - 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); nano::open_block open0 (send0.hash (), 1, key0.pub, key0.prv, key0.pub, 0); nano::open_block open1 (send0.hash (), 2, key0.pub, key0.prv, key0.pub, 0); node0->work_generate_blocking (send0); node0->work_generate_blocking (open0); node0->work_generate_blocking (open1); + // Both know about send0 + ASSERT_EQ (nano::process_result::progress, node0->process (send0).code); + ASSERT_EQ (nano::process_result::progress, node1->process (send0).code); + // Confirm send0 to allow starting and voting on the following blocks + for (auto node : system.nodes) { - auto transaction0 (node0->store.tx_begin_write ()); - auto transaction1 (node1->store.tx_begin_write ()); - // Both know about send0 - ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction0, send0).code); - ASSERT_EQ (nano::process_result::progress, node1->ledger.process (transaction1, send0).code); - // They disagree about open0/open1 - ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction0, open0).code); - ASSERT_EQ (nano::process_result::progress, node1->ledger.process (transaction1, open1).code); + node->block_confirm (node->block (node->latest (nano::test_genesis_key.pub))); + { + auto election = node->active.election (send0.qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (2s, node->active.empty ()); } + ASSERT_TIMELY (3s, node0->block_confirmed (send0.hash ())); + // They disagree about open0/open1 + ASSERT_EQ (nano::process_result::progress, node0->process (open0).code); + ASSERT_EQ (nano::process_result::progress, node1->process (open1).code); + ASSERT_FALSE (node1->ledger.block_exists (open0.hash ())); ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); ASSERT_TRUE (node1->active.empty ()); - system0.deadline_set (10s); - while (node1->ledger.block_exists (open1.hash ())) - { - // Poll until the outvoted block is evicted. - ASSERT_NO_ERROR (system0.poll ()); - } + ASSERT_TIMELY (10s, !node1->ledger.block_exists (open1.hash ()) && node1->ledger.block_exists (open0.hash ())); } // Unconfirmed blocks from bootstrap should be confirmed TEST (node, bootstrap_confirm_frontiers) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); + nano::system system0 (1); + nano::system system1 (1); auto node0 (system0.nodes[0]); auto node1 (system0.nodes[0]); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -1929,7 +2094,7 @@ TEST (node, bootstrap_confirm_frontiers) // Test that if we create a block that isn't confirmed, we sync. TEST (node, DISABLED_unconfirmed_send) { - nano::system system (24000, 2); + nano::system system (2); auto & node0 (*system.nodes[0]); auto & node1 (*system.nodes[1]); auto wallet0 (system.wallet (0)); @@ -1960,7 +2125,7 @@ TEST (node, DISABLED_unconfirmed_send) // Test that nodes can track nodes that have rep weight for priority broadcasting TEST (node, rep_list) { - nano::system system (24000, 2); + nano::system system (2); auto & node1 (*system.nodes[1]); auto wallet0 (system.wallet (0)); auto wallet1 (system.wallet (1)); @@ -1988,73 +2153,200 @@ TEST (node, rep_list) TEST (node, rep_weight) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); - - node.network.udp_channels.insert (nano::endpoint (boost::asio::ip::address_v6::loopback (), 24001), 0); + nano::genesis genesis; + nano::keypair keypair1; + nano::keypair keypair2; + nano::block_builder builder; + auto amount_pr (node.minimum_principal_weight () + 100); + auto amount_not_pr (node.minimum_principal_weight () - 100); + std::shared_ptr block1 = builder + .state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - amount_not_pr) + .link (keypair1.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .build (); + std::shared_ptr block2 = builder + .state () + .account (keypair1.pub) + .previous (0) + .representative (keypair1.pub) + .balance (amount_not_pr) + .link (block1->hash ()) + .sign (keypair1.prv, keypair1.pub) + .work (*system.work.generate (keypair1.pub)) + .build (); + std::shared_ptr block3 = builder + .state () + .account (nano::test_genesis_key.pub) + .previous (block1->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - amount_not_pr - amount_pr) + .link (keypair2.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (block1->hash ())) + .build (); + std::shared_ptr block4 = builder + .state () + .account (keypair2.pub) + .previous (0) + .representative (keypair2.pub) + .balance (amount_pr) + .link (block3->hash ()) + .sign (keypair2.prv, keypair2.pub) + .work (*system.work.generate (keypair2.pub)) + .build (); + { + auto transaction = node.store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block1).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block2).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block3).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block4).code); + } + node.network.udp_channels.insert (nano::endpoint (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()), 0); ASSERT_TRUE (node.rep_crawler.representatives (1).empty ()); - nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), 24000); - nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), 24002); - nano::endpoint endpoint2 (boost::asio::ip::address_v6::loopback (), 24003); - auto channel0 (std::make_shared (node.network.udp_channels, endpoint0, node.network_params.protocol.protocol_version)); - auto channel1 (std::make_shared (node.network.udp_channels, endpoint1, node.network_params.protocol.protocol_version)); - auto channel2 (std::make_shared (node.network.udp_channels, endpoint2, node.network_params.protocol.protocol_version)); - nano::amount amount100 (100); - nano::amount amount50 (50); - node.network.udp_channels.insert (endpoint2, node.network_params.protocol.protocol_version); + nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); + nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); + nano::endpoint endpoint2 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); + std::shared_ptr channel0 (std::make_shared (node.network.udp_channels, endpoint0, node.network_params.protocol.protocol_version)); + std::shared_ptr channel1 (std::make_shared (node.network.udp_channels, endpoint1, node.network_params.protocol.protocol_version)); + std::shared_ptr channel2 (std::make_shared (node.network.udp_channels, endpoint2, node.network_params.protocol.protocol_version)); node.network.udp_channels.insert (endpoint0, node.network_params.protocol.protocol_version); node.network.udp_channels.insert (endpoint1, node.network_params.protocol.protocol_version); - nano::keypair keypair1; - nano::keypair keypair2; - node.rep_crawler.response (channel0, keypair1.pub, amount100); - node.rep_crawler.response (channel1, keypair2.pub, amount50); - ASSERT_EQ (2, node.rep_crawler.representative_count ()); + node.network.udp_channels.insert (endpoint2, node.network_params.protocol.protocol_version); + auto vote0 = std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open); + auto vote1 = std::make_shared (keypair1.pub, keypair1.prv, 0, genesis.open); + auto vote2 = std::make_shared (keypair2.pub, keypair2.prv, 0, genesis.open); + node.rep_crawler.response (channel0, vote0); + node.rep_crawler.response (channel1, vote1); + node.rep_crawler.response (channel2, vote2); + system.deadline_set (5s); + while (node.rep_crawler.representative_count () != 2) + { + ASSERT_NO_ERROR (system.poll ()); + } // Make sure we get the rep with the most weight first auto reps (node.rep_crawler.representatives (1)); ASSERT_EQ (1, reps.size ()); - ASSERT_EQ (100, reps[0].weight.number ()); - ASSERT_EQ (keypair1.pub, reps[0].account); + ASSERT_EQ (node.balance (nano::test_genesis_key.pub), reps[0].weight.number ()); + ASSERT_EQ (nano::test_genesis_key.pub, reps[0].account); ASSERT_EQ (*channel0, reps[0].channel_ref ()); + ASSERT_TRUE (node.rep_crawler.is_pr (*channel0)); + ASSERT_FALSE (node.rep_crawler.is_pr (*channel1)); + ASSERT_TRUE (node.rep_crawler.is_pr (*channel2)); } TEST (node, rep_remove) { - nano::system system (24000, 1); - auto & node (*system.nodes[0]); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto & node = *system.add_node (node_flags); + nano::genesis genesis; + nano::keypair keypair1; + nano::keypair keypair2; + nano::block_builder builder; + std::shared_ptr block1 = builder + .state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - node.minimum_principal_weight () * 2) + .link (keypair1.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .build (); + std::shared_ptr block2 = builder + .state () + .account (keypair1.pub) + .previous (0) + .representative (keypair1.pub) + .balance (node.minimum_principal_weight () * 2) + .link (block1->hash ()) + .sign (keypair1.prv, keypair1.pub) + .work (*system.work.generate (keypair1.pub)) + .build (); + std::shared_ptr block3 = builder + .state () + .account (nano::test_genesis_key.pub) + .previous (block1->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - node.minimum_principal_weight () * 4) + .link (keypair2.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (block1->hash ())) + .build (); + std::shared_ptr block4 = builder + .state () + .account (keypair2.pub) + .previous (0) + .representative (keypair2.pub) + .balance (node.minimum_principal_weight () * 2) + .link (block3->hash ()) + .sign (keypair2.prv, keypair2.pub) + .work (*system.work.generate (keypair2.pub)) + .build (); + { + auto transaction = node.store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block1).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block2).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block3).code); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *block4).code); + } // Add inactive UDP representative channel - nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), 24001); - auto channel0 (std::make_shared (node.network.udp_channels, endpoint0, node.network_params.protocol.protocol_version)); + nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); + std::shared_ptr channel0 (std::make_shared (node.network.udp_channels, endpoint0, node.network_params.protocol.protocol_version)); nano::amount amount100 (100); node.network.udp_channels.insert (endpoint0, node.network_params.protocol.protocol_version); - nano::keypair keypair1; - node.rep_crawler.response (channel0, keypair1.pub, amount100); - ASSERT_EQ (1, node.rep_crawler.representative_count ()); + auto vote1 = std::make_shared (keypair1.pub, keypair1.prv, 0, genesis.open); + node.rep_crawler.response (channel0, vote1); + system.deadline_set (5s); + while (node.rep_crawler.representative_count () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } auto reps (node.rep_crawler.representatives (1)); ASSERT_EQ (1, reps.size ()); - ASSERT_EQ (100, reps[0].weight.number ()); + ASSERT_EQ (node.minimum_principal_weight () * 2, reps[0].weight.number ()); ASSERT_EQ (keypair1.pub, reps[0].account); ASSERT_EQ (*channel0, reps[0].channel_ref ()); + // This UDP channel is not reachable and should timeout + while (node.rep_crawler.representative_count () != 0) + { + ASSERT_NO_ERROR (system.poll ()); + } // Add working representative - auto node1 = system.add_node (nano::node_config (24002, system.logging)); + auto node1 = system.add_node (nano::node_config (nano::get_available_port (), system.logging)); system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); auto channel1 (node.network.find_channel (node1->network.endpoint ())); ASSERT_NE (nullptr, channel1); - node.rep_crawler.response (channel1, nano::test_genesis_key.pub, nano::genesis_amount); - ASSERT_EQ (2, node.rep_crawler.representative_count ()); + auto vote2 = std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open); + node.rep_crawler.response (channel1, vote2); + while (node.rep_crawler.representative_count () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } // Add inactive TCP representative channel - auto node2 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, nano::node_config (24003, system.logging), system.work)); + auto node2 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, nano::node_config (nano::get_available_port (), system.logging), system.work)); std::atomic done{ false }; std::weak_ptr node_w (node.shared ()); - node.network.tcp_channels.start_tcp (node2->network.endpoint (), [node_w, &done](std::shared_ptr channel2) { + auto vote3 = std::make_shared (keypair2.pub, keypair2.prv, 0, genesis.open); + node.network.tcp_channels.start_tcp (node2->network.endpoint (), [node_w, &done, &vote3, &system](std::shared_ptr channel2) { if (auto node_l = node_w.lock ()) { - nano::keypair keypair2; - node_l->rep_crawler.response (channel2, keypair2.pub, nano::Gxrb_ratio); - ASSERT_EQ (3, node_l->rep_crawler.representative_count ()); + node_l->rep_crawler.response (channel2, vote3); + while (node_l->rep_crawler.representative_count () != 2) + { + ASSERT_NO_ERROR (system.poll ()); + } done = true; } }); - system.deadline_set (10s); while (!done) { ASSERT_NO_ERROR (system.poll ()); @@ -2073,12 +2365,33 @@ TEST (node, rep_remove) ASSERT_EQ (node1->network.endpoint (), list[0]->get_endpoint ()); } +TEST (node, rep_connection_close) +{ + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + // Add working representative (node 2) + system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + system.deadline_set (10s); + while (node1.rep_crawler.representative_count () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + node2.stop (); + // Remove representative with closed channel + system.deadline_set (10s); + while (node1.rep_crawler.representative_count () != 0) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + // Test that nodes can disable representative voting TEST (node, no_voting) { - nano::system system (24000, 1); + nano::system system (1); auto & node0 (*system.nodes[0]); - nano::node_config node_config (24001, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; system.add_node (node_config); @@ -2100,52 +2413,52 @@ TEST (node, no_voting) TEST (node, send_callback) { - nano::system system (24000, 1); + nano::system system (1); + auto & node0 (*system.nodes[0]); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); - system.nodes[0]->config.callback_address = "localhost"; - system.nodes[0]->config.callback_port = 8010; - system.nodes[0]->config.callback_target = "/"; - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + node0.config.callback_address = "localhost"; + node0.config.callback_port = 8010; + node0.config.callback_target = "/"; + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node0.config.receive_minimum.number ())); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub).is_zero ()) + while (node0.balance (key2.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (std::numeric_limits::max () - node0.config.receive_minimum.number (), node0.balance (nano::test_genesis_key.pub)); } // Check that votes get replayed back to nodes if they sent an old sequence number. // This helps representatives continue from their last sequence number if their node is reinitialized and the old sequence number is lost TEST (node, vote_replay) { - nano::system system (24000, 2); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::keypair key; - auto open (std::make_shared (0, 1, key.pub, key.prv, key.pub, 0)); - system.nodes[0]->work_generate_blocking (*open); + nano::genesis genesis; for (auto i (0); i < 11000; ++i) { - auto transaction (system.nodes[1]->store.tx_begin_read ()); - auto vote (system.nodes[1]->store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, open)); + auto transaction (node1.store.tx_begin_read ()); + auto vote (node1.store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, genesis.open)); } + auto node2 = system.add_node (); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - nano::lock_guard lock (system.nodes[0]->store.get_cache_mutex ()); - auto vote (system.nodes[0]->store.vote_current (transaction, nano::test_genesis_key.pub)); + auto transaction (node2->store.tx_begin_read ()); + nano::lock_guard lock (node2->store.get_cache_mutex ()); + auto vote (node2->store.vote_current (transaction, nano::test_genesis_key.pub)); ASSERT_EQ (nullptr, vote); } - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); - ASSERT_NE (nullptr, block); + system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); auto done (false); system.deadline_set (20s); while (!done) { auto ec = system.poll (); - auto transaction (system.nodes[0]->store.tx_begin_read ()); - nano::lock_guard lock (system.nodes[0]->store.get_cache_mutex ()); - auto vote (system.nodes[0]->store.vote_current (transaction, nano::test_genesis_key.pub)); + auto transaction (node2->store.tx_begin_read ()); + nano::lock_guard lock (node2->store.get_cache_mutex ()); + auto vote (node2->store.vote_current (transaction, nano::test_genesis_key.pub)); done = vote && (vote->sequence >= 10000); ASSERT_NO_ERROR (ec); } @@ -2153,7 +2466,7 @@ TEST (node, vote_replay) TEST (node, balance_observer) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); std::atomic balances (0); nano::keypair key; @@ -2179,33 +2492,41 @@ TEST (node, balance_observer) } } -// ASSERT_NE (nullptr, attempt) sometimes fails -TEST (node, DISABLED_bootstrap_connection_scaling) +TEST (node, bootstrap_connection_scaling) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); - node1.bootstrap_initiator.bootstrap (); - auto attempt (node1.bootstrap_initiator.current_attempt ()); - ASSERT_NE (nullptr, attempt); - ASSERT_EQ (34, attempt->target_connections (25000)); - ASSERT_EQ (4, attempt->target_connections (0)); - ASSERT_EQ (64, attempt->target_connections (50000)); - ASSERT_EQ (64, attempt->target_connections (10000000000)); + ASSERT_EQ (34, node1.bootstrap_initiator.connections->target_connections (5000, 1)); + ASSERT_EQ (4, node1.bootstrap_initiator.connections->target_connections (0, 1)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 1)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 1)); + ASSERT_EQ (32, node1.bootstrap_initiator.connections->target_connections (5000, 0)); + ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (0, 0)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 0)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 0)); + ASSERT_EQ (36, node1.bootstrap_initiator.connections->target_connections (5000, 2)); + ASSERT_EQ (8, node1.bootstrap_initiator.connections->target_connections (0, 2)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 2)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 2)); node1.config.bootstrap_connections = 128; - ASSERT_EQ (64, attempt->target_connections (0)); - ASSERT_EQ (64, attempt->target_connections (50000)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (0, 1)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 1)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (0, 2)); + ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 2)); node1.config.bootstrap_connections_max = 256; - ASSERT_EQ (128, attempt->target_connections (0)); - ASSERT_EQ (256, attempt->target_connections (50000)); + ASSERT_EQ (128, node1.bootstrap_initiator.connections->target_connections (0, 1)); + ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (50000, 1)); + ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (0, 2)); + ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (50000, 2)); node1.config.bootstrap_connections_max = 0; - ASSERT_EQ (1, attempt->target_connections (0)); - ASSERT_EQ (1, attempt->target_connections (50000)); + ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (0, 1)); + ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (50000, 1)); } // Test stat counting at both type and detail levels TEST (node, stat_counting) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); node1.stats.add (nano::stat::type::ledger, nano::stat::dir::in, 1); node1.stats.add (nano::stat::type::ledger, nano::stat::dir::in, 5); @@ -2220,17 +2541,18 @@ TEST (node, stat_counting) TEST (node, online_reps) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); // 1 sample of minimum weight - ASSERT_EQ (system.nodes[0]->config.online_weight_minimum, system.nodes[0]->online_reps.online_stake ()); + ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.online_stake ()); auto vote (std::make_shared ()); - system.nodes[0]->online_reps.observe (nano::test_genesis_key.pub); + node1.online_reps.observe (nano::test_genesis_key.pub); // 1 minimum, 1 maximum - system.nodes[0]->online_reps.sample (); - ASSERT_EQ (nano::genesis_amount, system.nodes[0]->online_reps.online_stake ()); + node1.online_reps.sample (); + ASSERT_EQ (nano::genesis_amount, node1.online_reps.online_stake ()); // 2 minimum, 1 maximum - system.nodes[0]->online_reps.sample (); - ASSERT_EQ (system.nodes[0]->config.online_weight_minimum, system.nodes[0]->online_reps.online_stake ()); + node1.online_reps.sample (); + ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.online_stake ()); } TEST (node, block_confirm) @@ -2238,33 +2560,44 @@ TEST (node, block_confirm) std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 2, type); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system (2, type, node_flags); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::genesis genesis; nano::keypair key; system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - system.nodes[0]->block_processor.add (send1, nano::seconds_since_epoch ()); - system.nodes[1]->block_processor.add (send1, nano::seconds_since_epoch ()); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (genesis.hash ()))); + // A copy is necessary to avoid data races during ledger processing, which sets the sideband + auto send1_copy (std::make_shared (*send1)); + node1.block_processor.add (send1, nano::seconds_since_epoch ()); + node2.block_processor.add (send1_copy, nano::seconds_since_epoch ()); system.deadline_set (std::chrono::seconds (5)); - while (!system.nodes[0]->ledger.block_exists (send1->hash ()) || !system.nodes[1]->ledger.block_exists (send1->hash ())) + while (!node1.ledger.block_exists (send1->hash ()) || !node2.ledger.block_exists (send1_copy->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_TRUE (system.nodes[0]->ledger.block_exists (send1->hash ())); - ASSERT_TRUE (system.nodes[1]->ledger.block_exists (send1->hash ())); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (send1->hash ()))); + ASSERT_TRUE (node1.ledger.block_exists (send1->hash ())); + ASSERT_TRUE (node2.ledger.block_exists (send1_copy->hash ())); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (send1->hash ()))); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *send2).code); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send2).code); } { - auto transaction (system.nodes[1]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[1]->ledger.process (transaction, *send2).code); + auto transaction (node2.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node2.ledger.process (transaction, *send2).code); } - system.nodes[0]->block_confirm (send2); - ASSERT_TRUE (system.nodes[0]->active.list_confirmed ().empty ()); + node1.block_confirm (send2); + ASSERT_TRUE (node1.active.list_recently_cemented ().empty ()); system.deadline_set (10s); - while (system.nodes[0]->active.list_confirmed ().empty ()) + while (node1.active.list_recently_cemented ().empty ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -2273,7 +2606,7 @@ TEST (node, block_confirm) TEST (node, block_arrival) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); ASSERT_EQ (0, node.block_arrival.arrival.size ()); nano::block_hash hash1 (1); @@ -2288,13 +2621,13 @@ TEST (node, block_arrival) TEST (node, block_arrival_size) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); auto time (std::chrono::steady_clock::now () - nano::block_arrival::arrival_time_min - std::chrono::seconds (5)); nano::block_hash hash (0); for (auto i (0); i < nano::block_arrival::arrival_size_min * 2; ++i) { - node.block_arrival.arrival.insert (nano::block_arrival_info{ time, hash }); + node.block_arrival.arrival.push_back (nano::block_arrival_info{ time, hash }); ++hash.qwords[0]; } ASSERT_EQ (nano::block_arrival::arrival_size_min * 2, node.block_arrival.arrival.size ()); @@ -2304,13 +2637,13 @@ TEST (node, block_arrival_size) TEST (node, block_arrival_time) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); auto time (std::chrono::steady_clock::now ()); nano::block_hash hash (0); for (auto i (0); i < nano::block_arrival::arrival_size_min * 2; ++i) { - node.block_arrival.arrival.insert (nano::block_arrival_info{ time, hash }); + node.block_arrival.arrival.push_back (nano::block_arrival_info{ time, hash }); ++hash.qwords[0]; } ASSERT_EQ (nano::block_arrival::arrival_size_min * 2, node.block_arrival.arrival.size ()); @@ -2320,45 +2653,39 @@ TEST (node, block_arrival_time) TEST (node, confirm_quorum) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); // Put greater than online_weight_minimum in pending so quorum can't be reached - nano::amount new_balance (system.nodes[0]->config.online_weight_minimum.number () - nano::Gxrb_ratio); - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, new_balance, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); + nano::amount new_balance (node1.config.online_weight_minimum.number () - nano::Gxrb_ratio); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, new_balance, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (genesis.hash ()))); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *send1).code); + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); } system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, new_balance.number ()); system.deadline_set (10s); - while (system.nodes[0]->active.empty ()) - { - ASSERT_NO_ERROR (system.poll ()); - } - auto done (false); - while (!done) + while (node1.active.empty ()) { - ASSERT_FALSE (system.nodes[0]->active.empty ()); - { - nano::lock_guard guard (system.nodes[0]->active.mutex); - auto info (system.nodes[0]->active.roots.find (nano::qualified_root (send1->hash (), send1->hash ()))); - ASSERT_NE (system.nodes[0]->active.roots.end (), info); - done = info->election->confirmation_request_count > 2; - } ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (0, system.nodes[0]->balance (nano::test_genesis_key.pub)); + nano::lock_guard guard (node1.active.mutex); + auto info (node1.active.roots.find (nano::qualified_root (send1->hash (), send1->hash ()))); + ASSERT_NE (node1.active.roots.end (), info); + ASSERT_FALSE (info->election->confirmed ()); + ASSERT_EQ (1, info->election->last_votes.size ()); + ASSERT_EQ (0, node1.balance (nano::test_genesis_key.pub)); } TEST (node, local_votes_cache) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.receive_minimum = nano::genesis_amount; auto & node (*system.add_node (node_config)); nano::genesis genesis; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (send1->hash ()))); auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (send2->hash ()))); @@ -2367,19 +2694,50 @@ TEST (node, local_votes_cache) ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *send1).code); ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *send2).code); } + // Confirm blocks to allow voting + node.block_confirm (send2); + { + auto election = node.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (3s, node.ledger.cache.cemented_count == 3); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::confirm_req message1 (send1); nano::confirm_req message2 (send2); auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.network.process_message (message1, channel); + auto wait_vote_sequence = [&node, &system](unsigned sequence) { + std::shared_ptr current_vote; + system.deadline_set (5s); + while (current_vote == nullptr || current_vote->sequence < sequence) + { + { + nano::lock_guard lock (node.store.get_cache_mutex ()); + auto transaction (node.store.tx_begin_read ()); + current_vote = node.store.vote_current (transaction, nano::test_genesis_key.pub); + } + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (sequence, current_vote->sequence); + }; + wait_vote_sequence (1); + node.network.process_message (message2, channel); + wait_vote_sequence (2); for (auto i (0); i < 100; ++i) { node.network.process_message (message1, channel); node.network.process_message (message2, channel); } + for (int i = 0; i < 4; ++i) + { + system.poll (node.aggregator.max_delay); + } + // Make sure a new vote was not generated { nano::lock_guard lock (node.store.get_cache_mutex ()); - auto transaction (node.store.tx_begin_read ()); - auto current_vote (node.store.vote_current (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (current_vote->sequence, 2); + ASSERT_EQ (2, node.store.vote_current (node.store.tx_begin_read (), nano::test_genesis_key.pub)->sequence); } // Max cache { @@ -2391,42 +2749,100 @@ TEST (node, local_votes_cache) { node.network.process_message (message3, channel); } + for (int i = 0; i < 4; ++i) { - nano::lock_guard lock (node.store.get_cache_mutex ()); - auto transaction (node.store.tx_begin_read ()); - auto current_vote (node.store.vote_current (transaction, nano::test_genesis_key.pub)); - ASSERT_EQ (current_vote->sequence, 3); + system.poll (node.aggregator.max_delay); } - ASSERT_TRUE (node.votes_cache.find (send1->hash ()).empty ()); + wait_vote_sequence (3); + ASSERT_TIMELY (3s, node.votes_cache.find (send1->hash ()).empty ()); ASSERT_FALSE (node.votes_cache.find (send2->hash ()).empty ()); ASSERT_FALSE (node.votes_cache.find (send3->hash ()).empty ()); } -TEST (node, local_votes_cache_generate_new_vote) +TEST (node, local_votes_cache_batch) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node (*system.add_node (node_config)); + ASSERT_GE (node.network_params.voting.max_cache, 2); nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (send1->hash ()))); + std::vector> blocks{ genesis.open, send1 }; + std::vector> batch{ { genesis.open->hash (), genesis.open->root () }, { send1->hash (), send1->root () } }; { auto transaction (node.store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *send1).code); } + nano::confirm_req message (batch); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + // Generates and sends one vote for both hashes which is then cached + node.network.process_message (message, channel); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) < 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_FALSE (node.votes_cache.find (genesis.open->hash ()).empty ()); + ASSERT_FALSE (node.votes_cache.find (send1->hash ()).empty ()); + // Only one confirm_ack should be sent if all hashes are part of the same vote + node.network.process_message (message, channel); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) < 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + // Test when votes are different + node.votes_cache.remove (genesis.open->hash ()); + node.votes_cache.remove (send1->hash ()); + node.network.process_message (nano::confirm_req (genesis.open->hash (), genesis.open->root ()), channel); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) < 3) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (3, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + node.network.process_message (nano::confirm_req (send1->hash (), send1->root ()), channel); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) < 4) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (4, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + // There are two different votes, so both should be sent in response + node.network.process_message (message, channel); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) < 6) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (6, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} + +TEST (node, local_votes_cache_generate_new_vote) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); // Repsond with cached vote - nano::confirm_req message1 (send1); + nano::confirm_req message1 (genesis.open); auto channel (node.network.udp_channels.create (node.network.endpoint ())); - for (auto i (0); i < 100; ++i) + node.network.process_message (message1, channel); + system.deadline_set (3s); + while (node.votes_cache.find (genesis.open->hash ()).empty ()) { - node.network.process_message (message1, channel); + ASSERT_NO_ERROR (system.poll ()); } - auto votes1 (node.votes_cache.find (send1->hash ())); + auto votes1 (node.votes_cache.find (genesis.open->hash ())); ASSERT_EQ (1, votes1.size ()); ASSERT_EQ (1, votes1[0]->blocks.size ()); - ASSERT_EQ (send1->hash (), boost::get (votes1[0]->blocks[0])); + ASSERT_EQ (genesis.open->hash (), boost::get (votes1[0]->blocks[0])); { nano::lock_guard lock (node.store.get_cache_mutex ()); auto transaction (node.store.tx_begin_read ()); @@ -2434,20 +2850,20 @@ TEST (node, local_votes_cache_generate_new_vote) ASSERT_EQ (current_vote->sequence, 1); ASSERT_EQ (current_vote, votes1[0]); } - { - auto transaction (node.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node.ledger.process (transaction, *send2).code); - } - // Generate new vote for request with 2 hashes (one of hashes is cached) - std::vector> roots_hashes{ std::make_pair (send1->hash (), send1->root ()), std::make_pair (send2->hash (), send2->root ()) }; + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + // One of the hashes is cached + std::vector> roots_hashes{ std::make_pair (genesis.open->hash (), genesis.open->root ()), std::make_pair (send1->hash (), send1->root ()) }; nano::confirm_req message2 (roots_hashes); - for (auto i (0); i < 100; ++i) + node.network.process_message (message2, channel); + system.deadline_set (3s); + while (node.votes_cache.find (send1->hash ()).empty ()) { - node.network.process_message (message2, channel); + ASSERT_NO_ERROR (system.poll ()); } auto votes2 (node.votes_cache.find (send1->hash ())); ASSERT_EQ (1, votes2.size ()); - ASSERT_EQ (2, votes2[0]->blocks.size ()); + ASSERT_EQ (1, votes2[0]->blocks.size ()); { nano::lock_guard lock (node.store.get_cache_mutex ()); auto transaction (node.store.tx_begin_read ()); @@ -2455,71 +2871,101 @@ TEST (node, local_votes_cache_generate_new_vote) ASSERT_EQ (current_vote->sequence, 2); ASSERT_EQ (current_vote, votes2[0]); } + ASSERT_FALSE (node.votes_cache.find (genesis.open->hash ()).empty ()); ASSERT_FALSE (node.votes_cache.find (send1->hash ()).empty ()); - ASSERT_FALSE (node.votes_cache.find (send2->hash ()).empty ()); + // First generated + again cached + new generated + ASSERT_EQ (3, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} + +// Tests that the max cache size is inversely proportional to the number of voting accounts +TEST (node, local_votes_cache_size) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.vote_minimum = 0; // wallet will pick up the second account as voting even if unopened + auto & node (*system.add_node (node_config)); + ASSERT_EQ (node.network_params.voting.max_cache, 2); // effective cache size is 1 with 2 voting accounts + nano::keypair key; + auto & wallet (*system.wallet (0)); + wallet.insert_adhoc (nano::test_genesis_key.prv); + wallet.insert_adhoc (nano::keypair ().prv); + ASSERT_EQ (2, node.wallets.reps ().voting); + auto transaction (node.store.tx_begin_read ()); + auto vote1 (node.store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, { nano::genesis_hash })); + nano::block_hash hash (1); + auto vote2 (node.store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, { hash })); + node.votes_cache.add (vote1); + node.votes_cache.add (vote2); + auto existing2 (node.votes_cache.find (hash)); + ASSERT_EQ (1, existing2.size ()); + ASSERT_EQ (vote2, existing2.front ()); + ASSERT_EQ (0, node.votes_cache.find (nano::genesis_hash).size ()); } TEST (node, vote_republish) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - system.nodes[0]->process_active (send1); + auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node1.process_active (send1); system.deadline_set (5s); - while (!system.nodes[1]->block (send1->hash ())) + while (!node2.block (send1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - system.nodes[0]->active.publish (send2); + node1.active.publish (send2); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2)); - ASSERT_TRUE (system.nodes[0]->active.active (*send1)); - ASSERT_TRUE (system.nodes[1]->active.active (*send1)); - system.nodes[0]->vote_processor.vote (vote, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - while (!system.nodes[0]->block (send2->hash ())) + ASSERT_TRUE (node1.active.active (*send1)); + ASSERT_TRUE (node2.active.active (*send1)); + node1.vote_processor.vote (vote, std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); + while (!node1.block (send2->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - while (!system.nodes[1]->block (send2->hash ())) + while (!node2.block (send2->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_FALSE (system.nodes[0]->block (send1->hash ())); - ASSERT_FALSE (system.nodes[1]->block (send1->hash ())); + ASSERT_FALSE (node1.block (send1->hash ())); + ASSERT_FALSE (node2.block (send1->hash ())); system.deadline_set (5s); - while (system.nodes[1]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number () * 2) + while (node2.balance (key2.pub) != node1.config.receive_minimum.number () * 2) { ASSERT_NO_ERROR (system.poll ()); } - while (system.nodes[0]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number () * 2) + while (node1.balance (key2.pub) != node1.config.receive_minimum.number () * 2) { ASSERT_NO_ERROR (system.poll ()); } } +namespace nano +{ TEST (node, vote_by_hash_bundle) { // Keep max_hashes above system to ensure it is kept in scope as votes can be added during system destruction std::atomic max_hashes{ 0 }; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key1; system.wallet (0)->insert_adhoc (key1.prv); - system.nodes[0]->observers.vote.add ([&max_hashes](std::shared_ptr vote_a, std::shared_ptr channel_a) { + system.nodes[0]->observers.vote.add ([&max_hashes](std::shared_ptr vote_a, std::shared_ptr, nano::vote_code) { if (vote_a->blocks.size () > max_hashes) { max_hashes = vote_a->blocks.size (); } }); - nano::genesis genesis; for (int i = 1; i <= 200; i++) { - auto send (std::make_shared (genesis.hash (), key1.pub, std::numeric_limits::max () - (system.nodes[0]->config.receive_minimum.number () * i), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - system.nodes[0]->block_confirm (send); + system.nodes[0]->active.generator.add (nano::genesis_hash); } // Verify that bundling occurs. While reaching 12 should be common on most hardware in release mode, @@ -2530,47 +2976,57 @@ TEST (node, vote_by_hash_bundle) ASSERT_NO_ERROR (system.poll ()); } } +} TEST (node, vote_by_hash_republish) { std::vector types{ nano::transport::transport_type::tcp, nano::transport::transport_type::udp }; for (auto & type : types) { - nano::system system (24000, 2, type); + nano::node_flags node_flags; + if (type == nano::transport::transport_type::udp) + { + node_flags.disable_tcp_realtime = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_udp = false; + } + nano::system system (2, type, node_flags); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - system.nodes[0]->process_active (send1); + auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node1.process_active (send1); system.deadline_set (5s); - while (!system.nodes[1]->block (send1->hash ())) + while (!node2.block (send1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - system.nodes[0]->active.publish (send2); + node1.active.publish (send2); std::vector vote_blocks; vote_blocks.push_back (send2->hash ()); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - ASSERT_TRUE (system.nodes[0]->active.active (*send1)); - ASSERT_TRUE (system.nodes[1]->active.active (*send1)); - system.nodes[0]->vote_processor.vote (vote, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - while (!system.nodes[0]->block (send2->hash ())) + ASSERT_TRUE (node1.active.active (*send1)); + ASSERT_TRUE (node2.active.active (*send1)); + node1.vote_processor.vote (vote, std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); + while (!node1.block (send2->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - while (!system.nodes[1]->block (send2->hash ())) + while (!node2.block (send2->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_FALSE (system.nodes[0]->block (send1->hash ())); - ASSERT_FALSE (system.nodes[1]->block (send1->hash ())); + ASSERT_FALSE (node1.block (send1->hash ())); + ASSERT_FALSE (node2.block (send1->hash ())); system.deadline_set (5s); - while (system.nodes[1]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number () * 2) + while (node2.balance (key2.pub) != node1.config.receive_minimum.number () * 2) { ASSERT_NO_ERROR (system.poll ()); } - while (system.nodes[0]->balance (key2.pub) != system.nodes[0]->config.receive_minimum.number () * 2) + while (node1.balance (key2.pub) != node1.config.receive_minimum.number () * 2) { ASSERT_NO_ERROR (system.poll ()); } @@ -2579,44 +3035,46 @@ TEST (node, vote_by_hash_republish) TEST (node, vote_by_hash_epoch_block_republish) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto epoch1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, system.nodes[0]->ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - system.nodes[0]->process_active (send1); + auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto epoch1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node1.process_active (send1); system.deadline_set (5s); - while (!system.nodes[1]->block (send1->hash ())) + while (!node2.block (send1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - system.nodes[0]->active.publish (epoch1); + node1.active.publish (epoch1); std::vector vote_blocks; vote_blocks.push_back (epoch1->hash ()); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - ASSERT_TRUE (system.nodes[0]->active.active (*send1)); - ASSERT_TRUE (system.nodes[1]->active.active (*send1)); - system.nodes[0]->vote_processor.vote (vote, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - while (!system.nodes[0]->block (epoch1->hash ())) + ASSERT_TRUE (node1.active.active (*send1)); + ASSERT_TRUE (node2.active.active (*send1)); + node1.vote_processor.vote (vote, std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); + while (!node1.block (epoch1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - while (!system.nodes[1]->block (epoch1->hash ())) + while (!node2.block (epoch1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_FALSE (system.nodes[0]->block (send1->hash ())); - ASSERT_FALSE (system.nodes[1]->block (send1->hash ())); + ASSERT_FALSE (node1.block (send1->hash ())); + ASSERT_FALSE (node2.block (send1->hash ())); } TEST (node, epoch_conflict_confirm) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node0 = system.add_node (node_config); - node_config.peering_port = 24001; + node_config.peering_port = nano::get_available_port (); auto node1 = system.add_node (node_config); nano::keypair key; nano::genesis genesis; @@ -2624,34 +3082,40 @@ TEST (node, epoch_conflict_confirm) auto send (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); auto open (std::make_shared (key.pub, 0, key.pub, 1, send->hash (), key.prv, key.pub, *system.work.generate (key.pub))); auto change (std::make_shared (key.pub, open->hash (), key.pub, 1, 0, key.prv, key.pub, *system.work.generate (open->hash ()))); - auto epoch (std::make_shared (change->root (), 0, 0, 0, node0->ledger.epoch_link (nano::epoch::epoch_1), epoch_signer.prv, epoch_signer.pub, *system.work.generate (open->hash ()))); - { - auto transaction (node0->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node0->block_processor.process_one (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node0->block_processor.process_one (transaction, open).code); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, open->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send->hash ()))); + auto epoch_open (std::make_shared (change->root (), 0, 0, 0, node0->ledger.epoch_link (nano::epoch::epoch_1), epoch_signer.prv, epoch_signer.pub, *system.work.generate (open->hash ()))); + ASSERT_EQ (nano::process_result::progress, node1->process (*send).code); + ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); + ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); + // Confirm block in node1 to allow generating votes + node1->block_confirm (open); + { + auto election (node1->active.election (open->qualified_root ())); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node1->active.mutex); + election->confirm_once (); } + ASSERT_TIMELY (3s, node1->block_confirmed (open->hash ())); { - auto transaction (node1->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node1->block_processor.process_one (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node1->block_processor.process_one (transaction, open).code); + nano::block_post_events events; + auto transaction (node0->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node0->block_processor.process_one (transaction, events, send).code); + ASSERT_EQ (nano::process_result::progress, node0->block_processor.process_one (transaction, events, send2).code); + ASSERT_EQ (nano::process_result::progress, node0->block_processor.process_one (transaction, events, open).code); } node0->process_active (change); - node0->process_active (epoch); - node0->block_processor.flush (); + node0->process_active (epoch_open); system.deadline_set (5s); - while (!node0->block (change->hash ()) || !node0->block (epoch->hash ()) || !node1->block (change->hash ()) || !node1->block (epoch->hash ())) - { - ASSERT_NO_ERROR (system.poll ()); - } - system.deadline_set (5s); - while (node0->active.size () != 2) + while (!node0->block (change->hash ()) || !node0->block (epoch_open->hash ()) || !node1->block (change->hash ()) || !node1->block (epoch_open->hash ())) { ASSERT_NO_ERROR (system.poll ()); } + nano::blocks_confirm (*node0, { change, epoch_open }); + ASSERT_EQ (2, node0->active.size ()); { nano::lock_guard lock (node0->active.mutex); ASSERT_TRUE (node0->active.blocks.find (change->hash ()) != node0->active.blocks.end ()); - ASSERT_TRUE (node0->active.blocks.find (epoch->hash ()) != node0->active.blocks.end ()); + ASSERT_TRUE (node0->active.blocks.find (epoch_open->hash ()) != node0->active.blocks.end ()); } system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); system.deadline_set (5s); @@ -2662,81 +3126,119 @@ TEST (node, epoch_conflict_confirm) { auto transaction (node0->store.tx_begin_read ()); ASSERT_TRUE (node0->ledger.store.block_exists (transaction, change->hash ())); - ASSERT_TRUE (node0->ledger.store.block_exists (transaction, epoch->hash ())); + ASSERT_TRUE (node0->ledger.store.block_exists (transaction, epoch_open->hash ())); } } TEST (node, fork_invalid_block_signature) { - nano::system system (24000, 2); + nano::system system; + nano::node_flags node_flags; + // Disabling republishing + waiting for a rollback before sending the correct vote below fixes an intermittent failure in this test + // If these are taken out, one of two things may cause the test two fail often: + // - Block *send2* might get processed before the rollback happens, simply due to timings, with code "fork", and not be processed again. Waiting for the rollback fixes this issue. + // - Block *send1* might get processed again after the rollback happens, which causes *send2* to be processed with code "fork". Disabling block republishing ensures "send1" is not processed again. + // An alternative would be to repeatedly flood the correct vote + node_flags.disable_block_processor_republishing = true; + auto & node1 (*system.add_node (node_flags)); + auto & node2 (*system.add_node (node_flags)); nano::keypair key2; nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); auto send2_corrupt (std::make_shared (*send2)); send2_corrupt->signature = nano::signature (123); - system.nodes[0]->process_active (send1); + auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2)); + auto vote_corrupt (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2_corrupt)); + + node1.process_active (send1); system.deadline_set (5s); - while (!system.nodes[0]->block (send1->hash ())) + while (!node1.block (send1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2)); - auto vote_corrupt (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2_corrupt)); - system.nodes[1]->network.flood_vote (vote_corrupt); - ASSERT_NO_ERROR (system.poll ()); - system.nodes[1]->network.flood_vote (vote); - while (system.nodes[0]->block (send1->hash ())) + // Send the vote with the corrupt block signature + node2.network.flood_vote (vote_corrupt, 1.0f); + // Wait for the rollback + ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::rollback, nano::stat::detail::all)); + // Send the vote with the correct block + node2.network.flood_vote (vote, 1.0f); + while (node1.block (send1->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - while (!system.nodes[0]->block (send2->hash ())) + while (!node1.block (send2->hash ())) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (system.nodes[0]->block (send2->hash ())->block_signature (), send2->block_signature ()); + ASSERT_EQ (node1.block (send2->hash ())->block_signature (), send2->block_signature ()); } -TEST (node, fork_invalid_block_signature_vote_by_hash) +TEST (node, fork_election_invalid_block_signature) { - nano::system system (24000, 1); - nano::keypair key2; + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - system.nodes[0]->config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2_corrupt (std::make_shared (*send2)); - send2_corrupt->signature = nano::signature (123); - system.nodes[0]->process_active (send1); + nano::block_builder builder; + std::shared_ptr send1 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .build (); + std::shared_ptr send2 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .build (); + std::shared_ptr send3 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, 0) // Invalid signature + .build (); + auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); + node1.network.process_message (nano::publish (send1), channel1); system.deadline_set (5s); - while (!system.nodes[0]->block (send1->hash ())) - { - ASSERT_NO_ERROR (system.poll ()); - } - system.nodes[0]->active.publish (send2_corrupt); - ASSERT_NO_ERROR (system.poll ()); - system.nodes[0]->active.publish (send2); - std::vector vote_blocks; - vote_blocks.push_back (send2->hash ()); - auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - nano::unique_lock lock (system.nodes[0]->active.mutex); - system.nodes[0]->vote_processor.vote_blocking (transaction, vote, std::make_shared (system.nodes[0]->network.udp_channels, system.nodes[0]->network.endpoint (), system.nodes[0]->network_params.protocol.protocol_version)); - } - while (system.nodes[0]->block (send1->hash ())) + std::shared_ptr election; + while (election == nullptr) { ASSERT_NO_ERROR (system.poll ()); + nano::lock_guard lock (node1.active.mutex); + auto existing = node1.active.blocks.find (send1->hash ()); + if (existing != node1.active.blocks.end ()) + { + election = existing->second; + } } - while (!system.nodes[0]->block (send2->hash ())) + nano::unique_lock lock (node1.active.mutex); + ASSERT_EQ (1, election->blocks.size ()); + lock.unlock (); + node1.network.process_message (nano::publish (send3), channel1); + node1.network.process_message (nano::publish (send2), channel1); + lock.lock (); + while (election->blocks.size () == 1) { + lock.unlock (); ASSERT_NO_ERROR (system.poll ()); + lock.lock (); } - ASSERT_EQ (system.nodes[0]->block (send2->hash ())->block_signature (), send2->block_signature ()); + ASSERT_EQ (election->blocks[send2->hash ()]->block_signature (), send2->block_signature ()); } TEST (node, block_processor_signatures) { - nano::system system0 (24000, 1); + nano::system system0 (1); auto & node1 (*system0.nodes[0]); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::block_hash latest (system0.nodes[0]->latest (nano::test_genesis_key.pub)); @@ -2761,6 +3263,7 @@ TEST (node, block_processor_signatures) { auto transaction (node1.store.tx_begin_write ()); node1.store.unchecked_put (transaction, send5->previous (), send5); + ++node1.ledger.cache.unchecked_count; } auto receive1 (std::make_shared (key1.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, 0)); node1.work_generate_blocking (*receive1); @@ -2792,10 +3295,11 @@ TEST (node, block_processor_signatures) /* * State blocks go through a different signature path, ensure invalidly signed state blocks are rejected + * This test can freeze if the wake conditions in block_processor::flush are off, for that reason this is done async here */ TEST (node, block_processor_reject_state) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); nano::genesis genesis; auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); @@ -2803,48 +3307,23 @@ TEST (node, block_processor_reject_state) send1->signature.bytes[0] ^= 1; ASSERT_FALSE (node.ledger.block_exists (send1->hash ())); node.process_active (send1); - node.block_processor.flush (); + auto flushed = std::async (std::launch::async, [&node] { node.block_processor.flush (); }); + ASSERT_NE (std::future_status::timeout, flushed.wait_for (5s)); ASSERT_FALSE (node.ledger.block_exists (send1->hash ())); auto send2 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node.work_generate_blocking (*send2); node.process_active (send2); - node.block_processor.flush (); + auto flushed2 = std::async (std::launch::async, [&node] { node.block_processor.flush (); }); + ASSERT_NE (std::future_status::timeout, flushed2.wait_for (5s)); ASSERT_TRUE (node.ledger.block_exists (send2->hash ())); } -TEST (node, block_processor_reject_rolled_back) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node = *system.add_node (node_config); - nano::genesis genesis; - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node.work_generate_blocking (*send1); - node.block_processor.add (send1); - node.block_processor.flush (); - ASSERT_TRUE (node.ledger.block_exists (send1->hash ())); - auto send2 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node.work_generate_blocking (*send2); - // Force block send2 & rolling back block send1 - node.block_processor.force (send2); - node.block_processor.flush (); - ASSERT_FALSE (node.ledger.block_exists (send1->hash ())); - ASSERT_TRUE (node.ledger.block_exists (send2->hash ())); - ASSERT_TRUE (node.active.empty ()); - // Block send1 cannot be processed & start fork resolution election - node.block_processor.add (send1); - node.block_processor.flush (); - ASSERT_FALSE (node.ledger.block_exists (send1->hash ())); - ASSERT_TRUE (node.active.empty ()); -} - TEST (node, block_processor_full) { nano::system system; nano::node_flags node_flags; - node_flags.block_processor_full_size = 2; - auto & node = *system.add_node (nano::node_config (24000, system.logging), node_flags); + node_flags.block_processor_full_size = 3; + auto & node = *system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); nano::genesis genesis; auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node.work_generate_blocking (*send1); @@ -2853,7 +3332,7 @@ TEST (node, block_processor_full) auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node.work_generate_blocking (*send3); // The write guard prevents block processor doing any writes - auto write_guard = node.write_database_queue.wait (nano::writer::confirmation_height); + auto write_guard = node.write_database_queue.wait (nano::writer::testing); node.block_processor.add (send1); ASSERT_FALSE (node.block_processor.full ()); node.block_processor.add (send2); @@ -2871,8 +3350,8 @@ TEST (node, block_processor_half_full) { nano::system system; nano::node_flags node_flags; - node_flags.block_processor_full_size = 4; - auto & node = *system.add_node (nano::node_config (24000, system.logging), node_flags); + node_flags.block_processor_full_size = 6; + auto & node = *system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); nano::genesis genesis; auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node.work_generate_blocking (*send1); @@ -2881,7 +3360,7 @@ TEST (node, block_processor_half_full) auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3 * nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node.work_generate_blocking (*send3); // The write guard prevents block processor doing any writes - auto write_guard = node.write_database_queue.wait (nano::writer::confirmation_height); + auto write_guard = node.write_database_queue.wait (nano::writer::testing); node.block_processor.add (send1); ASSERT_FALSE (node.block_processor.half_full ()); node.block_processor.add (send2); @@ -2898,7 +3377,7 @@ TEST (node, block_processor_half_full) TEST (node, confirm_back) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key; auto & node (*system.nodes[0]); nano::genesis genesis; @@ -2909,16 +3388,12 @@ TEST (node, confirm_back) node.process_active (send1); node.process_active (open); node.process_active (send2); - node.block_processor.flush (); + nano::blocks_confirm (node, { send1, open, send2 }); ASSERT_EQ (3, node.active.size ()); std::vector vote_blocks; vote_blocks.push_back (send2->hash ()); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - { - auto transaction (node.store.tx_begin_read ()); - nano::unique_lock lock (node.active.mutex); - node.vote_processor.vote_blocking (transaction, vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); - } + node.vote_processor.vote_blocking (vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); system.deadline_set (10s); while (!node.active.empty ()) { @@ -2928,15 +3403,16 @@ TEST (node, confirm_back) TEST (node, peers) { - nano::system system (24000, 1); - ASSERT_TRUE (system.nodes.front ()->network.empty ()); + nano::system system (1); + auto node1 (system.nodes[0]); + ASSERT_TRUE (node1->network.empty ()); - auto node (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); - system.nodes.push_back (node); + auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + system.nodes.push_back (node2); - auto endpoint = system.nodes.front ()->network.endpoint (); + auto endpoint = node1->network.endpoint (); nano::endpoint_key endpoint_key{ endpoint.address ().to_v6 ().to_bytes (), endpoint.port () }; - auto & store = system.nodes.back ()->store; + auto & store = node2->store; { // Add a peer to the database auto transaction (store.tx_begin_write ()); @@ -2946,109 +3422,118 @@ TEST (node, peers) store.peer_put (transaction, nano::endpoint_key{ boost::asio::ip::address_v6::any ().to_bytes (), 55555 }); } - node->start (); + node2->start (); system.deadline_set (10s); - while (system.nodes.back ()->network.empty () || system.nodes.front ()->network.empty ()) + while (node2->network.empty () || node1->network.empty ()) { ASSERT_NO_ERROR (system.poll ()); } // Wait to finish TCP node ID handshakes system.deadline_set (10s); - while (system.nodes[0]->bootstrap.realtime_count == 0 || system.nodes[1]->bootstrap.realtime_count == 0) + while (node1->bootstrap.realtime_count == 0 || node2->bootstrap.realtime_count == 0) { ASSERT_NO_ERROR (system.poll ()); } // Confirm that the peers match with the endpoints we are expecting - ASSERT_EQ (1, system.nodes.front ()->network.size ()); - auto list1 (system.nodes[0]->network.list (2)); - ASSERT_EQ (system.nodes[1]->network.endpoint (), list1[0]->get_endpoint ()); + ASSERT_EQ (1, node1->network.size ()); + auto list1 (node1->network.list (2)); + ASSERT_EQ (node2->network.endpoint (), list1[0]->get_endpoint ()); ASSERT_EQ (nano::transport::transport_type::tcp, list1[0]->get_type ()); - ASSERT_EQ (1, node->network.size ()); - auto list2 (system.nodes[1]->network.list (2)); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (1, node2->network.size ()); + auto list2 (node2->network.list (2)); + ASSERT_EQ (node1->network.endpoint (), list2[0]->get_endpoint ()); ASSERT_EQ (nano::transport::transport_type::tcp, list2[0]->get_type ()); // Stop the peer node and check that it is removed from the store - system.nodes.front ()->stop (); + node1->stop (); system.deadline_set (10s); - while (system.nodes.back ()->network.size () == 1) + while (node2->network.size () == 1) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_TRUE (system.nodes.back ()->network.empty ()); + ASSERT_TRUE (node2->network.empty ()); // Uncontactable peer should not be stored auto transaction (store.tx_begin_read ()); ASSERT_EQ (store.peer_count (transaction), 1); ASSERT_TRUE (store.peer_exists (transaction, endpoint_key)); - node->stop (); + node2->stop (); } TEST (node, peer_cache_restart) { - nano::system system (24000, 1); - ASSERT_TRUE (system.nodes[0]->network.empty ()); - auto endpoint = system.nodes[0]->network.endpoint (); + nano::system system (1); + auto node1 (system.nodes[0]); + ASSERT_TRUE (node1->network.empty ()); + auto endpoint = node1->network.endpoint (); nano::endpoint_key endpoint_key{ endpoint.address ().to_v6 ().to_bytes (), endpoint.port () }; auto path (nano::unique_path ()); { - auto node (std::make_shared (system.io_ctx, 24001, path, system.alarm, system.logging, system.work)); - system.nodes.push_back (node); - auto & store = node->store; + auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), path, system.alarm, system.logging, system.work)); + system.nodes.push_back (node2); + auto & store = node2->store; { // Add a peer to the database auto transaction (store.tx_begin_write ()); store.peer_put (transaction, endpoint_key); } - node->start (); + node2->start (); system.deadline_set (10s); - while (node->network.empty ()) + while (node2->network.empty ()) { ASSERT_NO_ERROR (system.poll ()); } // Confirm that the peers match with the endpoints we are expecting - auto list (node->network.list (2)); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list[0]->get_endpoint ()); - ASSERT_EQ (1, node->network.size ()); - node->stop (); + auto list (node2->network.list (2)); + ASSERT_EQ (node1->network.endpoint (), list[0]->get_endpoint ()); + ASSERT_EQ (1, node2->network.size ()); + node2->stop (); } // Restart node { nano::node_flags node_flags; node_flags.read_only = true; - auto node (std::make_shared (system.io_ctx, 24002, path, system.alarm, system.logging, system.work, node_flags)); - system.nodes.push_back (node); + auto node3 (std::make_shared (system.io_ctx, nano::get_available_port (), path, system.alarm, system.logging, system.work, node_flags)); + system.nodes.push_back (node3); // Check cached peers after restart - node->network.start (); - node->add_initial_peers (); + node3->network.start (); + node3->add_initial_peers (); - auto & store = node->store; + auto & store = node3->store; { auto transaction (store.tx_begin_read ()); ASSERT_EQ (store.peer_count (transaction), 1); ASSERT_TRUE (store.peer_exists (transaction, endpoint_key)); } system.deadline_set (10s); - while (node->network.empty ()) + while (node3->network.empty ()) { ASSERT_NO_ERROR (system.poll ()); } // Confirm that the peers match with the endpoints we are expecting - auto list (node->network.list (2)); - ASSERT_EQ (system.nodes[0]->network.endpoint (), list[0]->get_endpoint ()); - ASSERT_EQ (1, node->network.size ()); - node->stop (); + auto list (node3->network.list (2)); + ASSERT_EQ (node1->network.endpoint (), list[0]->get_endpoint ()); + ASSERT_EQ (1, node3->network.size ()); + node3->stop (); } } TEST (node, unchecked_cleanup) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key; auto & node (*system.nodes[0]); auto open (std::make_shared (key.pub, 0, key.pub, 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); + std::vector bytes; + { + nano::vectorstream stream (bytes); + open->serialize (stream); + } + // Add to the blocks filter + // Should be cleared after unchecked cleanup + ASSERT_FALSE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); node.process_active (open); node.block_processor.flush (); node.config.unchecked_cutoff_time = std::chrono::seconds (2); @@ -3056,20 +3541,25 @@ TEST (node, unchecked_cleanup) auto transaction (node.store.tx_begin_read ()); auto unchecked_count (node.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node.ledger.cache.unchecked_count); } std::this_thread::sleep_for (std::chrono::seconds (1)); node.unchecked_cleanup (); + ASSERT_TRUE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); { auto transaction (node.store.tx_begin_read ()); auto unchecked_count (node.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 1); + ASSERT_EQ (unchecked_count, node.ledger.cache.unchecked_count); } std::this_thread::sleep_for (std::chrono::seconds (2)); node.unchecked_cleanup (); + ASSERT_FALSE (node.network.publish_filter.apply (bytes.data (), bytes.size ())); { auto transaction (node.store.tx_begin_read ()); auto unchecked_count (node.store.unchecked_count (transaction)); ASSERT_EQ (unchecked_count, 0); + ASSERT_EQ (unchecked_count, node.ledger.cache.unchecked_count); } } @@ -3080,17 +3570,14 @@ TEST (node, dont_write_lock_node) std::promise write_lock_held_promise; std::promise finished_promise; - // clang-format off std::thread ([&path, &write_lock_held_promise, &finished_promise]() { nano::logger_mt logger; auto store = nano::make_store (logger, path, false, true); { nano::genesis genesis; - nano::rep_weights rep_weights; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; + nano::ledger_cache ledger_cache; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, rep_weights, cemented_count, block_count_cache); + store->initialize (transaction, genesis, ledger_cache); } // Hold write lock open until main thread is done needing it @@ -3099,7 +3586,6 @@ TEST (node, dont_write_lock_node) finished_promise.get_future ().wait (); }) .detach (); - // clang-format off write_lock_held_promise.get_future ().wait (); @@ -3112,11 +3598,14 @@ TEST (node, bidirectional_tcp) { nano::system system; nano::node_flags node_flags; - node_flags.disable_udp = true; // Disable UDP connections - nano::node_config node_config (24000, system.logging); + // Disable bootstrap to start elections for new blocks + node_flags.disable_legacy_bootstrap = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_wallet_bootstrap = true; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node1 = system.add_node (node_config, node_flags); - node_config.peering_port = 24001; + node_config.peering_port = nano::get_available_port (); node_config.tcp_incoming_connections_max = 0; // Disable incoming TCP connections for node 2 auto node2 = system.add_node (node_config, node_flags); // Check network connections @@ -3141,22 +3630,32 @@ TEST (node, bidirectional_tcp) { ASSERT_NO_ERROR (system.poll ()); } - // Test block confirmation from node 1 + // Test block confirmation from node 1 (add representative to node 1) system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + // Wait to find new reresentative + system.deadline_set (10s); + while (node2->rep_crawler.representative_count () == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + /* Wait for confirmation + To check connection we need only node 2 confirmation status + Node 1 election can be unconfirmed because representative private key was inserted after election start (and node 2 isn't flooding new votes to principal representatives) */ bool confirmed (false); system.deadline_set (10s); while (!confirmed) { - auto transaction1 (node1->store.tx_begin_read ()); auto transaction2 (node2->store.tx_begin_read ()); - confirmed = node1->ledger.block_confirmed (transaction1, send1->hash ()) && node2->ledger.block_confirmed (transaction2, send1->hash ()); + confirmed = node2->ledger.block_confirmed (transaction2, send1->hash ()); ASSERT_NO_ERROR (system.poll ()); } - // Test block propagation from node 2 + // Test block propagation & confirmation from node 2 (remove representative from node 1) { auto transaction (system.wallet (0)->wallets.tx_begin_write ()); system.wallet (0)->store.erase (transaction, nano::test_genesis_key.pub); } + /* Test block propagation from node 2 + Node 2 has only ephemeral TCP port open. Node 1 cannot establish connection to node 2 listening port */ auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1->work_generate_blocking (send1->hash ()))); node2->process_active (send2); node2->block_processor.flush (); @@ -3165,33 +3664,146 @@ TEST (node, bidirectional_tcp) { ASSERT_NO_ERROR (system.poll ()); } - // Test block confirmation from node 2 + // Test block confirmation from node 2 (add representative to node 2) system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); - confirmed = false; + // Wait to find changed reresentative system.deadline_set (10s); + while (node1->rep_crawler.representative_count () == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + /* Wait for confirmation + To check connection we need only node 1 confirmation status + Node 2 election can be unconfirmed because representative private key was inserted after election start (and node 1 isn't flooding new votes to principal representatives) */ + confirmed = false; + system.deadline_set (20s); while (!confirmed) { auto transaction1 (node1->store.tx_begin_read ()); - auto transaction2 (node2->store.tx_begin_read ()); - confirmed = node1->ledger.block_confirmed (transaction1, send2->hash ()) && node2->ledger.block_confirmed (transaction2, send2->hash ()); + confirmed = node1->ledger.block_confirmed (transaction1, send2->hash ()); ASSERT_NO_ERROR (system.poll ()); } } +// Tests that local blocks are flooded to all principal representatives +// Sanitizers or running within valgrind use different timings and number of nodes +TEST (node, aggressive_flooding) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + node_flags.disable_block_processor_republishing = true; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_bootstrap_bulk_pull_server = true; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.disable_legacy_bootstrap = true; + node_flags.disable_wallet_bootstrap = true; + auto & node1 (*system.add_node (node_flags)); + auto & wallet1 (*system.wallet (0)); + wallet1.insert_adhoc (nano::test_genesis_key.prv); + std::vector, std::shared_ptr>> nodes_wallets; + bool const sanitizer_or_valgrind (is_sanitizer_build || nano::running_within_valgrind ()); + nodes_wallets.resize (!sanitizer_or_valgrind ? 5 : 3); + + std::generate (nodes_wallets.begin (), nodes_wallets.end (), [&system, node_flags]() { + nano::node_config node_config (nano::get_available_port (), system.logging); + auto node (system.add_node (node_config, node_flags)); + return std::make_pair (node, system.wallet (system.nodes.size () - 1)); + }); + + // This test is only valid if a non-aggressive flood would not reach every peer + ASSERT_TIMELY (5s, node1.network.size () == nodes_wallets.size ()); + ASSERT_LT (node1.network.fanout (), nodes_wallets.size ()); + + // Send a large amount to create a principal representative in each node + auto large_amount = (nano::genesis_amount / 2) / nodes_wallets.size (); + std::vector> genesis_blocks; + for (auto & node_wallet : nodes_wallets) + { + nano::keypair keypair; + node_wallet.second->store.representative_set (node_wallet.first->wallets.tx_begin_write (), keypair.pub); + node_wallet.second->insert_adhoc (keypair.prv); + auto block (wallet1.send_action (nano::test_genesis_key.pub, keypair.pub, large_amount)); + genesis_blocks.push_back (block); + } + + // Ensure all nodes have the full genesis chain + for (auto & node_wallet : nodes_wallets) + { + for (auto const & block : genesis_blocks) + { + node_wallet.first->process (*block); + } + ASSERT_EQ (node1.latest (nano::test_genesis_key.pub), node_wallet.first->latest (nano::test_genesis_key.pub)); + } + + // Wait until the main node sees all representatives + ASSERT_TIMELY (!sanitizer_or_valgrind ? 10s : 40s, node1.rep_crawler.principal_representatives ().size () == nodes_wallets.size ()); + + // Generate blocks and ensure they are sent to all representatives + nano::block_builder builder; + std::shared_ptr block{}; + { + auto transaction (node1.store.tx_begin_read ()); + block = builder.state () + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .previous (node1.ledger.latest (transaction, nano::test_genesis_key.pub)) + .balance (node1.ledger.account_balance (transaction, nano::test_genesis_key.pub) - 1) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*node1.work_generate_blocking (node1.ledger.latest (transaction, nano::test_genesis_key.pub))) + .build (); + } + // Processing locally goes through the aggressive block flooding path + node1.process_local (block, false); + + auto all_have_block = [&nodes_wallets](nano::block_hash const & hash_a) { + return std::all_of (nodes_wallets.begin (), nodes_wallets.end (), [hash = hash_a](auto const & node_wallet) { + return node_wallet.first->block (hash) != nullptr; + }); + }; + + ASSERT_TIMELY (!sanitizer_or_valgrind ? 5s : 25s, all_have_block (block->hash ())); + + // Do the same for a wallet block + auto wallet_block = wallet1.send_sync (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 10); + ASSERT_TIMELY (!sanitizer_or_valgrind ? 5s : 25s, all_have_block (wallet_block)); + + // All blocks: genesis + (send+open) for each representative + 2 local blocks + // The main node only sees all blocks if other nodes are flooding their PR's open block to all other PRs + ASSERT_EQ (1 + 2 * nodes_wallets.size () + 2, node1.ledger.cache.block_count); +} + +// Tests that upon changing the default difficulty, max generation difficulty changes proportionally +TEST (node, max_work_generate_difficulty) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.max_work_generate_multiplier = 2.0; + auto & node = *system.add_node (node_config); + auto initial_difficulty = node.default_difficulty (nano::work_version::work_1); + ASSERT_EQ (node.max_work_generate_difficulty (nano::work_version::work_1), nano::difficulty::from_multiplier (node.config.max_work_generate_multiplier, initial_difficulty)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2)); + auto final_difficulty = node.default_difficulty (nano::work_version::work_1); + ASSERT_NE (final_difficulty, initial_difficulty); + ASSERT_EQ (node.max_work_generate_difficulty (nano::work_version::work_1), nano::difficulty::from_multiplier (node.config.max_work_generate_multiplier, final_difficulty)); +} + TEST (active_difficulty, recalculate_work) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; auto & node1 = *system.add_node (node_config); nano::genesis genesis; nano::keypair key1; - ASSERT_EQ (node1.network_params.network.publish_threshold, node1.active.active_difficulty ()); + ASSERT_EQ (node1.network_params.network.publish_thresholds.epoch_1, node1.active.active_difficulty ()); auto send1 (std::make_shared (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send1); - uint64_t difficulty1; - nano::work_validate (*send1, &difficulty1); - auto multiplier1 = nano::difficulty::to_multiplier (difficulty1, node1.network_params.network.publish_threshold); + auto multiplier1 = nano::difficulty::to_multiplier (send1->difficulty (), node1.network_params.network.publish_thresholds.epoch_1); // Process as local block node1.process_active (send1); system.deadline_set (2s); @@ -3200,7 +3812,7 @@ TEST (active_difficulty, recalculate_work) ASSERT_NO_ERROR (system.poll ()); } auto sum (std::accumulate (node1.active.multipliers_cb.begin (), node1.active.multipliers_cb.end (), double(0))); - ASSERT_EQ (node1.active.active_difficulty (), nano::difficulty::from_multiplier (sum / node1.active.multipliers_cb.size (), node1.network_params.network.publish_threshold)); + ASSERT_EQ (node1.active.active_difficulty (), nano::difficulty::from_multiplier (sum / node1.active.multipliers_cb.size (), node1.network_params.network.publish_thresholds.epoch_1)); nano::unique_lock lock (node1.active.mutex); // Fake history records to force work recalculation for (auto i (0); i < node1.active.multipliers_cb.size (); i++) @@ -3208,15 +3820,634 @@ TEST (active_difficulty, recalculate_work) node1.active.multipliers_cb.push_back (multiplier1 * (1 + i / 100.)); } node1.work_generate_blocking (*send1); - uint64_t difficulty2; - nano::work_validate (*send1, &difficulty2); node1.process_active (send1); - node1.active.update_active_difficulty (lock); + node1.active.update_active_multiplier (lock); sum = std::accumulate (node1.active.multipliers_cb.begin (), node1.active.multipliers_cb.end (), double(0)); - ASSERT_EQ (node1.active.trended_active_difficulty, nano::difficulty::from_multiplier (sum / node1.active.multipliers_cb.size (), node1.network_params.network.publish_threshold)); + ASSERT_EQ (node1.active.trended_active_multiplier, sum / node1.active.multipliers_cb.size ()); lock.unlock (); } +TEST (node, node_sequence) +{ + nano::system system (3); + ASSERT_EQ (0, system.nodes[0]->node_seq); + ASSERT_EQ (0, system.nodes[0]->node_seq); + ASSERT_EQ (1, system.nodes[1]->node_seq); + ASSERT_EQ (2, system.nodes[2]->node_seq); +} + +TEST (node, rollback_vote_self) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node = *system.add_node (flags); + nano::state_block_builder builder; + nano::keypair key; + auto weight = node.config.online_weight_minimum.number (); + std::shared_ptr send1 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - weight) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + std::shared_ptr open = builder.make_block () + .account (key.pub) + .previous (0) + .representative (key.pub) + .link (send1->hash ()) + .balance (weight) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + std::shared_ptr send2 = builder.make_block () + .from (*send1) + .previous (send1->hash ()) + .balance (send1->balance ().number () - 1) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send1->hash ())) + .build (); + std::shared_ptr fork = builder.make_block () + .from (*send2) + .balance (send2->balance ().number () - 2) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .build (); + ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*open).code); + // Confirm blocks to allow voting + node.block_confirm (open); + { + auto election = node.active.election (open->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (5s, node.ledger.cache.cemented_count == 3); + ASSERT_EQ (weight, node.weight (key.pub)); + node.process_active (send2); + node.process_active (fork); + node.block_processor.flush (); + auto election = node.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election); + ASSERT_EQ (2, election->blocks.size ()); + // Insert genesis key in the wallet + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + { + // The write guard prevents the block processor from performing the rollback + auto write_guard = node.write_database_queue.wait (nano::writer::testing); + { + nano::lock_guard guard (node.active.mutex); + ASSERT_EQ (1, election->last_votes.size ()); + // Vote with key to switch the winner + election->vote (key.pub, 0, fork->hash ()); + ASSERT_EQ (2, election->last_votes.size ()); + // The winner changed + ASSERT_EQ (election->status.winner, fork); + } + // Even without the rollback being finished, the aggregator must reply with a vote for the new winner, not the old one + ASSERT_TRUE (node.votes_cache.find (send2->hash ()).empty ()); + ASSERT_TRUE (node.votes_cache.find (fork->hash ()).empty ()); + auto & node2 = *system.add_node (); + auto channel (node.network.udp_channels.create (node2.network.endpoint ())); + node.aggregator.add (channel, { { send2->hash (), send2->root () } }); + ASSERT_TIMELY (5s, !node.votes_cache.find (fork->hash ()).empty ()); + ASSERT_TRUE (node.votes_cache.find (send2->hash ()).empty ()); + + // Going out of the scope allows the rollback to complete + } + // A vote is eventually generated from the local representative + ASSERT_TIMELY (5s, 3 == election->last_votes_size ()); + auto vote (election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election->last_votes.end (), vote); + ASSERT_EQ (fork->hash (), vote->second.hash); +} + +// Confirm a complex dependency graph starting from the first block +TEST (node, dependency_graph) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (config); + + nano::state_block_builder builder; + nano::keypair key1, key2, key3; + + // Send to key1 + std::shared_ptr gen_send1 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (key1.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + // Receive from genesis + auto key1_open = builder.make_block () + .account (key1.pub) + .previous (0) + .representative (key1.pub) + .link (gen_send1->hash ()) + .balance (1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1.pub)) + .build (); + // Send to genesis + auto key1_send1 = builder.make_block () + .account (key1.pub) + .previous (key1_open->hash ()) + .representative (key1.pub) + .link (nano::test_genesis_key.pub) + .balance (0) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_open->hash ())) + .build (); + // Receive from key1 + auto gen_receive = builder.make_block () + .from (*gen_send1) + .previous (gen_send1->hash ()) + .link (key1_send1->hash ()) + .balance (nano::genesis_amount) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (gen_send1->hash ())) + .build (); + // Send to key2 + auto gen_send2 = builder.make_block () + .from (*gen_receive) + .previous (gen_receive->hash ()) + .link (key2.pub) + .balance (gen_receive->balance ().number () - 2) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (gen_receive->hash ())) + .build (); + // Receive from genesis + auto key2_open = builder.make_block () + .account (key2.pub) + .previous (0) + .representative (key2.pub) + .link (gen_send2->hash ()) + .balance (2) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2.pub)) + .build (); + // Send to key3 + auto key2_send1 = builder.make_block () + .account (key2.pub) + .previous (key2_open->hash ()) + .representative (key2.pub) + .link (key3.pub) + .balance (1) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2_open->hash ())) + .build (); + // Receive from key2 + auto key3_open = builder.make_block () + .account (key3.pub) + .previous (0) + .representative (key3.pub) + .link (key2_send1->hash ()) + .balance (1) + .sign (key3.prv, key3.pub) + .work (*system.work.generate (key3.pub)) + .build (); + // Send to key1 + auto key2_send2 = builder.make_block () + .from (*key2_send1) + .previous (key2_send1->hash ()) + .link (key1.pub) + .balance (key2_send1->balance ().number () - 1) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2_send1->hash ())) + .build (); + // Receive from key2 + auto key1_receive = builder.make_block () + .from (*key1_send1) + .previous (key1_send1->hash ()) + .link (key2_send2->hash ()) + .balance (key1_send1->balance ().number () + 1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_send1->hash ())) + .build (); + // Send to key3 + auto key1_send2 = builder.make_block () + .from (*key1_receive) + .previous (key1_receive->hash ()) + .link (key3.pub) + .balance (key1_receive->balance ().number () - 1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_receive->hash ())) + .build (); + // Receive from key1 + auto key3_receive = builder.make_block () + .from (*key3_open) + .previous (key3_open->hash ()) + .link (key1_send2->hash ()) + .balance (key3_open->balance ().number () + 1) + .sign (key3.prv, key3.pub) + .work (*system.work.generate (key3_open->hash ())) + .build (); + // Upgrade key3 + auto key3_epoch = builder.make_block () + .from (*key3_receive) + .previous (key3_receive->hash ()) + .link (node.ledger.epoch_link (nano::epoch::epoch_1)) + .balance (key3_receive->balance ()) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (key3_receive->hash ())) + .build (); + + ASSERT_EQ (nano::process_result::progress, node.process (*gen_send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key1_open).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key1_send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*gen_receive).code); + ASSERT_EQ (nano::process_result::progress, node.process (*gen_send2).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key2_open).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key2_send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key3_open).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key2_send2).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key1_receive).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key1_send2).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key3_receive).code); + ASSERT_EQ (nano::process_result::progress, node.process (*key3_epoch).code); + ASSERT_TRUE (node.active.empty ()); + + // Hash -> Ancestors + std::unordered_map> dependency_graph{ + { key1_open->hash (), { gen_send1->hash () } }, + { key1_send1->hash (), { key1_open->hash () } }, + { gen_receive->hash (), { gen_send1->hash (), key1_open->hash () } }, + { gen_send2->hash (), { gen_receive->hash () } }, + { key2_open->hash (), { gen_send2->hash () } }, + { key2_send1->hash (), { key2_open->hash () } }, + { key3_open->hash (), { key2_send1->hash () } }, + { key2_send2->hash (), { key2_send1->hash () } }, + { key1_receive->hash (), { key1_send1->hash (), key2_send2->hash () } }, + { key1_send2->hash (), { key1_send1->hash () } }, + { key3_receive->hash (), { key3_open->hash (), key1_send2->hash () } }, + { key3_epoch->hash (), { key3_receive->hash () } }, + }; + ASSERT_EQ (node.ledger.cache.block_count - 2, dependency_graph.size ()); + + // Start an election for the first block of the dependency graph, and ensure all blocks are eventually confirmed + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node.block_confirm (gen_send1); + + ASSERT_NO_ERROR (system.poll_until_true (15s, [&] { + // Not many blocks should be active simultaneously + EXPECT_LT (node.active.size (), 6); + nano::lock_guard guard (node.active.mutex); + + // Ensure that active blocks have their ancestors confirmed + auto error = std::any_of (dependency_graph.cbegin (), dependency_graph.cend (), [&](auto entry) { + if (node.active.blocks.count (entry.first)) + { + for (auto ancestor : entry.second) + { + if (!node.block_confirmed (ancestor)) + { + return true; + } + } + } + return false; + }); + + EXPECT_FALSE (error); + return error || node.ledger.cache.cemented_count == node.ledger.cache.block_count; + })); + ASSERT_EQ (node.ledger.cache.cemented_count, node.ledger.cache.block_count); + ASSERT_TIMELY (5s, node.active.empty ()); +} + +// Confirm a complex dependency graph starting from a frontier +TEST (node, dependency_graph_frontier) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node1 = *system.add_node (config); + config.peering_port = nano::get_available_port (); + auto & node2 = *system.add_node (config); + + nano::state_block_builder builder; + nano::keypair key1, key2, key3; + + // Send to key1 + std::shared_ptr gen_send1 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (key1.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + // Receive from genesis + auto key1_open = builder.make_block () + .account (key1.pub) + .previous (0) + .representative (key1.pub) + .link (gen_send1->hash ()) + .balance (1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1.pub)) + .build (); + // Send to genesis + auto key1_send1 = builder.make_block () + .account (key1.pub) + .previous (key1_open->hash ()) + .representative (key1.pub) + .link (nano::test_genesis_key.pub) + .balance (0) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_open->hash ())) + .build (); + // Receive from key1 + auto gen_receive = builder.make_block () + .from (*gen_send1) + .previous (gen_send1->hash ()) + .link (key1_send1->hash ()) + .balance (nano::genesis_amount) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (gen_send1->hash ())) + .build (); + // Send to key2 + auto gen_send2 = builder.make_block () + .from (*gen_receive) + .previous (gen_receive->hash ()) + .link (key2.pub) + .balance (gen_receive->balance ().number () - 2) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (gen_receive->hash ())) + .build (); + // Receive from genesis + auto key2_open = builder.make_block () + .account (key2.pub) + .previous (0) + .representative (key2.pub) + .link (gen_send2->hash ()) + .balance (2) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2.pub)) + .build (); + // Send to key3 + auto key2_send1 = builder.make_block () + .account (key2.pub) + .previous (key2_open->hash ()) + .representative (key2.pub) + .link (key3.pub) + .balance (1) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2_open->hash ())) + .build (); + // Receive from key2 + auto key3_open = builder.make_block () + .account (key3.pub) + .previous (0) + .representative (key3.pub) + .link (key2_send1->hash ()) + .balance (1) + .sign (key3.prv, key3.pub) + .work (*system.work.generate (key3.pub)) + .build (); + // Send to key1 + auto key2_send2 = builder.make_block () + .from (*key2_send1) + .previous (key2_send1->hash ()) + .link (key1.pub) + .balance (key2_send1->balance ().number () - 1) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2_send1->hash ())) + .build (); + // Receive from key2 + auto key1_receive = builder.make_block () + .from (*key1_send1) + .previous (key1_send1->hash ()) + .link (key2_send2->hash ()) + .balance (key1_send1->balance ().number () + 1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_send1->hash ())) + .build (); + // Send to key3 + auto key1_send2 = builder.make_block () + .from (*key1_receive) + .previous (key1_receive->hash ()) + .link (key3.pub) + .balance (key1_receive->balance ().number () - 1) + .sign (key1.prv, key1.pub) + .work (*system.work.generate (key1_receive->hash ())) + .build (); + // Receive from key1 + auto key3_receive = builder.make_block () + .from (*key3_open) + .previous (key3_open->hash ()) + .link (key1_send2->hash ()) + .balance (key3_open->balance ().number () + 1) + .sign (key3.prv, key3.pub) + .work (*system.work.generate (key3_open->hash ())) + .build (); + // Upgrade key3 + auto key3_epoch = builder.make_block () + .from (*key3_receive) + .previous (key3_receive->hash ()) + .link (node1.ledger.epoch_link (nano::epoch::epoch_1)) + .balance (key3_receive->balance ()) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (key3_receive->hash ())) + .build (); + + for (auto const & node : system.nodes) + { + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *gen_send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key1_open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key1_send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *gen_receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *gen_send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key2_open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key2_send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key3_open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key2_send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key1_receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key1_send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key3_receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key3_epoch).code); + } + + ASSERT_TRUE (node1.active.empty () && node2.active.empty ()); + + // node1 can vote, but only on the first block + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + + // activate the graph frontier + // node2 activates dependencies in sequence until it reaches the first block + node2.block_confirm (node2.block (key3_epoch->hash ())); + + // Eventually the first block in the graph gets activated and confirmed via node1 + ASSERT_TIMELY (15s, node2.block_confirmed (gen_send1->hash ())); + + // Activate the first block in node1, allowing it to confirm all blocks for both nodes + node1.block_confirm (gen_send1); + ASSERT_TIMELY (15s, node1.ledger.cache.cemented_count == node1.ledger.cache.block_count); + ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == node2.ledger.cache.block_count); + ASSERT_TIMELY (5s, node1.active.empty () && node2.active.empty ()); +} + +namespace nano +{ +TEST (node, deferred_dependent_elections) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node = *system.add_node (flags); + auto & node2 = *system.add_node (flags); // node2 will be used to ensure all blocks are being propagated + + nano::state_block_builder builder; + nano::keypair key; + std::shared_ptr send1 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + std::shared_ptr open = builder.make_block () + .account (key.pub) + .previous (0) + .representative (key.pub) + .link (send1->hash ()) + .balance (1) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + std::shared_ptr send2 = builder.make_block () + .from (*send1) + .previous (send1->hash ()) + .balance (send1->balance ().number () - 1) + .link (key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send1->hash ())) + .build (); + std::shared_ptr receive = builder.make_block () + .from (*open) + .previous (open->hash ()) + .link (send2->hash ()) + .balance (2) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + std::shared_ptr fork = builder.make_block () + .from (*receive) + .representative (nano::test_genesis_key.pub) + .sign (key.prv, key.pub) + .build (); + node.process_active (send1); + node.block_processor.flush (); + auto election_send1 = node.active.election (send1->qualified_root ()); + ASSERT_NE (nullptr, election_send1); + + // Should process and republish but not start an election for any dependent blocks + node.process_active (open); + node.process_active (send2); + node.block_processor.flush (); + ASSERT_TRUE (node.block (open->hash ())); + ASSERT_TRUE (node.block (send2->hash ())); + ASSERT_FALSE (node.active.active (open->qualified_root ())); + ASSERT_FALSE (node.active.active (send2->qualified_root ())); + ASSERT_TIMELY (2s, node2.block (open->hash ())); + ASSERT_TIMELY (2s, node2.block (send2->hash ())); + + // Re-processing older blocks with updated work also does not start an election + node.work_generate_blocking (*open, open->difficulty ()); + node.process_active (open); + node.block_processor.flush (); + ASSERT_FALSE (node.active.active (open->qualified_root ())); + + // It is however possible to manually start an election from elsewhere + node.block_confirm (open); + ASSERT_TRUE (node.active.active (open->qualified_root ())); + + // Dropping an election allows restarting it [with higher work] + node.active.erase (*open); + ASSERT_FALSE (node.active.active (open->qualified_root ())); + ASSERT_NE (std::chrono::steady_clock::time_point{}, node.active.recently_dropped.find (open->qualified_root ())); + node.process_active (open); + node.block_processor.flush (); + ASSERT_TRUE (node.active.active (open->qualified_root ())); + + // Frontier confirmation also starts elections + ASSERT_NO_ERROR (system.poll_until_true (5s, [&node, &send2] { + nano::unique_lock lock (node.active.mutex); + node.active.frontiers_confirmation (lock); + lock.unlock (); + return node.active.election (send2->qualified_root ()) != nullptr; + })); + + // Drop both elections + node.active.erase (*open); + ASSERT_FALSE (node.active.active (open->qualified_root ())); + node.active.erase (*send2); + ASSERT_FALSE (node.active.active (send2->qualified_root ())); + + // Confirming send1 will automatically start elections for the dependents + { + nano::lock_guard guard (node.active.mutex); + election_send1->confirm_once (); + } + ASSERT_TIMELY (2s, node.block_confirmed (send1->hash ())); + ASSERT_TIMELY (2s, node.active.active (open->qualified_root ()) && node.active.active (send2->qualified_root ())); + auto election_open = node.active.election (open->qualified_root ()); + ASSERT_NE (nullptr, election_open); + auto election_send2 = node.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election_open); + + // Confirm one of the dependents of the receive but not the other, to ensure both have to be confirmed to start an election on processing + ASSERT_EQ (nano::process_result::progress, node.process (*receive).code); + ASSERT_FALSE (node.active.active (receive->qualified_root ())); + { + nano::lock_guard guard (node.active.mutex); + election_open->confirm_once (); + } + ASSERT_TIMELY (2s, node.block_confirmed (open->hash ())); + ASSERT_FALSE (node.ledger.can_vote (node.store.tx_begin_read (), *receive)); + std::this_thread::sleep_for (500ms); + ASSERT_FALSE (node.active.active (receive->qualified_root ())); + ASSERT_FALSE (node.ledger.rollback (node.store.tx_begin_write (), receive->hash ())); + ASSERT_FALSE (node.block (receive->hash ())); + node.process_active (receive); + node.block_processor.flush (); + ASSERT_TRUE (node.block (receive->hash ())); + ASSERT_FALSE (node.active.active (receive->qualified_root ())); + + // Processing a fork will also not start an election + ASSERT_EQ (nano::process_result::fork, node.process (*fork).code); + node.process_active (fork); + node.block_processor.flush (); + ASSERT_FALSE (node.active.active (receive->qualified_root ())); + + // Confirming the other dependency allows starting an election from a fork + { + nano::lock_guard guard (node.active.mutex); + election_send2->confirm_once (); + } + ASSERT_TIMELY (2s, node.block_confirmed (send2->hash ())); + ASSERT_TIMELY (2s, node.active.active (receive->qualified_root ())); + node.active.erase (*receive); + ASSERT_FALSE (node.active.active (receive->qualified_root ())); + node.process_active (fork); + node.block_processor.flush (); + ASSERT_TRUE (node.active.active (receive->qualified_root ())); +} +} + namespace { void add_required_children_node_config_tree (nano::jsonconfig & tree) diff --git a/nano/core_test/peer_container.cpp b/nano/core_test/peer_container.cpp index e18e5095e0..738e2617b4 100644 --- a/nano/core_test/peer_container.cpp +++ b/nano/core_test/peer_container.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -5,7 +6,7 @@ TEST (peer_container, empty_peers) { - nano::system system (24000, 1); + nano::system system (1); nano::network & network (system.nodes[0]->network); system.nodes[0]->network.cleanup (std::chrono::steady_clock::now ()); ASSERT_EQ (0, network.size ()); @@ -13,18 +14,19 @@ TEST (peer_container, empty_peers) TEST (peer_container, no_recontact) { - nano::system system (24000, 1); - nano::network & network (system.nodes[0]->network); + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::network & network (node1.network); auto observed_peer (0); auto observed_disconnect (false); nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), 10000); ASSERT_EQ (0, network.size ()); network.channel_observer = [&observed_peer](std::shared_ptr) { ++observed_peer; }; - system.nodes[0]->network.disconnect_observer = [&observed_disconnect]() { observed_disconnect = true; }; - auto channel (network.udp_channels.insert (endpoint1, system.nodes[0]->network_params.protocol.protocol_version)); + node1.network.disconnect_observer = [&observed_disconnect]() { observed_disconnect = true; }; + auto channel (network.udp_channels.insert (endpoint1, node1.network_params.protocol.protocol_version)); ASSERT_EQ (1, network.size ()); - ASSERT_EQ (channel, network.udp_channels.insert (endpoint1, system.nodes[0]->network_params.protocol.protocol_version)); - system.nodes[0]->network.cleanup (std::chrono::steady_clock::now () + std::chrono::seconds (5)); + ASSERT_EQ (channel, network.udp_channels.insert (endpoint1, node1.network_params.protocol.protocol_version)); + node1.network.cleanup (std::chrono::steady_clock::now () + std::chrono::seconds (5)); ASSERT_TRUE (network.empty ()); ASSERT_EQ (1, observed_peer); ASSERT_TRUE (observed_disconnect); @@ -32,14 +34,14 @@ TEST (peer_container, no_recontact) TEST (peer_container, no_self_incoming) { - nano::system system (24000, 1); + nano::system system (1); ASSERT_EQ (nullptr, system.nodes[0]->network.udp_channels.insert (system.nodes[0]->network.endpoint (), 0)); ASSERT_TRUE (system.nodes[0]->network.empty ()); } TEST (peer_container, reserved_peers_no_contact) { - nano::system system (24000, 1); + nano::system system (1); auto & channels (system.nodes[0]->network.udp_channels); ASSERT_EQ (nullptr, channels.insert (nano::endpoint (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x00000001)), 10000), 0)); ASSERT_EQ (nullptr, channels.insert (nano::endpoint (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0xc0000201)), 10000), 0)); @@ -53,32 +55,33 @@ TEST (peer_container, reserved_peers_no_contact) TEST (peer_container, split) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); auto now (std::chrono::steady_clock::now ()); nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), 100); nano::endpoint endpoint2 (boost::asio::ip::address_v6::loopback (), 101); - auto channel1 (system.nodes[0]->network.udp_channels.insert (endpoint1, 0)); + auto channel1 (node1.network.udp_channels.insert (endpoint1, 0)); ASSERT_NE (nullptr, channel1); - system.nodes[0]->network.udp_channels.modify (channel1, [&now](auto channel) { + node1.network.udp_channels.modify (channel1, [&now](auto channel) { channel->set_last_packet_received (now - std::chrono::seconds (1)); }); - auto channel2 (system.nodes[0]->network.udp_channels.insert (endpoint2, 0)); + auto channel2 (node1.network.udp_channels.insert (endpoint2, 0)); ASSERT_NE (nullptr, channel2); - system.nodes[0]->network.udp_channels.modify (channel2, [&now](auto channel) { + node1.network.udp_channels.modify (channel2, [&now](auto channel) { channel->set_last_packet_received (now + std::chrono::seconds (1)); }); - ASSERT_EQ (2, system.nodes[0]->network.size ()); - ASSERT_EQ (2, system.nodes[0]->network.udp_channels.size ()); - system.nodes[0]->network.cleanup (now); - ASSERT_EQ (1, system.nodes[0]->network.size ()); - ASSERT_EQ (1, system.nodes[0]->network.udp_channels.size ()); - auto list (system.nodes[0]->network.list (1)); + ASSERT_EQ (2, node1.network.size ()); + ASSERT_EQ (2, node1.network.udp_channels.size ()); + node1.network.cleanup (now); + ASSERT_EQ (1, node1.network.size ()); + ASSERT_EQ (1, node1.network.udp_channels.size ()); + auto list (node1.network.list (1)); ASSERT_EQ (endpoint2, list[0]->get_endpoint ()); } TEST (channels, fill_random_clear) { - nano::system system (24000, 1); + nano::system system (1); std::array target; std::fill (target.begin (), target.end (), nano::endpoint (boost::asio::ip::address_v6::loopback (), 10000)); system.nodes[0]->network.random_fill (target); @@ -87,7 +90,7 @@ TEST (channels, fill_random_clear) TEST (channels, fill_random_full) { - nano::system system (24000, 1); + nano::system system (1); for (uint16_t i (0u); i < 100u; ++i) { system.nodes[0]->network.udp_channels.insert (nano::endpoint (boost::asio::ip::address_v6::loopback (), i), 0); @@ -100,7 +103,7 @@ TEST (channels, fill_random_full) TEST (channels, fill_random_part) { - nano::system system (24000, 1); + nano::system system (1); std::array target; auto half (target.size () / 2); for (auto i (0); i < half; ++i) @@ -116,44 +119,70 @@ TEST (channels, fill_random_part) TEST (peer_container, list_fanout) { - nano::system system (24000, 1); - auto list1 (system.nodes[0]->network.list_fanout ()); + nano::system system (1); + auto & node (*system.nodes[0]); + ASSERT_EQ (0, node.network.size ()); + ASSERT_EQ (0.0, node.network.size_sqrt ()); + ASSERT_EQ (0, node.network.fanout ()); + auto list1 (node.network.list (node.network.fanout ())); ASSERT_TRUE (list1.empty ()); + auto add_peer = [&node](const uint16_t port_a) { + ASSERT_NE (nullptr, node.network.udp_channels.insert (nano::endpoint (boost::asio::ip::address_v6::loopback (), port_a), node.network_params.protocol.protocol_version)); + }; + add_peer (9998); + ASSERT_EQ (1, node.network.size ()); + ASSERT_EQ (1.f, node.network.size_sqrt ()); + ASSERT_EQ (1, node.network.fanout ()); + auto list2 (node.network.list (node.network.fanout ())); + ASSERT_EQ (1, list2.size ()); + add_peer (9999); + ASSERT_EQ (2, node.network.size ()); + ASSERT_EQ (std::sqrt (2.f), node.network.size_sqrt ()); + ASSERT_EQ (2, node.network.fanout ()); + auto list3 (node.network.list (node.network.fanout ())); + ASSERT_EQ (2, list3.size ()); for (auto i (0); i < 1000; ++i) { - ASSERT_NE (nullptr, system.nodes[0]->network.udp_channels.insert (nano::endpoint (boost::asio::ip::address_v6::loopback (), 10000 + i), system.nodes[0]->network_params.protocol.protocol_version)); + add_peer (10000 + i); } - auto list2 (system.nodes[0]->network.list_fanout ()); - ASSERT_EQ (32, list2.size ()); + ASSERT_EQ (1002, node.network.size ()); + ASSERT_EQ (std::sqrt (1002.f), node.network.size_sqrt ()); + size_t expected_size (std::ceil (std::sqrt (1002.f))); + ASSERT_EQ (expected_size, node.network.fanout ()); + auto list4 (node.network.list (node.network.fanout ())); + ASSERT_EQ (expected_size, list4.size ()); } // Test to make sure we don't repeatedly send keepalive messages to nodes that aren't responding TEST (peer_container, reachout) { - nano::system system (24000, 1); - nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), 24000); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + auto & node1 = *system.add_node (node_flags); + nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); // Make sure having been contacted by them already indicates we shouldn't reach out - system.nodes[0]->network.udp_channels.insert (endpoint0, system.nodes[0]->network_params.protocol.protocol_version); - ASSERT_TRUE (system.nodes[0]->network.reachout (endpoint0)); - nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), 24001); - ASSERT_FALSE (system.nodes[0]->network.reachout (endpoint1)); + node1.network.udp_channels.insert (endpoint0, node1.network_params.protocol.protocol_version); + ASSERT_TRUE (node1.network.reachout (endpoint0)); + nano::endpoint endpoint1 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); + ASSERT_FALSE (node1.network.reachout (endpoint1)); // Reaching out to them once should signal we shouldn't reach out again. - ASSERT_TRUE (system.nodes[0]->network.reachout (endpoint1)); + ASSERT_TRUE (node1.network.reachout (endpoint1)); // Make sure we don't purge new items - system.nodes[0]->network.cleanup (std::chrono::steady_clock::now () - std::chrono::seconds (10)); - ASSERT_TRUE (system.nodes[0]->network.reachout (endpoint1)); + node1.network.cleanup (std::chrono::steady_clock::now () - std::chrono::seconds (10)); + ASSERT_TRUE (node1.network.reachout (endpoint1)); // Make sure we purge old items - system.nodes[0]->network.cleanup (std::chrono::steady_clock::now () + std::chrono::seconds (10)); - ASSERT_FALSE (system.nodes[0]->network.reachout (endpoint1)); + node1.network.cleanup (std::chrono::steady_clock::now () + std::chrono::seconds (10)); + ASSERT_FALSE (node1.network.reachout (endpoint1)); } TEST (peer_container, depeer) { - nano::system system (24000, 1); - nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), 24001); + nano::system system (1); + nano::endpoint endpoint0 (boost::asio::ip::address_v6::loopback (), nano::get_available_port ()); nano::keepalive message; message.header.version_using = 1; - auto bytes (message.to_bytes ()); + auto bytes (message.to_bytes (false)); nano::message_buffer buffer = { bytes->data (), bytes->size (), endpoint0 }; system.nodes[0]->network.udp_channels.receive_action (&buffer); ASSERT_EQ (1, system.nodes[0]->stats.count (nano::stat::type::udp, nano::stat::detail::outdated_version)); diff --git a/nano/core_test/processor_service.cpp b/nano/core_test/processor_service.cpp index 81aec8ad49..85f5a5847c 100644 --- a/nano/core_test/processor_service.cpp +++ b/nano/core_test/processor_service.cpp @@ -1,12 +1,17 @@ +#include #include -#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include TEST (processor_service, bad_send_signature) { @@ -17,7 +22,7 @@ TEST (processor_service, bad_send_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); @@ -36,7 +41,7 @@ TEST (processor_service, bad_receive_signature) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::account_info info1; ASSERT_FALSE (store->account_get (transaction, nano::test_genesis_key.pub, info1)); diff --git a/nano/core_test/request_aggregator.cpp b/nano/core_test/request_aggregator.cpp new file mode 100644 index 0000000000..042b7cebb9 --- /dev/null +++ b/nano/core_test/request_aggregator.cpp @@ -0,0 +1,380 @@ +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +TEST (request_aggregator, one) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, node.aggregator.empty ()); + // Not yet in the ledger + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + // In the ledger but no vote generated yet + ASSERT_TIMELY (3s, 0 < node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TRUE (node.aggregator.empty ()); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + // Already cached + ASSERT_TIMELY (3s, node.aggregator.empty ()); + ASSERT_EQ (3, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} + +TEST (request_aggregator, one_update) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + std::vector> request; + request.emplace_back (genesis.hash (), genesis.open->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + node.aggregator.add (channel, request); + request.clear (); + request.emplace_back (send1->hash (), send1->root ()); + // Update the pool of requests with another hash + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + // In the ledger but no vote generated yet + ASSERT_TIMELY (3s, 0 < node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)) + ASSERT_TRUE (node.aggregator.empty ()); + ASSERT_EQ (2, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} + +TEST (request_aggregator, two) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + std::vector> request; + request.emplace_back (genesis.hash (), genesis.open->root ()); + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + // Process both blocks + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + // One vote should be generated for both blocks + ASSERT_TIMELY (3s, 0 < node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TRUE (node.aggregator.empty ()); + // The same request should now send the cached vote + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, node.aggregator.empty ()); + ASSERT_EQ (2, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + // Make sure the cached vote is for both hashes + auto vote2 (node.votes_cache.find (genesis.hash ())); + auto vote1 (node.votes_cache.find (send1->hash ())); + ASSERT_EQ (1, vote1.size ()); + ASSERT_EQ (1, vote2.size ()); + ASSERT_EQ (vote1.front (), vote2.front ()); +} + +TEST (request_aggregator, two_endpoints) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_rep_crawler = true; + auto & node1 (*system.add_node (node_config, node_flags)); + node_config.peering_port = nano::get_available_port (); + auto & node2 (*system.add_node (node_config, node_flags)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (genesis.hash ()))); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (node1.store.tx_begin_write (), *send1).code); + auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); + auto channel2 (node2.network.udp_channels.create (node2.network.endpoint ())); + ASSERT_NE (nano::transport::map_endpoint_to_v6 (channel1->get_endpoint ()), nano::transport::map_endpoint_to_v6 (channel2->get_endpoint ())); + // Use the aggregator from node1 only, making requests from both nodes + node1.aggregator.add (channel1, request); + node1.aggregator.add (channel2, request); + ASSERT_EQ (2, node1.aggregator.size ()); + // For the first request it generates the vote, for the second it uses the generated vote + ASSERT_TIMELY (3s, node1.aggregator.empty ()); + ASSERT_EQ (2, node1.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 0 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 1 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 1 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TIMELY (3s, 1 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); + ASSERT_TIMELY (3s, 1 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_TIMELY (3s, 0 == node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); +} + +TEST (request_aggregator, split) +{ + constexpr size_t max_vbh = nano::network::confirm_ack_hashes_max; + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + std::vector> request; + std::vector> blocks; + auto previous = genesis.hash (); + // Add max_vbh + 1 blocks and request votes for them + for (size_t i (0); i <= max_vbh; ++i) + { + nano::block_builder builder; + blocks.push_back (builder + .state () + .account (nano::test_genesis_key.pub) + .previous (previous) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - (i + 1)) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (previous)) + .build ()); + auto const & block = blocks.back (); + previous = block->hash (); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *block).code); + request.emplace_back (block->hash (), block->root ()); + } + // Confirm all blocks + node.block_confirm (blocks.back ()); + { + auto election (node.active.election (blocks.back ()->qualified_root ())); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (5s, max_vbh + 2 == node.ledger.cache.cemented_count); + ASSERT_EQ (max_vbh + 1, request.size ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + // In the ledger but no vote generated yet + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TRUE (node.aggregator.empty ()); + // Two votes were sent, the first one for 12 hashes and the second one for 1 hash + ASSERT_EQ (1, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 13 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); + ASSERT_TIMELY (3s, 0 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} + +TEST (request_aggregator, channel_lifetime) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + { + // The aggregator should extend the lifetime of the channel + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + } + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, 0 < node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); +} + +TEST (request_aggregator, channel_update) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + std::weak_ptr channel1_w; + { + auto channel1 (node.network.udp_channels.create (node.network.endpoint ())); + channel1_w = channel1; + node.aggregator.add (channel1, request); + auto channel2 (node.network.udp_channels.create (node.network.endpoint ())); + // The aggregator then hold channel2 and drop channel1 + node.aggregator.add (channel2, request); + } + // Both requests were for the same endpoint, so only one pool should exist + ASSERT_EQ (1, node.aggregator.size ()); + // channel1 is not being held anymore + ASSERT_EQ (nullptr, channel1_w.lock ()); + ASSERT_TIMELY (3s, 0 < node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0); +} + +TEST (request_aggregator, channel_max_queue) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.max_queued_requests = 1; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); +} + +TEST (request_aggregator, unique) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); +} + +TEST (request_aggregator, cannot_vote) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node (*system.add_node (flags)); + nano::genesis genesis; + nano::state_block_builder builder; + std::shared_ptr send1 = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 1) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + std::shared_ptr send2 = builder.make_block () + .from (*send1) + .previous (send1->hash ()) + .balance (send1->balance ().number () - 1) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send1->hash ())) + .build (); + ASSERT_EQ (nano::process_result::progress, node.process (*send1).code); + ASSERT_EQ (nano::process_result::progress, node.process (*send2).code); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_FALSE (node.ledger.can_vote (node.store.tx_begin_read (), *send2)); + + std::vector> request; + // Correct hash, correct root + request.emplace_back (send2->hash (), send2->root ()); + // Incorrect hash, correct root + request.emplace_back (1, send2->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, node.aggregator.empty ()); + ASSERT_EQ (1, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + + // With an ongoing election + node.block_confirm (send2); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, node.aggregator.empty ()); + ASSERT_EQ (2, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_TIMELY (3s, 4 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + + // Confirm send1 + node.block_confirm (send1); + { + auto election (node.active.election (send1->qualified_root ())); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + ASSERT_TIMELY (3s, node.ledger.can_vote (node.store.tx_begin_read (), *send2)); + node.aggregator.add (channel, request); + ASSERT_EQ (1, node.aggregator.size ()); + ASSERT_TIMELY (3s, node.aggregator.empty ()); + ASSERT_EQ (3, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (4, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote)); + ASSERT_TIMELY (3s, 2 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); +} diff --git a/nano/core_test/signing.cpp b/nano/core_test/signing.cpp index c485b9b88a..30704623f1 100644 --- a/nano/core_test/signing.cpp +++ b/nano/core_test/signing.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include @@ -72,7 +73,7 @@ TEST (signature_checker, many_multi_threaded) for (int i = 0; i < num_check_sizes; ++i) { auto check_size = check_sizes[i]; - assert (check_size > 0); + ASSERT_GT (check_size, 0); auto last_signature_index = check_size - 1; messages[i].resize (check_size); @@ -146,3 +147,55 @@ TEST (signature_checker, one) block.signature.bytes[31] ^= 0x1; verify_block (block, 1); } + +TEST (signature_checker, boundary_checks) +{ + // sizes container must be in incrementing order + std::vector sizes{ 0, 1 }; + auto add_boundary = [&sizes](size_t boundary) { + sizes.insert (sizes.end (), { boundary - 1, boundary, boundary + 1 }); + }; + + for (auto i = 1; i <= 5; ++i) + { + add_boundary (nano::signature_checker::batch_size * i); + } + + nano::signature_checker checker (1); + auto max_size = *(sizes.end () - 1); + std::vector hashes; + hashes.reserve (max_size); + std::vector messages; + messages.reserve (max_size); + std::vector lengths; + lengths.reserve (max_size); + std::vector pub_keys; + pub_keys.reserve (max_size); + std::vector signatures; + signatures.reserve (max_size); + nano::keypair key; + nano::state_block block (key.pub, 0, key.pub, 0, 0, key.prv, key.pub, 0); + + auto last_size = 0; + for (auto size : sizes) + { + // The size needed to append to existing containers, saves re-initializing from scratch each iteration + auto extra_size = size - last_size; + + std::vector verifications; + verifications.resize (size); + for (auto i (0); i < extra_size; ++i) + { + hashes.push_back (block.hash ()); + messages.push_back (hashes.back ().bytes.data ()); + lengths.push_back (sizeof (decltype (hashes)::value_type)); + pub_keys.push_back (block.hashables.account.bytes.data ()); + signatures.push_back (block.signature.bytes.data ()); + } + nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () }; + checker.verify (check); + bool all_valid = std::all_of (verifications.cbegin (), verifications.cend (), [](auto verification) { return verification == 1; }); + ASSERT_TRUE (all_valid); + last_size = size; + } +} diff --git a/nano/core_test/socket.cpp b/nano/core_test/socket.cpp index 10d3b102b4..b16552ee6a 100644 --- a/nano/core_test/socket.cpp +++ b/nano/core_test/socket.cpp @@ -1,18 +1,83 @@ #include +#include #include #include #include -#include - using namespace std::chrono_literals; +TEST (socket, drop_policy) +{ + auto node_flags = nano::inactive_node_flag_defaults (); + node_flags.read_only = false; + nano::inactive_node inactivenode (nano::unique_path (), node_flags); + auto node = inactivenode.node; + + nano::thread_runner runner (node->io_ctx, 1); + + std::vector> connections; + + // We're going to write twice the queue size + 1, and the server isn't reading + // The total number of drops should thus be 1 (the socket allows doubling the queue size for no_socket_drop) + size_t max_write_queue_size = 0; + { + auto client_dummy (std::make_shared (node, boost::none, nano::socket::concurrency::multi_writer)); + max_write_queue_size = client_dummy->get_max_write_queue_size (); + } + + auto func = [&](size_t total_message_count, nano::buffer_drop_policy drop_policy) { + auto server_port (nano::get_available_port ()); + boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::any (), server_port); + + auto server_socket (std::make_shared (node, endpoint, 1, nano::socket::concurrency::multi_writer)); + boost::system::error_code ec; + server_socket->start (ec); + ASSERT_FALSE (ec); + + // Accept connection, but don't read so the writer will drop. + server_socket->on_connection ([&connections](std::shared_ptr new_connection, boost::system::error_code const & ec_a) { + connections.push_back (new_connection); + return true; + }); + + auto client (std::make_shared (node, boost::none, nano::socket::concurrency::multi_writer)); + nano::util::counted_completion write_completion (total_message_count); + + client->async_connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v4::loopback (), server_port), + [client, total_message_count, node, &write_completion, &drop_policy](boost::system::error_code const & ec_a) { + for (int i = 0; i < total_message_count; i++) + { + std::vector buff (1); + client->async_write ( + nano::shared_const_buffer (std::move (buff)), [&write_completion](boost::system::error_code const & ec, size_t size_a) { + write_completion.increment (); + }, + drop_policy); + } + }); + write_completion.await_count_for (std::chrono::seconds (5)); + }; + + func (max_write_queue_size * 2 + 1, nano::buffer_drop_policy::no_socket_drop); + ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out)); + + func (max_write_queue_size + 1, nano::buffer_drop_policy::limiter); + // The stats are accumulated from before + ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out)); + + node->stop (); + runner.stop_event_processing (); + runner.join (); +} + TEST (socket, concurrent_writes) { auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node inactivenode (nano::unique_path (), 24000, node_flags); + nano::inactive_node inactivenode (nano::unique_path (), node_flags); auto node = inactivenode.node; // This gives more realistic execution than using system#poll, allowing writes to diff --git a/nano/core_test/telemetry.cpp b/nano/core_test/telemetry.cpp new file mode 100644 index 0000000000..c300ca01d7 --- /dev/null +++ b/nano/core_test/telemetry.cpp @@ -0,0 +1,652 @@ +#include +#include +#include +#include + +#include + +#include + +using namespace std::chrono_literals; + +TEST (telemetry, consolidate_data) +{ + auto time = 1582117035109; + + // Pick specific values so that we can check both mode and average are working correctly + nano::telemetry_data data; + data.account_count = 2; + data.block_count = 1; + data.cemented_count = 1; + data.protocol_version = 12; + data.peer_count = 2; + data.bandwidth_cap = 100; + data.unchecked_count = 3; + data.uptime = 6; + data.genesis_block = nano::block_hash (3); + data.major_version = 20; + data.minor_version = 1; + data.patch_version = 4; + data.pre_release_version = 6; + data.maker = 2; + data.timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (time)); + data.active_difficulty = 2; + + nano::telemetry_data data1; + data1.account_count = 5; + data1.block_count = 7; + data1.cemented_count = 4; + data1.protocol_version = 11; + data1.peer_count = 5; + data1.bandwidth_cap = 0; + data1.unchecked_count = 1; + data1.uptime = 10; + data1.genesis_block = nano::block_hash (4); + data1.major_version = 10; + data1.minor_version = 2; + data1.patch_version = 3; + data1.pre_release_version = 6; + data1.maker = 2; + data1.timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (time + 1)); + data1.active_difficulty = 3; + + nano::telemetry_data data2; + data2.account_count = 3; + data2.block_count = 3; + data2.cemented_count = 2; + data2.protocol_version = 11; + data2.peer_count = 4; + data2.bandwidth_cap = 0; + data2.unchecked_count = 2; + data2.uptime = 3; + data2.genesis_block = nano::block_hash (4); + data2.major_version = 20; + data2.minor_version = 1; + data2.patch_version = 4; + data2.pre_release_version = 6; + data2.maker = 2; + data2.timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (time)); + data2.active_difficulty = 2; + + std::vector all_data{ data, data1, data2 }; + + auto consolidated_telemetry_data = nano::consolidate_telemetry_data (all_data); + ASSERT_EQ (consolidated_telemetry_data.account_count, 3); + ASSERT_EQ (consolidated_telemetry_data.block_count, 3); + ASSERT_EQ (consolidated_telemetry_data.cemented_count, 2); + ASSERT_EQ (consolidated_telemetry_data.protocol_version, 11); + ASSERT_EQ (consolidated_telemetry_data.peer_count, 3); + ASSERT_EQ (consolidated_telemetry_data.bandwidth_cap, 0); + ASSERT_EQ (consolidated_telemetry_data.unchecked_count, 2); + ASSERT_EQ (consolidated_telemetry_data.uptime, 6); + ASSERT_EQ (consolidated_telemetry_data.genesis_block, nano::block_hash (4)); + ASSERT_EQ (consolidated_telemetry_data.major_version, 20); + ASSERT_EQ (consolidated_telemetry_data.minor_version, 1); + ASSERT_EQ (consolidated_telemetry_data.patch_version, 4); + ASSERT_EQ (consolidated_telemetry_data.pre_release_version, 6); + ASSERT_EQ (consolidated_telemetry_data.maker, 2); + ASSERT_EQ (consolidated_telemetry_data.timestamp, std::chrono::system_clock::time_point (std::chrono::milliseconds (time))); + ASSERT_EQ (consolidated_telemetry_data.active_difficulty, 2); + + // Modify the metrics which may be either the mode or averages to ensure all are tested. + all_data[2].bandwidth_cap = 53; + all_data[2].protocol_version = 13; + all_data[2].genesis_block = nano::block_hash (3); + all_data[2].major_version = 10; + all_data[2].minor_version = 2; + all_data[2].patch_version = 3; + all_data[2].pre_release_version = 6; + all_data[2].maker = 2; + + auto consolidated_telemetry_data1 = nano::consolidate_telemetry_data (all_data); + ASSERT_EQ (consolidated_telemetry_data1.major_version, 10); + ASSERT_EQ (consolidated_telemetry_data1.minor_version, 2); + ASSERT_EQ (consolidated_telemetry_data1.patch_version, 3); + ASSERT_EQ (consolidated_telemetry_data1.pre_release_version, 6); + ASSERT_EQ (consolidated_telemetry_data1.maker, 2); + ASSERT_TRUE (consolidated_telemetry_data1.protocol_version == 11 || consolidated_telemetry_data1.protocol_version == 12 || consolidated_telemetry_data1.protocol_version == 13); + ASSERT_EQ (consolidated_telemetry_data1.bandwidth_cap, 51); + ASSERT_EQ (consolidated_telemetry_data1.genesis_block, nano::block_hash (3)); + + // Test equality operator + ASSERT_FALSE (consolidated_telemetry_data == consolidated_telemetry_data1); + ASSERT_EQ (consolidated_telemetry_data, consolidated_telemetry_data); +} + +TEST (telemetry, consolidate_data_remove_outliers) +{ + nano::telemetry_data data; + data.account_count = 2; + data.block_count = 1; + data.cemented_count = 1; + data.protocol_version = 12; + data.peer_count = 2; + data.bandwidth_cap = 100; + data.unchecked_count = 3; + data.uptime = 6; + data.genesis_block = nano::block_hash (3); + data.major_version = 20; + data.minor_version = 1; + data.patch_version = 5; + data.pre_release_version = 2; + data.maker = 1; + data.timestamp = std::chrono::system_clock::time_point (100ms); + data.active_difficulty = 10; + + // Insert 20 of these, and 2 outliers at the lower and upper bounds which should get removed + std::vector all_data (20, data); + + // Insert some outliers + nano::telemetry_data lower_bound_outlier_data; + lower_bound_outlier_data.account_count = 1; + lower_bound_outlier_data.block_count = 0; + lower_bound_outlier_data.cemented_count = 0; + lower_bound_outlier_data.protocol_version = 11; + lower_bound_outlier_data.peer_count = 0; + lower_bound_outlier_data.bandwidth_cap = 8; + lower_bound_outlier_data.unchecked_count = 1; + lower_bound_outlier_data.uptime = 2; + lower_bound_outlier_data.genesis_block = nano::block_hash (2); + lower_bound_outlier_data.major_version = 11; + lower_bound_outlier_data.minor_version = 1; + lower_bound_outlier_data.patch_version = 1; + lower_bound_outlier_data.pre_release_version = 1; + lower_bound_outlier_data.maker = 1; + lower_bound_outlier_data.timestamp = std::chrono::system_clock::time_point (1ms); + lower_bound_outlier_data.active_difficulty = 1; + all_data.push_back (lower_bound_outlier_data); + all_data.push_back (lower_bound_outlier_data); + + nano::telemetry_data upper_bound_outlier_data; + upper_bound_outlier_data.account_count = 99; + upper_bound_outlier_data.block_count = 99; + upper_bound_outlier_data.cemented_count = 99; + upper_bound_outlier_data.protocol_version = 99; + upper_bound_outlier_data.peer_count = 99; + upper_bound_outlier_data.bandwidth_cap = 999; + upper_bound_outlier_data.unchecked_count = 99; + upper_bound_outlier_data.uptime = 999; + upper_bound_outlier_data.genesis_block = nano::block_hash (99); + upper_bound_outlier_data.major_version = 99; + upper_bound_outlier_data.minor_version = 9; + upper_bound_outlier_data.patch_version = 9; + upper_bound_outlier_data.pre_release_version = 9; + upper_bound_outlier_data.maker = 9; + upper_bound_outlier_data.timestamp = std::chrono::system_clock::time_point (999ms); + upper_bound_outlier_data.active_difficulty = 99; + all_data.push_back (upper_bound_outlier_data); + all_data.push_back (upper_bound_outlier_data); + + auto consolidated_telemetry_data = nano::consolidate_telemetry_data (all_data); + ASSERT_EQ (data, consolidated_telemetry_data); +} + +TEST (telemetry, signatures) +{ + nano::keypair node_id; + nano::telemetry_data data; + data.node_id = node_id.pub; + data.major_version = 20; + data.minor_version = 1; + data.patch_version = 5; + data.pre_release_version = 2; + data.maker = 1; + data.timestamp = std::chrono::system_clock::time_point (100ms); + data.sign (node_id); + ASSERT_FALSE (data.validate_signature (nano::telemetry_data::size)); + auto signature = data.signature; + // Check that the signature is different if changing a piece of data + data.maker = 2; + data.sign (node_id); + ASSERT_NE (data.signature, signature); +} + +TEST (telemetry, no_peers) +{ + nano::system system (1); + + auto responses = system.nodes[0]->telemetry->get_metrics (); + ASSERT_TRUE (responses.empty ()); +} + +TEST (telemetry, basic) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_ongoing_telemetry_requests = true; + node_flags.disable_initial_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + // Request telemetry metrics + nano::telemetry_data telemetry_data; + auto server_endpoint = node_server->network.endpoint (); + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + { + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &server_endpoint, &telemetry_data](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + ASSERT_EQ (server_endpoint, response_a.endpoint); + telemetry_data = response_a.telemetry_data; + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + // Check the metrics are correct + nano::compare_default_telemetry_response_data (telemetry_data, node_server->network_params, node_server->config.bandwidth_limit, node_server->active.active_difficulty (), node_server->node_id); + + // Call again straight away. It should use the cache + { + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &telemetry_data](nano::telemetry_data_response const & response_a) { + ASSERT_EQ (telemetry_data, response_a.telemetry_data); + ASSERT_FALSE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + // Wait the cache period and check cache is not used + std::this_thread::sleep_for (nano::telemetry_cache_cutoffs::test); + + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &telemetry_data](nano::telemetry_data_response const & response_a) { + ASSERT_NE (telemetry_data, response_a.telemetry_data); + ASSERT_FALSE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (telemetry, receive_from_non_listening_channel) +{ + nano::system system; + auto node = system.add_node (); + nano::telemetry_ack message (nano::telemetry_data{}); + node->network.process_message (message, node->network.udp_channels.create (node->network.endpoint ())); + // We have not sent a telemetry_req message to this endpoint, so shouldn't count telemetry_ack received from it. + ASSERT_EQ (node->telemetry->telemetry_data_size (), 0); +} + +TEST (telemetry, over_udp) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_tcp_realtime = true; + node_flags.disable_udp = false; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + std::atomic done{ false }; + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &node_server](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + nano::compare_default_telemetry_response_data (response_a.telemetry_data, node_server->network_params, node_server->config.bandwidth_limit, node_server->active.active_difficulty (), node_server->node_id); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Check channels are indeed udp + ASSERT_EQ (1, node_client->network.size ()); + auto list1 (node_client->network.list (2)); + ASSERT_EQ (node_server->network.endpoint (), list1[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list1[0]->get_type ()); + ASSERT_EQ (1, node_server->network.size ()); + auto list2 (node_server->network.list (2)); + ASSERT_EQ (node_client->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list2[0]->get_type ()); +} + +TEST (telemetry, invalid_channel) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (nullptr, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_TRUE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (telemetry, blocking_request) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + wait_peer_connections (system); + + // Request telemetry metrics + std::atomic done{ false }; + std::function call_system_poll; + std::promise promise; + call_system_poll = [&call_system_poll, &worker = node_client->worker, &done, &system, &promise]() { + if (!done) + { + ASSERT_NO_ERROR (system.poll ()); + worker.push_task (call_system_poll); + } + else + { + promise.set_value (); + } + }; + + // Keep pushing system.polls in another thread (worker), because we will be blocking this thread and unable to do so. + system.deadline_set (10s); + node_client->worker.push_task (call_system_poll); + + // Now try single request metric + auto telemetry_data_response = node_client->telemetry->get_metrics_single_peer (node_client->network.find_channel (node_server->network.endpoint ())); + ASSERT_FALSE (telemetry_data_response.error); + nano::compare_default_telemetry_response_data (telemetry_data_response.telemetry_data, node_server->network_params, node_server->config.bandwidth_limit, node_server->active.active_difficulty (), node_server->node_id); + + done = true; + promise.get_future ().wait (); +} + +TEST (telemetry, disconnects) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + // Try and request metrics from a node which is turned off but a channel is not closed yet + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + node_server->stop (); + ASSERT_TRUE (channel); + + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_TRUE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (telemetry, dos_tcp) +{ + // Confirm that telemetry_reqs are not processed + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_ongoing_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + nano::telemetry_req message; + auto channel = node_client->network.tcp_channels.find_channel (nano::transport::map_endpoint_to_tcp (node_server->network.endpoint ())); + channel->send (message, [](boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + + system.deadline_set (10s); + while (1 != node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto orig = std::chrono::steady_clock::now (); + for (int i = 0; i < 10; ++i) + { + channel->send (message, [](boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + } + + system.deadline_set (10s); + while ((nano::telemetry_cache_cutoffs::test + orig) > std::chrono::steady_clock::now ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Should process no more telemetry_req messages + ASSERT_EQ (1, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + + // Now spam messages waiting for it to be processed + while (node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in) == 1) + { + channel->send (message); + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (telemetry, dos_udp) +{ + // Confirm that telemetry_reqs are not processed + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + node_flags.disable_tcp_realtime = true; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_ongoing_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + nano::telemetry_req message; + auto channel (node_client->network.udp_channels.create (node_server->network.endpoint ())); + channel->send (message, [](boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + + system.deadline_set (20s); + while (1 != node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto orig = std::chrono::steady_clock::now (); + for (int i = 0; i < 10; ++i) + { + channel->send (message, [](boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + } + + system.deadline_set (20s); + while ((nano::telemetry_cache_cutoffs::test + orig) > std::chrono::steady_clock::now ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Should process no more telemetry_req messages + ASSERT_EQ (1, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + + // Now spam messages waiting for it to be processed + system.deadline_set (20s); + while (node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in) == 1) + { + channel->send (message); + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (telemetry, disable_metrics) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + node_flags.disable_providing_telemetry_metrics = true; + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + // Try and request metrics from a node which is turned off but a channel is not closed yet + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + ASSERT_TRUE (channel); + + std::atomic done{ false }; + node_client->telemetry->get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_TRUE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // It should still be able to receive metrics though + done = false; + auto channel1 = node_server->network.find_channel (node_client->network.endpoint ()); + node_server->telemetry->get_metrics_single_peer_async (channel1, [&done, node_client](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + nano::compare_default_telemetry_response_data (response_a.telemetry_data, node_client->network_params, node_client->config.bandwidth_limit, node_client->active.active_difficulty (), node_client->node_id); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +namespace nano +{ +TEST (telemetry, remove_peer_different_genesis) +{ + nano::system system (1); + auto node0 (system.nodes[0]); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + // Change genesis block to something else in this test (this is the reference telemetry processing uses). + // Possible TSAN issue in the future if something else uses this, but will only appear in tests. + node1->network_params.ledger.genesis_hash = nano::block_hash ("0"); + node1->start (); + system.nodes.push_back (node1); + node0->network.merge_peer (node1->network.endpoint ()); + node1->network.merge_peer (node0->network.endpoint ()); + ASSERT_TIMELY (10s, node0->stats.count (nano::stat::type::telemetry, nano::stat::detail::different_genesis_hash) != 0 && node1->stats.count (nano::stat::type::telemetry, nano::stat::detail::different_genesis_hash) != 0); + + ASSERT_TIMELY (1s, 0 == node0->network.size ()); + ASSERT_TIMELY (1s, 0 == node1->network.size ()); + ASSERT_EQ (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::out), 1); + ASSERT_EQ (node1->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::out), 1); + + nano::lock_guard guard (node0->network.excluded_peers.mutex); + ASSERT_EQ (1, node0->network.excluded_peers.peers.get ().count (node1->network.endpoint ().address ())); + ASSERT_EQ (1, node1->network.excluded_peers.peers.get ().count (node0->network.endpoint ().address ())); +} + +// Peer exclusion is only fully supported for TCP-only nodes; peers can still reconnect through UDP +TEST (telemetry, remove_peer_different_genesis_udp) +{ + nano::node_flags node_flags; + node_flags.disable_udp = false; + node_flags.disable_tcp_realtime = true; + node_flags.disable_ongoing_telemetry_requests = true; + nano::system system (1, nano::transport::transport_type::udp, node_flags); + auto node0 (system.nodes[0]); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work, node_flags)); + node1->network_params.ledger.genesis_hash = nano::block_hash ("0"); + node1->start (); + system.nodes.push_back (node1); + auto channel0 (std::make_shared (node1->network.udp_channels, node0->network.endpoint (), node0->network_params.protocol.protocol_version)); + auto channel1 (std::make_shared (node0->network.udp_channels, node1->network.endpoint (), node1->network_params.protocol.protocol_version)); + node0->network.send_keepalive (channel1); + node1->network.send_keepalive (channel0); + + ASSERT_TIMELY (10s, node0->network.udp_channels.size () != 0 && node1->network.udp_channels.size () != 0); + ASSERT_EQ (node0->network.tcp_channels.size (), 0); + ASSERT_EQ (node1->network.tcp_channels.size (), 0); + + std::atomic done0{ false }; + std::atomic done1{ false }; + + node0->telemetry->get_metrics_single_peer_async (channel1, [&done0](nano::telemetry_data_response const & response_a) { + done0 = true; + }); + + node1->telemetry->get_metrics_single_peer_async (channel0, [&done1](nano::telemetry_data_response const & response_a) { + done1 = true; + }); + + ASSERT_TIMELY (10s, done0 && done1); + + ASSERT_EQ (node0->network.tcp_channels.size (), 0); + ASSERT_EQ (node1->network.tcp_channels.size (), 0); + + nano::lock_guard guard (node0->network.excluded_peers.mutex); + ASSERT_EQ (1, node0->network.excluded_peers.peers.get ().count (node1->network.endpoint ().address ())); + ASSERT_EQ (1, node1->network.excluded_peers.peers.get ().count (node0->network.endpoint ().address ())); +} + +TEST (telemetry, remove_peer_invalid_signature) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_udp = false; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_ongoing_telemetry_requests = true; + auto node = system.add_node (node_flags); + + auto channel = node->network.udp_channels.create (node->network.endpoint ()); + channel->set_node_id (node->node_id.pub); + // (Implementation detail) So that messages are not just discarded when requests were not sent. + node->telemetry->recent_or_initial_request_telemetry_data.emplace (channel->get_endpoint (), nano::telemetry_data (), std::chrono::steady_clock::now (), true); + + auto telemetry_data = nano::local_telemetry_data (node->ledger.cache, node->network, node->config.bandwidth_limit, node->network_params, node->startup_time, node->active.active_difficulty (), node->node_id); + // Change anything so that the signed message is incorrect + telemetry_data.block_count = 0; + auto telemetry_ack = nano::telemetry_ack (telemetry_data); + node->network.process_message (telemetry_ack, channel); + + ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::invalid_signature) > 0); + ASSERT_NO_ERROR (system.poll_until_true (3s, [&node, address = channel->get_endpoint ().address ()]() -> bool { + nano::lock_guard guard (node->network.excluded_peers.mutex); + return node->network.excluded_peers.peers.get ().count (address); + })); +} +} diff --git a/nano/core_test/testutil.hpp b/nano/core_test/testutil.hpp index 25b4034e69..469edd4071 100644 --- a/nano/core_test/testutil.hpp +++ b/nano/core_test/testutil.hpp @@ -1,10 +1,18 @@ #pragma once +#include #include -#include + +// This test header is unfortunately included in many different files. +// Requires guarding in some cases. +#ifndef IGNORE_GTEST_INCL +#include + +#include +#endif #include -#include +#include #include #include @@ -32,16 +40,26 @@ GTEST_TEST_ERROR_CODE ((condition.value () > 0), #condition, "An error was expected", "", \ GTEST_FATAL_FAILURE_) +/** Asserts that the condition becomes true within the deadline */ +#define ASSERT_TIMELY(time, condition) \ + system.deadline_set (time); \ + while (!(condition)) \ + { \ + ASSERT_NO_ERROR (system.poll ()); \ + } + /* Convenience globals for core_test */ namespace nano { using uint128_t = boost::multiprecision::uint128_t; class keypair; class public_key; +class block_hash; extern nano::keypair const & zero_key; extern nano::keypair const & test_genesis_key; extern std::string const & nano_test_genesis; extern std::string const & genesis_block; +extern nano::block_hash const & genesis_hash; extern nano::public_key const & nano_test_account; extern nano::public_key const & genesis_account; extern nano::public_key const & burn_account; @@ -51,7 +69,7 @@ class stringstream_mt_sink : public boost::iostreams::sink { public: stringstream_mt_sink () = default; - stringstream_mt_sink (const stringstream_mt_sink & sink) + stringstream_mt_sink (stringstream_mt_sink const & sink) { nano::lock_guard guard (mutex); ss << sink.ss.str (); @@ -185,9 +203,72 @@ namespace util return val; } + void increment_required_count () + { + ++required_count; + } + private: std::atomic count{ 0 }; - unsigned required_count; + std::atomic required_count; }; } + +inline uint16_t get_available_port () +{ + // Maximum possible sockets which may feasibly be used in 1 test + constexpr auto max = 200; + static uint16_t current = 0; + // Read the TEST_BASE_PORT environment and override the default base port if it exists + auto base_str = std::getenv ("TEST_BASE_PORT"); + uint16_t base_port = 24000; + if (base_str) + { + base_port = boost::lexical_cast (base_str); + } + + uint16_t const available_port = base_port + current; + ++current; + // Reset port number once we have reached the maximum + if (current == max) + { + current = 0; + } + + return available_port; +} + +#ifndef IGNORE_GTEST_INCL +inline void compare_default_telemetry_response_data_excluding_signature (nano::telemetry_data const & telemetry_data_a, nano::network_params const & network_params_a, uint64_t bandwidth_limit_a, uint64_t active_difficulty_a) +{ + ASSERT_EQ (telemetry_data_a.block_count, 1); + ASSERT_EQ (telemetry_data_a.cemented_count, 1); + ASSERT_EQ (telemetry_data_a.bandwidth_cap, bandwidth_limit_a); + ASSERT_EQ (telemetry_data_a.peer_count, 1); + ASSERT_EQ (telemetry_data_a.protocol_version, network_params_a.protocol.telemetry_protocol_version_min); + ASSERT_EQ (telemetry_data_a.unchecked_count, 0); + ASSERT_EQ (telemetry_data_a.account_count, 1); + ASSERT_LT (telemetry_data_a.uptime, 100); + ASSERT_EQ (telemetry_data_a.genesis_block, network_params_a.ledger.genesis_hash); + ASSERT_EQ (telemetry_data_a.major_version, nano::get_major_node_version ()); + ASSERT_EQ (telemetry_data_a.minor_version, nano::get_minor_node_version ()); + ASSERT_EQ (telemetry_data_a.patch_version, nano::get_patch_node_version ()); + ASSERT_EQ (telemetry_data_a.pre_release_version, nano::get_pre_release_node_version ()); + ASSERT_EQ (telemetry_data_a.maker, 0); + ASSERT_GT (telemetry_data_a.timestamp, std::chrono::system_clock::now () - std::chrono::seconds (100)); + ASSERT_EQ (telemetry_data_a.active_difficulty, active_difficulty_a); +} + +inline void compare_default_telemetry_response_data (nano::telemetry_data const & telemetry_data_a, nano::network_params const & network_params_a, uint64_t bandwidth_limit_a, uint64_t active_difficulty_a, nano::keypair const & node_id_a) +{ + ASSERT_FALSE (telemetry_data_a.validate_signature (nano::telemetry_data::size)); + nano::telemetry_data telemetry_data_l = telemetry_data_a; + telemetry_data_l.signature.clear (); + telemetry_data_l.sign (node_id_a); + // Signature should be different because uptime/timestamp will have changed. + ASSERT_NE (telemetry_data_a.signature, telemetry_data_l.signature); + compare_default_telemetry_response_data_excluding_signature (telemetry_data_a, network_params_a, bandwidth_limit_a, active_difficulty_a); + ASSERT_EQ (telemetry_data_a.node_id, node_id_a.pub); +} +#endif } diff --git a/nano/core_test/timer.cpp b/nano/core_test/timer.cpp index ffd65f46b2..1aa29739cb 100644 --- a/nano/core_test/timer.cpp +++ b/nano/core_test/timer.cpp @@ -42,6 +42,8 @@ TEST (timer, measure_and_compare) ASSERT_LT (t1.since_start (), 200ms); ASSERT_GT (t1.since_start (), 10ms); ASSERT_GE (t1.stop (), 50ms); + std::this_thread::sleep_for (50ms); + ASSERT_GT (t1.restart (), 10ms); } TEST (timer, cummulative_child) diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index e403d0b454..df5b2ca33e 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -104,6 +104,8 @@ TEST (toml, rpc_config_deserialize_defaults) ASSERT_EQ (conf.rpc_process.ipc_address, defaults.rpc_process.ipc_address); ASSERT_EQ (conf.rpc_process.ipc_port, defaults.rpc_process.ipc_port); ASSERT_EQ (conf.rpc_process.num_ipc_connections, defaults.rpc_process.num_ipc_connections); + + ASSERT_EQ (conf.rpc_logging.log_rpc, defaults.rpc_logging.log_rpc); } /** Empty config file should match a default config object */ @@ -120,6 +122,7 @@ TEST (toml, daemon_config_deserialize_defaults) [node.statistics.log] [node.statistics.sampling] [node.websocket] + [node.lmdb] [node.rocksdb] [opencl] [rpc] @@ -147,9 +150,11 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.allow_local_peers, defaults.node.allow_local_peers); ASSERT_EQ (conf.node.backup_before_upgrade, defaults.node.backup_before_upgrade); ASSERT_EQ (conf.node.bandwidth_limit, defaults.node.bandwidth_limit); + ASSERT_EQ (conf.node.bandwidth_limit_burst_ratio, defaults.node.bandwidth_limit_burst_ratio); ASSERT_EQ (conf.node.block_processor_batch_max_time, defaults.node.block_processor_batch_max_time); ASSERT_EQ (conf.node.bootstrap_connections, defaults.node.bootstrap_connections); ASSERT_EQ (conf.node.bootstrap_connections_max, defaults.node.bootstrap_connections_max); + ASSERT_EQ (conf.node.bootstrap_initiator_threads, defaults.node.bootstrap_initiator_threads); ASSERT_EQ (conf.node.bootstrap_fraction_numerator, defaults.node.bootstrap_fraction_numerator); ASSERT_EQ (conf.node.conf_height_processor_batch_min_time, defaults.node.conf_height_processor_batch_min_time); ASSERT_EQ (conf.node.confirmation_history_size, defaults.node.confirmation_history_size); @@ -157,7 +162,7 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.external_address, defaults.node.external_address); ASSERT_EQ (conf.node.external_port, defaults.node.external_port); ASSERT_EQ (conf.node.io_threads, defaults.node.io_threads); - ASSERT_EQ (conf.node.lmdb_max_dbs, defaults.node.lmdb_max_dbs); + ASSERT_EQ (conf.node.deprecated_lmdb_max_dbs, defaults.node.deprecated_lmdb_max_dbs); ASSERT_EQ (conf.node.max_work_generate_multiplier, defaults.node.max_work_generate_multiplier); ASSERT_EQ (conf.node.network_threads, defaults.node.network_threads); ASSERT_EQ (conf.node.secondary_work_peers, defaults.node.secondary_work_peers); @@ -180,6 +185,7 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.vote_minimum, defaults.node.vote_minimum); ASSERT_EQ (conf.node.work_peers, defaults.node.work_peers); ASSERT_EQ (conf.node.work_threads, defaults.node.work_threads); + ASSERT_EQ (conf.node.max_queued_requests, defaults.node.max_queued_requests); ASSERT_EQ (conf.node.logging.bulk_pull_logging_value, defaults.node.logging.bulk_pull_logging_value); ASSERT_EQ (conf.node.logging.flush, defaults.node.logging.flush); @@ -198,8 +204,11 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.logging.network_publish_logging_value, defaults.node.logging.network_publish_logging_value); ASSERT_EQ (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); ASSERT_EQ (conf.node.logging.node_lifetime_tracing_value, defaults.node.logging.node_lifetime_tracing_value); + ASSERT_EQ (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); + ASSERT_EQ (conf.node.logging.network_rejected_logging_value, defaults.node.logging.network_rejected_logging_value); ASSERT_EQ (conf.node.logging.rotation_size, defaults.node.logging.rotation_size); ASSERT_EQ (conf.node.logging.single_line_record_value, defaults.node.logging.single_line_record_value); + ASSERT_EQ (conf.node.logging.stable_log_filename, defaults.node.logging.stable_log_filename); ASSERT_EQ (conf.node.logging.timing_logging_value, defaults.node.logging.timing_logging_value); ASSERT_EQ (conf.node.logging.active_update_value, defaults.node.logging.active_update_value); ASSERT_EQ (conf.node.logging.upnp_details_logging_value, defaults.node.logging.upnp_details_logging_value); @@ -223,6 +232,8 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.ipc_config.transport_tcp.io_timeout, defaults.node.ipc_config.transport_tcp.io_timeout); ASSERT_EQ (conf.node.ipc_config.transport_tcp.io_threads, defaults.node.ipc_config.transport_tcp.io_threads); ASSERT_EQ (conf.node.ipc_config.transport_tcp.port, defaults.node.ipc_config.transport_tcp.port); + ASSERT_EQ (conf.node.ipc_config.flatbuffers.skip_unexpected_fields_in_json, defaults.node.ipc_config.flatbuffers.skip_unexpected_fields_in_json); + ASSERT_EQ (conf.node.ipc_config.flatbuffers.verify_buffers, defaults.node.ipc_config.flatbuffers.verify_buffers); ASSERT_EQ (conf.node.diagnostics_config.txn_tracking.enable, defaults.node.diagnostics_config.txn_tracking.enable); ASSERT_EQ (conf.node.diagnostics_config.txn_tracking.ignore_writes_below_block_processor_max_time, defaults.node.diagnostics_config.txn_tracking.ignore_writes_below_block_processor_max_time); @@ -239,6 +250,10 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.stat_config.log_counters_filename, defaults.node.stat_config.log_counters_filename); ASSERT_EQ (conf.node.stat_config.log_samples_filename, defaults.node.stat_config.log_samples_filename); + ASSERT_EQ (conf.node.lmdb_config.sync, defaults.node.lmdb_config.sync); + ASSERT_EQ (conf.node.lmdb_config.max_databases, defaults.node.lmdb_config.max_databases); + ASSERT_EQ (conf.node.lmdb_config.map_size, defaults.node.lmdb_config.map_size); + ASSERT_EQ (conf.node.rocksdb_config.enable, defaults.node.rocksdb_config.enable); ASSERT_EQ (conf.node.rocksdb_config.bloom_filter_bits, defaults.node.rocksdb_config.bloom_filter_bits); ASSERT_EQ (conf.node.rocksdb_config.block_cache, defaults.node.rocksdb_config.block_cache); @@ -380,9 +395,11 @@ TEST (toml, daemon_config_deserialize_no_defaults) allow_local_peers = false backup_before_upgrade = true bandwidth_limit = 999 + bandwidth_limit_burst_ratio = 999.9 block_processor_batch_max_time = 999 bootstrap_connections = 999 bootstrap_connections_max = 999 + bootstrap_initiator_threads = 999 bootstrap_fraction_numerator = 999 conf_height_processor_batch_min_time = 999 confirmation_history_size = 999 @@ -412,6 +429,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) work_threads = 999 work_watcher_period = 999 max_work_generate_multiplier = 1.0 + max_queued_requests = 999 frontiers_confirmation = "always" [node.diagnostics.txn_tracking] enable = true @@ -437,6 +455,10 @@ TEST (toml, daemon_config_deserialize_no_defaults) io_threads = 999 port = 999 + [node.ipc.flatbuffers] + skip_unexpected_fields_in_json = false + verify_buffers = false + [node.logging] bulk_pull = true flush = false @@ -451,12 +473,15 @@ TEST (toml, daemon_config_deserialize_no_defaults) network_keepalive = true network_message = true network_node_id_handshake = true + network_telemetry_logging = true + network_rejected_logging = true network_packet = true network_publish = true network_timeout = true node_lifetime_tracing = true rotation_size = 999 single_line_record = true + stable_log_filename = true timing = true active_update = true upnp_details = true @@ -481,6 +506,11 @@ TEST (toml, daemon_config_deserialize_no_defaults) enable = true port = 999 + [node.lmdb] + sync = "nosync_safe" + max_databases = 999 + map_size = 999 + [node.rocksdb] enable = true bloom_filter_bits = 10 @@ -532,9 +562,11 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.allow_local_peers, defaults.node.allow_local_peers); ASSERT_NE (conf.node.backup_before_upgrade, defaults.node.backup_before_upgrade); ASSERT_NE (conf.node.bandwidth_limit, defaults.node.bandwidth_limit); + ASSERT_NE (conf.node.bandwidth_limit_burst_ratio, defaults.node.bandwidth_limit_burst_ratio); ASSERT_NE (conf.node.block_processor_batch_max_time, defaults.node.block_processor_batch_max_time); ASSERT_NE (conf.node.bootstrap_connections, defaults.node.bootstrap_connections); ASSERT_NE (conf.node.bootstrap_connections_max, defaults.node.bootstrap_connections_max); + ASSERT_NE (conf.node.bootstrap_initiator_threads, defaults.node.bootstrap_initiator_threads); ASSERT_NE (conf.node.bootstrap_fraction_numerator, defaults.node.bootstrap_fraction_numerator); ASSERT_NE (conf.node.conf_height_processor_batch_min_time, defaults.node.conf_height_processor_batch_min_time); ASSERT_NE (conf.node.confirmation_history_size, defaults.node.confirmation_history_size); @@ -542,7 +574,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.external_address, defaults.node.external_address); ASSERT_NE (conf.node.external_port, defaults.node.external_port); ASSERT_NE (conf.node.io_threads, defaults.node.io_threads); - ASSERT_NE (conf.node.lmdb_max_dbs, defaults.node.lmdb_max_dbs); + ASSERT_NE (conf.node.deprecated_lmdb_max_dbs, defaults.node.deprecated_lmdb_max_dbs); ASSERT_NE (conf.node.max_work_generate_multiplier, defaults.node.max_work_generate_multiplier); ASSERT_NE (conf.node.frontiers_confirmation, defaults.node.frontiers_confirmation); ASSERT_NE (conf.node.network_threads, defaults.node.network_threads); @@ -566,6 +598,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.vote_minimum, defaults.node.vote_minimum); ASSERT_NE (conf.node.work_peers, defaults.node.work_peers); ASSERT_NE (conf.node.work_threads, defaults.node.work_threads); + ASSERT_NE (conf.node.max_queued_requests, defaults.node.max_queued_requests); ASSERT_NE (conf.node.logging.bulk_pull_logging_value, defaults.node.logging.bulk_pull_logging_value); ASSERT_NE (conf.node.logging.flush, defaults.node.logging.flush); @@ -580,12 +613,15 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.logging.network_keepalive_logging_value, defaults.node.logging.network_keepalive_logging_value); ASSERT_NE (conf.node.logging.network_message_logging_value, defaults.node.logging.network_message_logging_value); ASSERT_NE (conf.node.logging.network_node_id_handshake_logging_value, defaults.node.logging.network_node_id_handshake_logging_value); + ASSERT_NE (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); + ASSERT_NE (conf.node.logging.network_rejected_logging_value, defaults.node.logging.network_rejected_logging_value); ASSERT_NE (conf.node.logging.network_packet_logging_value, defaults.node.logging.network_packet_logging_value); ASSERT_NE (conf.node.logging.network_publish_logging_value, defaults.node.logging.network_publish_logging_value); ASSERT_NE (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); ASSERT_NE (conf.node.logging.node_lifetime_tracing_value, defaults.node.logging.node_lifetime_tracing_value); ASSERT_NE (conf.node.logging.rotation_size, defaults.node.logging.rotation_size); ASSERT_NE (conf.node.logging.single_line_record_value, defaults.node.logging.single_line_record_value); + ASSERT_NE (conf.node.logging.stable_log_filename, defaults.node.logging.stable_log_filename); ASSERT_NE (conf.node.logging.timing_logging_value, defaults.node.logging.timing_logging_value); ASSERT_NE (conf.node.logging.active_update_value, defaults.node.logging.active_update_value); ASSERT_NE (conf.node.logging.upnp_details_logging_value, defaults.node.logging.upnp_details_logging_value); @@ -609,6 +645,8 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.ipc_config.transport_tcp.io_timeout, defaults.node.ipc_config.transport_tcp.io_timeout); ASSERT_NE (conf.node.ipc_config.transport_tcp.io_threads, defaults.node.ipc_config.transport_tcp.io_threads); ASSERT_NE (conf.node.ipc_config.transport_tcp.port, defaults.node.ipc_config.transport_tcp.port); + ASSERT_NE (conf.node.ipc_config.flatbuffers.skip_unexpected_fields_in_json, defaults.node.ipc_config.flatbuffers.skip_unexpected_fields_in_json); + ASSERT_NE (conf.node.ipc_config.flatbuffers.verify_buffers, defaults.node.ipc_config.flatbuffers.verify_buffers); ASSERT_NE (conf.node.diagnostics_config.txn_tracking.enable, defaults.node.diagnostics_config.txn_tracking.enable); ASSERT_NE (conf.node.diagnostics_config.txn_tracking.ignore_writes_below_block_processor_max_time, defaults.node.diagnostics_config.txn_tracking.ignore_writes_below_block_processor_max_time); @@ -625,6 +663,10 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.stat_config.log_counters_filename, defaults.node.stat_config.log_counters_filename); ASSERT_NE (conf.node.stat_config.log_samples_filename, defaults.node.stat_config.log_samples_filename); + ASSERT_NE (conf.node.lmdb_config.sync, defaults.node.lmdb_config.sync); + ASSERT_NE (conf.node.lmdb_config.max_databases, defaults.node.lmdb_config.max_databases); + ASSERT_NE (conf.node.lmdb_config.map_size, defaults.node.lmdb_config.map_size); + ASSERT_NE (conf.node.rocksdb_config.enable, defaults.node.rocksdb_config.enable); ASSERT_NE (conf.node.rocksdb_config.bloom_filter_bits, defaults.node.rocksdb_config.bloom_filter_bits); ASSERT_NE (conf.node.rocksdb_config.block_cache, defaults.node.rocksdb_config.block_cache); @@ -685,6 +727,8 @@ TEST (toml, rpc_config_deserialize_no_defaults) ipc_address = "0:0:0:0:0:ffff:7f01:101" ipc_port = 999 num_ipc_connections = 999 + [logging] + log_rpc = false )toml"; nano::tomlconfig toml; @@ -705,6 +749,8 @@ TEST (toml, rpc_config_deserialize_no_defaults) ASSERT_NE (conf.rpc_process.ipc_address, defaults.rpc_process.ipc_address); ASSERT_NE (conf.rpc_process.ipc_port, defaults.rpc_process.ipc_port); ASSERT_NE (conf.rpc_process.num_ipc_connections, defaults.rpc_process.num_ipc_connections); + + ASSERT_NE (conf.rpc_logging.log_rpc, defaults.rpc_logging.log_rpc); } /** There should be no required values **/ @@ -716,6 +762,7 @@ TEST (toml, rpc_config_no_required) ss << R"toml( [version] [process] + [logging] [secure] )toml"; @@ -758,3 +805,47 @@ TEST (toml, daemon_config_deserialize_errors) ASSERT_EQ (toml2.get_error ().get_message (), "frontiers_confirmation value is invalid (available: always, auto, disabled)"); ASSERT_EQ (conf2.node.frontiers_confirmation, nano::frontiers_confirmation_mode::invalid); } + +TEST (toml, daemon_read_config) +{ + auto path (nano::unique_path ()); + boost::filesystem::create_directories (path); + nano::daemon_config config; + std::vector invalid_overrides1{ "node.max_work_generate_multiplier=0" }; + std::string expected_message1{ "max_work_generate_multiplier must be greater than or equal to 1" }; + + std::vector invalid_overrides2{ "node.websocket.enable=true", "node.foo" }; + std::string expected_message2{ "Value must follow after a '=' at line 2" }; + + // Reading when there is no config file + ASSERT_FALSE (boost::filesystem::exists (nano::get_node_toml_config_path (path))); + ASSERT_FALSE (nano::read_node_config_toml (path, config)); + { + auto error = nano::read_node_config_toml (path, config, invalid_overrides1); + ASSERT_TRUE (error); + ASSERT_EQ (error.get_message (), expected_message1); + } + { + auto error = nano::read_node_config_toml (path, config, invalid_overrides2); + ASSERT_TRUE (error); + ASSERT_EQ (error.get_message (), expected_message2); + } + + // Create an empty config + nano::tomlconfig toml; + toml.write (nano::get_node_toml_config_path (path)); + + // Reading when there is a config file + ASSERT_TRUE (boost::filesystem::exists (nano::get_node_toml_config_path (path))); + ASSERT_FALSE (nano::read_node_config_toml (path, config)); + { + auto error = nano::read_node_config_toml (path, config, invalid_overrides1); + ASSERT_TRUE (error); + ASSERT_EQ (error.get_message (), expected_message1); + } + { + auto error = nano::read_node_config_toml (path, config, invalid_overrides2); + ASSERT_TRUE (error); + ASSERT_EQ (error.get_message (), expected_message2); + } +} diff --git a/nano/core_test/uint256_union.cpp b/nano/core_test/uint256_union.cpp index 1efb4ed17b..eb8304d2df 100644 --- a/nano/core_test/uint256_union.cpp +++ b/nano/core_test/uint256_union.cpp @@ -464,6 +464,42 @@ TEST (uint64_t, parse) ASSERT_TRUE (nano::from_string_hex ("", value4)); } +TEST (uint256_union, hash) +{ + ASSERT_EQ (4, nano::uint256_union{}.qwords.size ()); + std::hash h{}; + for (size_t i (0), n (nano::uint256_union{}.bytes.size ()); i < n; ++i) + { + nano::uint256_union x1{ 0 }; + nano::uint256_union x2{ 0 }; + x2.bytes[i] = 1; + ASSERT_NE (h (x1), h (x2)); + } +} + +TEST (uint512_union, hash) +{ + ASSERT_EQ (2, nano::uint512_union{}.uint256s.size ()); + std::hash h{}; + for (size_t i (0), n (nano::uint512_union{}.bytes.size ()); i < n; ++i) + { + nano::uint512_union x1{ 0 }; + nano::uint512_union x2{ 0 }; + x2.bytes[i] = 1; + ASSERT_NE (h (x1), h (x2)); + } + for (auto part (0); part < nano::uint512_union{}.uint256s.size (); ++part) + { + for (size_t i (0), n (nano::uint512_union{}.uint256s[part].bytes.size ()); i < n; ++i) + { + nano::uint512_union x1{ 0 }; + nano::uint512_union x2{ 0 }; + x2.uint256s[part].bytes[i] = 1; + ASSERT_NE (h (x1), h (x2)); + } + } +} + namespace { template diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index 2304ceb54f..afc2da014b 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -1,22 +1,104 @@ +#include +#include +#include #include #include +#include #include #include -namespace +#include + +using namespace std::chrono_literals; + +TEST (rate, basic) { -std::atomic passed_sleep{ false }; + nano::rate::token_bucket bucket (10, 10); + + // Initial burst + ASSERT_TRUE (bucket.try_consume (10)); + ASSERT_FALSE (bucket.try_consume (10)); + + // With a fill rate of 10 tokens/sec, await 1/3 sec and get 3 tokens + std::this_thread::sleep_for (300ms); + ASSERT_TRUE (bucket.try_consume (3)); + ASSERT_FALSE (bucket.try_consume (10)); + + // Allow time for the bucket to completely refill and do a full burst + std::this_thread::sleep_for (1s); + ASSERT_TRUE (bucket.try_consume (10)); + ASSERT_EQ (bucket.largest_burst (), 10); +} + +TEST (rate, network) +{ + // For the purpose of the test, one token represents 1MB instead of one byte. + // Allow for 10 mb/s bursts (max bucket size), 5 mb/s long term rate + nano::rate::token_bucket bucket (10, 5); + + // Initial burst of 10 mb/s over two calls + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 5); + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 10); + ASSERT_FALSE (bucket.try_consume (5)); -void func () + // After 200 ms, the 5 mb/s fillrate means we have 1 mb available + std::this_thread::sleep_for (200ms); + ASSERT_TRUE (bucket.try_consume (1)); + ASSERT_FALSE (bucket.try_consume (1)); +} + +TEST (rate, unlimited) { - std::this_thread::sleep_for (std::chrono::seconds (1)); - passed_sleep = true; + nano::rate::token_bucket bucket (0, 0); + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 5); + ASSERT_TRUE (bucket.try_consume (1e9)); + ASSERT_EQ (bucket.largest_burst (), 1e9); + + // With unlimited tokens, consuming always succeed + ASSERT_TRUE (bucket.try_consume (1e9)); + ASSERT_EQ (bucket.largest_burst (), 1e9); } + +TEST (optional_ptr, basic) +{ + struct valtype + { + int64_t x{ 1 }; + int64_t y{ 2 }; + int64_t z{ 3 }; + }; + + nano::optional_ptr opt; + ASSERT_FALSE (opt); + ASSERT_FALSE (opt.is_initialized ()); + + { + auto val = valtype{}; + opt = val; + ASSERT_LT (sizeof (opt), sizeof (val)); + std::unique_ptr uptr; + ASSERT_EQ (sizeof (opt), sizeof (uptr)); + } + ASSERT_TRUE (opt); + ASSERT_TRUE (opt.is_initialized ()); + ASSERT_EQ (opt->x, 1); + ASSERT_EQ (opt->y, 2); + ASSERT_EQ (opt->z, 3); } TEST (thread, worker) { + std::atomic passed_sleep{ false }; + + auto func = [&passed_sleep]() { + std::this_thread::sleep_for (std::chrono::seconds (1)); + passed_sleep = true; + }; + nano::worker worker; worker.push_task (func); ASSERT_FALSE (passed_sleep); @@ -82,3 +164,70 @@ TEST (filesystem, move_all_files) ASSERT_FALSE (boost::filesystem::exists (dummy_file1)); ASSERT_FALSE (boost::filesystem::exists (dummy_file2)); } + +TEST (relaxed_atomic_integral, basic) +{ + nano::relaxed_atomic_integral atomic{ 0 }; + ASSERT_EQ (0, atomic++); + ASSERT_EQ (1, atomic); + ASSERT_EQ (2, ++atomic); + ASSERT_EQ (2, atomic); + ASSERT_EQ (2, atomic.load ()); + ASSERT_EQ (2, atomic--); + ASSERT_EQ (1, atomic); + ASSERT_EQ (0, --atomic); + ASSERT_EQ (0, atomic); + ASSERT_EQ (0, atomic.fetch_add (2)); + ASSERT_EQ (2, atomic); + ASSERT_EQ (2, atomic.fetch_sub (1)); + ASSERT_EQ (1, atomic); + atomic.store (3); + ASSERT_EQ (3, atomic); + + uint32_t expected{ 2 }; + ASSERT_FALSE (atomic.compare_exchange_strong (expected, 1)); + ASSERT_EQ (3, expected); + ASSERT_EQ (3, atomic); + ASSERT_TRUE (atomic.compare_exchange_strong (expected, 1)); + ASSERT_EQ (1, atomic); + ASSERT_EQ (3, expected); + + // Weak can fail spuriously, try a few times + bool res{ false }; + for (int i = 0; i < 1000; ++i) + { + res |= atomic.compare_exchange_weak (expected, 2); + expected = 1; + } + ASSERT_TRUE (res); + ASSERT_EQ (2, atomic); +} + +TEST (relaxed_atomic_integral, many_threads) +{ + std::vector threads; + auto num = 4; + nano::relaxed_atomic_integral atomic{ 0 }; + for (int i = 0; i < num; ++i) + { + threads.emplace_back ([&atomic] { + for (int i = 0; i < 10000; ++i) + { + ++atomic; + atomic--; + atomic++; + --atomic; + atomic.fetch_add (2); + atomic.fetch_sub (2); + } + }); + } + + for (auto & thread : threads) + { + thread.join (); + } + + // Check values + ASSERT_EQ (0, atomic); +} \ No newline at end of file diff --git a/nano/core_test/versioning.cpp b/nano/core_test/versioning.cpp index 8503ca11b7..f47bec0b89 100644 --- a/nano/core_test/versioning.cpp +++ b/nano/core_test/versioning.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -10,14 +11,14 @@ TEST (versioning, account_info_v1) auto file (nano::unique_path ()); nano::account account (1); nano::open_block open (1, 2, 3, nullptr); + open.sideband_set ({}); nano::account_info_v1 v1 (open.hash (), open.hash (), 3, 4); { nano::logger_mt logger; nano::mdb_store store (logger, file); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store.block_put (transaction, open.hash (), open, sideband); + store.block_put (transaction, open.hash (), open); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (account), nano::mdb_val (sizeof (v1), &v1), 0)); ASSERT_EQ (0, status); store.version_put (transaction, 1); @@ -35,9 +36,9 @@ TEST (versioning, account_info_v1) ASSERT_EQ (v1.modified, v_latest.modified); ASSERT_EQ (v1.rep_block, open.hash ()); ASSERT_EQ (1, v_latest.block_count); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height)); - ASSERT_EQ (0, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); ASSERT_EQ (nano::epoch::epoch_0, v_latest.epoch ()); } @@ -46,14 +47,14 @@ TEST (versioning, account_info_v5) auto file (nano::unique_path ()); nano::account account (1); nano::open_block open (1, 2, 3, nullptr); + open.sideband_set ({}); nano::account_info_v5 v5 (open.hash (), open.hash (), open.hash (), 3, 4); { nano::logger_mt logger; nano::mdb_store store (logger, file); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store.block_put (transaction, open.hash (), open, sideband); + store.block_put (transaction, open.hash (), open); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (account), nano::mdb_val (sizeof (v5), &v5), 0)); ASSERT_EQ (0, status); store.version_put (transaction, 5); @@ -71,9 +72,9 @@ TEST (versioning, account_info_v5) ASSERT_EQ (v5.modified, v_latest.modified); ASSERT_EQ (v5.rep_block, open.hash ()); ASSERT_EQ (1, v_latest.block_count); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height)); - ASSERT_EQ (0, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); ASSERT_EQ (nano::epoch::epoch_0, v_latest.epoch ()); } @@ -82,14 +83,14 @@ TEST (versioning, account_info_v13) auto file (nano::unique_path ()); nano::account account (1); nano::open_block open (1, 2, 3, nullptr); + open.sideband_set ({}); nano::account_info_v13 v13 (open.hash (), open.hash (), open.hash (), 3, 4, 10, nano::epoch::epoch_0); { nano::logger_mt logger; nano::mdb_store store (logger, file); ASSERT_FALSE (store.init_error ()); auto transaction (store.tx_begin_write ()); - nano::block_sideband sideband (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0); - store.block_put (transaction, open.hash (), open, sideband); + store.block_put (transaction, open.hash (), open); auto status (mdb_put (store.env.tx (transaction), store.accounts_v0, nano::mdb_val (account), nano::mdb_val (v13), 0)); ASSERT_EQ (0, status); store.version_put (transaction, 13); @@ -107,8 +108,8 @@ TEST (versioning, account_info_v13) ASSERT_EQ (v13.modified, v_latest.modified); ASSERT_EQ (v13.rep_block, open.hash ()); ASSERT_EQ (v13.block_count, v_latest.block_count); - uint64_t confirmation_height; - ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height)); - ASSERT_EQ (0, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (store.confirmation_height_get (transaction, account, confirmation_height_info)); + ASSERT_EQ (0, confirmation_height_info.height); ASSERT_EQ (v13.epoch, v_latest.epoch ()); } diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp new file mode 100644 index 0000000000..a6317f7636 --- /dev/null +++ b/nano/core_test/vote_processor.cpp @@ -0,0 +1,291 @@ +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +TEST (vote_processor, codes) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::genesis genesis; + nano::keypair key; + auto vote (std::make_shared (key.pub, key.prv, 1, std::vector{ genesis.open->hash () })); + auto vote_invalid = std::make_shared (*vote); + vote_invalid->signature.bytes[0] ^= 1; + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + + // Invalid signature + ASSERT_EQ (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel, false)); + + // Hint of pre-validation + ASSERT_NE (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel, true)); + + // No ongoing election + ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel)); + + // First vote from an account for an ongoing election + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); + ASSERT_TRUE (node.active.insert (genesis.open).inserted); + ASSERT_EQ (nano::vote_code::vote, node.vote_processor.vote_blocking (vote, channel)); + + // Processing the same vote is a replay + ASSERT_EQ (nano::vote_code::replay, node.vote_processor.vote_blocking (vote, channel)); + + // Invalid takes precedence + ASSERT_EQ (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel)); + + // A higher sequence is not a replay + ++vote->sequence; + ASSERT_EQ (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote, channel)); + vote->signature = nano::sign_message (key.prv, key.pub, vote->hash ()); + ASSERT_EQ (nano::vote_code::vote, node.vote_processor.vote_blocking (vote, channel)); + + // Once the election is removed (confirmed / dropped) the vote is again indeterminate + node.active.erase (*genesis.open); + ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel)); +} + +TEST (vote_processor, flush) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::genesis genesis; + auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, std::vector{ genesis.open->hash () })); + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + for (unsigned i = 0; i < 2000; ++i) + { + auto new_vote (std::make_shared (*vote)); + node.vote_processor.vote (new_vote, channel); + ++vote->sequence; // invalidates votes without signing again + } + node.vote_processor.flush (); + ASSERT_TRUE (node.vote_processor.empty ()); +} + +TEST (vote_processor, invalid_signature) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::genesis genesis; + nano::keypair key; + auto vote (std::make_shared (key.pub, key.prv, 1, std::vector{ genesis.open->hash () })); + auto vote_invalid = std::make_shared (*vote); + vote_invalid->signature.bytes[0] ^= 1; + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); + auto election (node.active.insert (genesis.open)); + ASSERT_TRUE (election.election && election.inserted); + ASSERT_EQ (1, election.election->last_votes.size ()); + node.vote_processor.vote (vote_invalid, channel); + node.vote_processor.flush (); + ASSERT_EQ (1, election.election->last_votes.size ()); + node.vote_processor.vote (vote, channel); + node.vote_processor.flush (); + ASSERT_EQ (2, election.election->last_votes.size ()); +} + +TEST (vote_processor, no_capacity) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.vote_processor_capacity = 0; + auto & node (*system.add_node (node_flags)); + nano::genesis genesis; + nano::keypair key; + auto vote (std::make_shared (key.pub, key.prv, 1, std::vector{ genesis.open->hash () })); + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + ASSERT_TRUE (node.vote_processor.vote (vote, channel)); +} + +TEST (vote_processor, overflow) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.vote_processor_capacity = 1; + auto & node (*system.add_node (node_flags)); + nano::genesis genesis; + nano::keypair key; + auto vote (std::make_shared (key.pub, key.prv, 1, std::vector{ genesis.open->hash () })); + auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + + // No way to lock the processor, but queueing votes in quick succession must result in overflow + size_t not_processed{ 0 }; + size_t const total{ 1000 }; + for (unsigned i = 0; i < total; ++i) + { + if (node.vote_processor.vote (vote, channel)) + { + ++not_processed; + } + } + ASSERT_GT (not_processed, 0); + ASSERT_LT (not_processed, total); + ASSERT_EQ (not_processed, node.stats.count (nano::stat::type::vote, nano::stat::detail::vote_overflow)); +} + +namespace nano +{ +TEST (vote_processor, weights) +{ + nano::system system (4); + auto & node (*system.nodes[0]); + + // Create representatives of different weight levels + // The online stake will be the minimum configurable due to online_reps sampling in tests + auto const online = node.config.online_weight_minimum.number (); + auto const level0 = online / 5000; // 0.02% + auto const level1 = online / 500; // 0.2% + auto const level2 = online / 50; // 2% + + nano::keypair key0; + nano::keypair key1; + nano::keypair key2; + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (1)->insert_adhoc (key0.prv); + system.wallet (2)->insert_adhoc (key1.prv); + system.wallet (3)->insert_adhoc (key2.prv); + system.wallet (1)->store.representative_set (system.nodes[1]->wallets.tx_begin_write (), key0.pub); + system.wallet (2)->store.representative_set (system.nodes[2]->wallets.tx_begin_write (), key1.pub); + system.wallet (3)->store.representative_set (system.nodes[3]->wallets.tx_begin_write (), key2.pub); + system.wallet (0)->send_sync (nano::test_genesis_key.pub, key0.pub, level0); + system.wallet (0)->send_sync (nano::test_genesis_key.pub, key1.pub, level1); + system.wallet (0)->send_sync (nano::test_genesis_key.pub, key2.pub, level2); + + // Wait for representatives + system.deadline_set (10s); + while (node.ledger.cache.rep_weights.get_rep_amounts ().size () != 4) + { + ASSERT_NO_ERROR (system.poll ()); + } + node.vote_processor.calculate_weights (); + + ASSERT_EQ (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key0.pub)); + ASSERT_EQ (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key0.pub)); + ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key0.pub)); + + ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key1.pub)); + ASSERT_EQ (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key1.pub)); + ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key1.pub)); + + ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key2.pub)); + ASSERT_NE (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key2.pub)); + ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key2.pub)); + + ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (nano::test_genesis_key.pub)); + ASSERT_NE (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (nano::test_genesis_key.pub)); + ASSERT_NE (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (nano::test_genesis_key.pub)); +} +} + +TEST (vote_processor, no_broadcast_local) +{ + nano::system system; + nano::node_flags flags; + flags.disable_request_loop = true; + auto & node (*system.add_node (flags)); + system.add_node (flags); + nano::block_builder builder; + std::error_code ec; + // Reduce the weight of genesis to 2x default min voting weight + nano::keypair key; + std::shared_ptr send = builder.state () + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .previous (nano::genesis_hash) + .balance (2 * node.config.vote_minimum.number ()) + .link (key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (ec); + ASSERT_FALSE (ec); + ASSERT_EQ (nano::process_result::progress, node.process_local (send).code); + ASSERT_EQ (2 * node.config.vote_minimum.number (), node.weight (nano::test_genesis_key.pub)); + // Insert account in wallet + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node.wallets.compute_reps (); + ASSERT_TRUE (node.wallets.reps ().exists (nano::test_genesis_key.pub)); + ASSERT_FALSE (node.wallets.reps ().have_half_rep ()); + // Process a vote + auto vote (node.store.vote_generate (node.store.tx_begin_read (), nano::test_genesis_key.pub, nano::test_genesis_key.prv, { send->hash () })); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote)); + // Make sure the vote was processed + auto election (node.active.election (send->qualified_root ())); + ASSERT_NE (nullptr, election); + auto existing (election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election->last_votes.end (), existing); + ASSERT_EQ (vote->sequence, existing->second.sequence); + // Ensure the vote, from a local representative, was not broadcast on processing - it should be flooded on generation instead + ASSERT_EQ (0, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); + + // Repeat test with no representative + // Erase account from the wallet + system.wallet (0)->store.erase (node.wallets.tx_begin_write (), nano::test_genesis_key.pub); + node.wallets.compute_reps (); + ASSERT_FALSE (node.wallets.reps ().exists (nano::test_genesis_key.pub)); + + std::shared_ptr send2 = builder.state () + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .previous (send->hash ()) + .balance (node.config.vote_minimum) + .link (key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (send->hash ())) + .build (ec); + ASSERT_FALSE (ec); + ASSERT_EQ (nano::process_result::progress, node.process_local (send2).code); + ASSERT_EQ (node.config.vote_minimum, node.weight (nano::test_genesis_key.pub)); + node.block_confirm (send2); + // Process a vote + auto vote2 (node.store.vote_generate (node.store.tx_begin_read (), nano::test_genesis_key.pub, nano::test_genesis_key.prv, { send2->hash () })); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2)); + // Make sure the vote was processed + auto election2 (node.active.election (send2->qualified_root ())); + ASSERT_NE (nullptr, election2); + auto existing2 (election2->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election2->last_votes.end (), existing2); + ASSERT_EQ (vote2->sequence, existing2->second.sequence); + // Ensure the vote was broadcast + ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); + + // Repeat test with a PR in the wallet + // Increase the genesis weight again + std::shared_ptr open = builder.state () + .account (key.pub) + .representative (nano::test_genesis_key.pub) + .previous (0) + .balance (nano::genesis_amount - 2 * node.config.vote_minimum.number ()) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (ec); + ASSERT_FALSE (ec); + ASSERT_EQ (nano::process_result::progress, node.process_local (open).code); + ASSERT_EQ (nano::genesis_amount - node.config.vote_minimum.number (), node.weight (nano::test_genesis_key.pub)); + node.block_confirm (open); + // Insert account in wallet + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node.wallets.compute_reps (); + ASSERT_TRUE (node.wallets.reps ().exists (nano::test_genesis_key.pub)); + ASSERT_TRUE (node.wallets.reps ().have_half_rep ()); + // Process a vote + auto vote3 (node.store.vote_generate (node.store.tx_begin_read (), nano::test_genesis_key.pub, nano::test_genesis_key.prv, { open->hash () })); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote3)); + // Make sure the vote was processed + auto election3 (node.active.election (open->qualified_root ())); + ASSERT_NE (nullptr, election3); + auto existing3 (election3->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election3->last_votes.end (), existing3); + ASSERT_EQ (vote3->sequence, existing3->second.sequence); + // Ensure the vote wass not broadcasst + ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_EQ (3, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); +} diff --git a/nano/core_test/wallet.cpp b/nano/core_test/wallet.cpp index 414b6e72ee..add7e69e33 100644 --- a/nano/core_test/wallet.cpp +++ b/nano/core_test/wallet.cpp @@ -1,11 +1,12 @@ #include #include +#include #include #include #include -#include +#include using namespace std::chrono_literals; unsigned constexpr nano::wallet_store::version_current; @@ -163,7 +164,7 @@ TEST (wallet, two_item_iteration) TEST (wallet, insufficient_spend_one) { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 500)); @@ -173,27 +174,28 @@ TEST (wallet, insufficient_spend_one) TEST (wallet, spend_all_one) { - nano::system system (24000, 1); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::block_hash latest1 (node1.latest (nano::test_genesis_key.pub)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, std::numeric_limits::max ())); nano::account_info info2; { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info2); + auto transaction (node1.store.tx_begin_read ()); + node1.store.account_get (transaction, nano::test_genesis_key.pub, info2); ASSERT_NE (latest1, info2.head); - auto block (system.nodes[0]->store.block_get (transaction, info2.head)); + auto block (node1.store.block_get (transaction, info2.head)); ASSERT_NE (nullptr, block); ASSERT_EQ (latest1, block->previous ()); } ASSERT_TRUE (info2.balance.is_zero ()); - ASSERT_EQ (0, system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node1.balance (nano::test_genesis_key.pub)); } TEST (wallet, send_async) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; boost::thread thread ([&system]() { @@ -206,13 +208,14 @@ TEST (wallet, send_async) std::atomic success (false); system.wallet (0)->send_async (nano::test_genesis_key.pub, key2.pub, std::numeric_limits::max (), [&success](std::shared_ptr block_a) { ASSERT_NE (nullptr, block_a); success = true; }); thread.join (); - ASSERT_TRUE (success); + ASSERT_TIMELY (2s, success); } TEST (wallet, spend) { - nano::system system (24000, 1); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::system system (1); + auto & node1 (*system.nodes[0]); + nano::block_hash latest1 (node1.latest (nano::test_genesis_key.pub)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; // Sending from empty accounts should always be an error. Accounts need to be opened with an open block, not a send block. @@ -220,20 +223,20 @@ TEST (wallet, spend) ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, std::numeric_limits::max ())); nano::account_info info2; { - auto transaction (system.nodes[0]->store.tx_begin_read ()); - system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info2); + auto transaction (node1.store.tx_begin_read ()); + node1.store.account_get (transaction, nano::test_genesis_key.pub, info2); ASSERT_NE (latest1, info2.head); - auto block (system.nodes[0]->store.block_get (transaction, info2.head)); + auto block (node1.store.block_get (transaction, info2.head)); ASSERT_NE (nullptr, block); ASSERT_EQ (latest1, block->previous ()); } ASSERT_TRUE (info2.balance.is_zero ()); - ASSERT_EQ (0, system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (0, node1.balance (nano::test_genesis_key.pub)); } TEST (wallet, change) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; auto block1 (system.nodes[0]->rep_block (nano::test_genesis_key.pub)); @@ -246,7 +249,7 @@ TEST (wallet, change) TEST (wallet, partial_spend) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, 500)); @@ -255,7 +258,7 @@ TEST (wallet, partial_spend) TEST (wallet, spend_no_previous) { - nano::system system (24000, 1); + nano::system system (1); { system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto transaction (system.nodes[0]->store.tx_begin_read ()); @@ -595,7 +598,7 @@ TEST (wallet_store, move) TEST (wallet_store, import) { - nano::system system (24000, 2); + nano::system system (2); auto wallet1 (system.wallet (0)); auto wallet2 (system.wallet (1)); nano::keypair key1; @@ -610,7 +613,7 @@ TEST (wallet_store, import) TEST (wallet_store, fail_import_bad_password) { - nano::system system (24000, 2); + nano::system system (2); auto wallet1 (system.wallet (0)); auto wallet2 (system.wallet (1)); nano::keypair key1; @@ -624,7 +627,7 @@ TEST (wallet_store, fail_import_bad_password) TEST (wallet_store, fail_import_corrupt) { - nano::system system (24000, 2); + nano::system system (2); auto wallet1 (system.wallet (1)); std::string json; auto error (wallet1->import (json, "1")); @@ -634,7 +637,7 @@ TEST (wallet_store, fail_import_corrupt) // Test work is precached when a key is inserted TEST (wallet, work) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); wallet->insert_adhoc (nano::test_genesis_key.prv); nano::genesis genesis; @@ -646,7 +649,7 @@ TEST (wallet, work) uint64_t work (0); if (!wallet->store.work_get (transaction, nano::test_genesis_key.pub, work)) { - done = !nano::work_validate (genesis.hash (), work); + done = nano::work_difficulty (genesis.open->work_version (), genesis.hash (), work) >= system.nodes[0]->default_difficulty (genesis.open->work_version ()); } ASSERT_NO_ERROR (system.poll ()); } @@ -654,21 +657,22 @@ TEST (wallet, work) TEST (wallet, work_generate) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); auto wallet (system.wallet (0)); - nano::uint128_t amount1 (system.nodes[0]->balance (nano::test_genesis_key.pub)); + nano::uint128_t amount1 (node1.balance (nano::test_genesis_key.pub)); uint64_t work1; wallet->insert_adhoc (nano::test_genesis_key.prv); nano::account account1; { - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node1.wallets.tx_begin_read ()); account1 = system.account (transaction, 0); } nano::keypair key; - wallet->send_action (nano::test_genesis_key.pub, key.pub, 100); + auto block (wallet->send_action (nano::test_genesis_key.pub, key.pub, 100)); system.deadline_set (10s); - auto transaction (system.nodes[0]->store.tx_begin_read ()); - while (system.nodes[0]->ledger.account_balance (transaction, nano::test_genesis_key.pub) == amount1) + auto transaction (node1.store.tx_begin_read ()); + while (node1.ledger.account_balance (transaction, nano::test_genesis_key.pub) == amount1) { ASSERT_NO_ERROR (system.poll ()); } @@ -677,15 +681,47 @@ TEST (wallet, work_generate) while (again) { ASSERT_NO_ERROR (system.poll ()); - auto block_transaction (system.nodes[0]->store.tx_begin_read ()); + auto block_transaction (node1.store.tx_begin_read ()); auto transaction (system.wallet (0)->wallets.tx_begin_read ()); - again = wallet->store.work_get (transaction, account1, work1) || nano::work_validate (system.nodes[0]->ledger.latest_root (block_transaction, account1), work1); + again = wallet->store.work_get (transaction, account1, work1) || nano::work_difficulty (block->work_version (), node1.ledger.latest_root (block_transaction, account1), work1) < node1.default_difficulty (block->work_version ()); + } +} + +TEST (wallet, work_cache_delayed) +{ + nano::system system (1); + auto & node1 (*system.nodes[0]); + auto wallet (system.wallet (0)); + uint64_t work1; + wallet->insert_adhoc (nano::test_genesis_key.prv); + nano::account account1; + { + auto transaction (node1.wallets.tx_begin_read ()); + account1 = system.account (transaction, 0); } + nano::keypair key; + auto block1 (wallet->send_action (nano::test_genesis_key.pub, key.pub, 100)); + ASSERT_EQ (block1->hash (), node1.latest (nano::test_genesis_key.pub)); + auto block2 (wallet->send_action (nano::test_genesis_key.pub, key.pub, 100)); + ASSERT_EQ (block2->hash (), node1.latest (nano::test_genesis_key.pub)); + ASSERT_EQ (block2->hash (), node1.wallets.delayed_work->operator[] (nano::test_genesis_key.pub)); + auto threshold (node1.default_difficulty (nano::work_version::work_1)); + auto again (true); + system.deadline_set (10s); + while (again) + { + ASSERT_NO_ERROR (system.poll ()); + if (!wallet->store.work_get (node1.wallets.tx_begin_read (), account1, work1)) + { + again = nano::work_difficulty (nano::work_version::work_1, block2->hash (), work1) < threshold; + } + } + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, block2->hash (), work1), threshold); } TEST (wallet, insert_locked) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); { auto transaction (wallet->wallets.tx_begin_write ()); @@ -700,7 +736,7 @@ TEST (wallet, insert_locked) TEST (wallet, version_1_upgrade) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); wallet->enter_initial_password (); nano::keypair key; @@ -816,7 +852,7 @@ TEST (wallet, reseed) TEST (wallet, insert_deterministic_locked) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); auto transaction (wallet->wallets.tx_begin_write ()); wallet->store.rekey (transaction, "1"); @@ -828,7 +864,7 @@ TEST (wallet, insert_deterministic_locked) TEST (wallet, version_2_upgrade) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); auto transaction (wallet->wallets.tx_begin_write ()); wallet->store.rekey (transaction, "1"); @@ -848,7 +884,7 @@ TEST (wallet, version_2_upgrade) TEST (wallet, version_3_upgrade) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); auto transaction (wallet->wallets.tx_begin_write ()); wallet->store.rekey (transaction, "1"); @@ -884,12 +920,11 @@ TEST (wallet, version_3_upgrade) TEST (wallet, upgrade_backup) { - nano::system system (24000, 1); + nano::system system (1); auto dir (nano::unique_path ()); namespace fs = boost::filesystem; fs::create_directory (dir); /** Returns 'dir' if backup file cannot be found */ - // clang-format off auto get_backup_path = [&dir]() { for (fs::directory_iterator itr (dir); itr != fs::directory_iterator (); ++itr) { @@ -900,11 +935,10 @@ TEST (wallet, upgrade_backup) } return dir; }; - // clang-format on auto wallet_id = nano::random_wallet_id (); { - auto node1 (std::make_shared (system.io_ctx, 24001, dir, system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), dir, system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto wallet (node1->wallets.create (wallet_id)); ASSERT_NE (nullptr, wallet); @@ -915,7 +949,7 @@ TEST (wallet, upgrade_backup) // Check with config backup_before_upgrade = false { - auto node1 (std::make_shared (system.io_ctx, 24001, dir, system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), dir, system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto wallet (node1->wallets.open (wallet_id)); ASSERT_NE (nullptr, wallet); @@ -927,7 +961,7 @@ TEST (wallet, upgrade_backup) // Now do the upgrade and confirm that backup is saved { - nano::node_config node_config (24001, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.backup_before_upgrade = true; auto node1 (std::make_shared (system.io_ctx, dir, system.alarm, node_config, system.work)); ASSERT_FALSE (node1->init_error ()); @@ -941,13 +975,13 @@ TEST (wallet, upgrade_backup) TEST (wallet, no_work) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv, false); nano::keypair key2; auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, std::numeric_limits::max (), false)); ASSERT_NE (nullptr, block); ASSERT_NE (0, block->block_work ()); - ASSERT_FALSE (nano::work_validate (block->root (), block->block_work ())); + ASSERT_GE (block->difficulty (), nano::work_threshold (block->work_version (), block->sideband ().details)); auto transaction (system.wallet (0)->wallets.tx_begin_read ()); uint64_t cached_work (0); system.wallet (0)->store.work_get (transaction, nano::test_genesis_key.pub, cached_work); @@ -956,7 +990,7 @@ TEST (wallet, no_work) TEST (wallet, send_race) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; for (auto i (1); i < 60; ++i) @@ -968,7 +1002,7 @@ TEST (wallet, send_race) TEST (wallet, password_race) { - nano::system system (24000, 1); + nano::system system (1); nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); auto wallet = system.wallet (0); std::thread thread ([&wallet]() { @@ -996,7 +1030,7 @@ TEST (wallet, password_race) TEST (wallet, password_race_corrupt_seed) { - nano::system system (24000, 1); + nano::system system (1); nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); auto wallet = system.wallet (0); nano::raw_key seed; @@ -1066,7 +1100,7 @@ TEST (wallet, password_race_corrupt_seed) TEST (wallet, change_seed) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); wallet->enter_initial_password (); nano::raw_key seed1; @@ -1092,7 +1126,7 @@ TEST (wallet, change_seed) TEST (wallet, deterministic_restore) { - nano::system system (24000, 1); + nano::system system (1); auto wallet (system.wallet (0)); wallet->enter_initial_password (); nano::raw_key seed1; @@ -1121,37 +1155,32 @@ TEST (wallet, deterministic_restore) ASSERT_TRUE (wallet->exists (pub)); } -TEST (wallet, work_watcher_update) +TEST (work_watcher, update) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.work_watcher_period = 1s; - auto & node = *system.add_node (node_config); + node_config.max_work_generate_multiplier = 1e6; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config, node_flags); auto & wallet (*system.wallet (0)); wallet.insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; auto const block1 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); - uint64_t difficulty1 (0); - nano::work_validate (*block1, &difficulty1); + auto difficulty1 (block1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (block1->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1)); auto const block2 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 200)); - uint64_t difficulty2 (0); - nano::work_validate (*block2, &difficulty2); - auto multiplier = nano::difficulty::to_multiplier (std::max (difficulty1, difficulty2), node.network_params.network.publish_threshold); - uint64_t updated_difficulty1{ difficulty1 }, updated_difficulty2{ difficulty2 }; + auto difficulty2 (block2->difficulty ()); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (block2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1)); + double updated_multiplier1{ multiplier1 }, updated_multiplier2{ multiplier2 }, target_multiplier{ std::max (multiplier1, multiplier2) + 1e-6 }; { - nano::unique_lock lock (node.active.mutex); - // Prevent active difficulty repopulating multipliers - node.network_params.network.request_interval_ms = 10000; - //fill multipliers_cb and update active difficulty; - for (auto i (0); i < node.active.multipliers_cb.size (); i++) - { - node.active.multipliers_cb.push_back (multiplier * (1.5 + i / 100.)); - } - node.active.update_active_difficulty (lock); + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = target_multiplier; } system.deadline_set (20s); - while (updated_difficulty1 == difficulty1 || updated_difficulty2 == difficulty2) + while (updated_multiplier1 == multiplier1 || updated_multiplier2 == multiplier2) { { nano::lock_guard guard (node.active.mutex); @@ -1159,92 +1188,166 @@ TEST (wallet, work_watcher_update) auto const existing (node.active.roots.find (block1->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty1 = existing->difficulty; + updated_multiplier1 = existing->multiplier; } { auto const existing (node.active.roots.find (block2->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty2 = existing->difficulty; + updated_multiplier2 = existing->multiplier; } } ASSERT_NO_ERROR (system.poll ()); } - ASSERT_GT (updated_difficulty1, difficulty1); - ASSERT_GT (updated_difficulty2, difficulty2); + ASSERT_GT (updated_multiplier1, multiplier1); + ASSERT_GT (updated_multiplier2, multiplier2); } -TEST (wallet, work_watcher_generation_disabled) +TEST (work_watcher, propagate) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.work_watcher_period = 1s; - node_config.work_threads = 0; - auto & node = *system.add_node (node_config); - nano::work_pool pool (std::numeric_limits::max ()); - nano::genesis genesis; + node_config.max_work_generate_multiplier = 1e6; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config, node_flags); + auto & wallet (*system.wallet (0)); + wallet.insert_adhoc (nano::test_genesis_key.prv); + node_config.peering_port = nano::get_available_port (); + auto & node_passive = *system.add_node (node_config); nano::keypair key; - auto block (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Mxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ()))); - uint64_t difficulty (0); - ASSERT_FALSE (nano::work_validate (*block, &difficulty)); - node.wallets.watcher->add (block); - ASSERT_FALSE (node.process_local (block).code != nano::process_result::progress); - ASSERT_TRUE (node.wallets.watcher->is_watched (block->qualified_root ())); - auto multiplier = nano::difficulty::to_multiplier (difficulty, node.network_params.network.publish_threshold); - uint64_t updated_difficulty{ difficulty }; + auto const block (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); + system.deadline_set (5s); + while (!node_passive.ledger.block_exists (block->hash ())) { - nano::unique_lock lock (node.active.mutex); - // Prevent active difficulty repopulating multipliers - node.network_params.network.request_interval_ms = 10000; - //fill multipliers_cb and update active difficulty; - for (auto i (0); i < node.active.multipliers_cb.size (); i++) + ASSERT_NO_ERROR (system.poll ()); + } + auto const multiplier (nano::normalized_multiplier (nano::difficulty::to_multiplier (block->difficulty (), nano::work_threshold (block->work_version (), nano::block_details (nano::epoch::epoch_0, false, false, false))), node.network_params.network.publish_thresholds.epoch_1)); + auto updated_multiplier{ multiplier }; + auto propagated_multiplier{ multiplier }; + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = multiplier * 1.001; + } + bool updated{ false }; + bool propagated{ false }; + system.deadline_set (10s); + while (!(updated && propagated)) + { + { + nano::lock_guard guard (node.active.mutex); + { + auto const existing (node.active.roots.find (block->qualified_root ())); + ASSERT_NE (existing, node.active.roots.end ()); + updated_multiplier = existing->multiplier; + } + } { - node.active.multipliers_cb.push_back (multiplier * (1.5 + i / 100.)); + nano::lock_guard guard (node_passive.active.mutex); + { + auto const existing (node_passive.active.roots.find (block->qualified_root ())); + ASSERT_NE (existing, node_passive.active.roots.end ()); + propagated_multiplier = existing->multiplier; + } } - node.active.update_active_difficulty (lock); + updated = updated_multiplier != multiplier; + propagated = propagated_multiplier != multiplier; + ASSERT_NO_ERROR (system.poll ()); } - std::this_thread::sleep_for (5s); + ASSERT_GT (updated_multiplier, multiplier); + ASSERT_EQ (propagated_multiplier, updated_multiplier); +} - nano::lock_guard guard (node.active.mutex); +TEST (work_watcher, removed_after_win) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + auto & wallet (*system.wallet (0)); + wallet.insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + ASSERT_EQ (0, wallet.wallets.watcher->size ()); + auto const block1 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); + ASSERT_EQ (1, wallet.wallets.watcher->size ()); + system.deadline_set (5s); + while (node.wallets.watcher->is_watched (block1->qualified_root ())) { - auto const existing (node.active.roots.find (block->qualified_root ())); - //if existing is junk the block has been confirmed already - ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty = existing->difficulty; + ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (updated_difficulty, difficulty); - ASSERT_TRUE (node.distributed_work.items.empty ()); + ASSERT_EQ (0, node.wallets.watcher->size ()); } -TEST (wallet, work_watcher_removed) +TEST (work_watcher, removed_after_lose) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; node_config.work_watcher_period = 1s; auto & node = *system.add_node (node_config); - (void)node; auto & wallet (*system.wallet (0)); wallet.insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - ASSERT_EQ (0, wallet.wallets.watcher->size ()); - auto const block (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); - ASSERT_EQ (1, wallet.wallets.watcher->size ()); - auto transaction (wallet.wallets.tx_begin_write ()); - system.deadline_set (3s); - while (0 == wallet.wallets.watcher->size ()) + auto const block1 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); + ASSERT_TRUE (node.wallets.watcher->is_watched (block1->qualified_root ())); + nano::genesis genesis; + auto fork1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::xrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node.process_active (fork1); + node.block_processor.flush (); + auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, fork1)); + nano::confirm_ack message (vote); + node.network.process_message (message, nullptr); + system.deadline_set (5s); + while (node.wallets.watcher->is_watched (block1->qualified_root ())) { ASSERT_NO_ERROR (system.poll ()); } + ASSERT_EQ (0, node.wallets.watcher->size ()); } -TEST (wallet, work_watcher_cancel) +TEST (work_watcher, generation_disabled) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; + node_config.work_watcher_period = 1s; + node_config.work_threads = 0; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config); + ASSERT_FALSE (node.work_generation_enabled ()); + nano::work_pool pool (std::numeric_limits::max ()); + nano::genesis genesis; + nano::keypair key; + auto block (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Mxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (genesis.hash ()))); + auto difficulty (block->difficulty ()); + node.wallets.watcher->add (block); + ASSERT_FALSE (node.process_local (block).code != nano::process_result::progress); + ASSERT_TRUE (node.wallets.watcher->is_watched (block->qualified_root ())); + auto multiplier = nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty, nano::work_threshold (block->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1); + double updated_multiplier{ multiplier }; + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = multiplier * 10; + } + std::this_thread::sleep_for (2s); + ASSERT_TRUE (node.wallets.watcher->is_watched (block->qualified_root ())); + { + nano::lock_guard guard (node.active.mutex); + auto const existing (node.active.roots.find (block->qualified_root ())); + ASSERT_NE (existing, node.active.roots.end ()); + updated_multiplier = existing->multiplier; + } + ASSERT_EQ (updated_multiplier, multiplier); + ASSERT_TRUE (node.distributed_work.items.empty ()); +} + +TEST (work_watcher, cancel) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.work_watcher_period = 1s; node_config.max_work_generate_multiplier = 1e6; - node_config.max_work_generate_difficulty = nano::difficulty::from_multiplier (node_config.max_work_generate_multiplier, nano::network_constants::publish_test_threshold); node_config.enable_voting = false; auto & node = *system.add_node (node_config); auto & wallet (*system.wallet (0)); @@ -1252,8 +1355,6 @@ TEST (wallet, work_watcher_cancel) nano::keypair key; auto work1 (node.work_generate_blocking (nano::test_genesis_key.pub)); auto const block1 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100, *work1, false)); - uint64_t difficulty1 (0); - nano::work_validate (*block1, &difficulty1); { nano::unique_lock lock (node.active.mutex); // Prevent active difficulty repopulating multipliers @@ -1263,7 +1364,7 @@ TEST (wallet, work_watcher_cancel) { node.active.multipliers_cb.push_back (node.config.max_work_generate_multiplier); } - node.active.update_active_difficulty (lock); + node.active.update_active_multiplier (lock); } // Wait for work generation to start system.deadline_set (5s); @@ -1289,3 +1390,186 @@ TEST (wallet, work_watcher_cancel) ASSERT_TRUE (wallet.wallets.watcher->is_watched (block1->qualified_root ())); } } + +// Ensure the minimum limited difficulty is enough for the highest threshold +TEST (wallet, limited_difficulty) +{ + nano::system system; + nano::genesis genesis; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.max_work_generate_multiplier = 1; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config, node_flags); + auto & wallet (*system.wallet (0)); + // Upgrade the genesis account to epoch 2 + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2)); + ASSERT_EQ (nano::epoch::epoch_2, node.store.block_version (node.store.tx_begin_read (), node.latest (nano::test_genesis_key.pub))); + wallet.insert_adhoc (nano::test_genesis_key.prv, false); + { + // Force active difficulty to an impossibly high value + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = 1024 * 1024 * 1024; + } + ASSERT_EQ (node.max_work_generate_difficulty (nano::work_version::work_1), node.active.limited_active_difficulty (*genesis.open)); + auto send = wallet.send_action (nano::test_genesis_key.pub, nano::keypair ().pub, 1, 1); + ASSERT_NE (nullptr, send); +} + +TEST (wallet, epoch_2_validation) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + auto & wallet (*system.wallet (0)); + + // Upgrade the genesis account to epoch 2 + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2)); + + wallet.insert_adhoc (nano::test_genesis_key.prv, false); + + // Test send and receive blocks + // An epoch 2 receive block should be generated with lower difficulty with high probability + auto tries = 0; + auto max_tries = 20; + auto amount = node.config.receive_minimum.number (); + while (++tries < max_tries) + { + auto send = wallet.send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, amount, 1); + ASSERT_NE (nullptr, send); + + auto receive = wallet.receive_action (*send, nano::test_genesis_key.pub, amount, 1); + ASSERT_NE (nullptr, receive); + if (receive->difficulty () < node.network_params.network.publish_thresholds.base) + { + ASSERT_GE (receive->difficulty (), node.network_params.network.publish_thresholds.epoch_2_receive); + break; + } + } + ASSERT_LT (tries, max_tries); + + // Test a change block + ASSERT_NE (nullptr, wallet.change_action (nano::test_genesis_key.pub, nano::keypair ().pub, 1)); +} + +// Receiving from an upgraded account uses the lower threshold and upgrades the receiving account +TEST (wallet, epoch_2_receive_propagation) +{ + auto tries = 0; + auto const max_tries = 20; + while (++tries < max_tries) + { + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node (*system.add_node (node_flags)); + auto & wallet (*system.wallet (0)); + + // Upgrade the genesis account to epoch 1 + auto epoch1 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + + nano::keypair key; + nano::state_block_builder builder; + + // Send and open the account + wallet.insert_adhoc (nano::test_genesis_key.prv, false); + wallet.insert_adhoc (key.prv, false); + auto amount = node.config.receive_minimum.number (); + auto send1 = wallet.send_action (nano::test_genesis_key.pub, key.pub, amount, 1); + ASSERT_NE (nullptr, send1); + ASSERT_NE (nullptr, wallet.receive_action (*send1, nano::test_genesis_key.pub, amount, 1)); + + // Upgrade the genesis account to epoch 2 + auto epoch2 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_2); + ASSERT_NE (nullptr, epoch2); + + // Send a block + auto send2 = wallet.send_action (nano::test_genesis_key.pub, key.pub, amount, 1); + ASSERT_NE (nullptr, send2); + + // Receiving should use the lower difficulty + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = 1.0; + } + auto receive2 = wallet.receive_action (*send2, key.pub, amount, 1); + ASSERT_NE (nullptr, receive2); + if (receive2->difficulty () < node.network_params.network.publish_thresholds.base) + { + ASSERT_GE (receive2->difficulty (), node.network_params.network.publish_thresholds.epoch_2_receive); + ASSERT_EQ (nano::epoch::epoch_2, node.store.block_version (node.store.tx_begin_read (), receive2->hash ())); + break; + } + } + ASSERT_LT (tries, max_tries); +} + +// Opening an upgraded account uses the lower threshold +TEST (wallet, epoch_2_receive_unopened) +{ + // Ensure the lower receive work is used when receiving + auto tries = 0; + auto const max_tries = 20; + while (++tries < max_tries) + { + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node (*system.add_node (node_flags)); + auto & wallet (*system.wallet (0)); + + // Upgrade the genesis account to epoch 1 + auto epoch1 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + + nano::keypair key; + nano::state_block_builder builder; + + // Send + wallet.insert_adhoc (nano::test_genesis_key.prv, false); + auto amount = node.config.receive_minimum.number (); + auto send1 = wallet.send_action (nano::test_genesis_key.pub, key.pub, amount, 1); + + // Upgrade unopened account to epoch_2 + auto epoch2_unopened = nano::state_block (key.pub, 0, 0, 0, node.network_params.ledger.epochs.link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key.pub, node.network_params.network.publish_thresholds.epoch_2)); + ASSERT_EQ (nano::process_result::progress, node.process (epoch2_unopened).code); + + wallet.insert_adhoc (key.prv, false); + + // Receiving should use the lower difficulty + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = 1.0; + } + auto receive1 = wallet.receive_action (*send1, key.pub, amount, 1); + ASSERT_NE (nullptr, receive1); + if (receive1->difficulty () < node.network_params.network.publish_thresholds.base) + { + ASSERT_GE (receive1->difficulty (), node.network_params.network.publish_thresholds.epoch_2_receive); + ASSERT_EQ (nano::epoch::epoch_2, node.store.block_version (node.store.tx_begin_read (), receive1->hash ())); + break; + } + } + ASSERT_LT (tries, max_tries); +} + +TEST (wallet, foreach_representative_deadlock) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node.wallets.compute_reps (); + ASSERT_EQ (1, node.wallets.reps ().voting); + node.wallets.foreach_representative ([&node](nano::public_key const & pub, nano::raw_key const & prv) { + if (node.wallets.mutex.try_lock ()) + { + node.wallets.mutex.unlock (); + } + else + { + ASSERT_FALSE (true); + } + }); +} diff --git a/nano/core_test/wallets.cpp b/nano/core_test/wallets.cpp index ecb7868618..a8521ff881 100644 --- a/nano/core_test/wallets.cpp +++ b/nano/core_test/wallets.cpp @@ -5,13 +5,11 @@ #include -#include - using namespace std::chrono_literals; TEST (wallets, open_create) { - nano::system system (24000, 1); + nano::system system (1); bool error (false); nano::wallets wallets (error, *system.nodes[0]); ASSERT_FALSE (error); @@ -25,7 +23,7 @@ TEST (wallets, open_create) TEST (wallets, open_existing) { - nano::system system (24000, 1); + nano::system system (1); auto id (nano::random_wallet_id ()); { bool error (false); @@ -55,7 +53,7 @@ TEST (wallets, open_existing) TEST (wallets, remove) { - nano::system system (24000, 1); + nano::system system (1); nano::wallet_id one (1); { bool error (false); @@ -79,7 +77,6 @@ TEST (wallets, remove) TEST (wallets, upgrade) { // Don't test this in rocksdb mode - static nano::network_constants network_constants; auto use_rocksdb_str = std::getenv ("TEST_USE_ROCKSDB"); if (use_rocksdb_str && boost::lexical_cast (use_rocksdb_str) == 1) { @@ -87,12 +84,12 @@ TEST (wallets, upgrade) } nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; system.add_node (node_config); auto path (nano::unique_path ()); auto id = nano::random_wallet_id (); - nano::node_config node_config1 (24001, system.logging); + nano::node_config node_config1 (nano::get_available_port (), system.logging); node_config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; { auto node1 (std::make_shared (system.io_ctx, path, system.alarm, node_config1, system.work)); @@ -113,8 +110,8 @@ TEST (wallets, upgrade) auto rep_block = node1->rep_block (nano::genesis_account); nano::account_info_v13 account_info_v13 (info.head, rep_block, info.open_block, info.balance, info.modified, info.block_count, info.epoch ()); auto status (mdb_put (mdb_store.env.tx (transaction_destination), info.epoch () == nano::epoch::epoch_0 ? mdb_store.accounts_v0 : mdb_store.accounts_v1, nano::mdb_val (nano::test_genesis_key.pub), nano::mdb_val (account_info_v13), 0)); - (void)status; - assert (status == 0); + ASSERT_EQ (status, 0); + mdb_store.confirmation_height_del (transaction_destination, nano::genesis_account); } auto node1 (std::make_shared (system.io_ctx, path, system.alarm, node_config1, system.work)); ASSERT_EQ (1, node1->wallets.items.size ()); @@ -132,11 +129,11 @@ TEST (wallets, upgrade) // Keeps breaking whenever we add new DBs TEST (wallets, DISABLED_wallet_create_max) { - nano::system system (24000, 1); + nano::system system (1); bool error (false); nano::wallets wallets (error, *system.nodes[0]); const int nonWalletDbs = 19; - for (int i = 0; i < system.nodes[0]->config.lmdb_max_dbs - nonWalletDbs; i++) + for (int i = 0; i < system.nodes[0]->config.deprecated_lmdb_max_dbs - nonWalletDbs; i++) { auto wallet_id = nano::random_wallet_id (); auto wallet = wallets.create (wallet_id); @@ -155,28 +152,29 @@ TEST (wallets, DISABLED_wallet_create_max) TEST (wallets, reload) { - nano::system system (24000, 1); + nano::system system (1); + auto & node1 (*system.nodes[0]); nano::wallet_id one (1); bool error (false); ASSERT_FALSE (error); - ASSERT_EQ (1, system.nodes[0]->wallets.items.size ()); + ASSERT_EQ (1, node1.wallets.items.size ()); { - nano::lock_guard lock_wallet (system.nodes[0]->wallets.mutex); - nano::inactive_node node (system.nodes[0]->application_path, 24001); + nano::lock_guard lock_wallet (node1.wallets.mutex); + nano::inactive_node node (node1.application_path); auto wallet (node.node->wallets.create (one)); ASSERT_NE (wallet, nullptr); } system.deadline_set (5s); - while (system.nodes[0]->wallets.open (one) == nullptr) + while (node1.wallets.open (one) == nullptr) { system.poll (); } - ASSERT_EQ (2, system.nodes[0]->wallets.items.size ()); + ASSERT_EQ (2, node1.wallets.items.size ()); } TEST (wallets, vote_minimum) { - nano::system system (24000, 1); + nano::system system (1); auto & node1 (*system.nodes[0]); nano::keypair key1; nano::keypair key2; @@ -198,3 +196,28 @@ TEST (wallets, vote_minimum) node1.wallets.compute_reps (); ASSERT_EQ (2, wallet->representatives.size ()); } + +TEST (wallets, exists) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + nano::keypair key1; + nano::keypair key2; + { + auto transaction (node.wallets.tx_begin_read ()); + ASSERT_FALSE (node.wallets.exists (transaction, key1.pub)); + ASSERT_FALSE (node.wallets.exists (transaction, key2.pub)); + } + system.wallet (0)->insert_adhoc (key1.prv); + { + auto transaction (node.wallets.tx_begin_read ()); + ASSERT_TRUE (node.wallets.exists (transaction, key1.pub)); + ASSERT_FALSE (node.wallets.exists (transaction, key2.pub)); + } + system.wallet (0)->insert_adhoc (key2.prv); + { + auto transaction (node.wallets.tx_begin_read ()); + ASSERT_TRUE (node.wallets.exists (transaction, key1.pub)); + ASSERT_TRUE (node.wallets.exists (transaction, key2.pub)); + } +} diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index 00b210aa77..de0559d286 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -1,7 +1,6 @@ -#include -#include +#include +#include #include -#include #include #include @@ -10,9 +9,7 @@ #include #include -#include #include -#include #include #include #include @@ -20,188 +17,91 @@ using namespace std::chrono_literals; -namespace -{ -/** This variable must be set to false before setting up every thread that makes a websocket test call (and needs ack), to be safe */ -std::atomic ack_ready{ false }; - -/** An optionally blocking websocket client for testing */ -boost::optional websocket_test_call (std::string host, std::string port, std::string message_a, bool await_ack, bool await_response, const std::chrono::seconds response_deadline = 5s) -{ - if (await_ack) - { - ack_ready = false; - } - - boost::optional ret; - boost::asio::io_context ioc; - boost::asio::ip::tcp::resolver resolver{ ioc }; - auto ws (std::make_shared> (ioc)); - - auto const results = resolver.resolve (host, port); - boost::asio::connect (ws->next_layer (), results.begin (), results.end ()); - - ws->handshake (host, "/"); - ws->text (true); - ws->write (boost::asio::buffer (message_a)); - - if (await_ack) - { - boost::beast::flat_buffer buffer; - ws->read (buffer); - ack_ready = true; - } - - if (await_response) - { - assert (response_deadline > 0s); - auto buffer (std::make_shared ()); - ws->async_read (*buffer, [&ret, ws, buffer](boost::beast::error_code const & ec, std::size_t const n) { - if (!ec) - { - std::ostringstream res; - res << beast_buffers (buffer->data ()); - ret = res.str (); - } - }); - ioc.run_one_for (response_deadline); - } - - if (ws->is_open ()) - { - ws->async_close (boost::beast::websocket::close_code::normal, [ws](boost::beast::error_code const & ec) { - // A synchronous close usually hangs in tests when the server's io_context stops looping - // An async_close solves this problem - }); - } - return ret; -} -} - -/** Tests clients subscribing multiple times or unsubscribing without a subscription */ +// Tests clients subscribing multiple times or unsubscribing without a subscription TEST (websocket, subscription_edge) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; - - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->start (); - system.nodes.push_back (node1); + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); - // First subscription - { - ack_ready = false; - std::thread subscription_thread ([]() { - websocket_test_call ("::1", "24078", R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json", true, false); - }); - system.deadline_set (5s); - while (!ack_ready) - { - ASSERT_NO_ERROR (system.poll ()); - } - subscription_thread.join (); - ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); - } - - // Second subscription, should not increase subscriber count, only update the subscription - { - ack_ready = false; - std::thread subscription_thread ([]() { - websocket_test_call ("::1", "24078", R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json", true, false); - }); - system.deadline_set (5s); - while (!ack_ready) - { - ASSERT_NO_ERROR (system.poll ()); - } - subscription_thread.join (); - ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); - } - - // First unsub - { - ack_ready = false; - std::thread unsub_thread ([]() { - websocket_test_call ("::1", "24078", R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json", true, false); - }); - system.deadline_set (5s); - while (!ack_ready) - { - ASSERT_NO_ERROR (system.poll ()); - } - unsub_thread.join (); - ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); - } + auto task = ([config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + client.send_message (R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + EXPECT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + client.send_message (R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + EXPECT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + }); + auto future = std::async (std::launch::async, task); - // Second unsub, should acknowledge but not decrease subscriber count + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) { - ack_ready = false; - std::thread unsub_thread ([]() { - websocket_test_call ("::1", "24078", R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json", true, false); - }); - system.deadline_set (5s); - while (!ack_ready) - { - ASSERT_NO_ERROR (system.poll ()); - } - unsub_thread.join (); - ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + ASSERT_NO_ERROR (system.poll ()); } - - node1->stop (); } -// Test client subscribing to changes in active_difficulty +// Test client subscribing to changes in active_multiplier TEST (websocket, active_difficulty) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; + config.websocket_config.port = nano::get_available_port (); + nano::node_flags node_flags; + // Disable auto-updating active difficulty (multiplier) to prevent intermittent failures + node_flags.disable_request_loop = true; + auto node1 (system.add_node (config, node_flags)); - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->start (); - system.nodes.push_back (node1); + // "Start" epoch 2 + node1->ledger.cache.epoch_2_started = true; + ASSERT_EQ (node1->default_difficulty (nano::work_version::work_1), node1->network_params.network.publish_thresholds.epoch_2); ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::active_difficulty)); - // Subscribe to active_difficulty and wait for response asynchronously - ack_ready = false; - auto client_task = ([]() -> boost::optional { - auto response = websocket_test_call ("::1", "24078", R"json({"action": "subscribe", "topic": "active_difficulty", "ack": true})json", true, true); - return response; + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "active_difficulty", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::active_difficulty)); + return client.get_response (); }); - auto client_future = std::async (std::launch::async, client_task); + auto future = std::async (std::launch::async, task); - // Wait for acknowledge system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::active_difficulty)); - // Fake history records to force trended_active_difficulty change + // Fake history records and force a trended_active_multiplier change { nano::unique_lock lock (node1->active.mutex); node1->active.multipliers_cb.push_front (10.); + node1->active.update_active_multiplier (lock); } - // Wait to receive the active_difficulty message system.deadline_set (5s); - while (client_future.wait_for (std::chrono::seconds (0)) != std::future_status::ready) + while (future.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } // Check active_difficulty response - auto response = client_future.get (); + boost::optional response = future.get (); ASSERT_TRUE (response); std::stringstream stream; stream << response; @@ -212,62 +112,55 @@ TEST (websocket, active_difficulty) auto message_contents = event.get_child ("message"); uint64_t network_minimum; nano::from_string_hex (message_contents.get ("network_minimum"), network_minimum); - ASSERT_EQ (network_minimum, node1->network_params.network.publish_threshold); + ASSERT_EQ (network_minimum, node1->default_difficulty (nano::work_version::work_1)); uint64_t network_current; nano::from_string_hex (message_contents.get ("network_current"), network_current); ASSERT_EQ (network_current, node1->active.active_difficulty ()); double multiplier = message_contents.get ("multiplier"); - ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (node1->active.active_difficulty (), node1->network_params.network.publish_threshold), 1e-6); - - node1->stop (); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (node1->active.active_difficulty (), node1->default_difficulty (nano::work_version::work_1)), 1e-6); } -/** Subscribes to block confirmations, confirms a block and then awaits websocket notification */ +// Subscribes to block confirmations, confirms a block and then awaits websocket notification TEST (websocket, confirmation) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; - - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->wallets.create (nano::random_wallet_id ()); - node1->start (); - system.nodes.push_back (node1); - - // Start websocket test-client in a separate thread - ack_ready = false; - std::atomic confirmation_event_received{ false }; - ASSERT_FALSE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation)); - std::thread client_thread ([&confirmation_event_received]() { - // This will expect two results: the acknowledgement of the subscription - // and then the block confirmation message - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json", true, true); - ASSERT_TRUE (response); + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + std::atomic unsubscribed{ false }; + auto task = ([&ack_ready, &unsubscribed, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + auto response = client.get_response (); + EXPECT_TRUE (response); boost::property_tree::ptree event; std::stringstream stream; stream << response.get (); boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "confirmation"); - confirmation_event_received = true; + EXPECT_EQ (event.get ("topic"), "confirmation"); + client.send_message (R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json"); + client.await_ack (); + unsubscribed = true; + EXPECT_FALSE (client.get_response (1s)); }); + auto future = std::async (std::launch::async, task); - // Wait for the subscription to be acknowledged system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation)); nano::keypair key; - system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto balance = nano::genesis_amount; auto send_amount = node1->config.online_weight_minimum.number () + 1; // Quick-confirm a block, legacy blocks should work without filtering @@ -278,39 +171,11 @@ TEST (websocket, confirmation) node1->process_active (send); } - // Wait for the confirmation to be received system.deadline_set (5s); - while (!confirmation_event_received) + while (!unsubscribed) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - client_thread.join (); - - std::atomic unsubscribe_ack_received{ false }; - std::thread client_thread_2 ([&unsubscribe_ack_received]() { - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "confirmation", "ack": true})json", true, true); - ASSERT_TRUE (response); - boost::property_tree::ptree event; - std::stringstream stream; - stream << response.get (); - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "confirmation"); - - // Unsubscribe action, expects an acknowledge but no response follows - websocket_test_call ("::1", "24078", - R"json({"action": "unsubscribe", "topic": "confirmation", "ack": true})json", true, true, 1s); - unsubscribe_ack_received = true; - }); - - // Wait for the subscription to be acknowledged - system.deadline_set (5s); - while (!ack_ready) - { - ASSERT_NO_ERROR (system.poll ()); - } - ack_ready = false; // Quick confirm a state block { @@ -320,56 +185,38 @@ TEST (websocket, confirmation) node1->process_active (send); } - // Wait for the unsubscribe action to be acknowledged system.deadline_set (5s); - while (!unsubscribe_ack_received) + while (future.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - client_thread_2.join (); - - node1->stop (); } -/** Tests getting notification of an erased election */ +// Tests getting notification of an erased election TEST (websocket, stopped_election) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; - - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->wallets.create (nano::random_wallet_id ()); - node1->start (); - system.nodes.push_back (node1); - - // Start websocket test-client in a separate thread - ack_ready = false; - std::atomic client_thread_finished{ false }; - ASSERT_FALSE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation)); - std::thread client_thread ([&client_thread_finished]() { - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "stopped_election", "ack": "true"})json", true, true, 5s); - - ASSERT_TRUE (response); - boost::property_tree::ptree event; - std::stringstream stream; - stream << response.get (); - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "stopped_election"); - client_thread_finished = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "stopped_election", "ack": "true"})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::stopped_election)); + return client.get_response (); }); + auto future = std::async (std::launch::async, task); - // Wait for subscribe acknowledgement system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; // Create election, then erase it, causing a websocket message to be emitted nano::keypair key1; @@ -381,54 +228,50 @@ TEST (websocket, stopped_election) node1->block_processor.flush (); node1->active.erase (*send1); - // Wait for subscribe acknowledgement system.deadline_set (5s); - while (!client_thread_finished) + while (future.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - client_thread.join (); - node1->stop (); + auto response = future.get (); + ASSERT_TRUE (response); + boost::property_tree::ptree event; + std::stringstream stream; + stream << response.get (); + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "stopped_election"); } -/** Tests the filtering options of block confirmations */ +// Tests the filtering options of block confirmations TEST (websocket, confirmation_options) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; - - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->wallets.create (nano::random_wallet_id ()); - node1->start (); - system.nodes.push_back (node1); - - // Start websocket test-client in a separate thread - ack_ready = false; - std::atomic client_thread_finished{ false }; - ASSERT_FALSE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation)); - std::thread client_thread ([&client_thread_finished]() { - // Subscribe initially with a specific invalid account - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "accounts": ["xrb_invalid"]}})json", true, true, 1s); - - ASSERT_FALSE (response); - client_thread_finished = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task1 = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "accounts": ["xrb_invalid"]}})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + auto response = client.get_response (1s); + EXPECT_FALSE (response); }); + auto future1 = std::async (std::launch::async, task1); - // Wait for subscribe acknowledgement system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; // Confirm a state block for an in-wallet account - system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; auto balance = nano::genesis_amount; auto send_amount = node1->config.online_weight_minimum.number () + 1; @@ -440,56 +283,28 @@ TEST (websocket, confirmation_options) previous = send->hash (); } - // Wait for client thread to finish, no confirmation message should be received with given filter system.deadline_set (5s); - while (!client_thread_finished) + while (future1.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - std::atomic client_thread_2_finished{ false }; - std::thread client_thread_2 ([&client_thread_2_finished]() { - // Re-subscribe with options for all local wallet accounts - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "all_local_accounts": "true", "include_election_info": "true"}})json", true, true); - ASSERT_TRUE (response); - boost::property_tree::ptree event; - std::stringstream stream; - stream << response.get (); - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "confirmation"); - try - { - boost::property_tree::ptree election_info = event.get_child ("message.election_info"); - auto tally (election_info.get ("tally")); - auto time (election_info.get ("time")); - // Duration and request count may be zero on testnet, so we only check that they're present - ASSERT_EQ (1, election_info.count ("duration")); - ASSERT_EQ (1, election_info.count ("request_count")); - // Make sure tally and time are non-zero. - ASSERT_NE ("0", tally); - ASSERT_NE ("0", time); - } - catch (std::runtime_error const & ex) - { - FAIL () << ex.what (); - } - - client_thread_2_finished = true; + ack_ready = false; + auto task2 = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "all_local_accounts": "true", "include_election_info": "true"}})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + return client.get_response (); }); + auto future2 = std::async (std::launch::async, task2); - node1->block_processor.flush (); - // Wait for the subscribe action to be acknowledged system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation)); // Quick-confirm another block { @@ -499,23 +314,55 @@ TEST (websocket, confirmation_options) previous = send->hash (); } - node1->block_processor.flush (); - // Wait for confirmation message system.deadline_set (5s); - while (!client_thread_2_finished) + while (future2.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - std::atomic client_thread_3_finished{ false }; - std::thread client_thread_3 ([&client_thread_3_finished]() { - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "all_local_accounts": "true"}})json", true, true, 1s); + auto response2 = future2.get (); + ASSERT_TRUE (response2); + boost::property_tree::ptree event; + std::stringstream stream; + stream << response2.get (); + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "confirmation"); + try + { + boost::property_tree::ptree election_info = event.get_child ("message.election_info"); + auto tally (election_info.get ("tally")); + auto time (election_info.get ("time")); + // Duration and request count may be zero on testnet, so we only check that they're present + ASSERT_EQ (1, election_info.count ("duration")); + ASSERT_EQ (1, election_info.count ("request_count")); + ASSERT_EQ (1, election_info.count ("voters")); + ASSERT_GE (1, election_info.get ("blocks")); + // Make sure tally and time are non-zero. + ASSERT_NE ("0", tally); + ASSERT_NE ("0", time); + } + catch (std::runtime_error const & ex) + { + FAIL () << ex.what (); + } - ASSERT_FALSE (response); - client_thread_3_finished = true; + ack_ready = false; + auto task3 = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "all_local_accounts": "true"}})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + auto response = client.get_response (1s); + EXPECT_FALSE (response); }); + auto future3 = std::async (std::launch::async, task3); + + system.deadline_set (5s); + while (!ack_ready) + { + ASSERT_NO_ERROR (system.poll ()); + } // Confirm a legacy block // When filtering options are enabled, legacy blocks are always filtered @@ -526,130 +373,216 @@ TEST (websocket, confirmation_options) previous = send->hash (); } - node1->block_processor.flush (); - // Wait for client thread to finish, no confirmation message should be received system.deadline_set (5s); - while (!client_thread_3_finished) + while (future1.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - client_thread.join (); - client_thread_2.join (); - client_thread_3.join (); - node1->stop (); } -/** Subscribes to votes, sends a block and awaits websocket notification of a vote arrival */ -TEST (websocket, vote) +// Tests updating options of block confirmations +TEST (websocket, confirmation_options_update) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic added{ false }; + std::atomic deleted{ false }; + auto task = ([&added, &deleted, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + // Subscribe initially with empty options, everything will be filtered + client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {}})json"); + client.await_ack (); + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + // Now update filter with an account and wait for a response + std::string add_message = boost::str (boost::format (R"json({"action": "update", "topic": "confirmation", "ack": "true", "options": {"accounts_add": ["%1%"]}})json") % nano::test_genesis_key.pub.to_account ()); + client.send_message (add_message); + client.await_ack (); + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + added = true; + EXPECT_TRUE (client.get_response ()); + // Update the filter again, removing the account + std::string delete_message = boost::str (boost::format (R"json({"action": "update", "topic": "confirmation", "ack": "true", "options": {"accounts_del": ["%1%"]}})json") % nano::test_genesis_key.pub.to_account ()); + client.send_message (delete_message); + client.await_ack (); + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation)); + deleted = true; + EXPECT_FALSE (client.get_response (1s)); + }); + auto future = std::async (std::launch::async, task); - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->wallets.create (nano::random_wallet_id ()); - node1->start (); - system.nodes.push_back (node1); + // Wait for update acknowledgement + system.deadline_set (5s); + while (!added) + { + ASSERT_NO_ERROR (system.poll ()); + } - // Start websocket test-client in a separate thread - ack_ready = false; - std::atomic client_thread_finished{ false }; - ASSERT_FALSE (node1->websocket_server->any_subscriber (nano::websocket::topic::vote)); - std::thread client_thread ([&client_thread_finished]() { - // This will expect two results: the acknowledgement of the subscription - // and then the vote message - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "vote", "ack": true})json", true, true); - - ASSERT_TRUE (response); - boost::property_tree::ptree event; - std::stringstream stream; - stream << response; - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "vote"); - client_thread_finished = true; + // Confirm a block + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::genesis genesis; + nano::keypair key; + auto previous (node1->latest (nano::test_genesis_key.pub)); + auto send (std::make_shared (nano::test_genesis_key.pub, previous, nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); + node1->process_active (send); + + // Wait for delete acknowledgement + system.deadline_set (5s); + while (!deleted) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Confirm another block + previous = send->hash (); + auto send2 (std::make_shared (nano::test_genesis_key.pub, previous, nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); + node1->process_active (send2); + + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +// Subscribes to votes, sends a block and awaits websocket notification of a vote arrival +TEST (websocket, vote) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "vote", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::vote)); + return client.get_response (); }); + auto future = std::async (std::launch::async, task); - // Wait for the subscription to be acknowledged system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::vote)); // Quick-confirm a block nano::keypair key; - system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::block_hash previous (node1->latest (nano::test_genesis_key.pub)); auto send (std::make_shared (nano::test_genesis_key.pub, previous, nano::test_genesis_key.pub, nano::genesis_amount - (node1->config.online_weight_minimum.number () + 1), key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous))); node1->process_active (send); - // Wait for the websocket client to receive the vote message system.deadline_set (5s); - while (!client_thread_finished) + while (future.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - client_thread.join (); - node1->stop (); + auto response = future.get (); + ASSERT_TRUE (response); + boost::property_tree::ptree event; + std::stringstream stream; + stream << response; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "vote"); } -/** Tests vote subscription options */ -TEST (websocket, vote_options) +// Tests vote subscription options - vote type +TEST (websocket, vote_options_type) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"include_replays": "true", "include_indeterminate": "false"}})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::vote)); + return client.get_response (); + }); + auto future = std::async (std::launch::async, task); + + system.deadline_set (5s); + while (!ack_ready) + { + ASSERT_NO_ERROR (system.poll ()); + } - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->wallets.create (nano::random_wallet_id ()); - node1->start (); - system.nodes.push_back (node1); + // Custom made votes for simplicity + nano::genesis genesis; + auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open)); + nano::websocket::message_builder builder; + auto msg (builder.vote_received (vote, nano::vote_code::replay)); + node1->websocket_server->broadcast (msg); - // Start websocket test-client in a separate thread - ack_ready = false; - std::atomic client_thread_finished{ false }; - ASSERT_FALSE (node1->websocket_server->any_subscriber (nano::websocket::topic::vote)); - std::thread client_thread ([&client_thread_finished]() { - std::ostringstream data; - data << R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"representatives": [")json" - << nano::test_genesis_key.pub.to_account () - << R"json("]}})json"; - auto response = websocket_test_call ("::1", "24078", data.str (), true, true); - - ASSERT_TRUE (response); + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto response = future.get (); + ASSERT_TRUE (response); + boost::property_tree::ptree event; + std::stringstream stream; + stream << response; + boost::property_tree::read_json (stream, event); + auto message_contents = event.get_child ("message"); + ASSERT_EQ (1, message_contents.count ("type")); + ASSERT_EQ ("replay", message_contents.get ("type")); +} + +// Tests vote subscription options - list of representatives +TEST (websocket, vote_options_representatives) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task1 = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + std::string message = boost::str (boost::format (R"json({"action": "subscribe", "topic": "vote", "ack": "true", "options": {"representatives": ["%1%"]}})json") % nano::test_genesis_key.pub.to_account ()); + client.send_message (message); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::vote)); + auto response = client.get_response (); + EXPECT_TRUE (response); boost::property_tree::ptree event; std::stringstream stream; stream << response; boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "vote"); - client_thread_finished = true; + EXPECT_EQ (event.get ("topic"), "vote"); }); + auto future1 = std::async (std::launch::async, task1); - // Wait for the subscription to be acknowledged system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::vote)); // Quick-confirm a block nano::keypair key; auto balance = nano::genesis_amount; - system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send_amount = node1->config.online_weight_minimum.number () + 1; auto confirm_block = [&]() { nano::block_hash previous (node1->latest (nano::test_genesis_key.pub)); @@ -659,22 +592,24 @@ TEST (websocket, vote_options) }; confirm_block (); - // Wait for the websocket client to receive the vote message system.deadline_set (5s); - while (!client_thread_finished || node1->websocket_server->any_subscriber (nano::websocket::topic::vote)) + while (future1.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - std::atomic client_thread_2_finished{ false }; - std::thread client_thread_2 ([&client_thread_2_finished]() { - auto response = websocket_test_call ("::1", "24078", - R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"representatives": ["xrb_invalid"]}})json", true, true, 1s); - - // No response expected given the filter - ASSERT_FALSE (response); - client_thread_2_finished = true; + ack_ready = false; + auto task2 = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "vote", "ack": "true", "options": {"representatives": ["xrb_invalid"]}})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::vote)); + auto response = client.get_response (); + // A list of invalid representatives is the same as no filter + EXPECT_TRUE (response); }); + auto future2 = std::async (std::launch::async, task2); // Wait for the subscription to be acknowledged system.deadline_set (5s); @@ -682,47 +617,39 @@ TEST (websocket, vote_options) { ASSERT_NO_ERROR (system.poll ()); } - ack_ready = false; - - ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::vote)); // Confirm another block confirm_block (); - // No response expected system.deadline_set (5s); - while (!client_thread_2_finished) + while (future2.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } - - client_thread.join (); - client_thread_2.join (); - node1->stop (); } // Test client subscribing to notifications for work generation TEST (websocket, work) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; - - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->start (); - system.nodes.push_back (node1); + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::work)); // Subscribe to work and wait for response asynchronously - ack_ready = false; - auto client_task = ([]() -> boost::optional { - auto response = websocket_test_call ("::1", "24078", R"json({"action": "subscribe", "topic": "work", "ack": true})json", true, true); - return response; + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "work", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::work)); + return client.get_response (); }); - auto client_future = std::async (std::launch::async, client_task); + auto future = std::async (std::launch::async, task); // Wait for acknowledge system.deadline_set (5s); @@ -739,13 +666,13 @@ TEST (websocket, work) // Wait for the work notification system.deadline_set (5s); - while (client_future.wait_for (std::chrono::seconds (0)) != std::future_status::ready) + while (future.wait_for (0s) != std::future_status::ready) { ASSERT_NO_ERROR (system.poll ()); } // Check the work notification message - auto response = client_future.get (); + auto response = future.get (); ASSERT_TRUE (response); std::stringstream stream; stream << response; @@ -759,16 +686,17 @@ TEST (websocket, work) ASSERT_EQ (1, contents.count ("request")); auto & request = contents.get_child ("request"); + ASSERT_EQ (request.get ("version"), nano::to_string (nano::work_version::work_1)); ASSERT_EQ (request.get ("hash"), hash.to_string ()); - ASSERT_EQ (request.get ("difficulty"), nano::to_string_hex (node1->network_params.network.publish_threshold)); + ASSERT_EQ (request.get ("difficulty"), nano::to_string_hex (node1->default_difficulty (nano::work_version::work_1))); ASSERT_EQ (request.get ("multiplier"), 1.0); ASSERT_EQ (1, contents.count ("result")); auto & result = contents.get_child ("result"); uint64_t result_difficulty; nano::from_string_hex (result.get ("difficulty"), result_difficulty); - ASSERT_GE (result_difficulty, node1->network_params.network.publish_threshold); - ASSERT_NEAR (result.get ("multiplier"), nano::difficulty::to_multiplier (result_difficulty, node1->network_params.network.publish_threshold), 1e-6); + ASSERT_GE (result_difficulty, node1->default_difficulty (nano::work_version::work_1)); + ASSERT_NEAR (result.get ("multiplier"), nano::difficulty::to_multiplier (result_difficulty, node1->default_difficulty (nano::work_version::work_1)), 1e-6); ASSERT_EQ (result.get ("work"), nano::to_string_hex (work.get ())); ASSERT_EQ (1, contents.count ("bad_peers")); @@ -778,26 +706,266 @@ TEST (websocket, work) ASSERT_EQ (contents.get ("reason"), ""); } -/** Tests clients subscribing multiple times or unsubscribing without a subscription */ -TEST (websocket, ws_keepalive) +// Test client subscribing to notifications for bootstrap +TEST (websocket, bootstrap) { - nano::system system (24000, 1); - nano::node_config config; - nano::node_flags node_flags; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); config.websocket_config.enabled = true; - config.websocket_config.port = 24078; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); - auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); - node1->start (); - system.nodes.push_back (node1); - ack_ready = false; - std::thread subscription_thread ([]() { - websocket_test_call ("::1", "24078", R"json({"action": "ping"})json", true, false); + ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::bootstrap)); + + // Subscribe to bootstrap and wait for response asynchronously + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "bootstrap", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::bootstrap)); + return client.get_response (); }); + auto future = std::async (std::launch::async, task); + + // Wait for acknowledge system.deadline_set (5s); while (!ack_ready) { ASSERT_NO_ERROR (system.poll ()); } - subscription_thread.join (); -} \ No newline at end of file + + // Start bootstrap attempt + node1->bootstrap_initiator.bootstrap (true, "123abc"); + ASSERT_NE (nullptr, node1->bootstrap_initiator.current_attempt ()); + + // Wait for the bootstrap notification + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Check the bootstrap notification message + auto response = future.get (); + ASSERT_TRUE (response); + std::stringstream stream; + stream << response; + boost::property_tree::ptree event; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "bootstrap"); + + auto & contents = event.get_child ("message"); + ASSERT_EQ (contents.get ("reason"), "started"); + ASSERT_EQ (contents.get ("id"), "123abc"); + ASSERT_EQ (contents.get ("mode"), "legacy"); + + // Wait for bootstrap finish + system.deadline_set (5s); + while (node1->bootstrap_initiator.in_progress ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (websocket, bootstrap_exited) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + // Start bootstrap, exit after subscription + std::atomic bootstrap_started{ false }; + nano::util::counted_completion subscribed_completion (1); + std::thread bootstrap_thread ([node1, &system, &bootstrap_started, &subscribed_completion]() { + std::shared_ptr attempt; + while (attempt == nullptr) + { + std::this_thread::sleep_for (50ms); + node1->bootstrap_initiator.bootstrap (true, "123abc"); + attempt = node1->bootstrap_initiator.current_attempt (); + } + ASSERT_NE (nullptr, attempt); + bootstrap_started = true; + EXPECT_FALSE (subscribed_completion.await_count_for (5s)); + }); + + // Wait for bootstrap start + system.deadline_set (5s); + while (!bootstrap_started) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Subscribe to bootstrap and wait for response asynchronously + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "bootstrap", "ack": true})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::bootstrap)); + return client.get_response (); + }); + auto future = std::async (std::launch::async, task); + + // Wait for acknowledge + system.deadline_set (5s); + while (!ack_ready) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Wait for the bootstrap notification + subscribed_completion.increment (); + bootstrap_thread.join (); + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Check the bootstrap notification message + auto response = future.get (); + ASSERT_TRUE (response); + std::stringstream stream; + stream << response; + boost::property_tree::ptree event; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "bootstrap"); + + auto & contents = event.get_child ("message"); + ASSERT_EQ (contents.get ("reason"), "exited"); + ASSERT_EQ (contents.get ("id"), "123abc"); + ASSERT_EQ (contents.get ("mode"), "legacy"); + ASSERT_EQ (contents.get ("total_blocks"), 0); + ASSERT_LT (contents.get ("duration"), 15000); +} + +// Tests sending keepalive +TEST (websocket, ws_keepalive) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + auto task = ([config]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "ping"})json"); + client.await_ack (); + }); + auto future = std::async (std::launch::async, task); + + system.deadline_set (5s); + while (future.wait_for (0s) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +// Tests sending telemetry +TEST (websocket, telemetry) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_ongoing_telemetry_requests = true; + auto node1 (system.add_node (config, node_flags)); + config.peering_port = nano::get_available_port (); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node2 (system.add_node (config, node_flags)); + + wait_peer_connections (system); + + std::atomic done{ false }; + auto task = ([config = node1->config, &node1, &done]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "telemetry", "ack": true})json"); + client.await_ack (); + done = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::telemetry)); + return client.get_response (); + }); + + auto future = std::async (std::launch::async, task); + + ASSERT_TIMELY (10s, done); + + node1->telemetry->get_metrics_single_peer_async (node1->network.find_channel (node2->network.endpoint ()), [](auto const & response_a) { + ASSERT_FALSE (response_a.error); + }); + + ASSERT_TIMELY (10s, future.wait_for (0s) == std::future_status::ready); + + // Check the telemetry notification message + auto response = future.get (); + + std::stringstream stream; + stream << response; + boost::property_tree::ptree event; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "telemetry"); + + auto & contents = event.get_child ("message"); + nano::jsonconfig telemetry_contents (contents); + nano::telemetry_data telemetry_data; + telemetry_data.deserialize_json (telemetry_contents, false); + compare_default_telemetry_response_data (telemetry_data, node2->network_params, node2->config.bandwidth_limit, node2->active.active_difficulty (), node2->node_id); + + ASSERT_EQ (contents.get ("address"), node2->network.endpoint ().address ().to_string ()); + ASSERT_EQ (contents.get ("port"), node2->network.endpoint ().port ()); + + // Other node should have no subscribers + EXPECT_EQ (0, node2->websocket_server->subscriber_count (nano::websocket::topic::telemetry)); +} + +TEST (websocket, new_unconfirmed_block) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.websocket_config.enabled = true; + config.websocket_config.port = nano::get_available_port (); + auto node1 (system.add_node (config)); + + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, node1]() { + fake_websocket_client client (config.websocket_config.port); + client.send_message (R"json({"action": "subscribe", "topic": "new_unconfirmed_block", "ack": "true"})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::new_unconfirmed_block)); + return client.get_response (); + }); + auto future = std::async (std::launch::async, task); + + ASSERT_TIMELY (5s, ack_ready); + + // Process a new block + nano::genesis genesis; + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 1, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node1->process_local (send1).code); + + ASSERT_TIMELY (5s, future.wait_for (0s) == std::future_status::ready); + + // Check the response + boost::optional response = future.get (); + ASSERT_TRUE (response); + std::stringstream stream; + stream << response; + boost::property_tree::ptree event; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "new_unconfirmed_block"); + + auto message_contents = event.get_child ("message"); + ASSERT_EQ ("state", message_contents.get ("type")); + ASSERT_EQ ("send", message_contents.get ("subtype")); +} diff --git a/nano/core_test/work_pool.cpp b/nano/core_test/work_pool.cpp index f30bd39ca0..a04b59210e 100644 --- a/nano/core_test/work_pool.cpp +++ b/nano/core_test/work_pool.cpp @@ -1,20 +1,26 @@ #include +#include #include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include + TEST (work, one) { nano::network_constants network_constants; nano::work_pool pool (std::numeric_limits::max ()); nano::change_block block (1, 1, nano::keypair ().prv, 3, 4); block.block_work_set (*pool.generate (block.root ())); - uint64_t difficulty; - ASSERT_FALSE (nano::work_validate (block, &difficulty)); - ASSERT_LT (network_constants.publish_threshold, difficulty); + ASSERT_LT (nano::work_threshold_base (block.work_version ()), block.difficulty ()); } TEST (work, disabled) @@ -30,12 +36,9 @@ TEST (work, validate) nano::network_constants network_constants; nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send_block (1, 1, 2, nano::keypair ().prv, 4, 6); - uint64_t difficulty; - ASSERT_TRUE (nano::work_validate (send_block, &difficulty)); - ASSERT_LT (difficulty, network_constants.publish_threshold); + ASSERT_LT (send_block.difficulty (), nano::work_threshold_base (send_block.work_version ())); send_block.block_work_set (*pool.generate (send_block.root ())); - ASSERT_FALSE (nano::work_validate (send_block, &difficulty)); - ASSERT_LT (network_constants.publish_threshold, difficulty); + ASSERT_LT (nano::work_threshold_base (send_block.work_version ()), send_block.difficulty ()); } TEST (work, cancel) @@ -46,7 +49,8 @@ TEST (work, cancel) while (!done) { nano::root key (1); - pool.generate (key, [&done](boost::optional work_a) { + pool.generate ( + nano::work_version::work_1, key, nano::network_constants ().publish_thresholds.base, [&done](boost::optional work_a) { done = !work_a; }); pool.cancel (key); @@ -64,12 +68,13 @@ TEST (work, cancel_many) nano::root key4 (1); nano::root key5 (3); nano::root key6 (1); - pool.generate (key1, [](boost::optional) {}); - pool.generate (key2, [](boost::optional) {}); - pool.generate (key3, [](boost::optional) {}); - pool.generate (key4, [](boost::optional) {}); - pool.generate (key5, [](boost::optional) {}); - pool.generate (key6, [](boost::optional) {}); + nano::network_constants constants; + pool.generate (nano::work_version::work_1, key1, constants.publish_thresholds.base, [](boost::optional) {}); + pool.generate (nano::work_version::work_1, key2, constants.publish_thresholds.base, [](boost::optional) {}); + pool.generate (nano::work_version::work_1, key3, constants.publish_thresholds.base, [](boost::optional) {}); + pool.generate (nano::work_version::work_1, key4, constants.publish_thresholds.base, [](boost::optional) {}); + pool.generate (nano::work_version::work_1, key5, constants.publish_thresholds.base, [](boost::optional) {}); + pool.generate (nano::work_version::work_1, key6, constants.publish_thresholds.base, [](boost::optional) {}); pool.cancel (key1); } @@ -88,8 +93,8 @@ TEST (work, opencl) if (opencl != nullptr) { // 0 threads, should add 1 for managing OpenCL - nano::work_pool pool (0, std::chrono::nanoseconds (0), [&opencl](nano::root const & root_a, uint64_t difficulty_a, std::atomic & ticket_a) { - return opencl->generate_work (root_a, difficulty_a); + nano::work_pool pool (0, std::chrono::nanoseconds (0), [&opencl](nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::atomic & ticket_a) { + return opencl->generate_work (version_a, root_a, difficulty_a); }); ASSERT_NE (nullptr, pool.opencl); nano::root root; @@ -98,10 +103,8 @@ TEST (work, opencl) for (auto i (0); i < 16; ++i) { nano::random_pool::generate_block (root.bytes.data (), root.bytes.size ()); - auto result (*pool.generate (root, difficulty)); - uint64_t result_difficulty (0); - ASSERT_FALSE (nano::work_validate (root, result, &result_difficulty)); - ASSERT_GE (result_difficulty, difficulty); + auto result (*pool.generate (nano::work_version::work_1, root, difficulty)); + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, root, result), difficulty); difficulty += difficulty_add; } } @@ -138,20 +141,20 @@ TEST (work, difficulty) uint64_t difficulty1 (0xff00000000000000); uint64_t difficulty2 (0xfff0000000000000); uint64_t difficulty3 (0xffff000000000000); - uint64_t nonce1 (0); + uint64_t result_difficulty1 (0); do { - auto work1 = *pool.generate (root, difficulty1); - nano::work_validate (root, work1, &nonce1); - } while (nonce1 > difficulty2); - ASSERT_GT (nonce1, difficulty1); - uint64_t nonce2 (0); + auto work1 = *pool.generate (nano::work_version::work_1, root, difficulty1); + result_difficulty1 = nano::work_difficulty (nano::work_version::work_1, root, work1); + } while (result_difficulty1 > difficulty2); + ASSERT_GT (result_difficulty1, difficulty1); + uint64_t result_difficulty2 (0); do { - auto work2 = *pool.generate (root, difficulty2); - nano::work_validate (root, work2, &nonce2); - } while (nonce2 > difficulty3); - ASSERT_GT (nonce2, difficulty2); + auto work2 = *pool.generate (nano::work_version::work_1, root, difficulty2); + result_difficulty2 = nano::work_difficulty (nano::work_version::work_1, root, work2); + } while (result_difficulty2 > difficulty3); + ASSERT_GT (result_difficulty2, difficulty2); } TEST (work, eco_pow) @@ -167,13 +170,13 @@ TEST (work, eco_pow) nano::root root (1); uint64_t difficulty1 (0xff00000000000000); uint64_t difficulty2 (0xfff0000000000000); - uint64_t nonce (0); + uint64_t result_difficulty (0); do { - auto work = *pool.generate (root, difficulty1); - nano::work_validate (root, work, &nonce); - } while (nonce > difficulty2); - ASSERT_GT (nonce, difficulty1); + auto work = *pool.generate (nano::work_version::work_1, root, difficulty1); + result_difficulty = nano::work_difficulty (nano::work_version::work_1, root, work); + } while (result_difficulty > difficulty2); + ASSERT_GT (result_difficulty, difficulty1); } promise.set_value_at_thread_exit (timer.stop ()); diff --git a/nano/crypto_lib/CMakeLists.txt b/nano/crypto_lib/CMakeLists.txt index c2b509ae82..23b076638e 100644 --- a/nano/crypto_lib/CMakeLists.txt +++ b/nano/crypto_lib/CMakeLists.txt @@ -1,7 +1,8 @@ add_library (crypto_lib interface.cpp + random_pool.hpp random_pool.cpp - random_pool.hpp) + random_pool_shuffle.hpp) target_link_libraries (crypto_lib blake2 diff --git a/nano/crypto_lib/random_pool.cpp b/nano/crypto_lib/random_pool.cpp index 984f51f69c..baaa7e7813 100644 --- a/nano/crypto_lib/random_pool.cpp +++ b/nano/crypto_lib/random_pool.cpp @@ -1,22 +1,32 @@ #include +#include + std::mutex nano::random_pool::mutex; -CryptoPP::AutoSeededRandomPool nano::random_pool::pool; void nano::random_pool::generate_block (unsigned char * output, size_t size) { + auto & pool = get_pool (); std::lock_guard guard (mutex); pool.GenerateBlock (output, size); } unsigned nano::random_pool::generate_word32 (unsigned min, unsigned max) { + auto & pool = get_pool (); std::lock_guard guard (mutex); return pool.GenerateWord32 (min, max); } unsigned char nano::random_pool::generate_byte () { + auto & pool = get_pool (); std::lock_guard guard (mutex); return pool.GenerateByte (); } + +CryptoPP::AutoSeededRandomPool & nano::random_pool::get_pool () +{ + static CryptoPP::AutoSeededRandomPool pool; + return pool; +} diff --git a/nano/crypto_lib/random_pool.hpp b/nano/crypto_lib/random_pool.hpp index 8789d236ba..2afd591fc5 100644 --- a/nano/crypto_lib/random_pool.hpp +++ b/nano/crypto_lib/random_pool.hpp @@ -1,9 +1,12 @@ #pragma once -#include - #include +namespace CryptoPP +{ +class AutoSeededRandomPool; +} + namespace nano { /** While this uses CryptoPP do not call any of these functions from global scope, as they depend on global variables inside the CryptoPP library which may not have been initialized yet due to an undefined order for globals in different translation units. To make sure this is not an issue, there should be no ASAN warnings at startup on Mac/Clang in the CryptoPP files. */ @@ -14,19 +17,15 @@ class random_pool static unsigned generate_word32 (unsigned min, unsigned max); static unsigned char generate_byte (); - template - static void shuffle (Iter begin, Iter end) - { - std::lock_guard guard (mutex); - pool.Shuffle (begin, end); - } - random_pool () = delete; random_pool (random_pool const &) = delete; random_pool & operator= (random_pool const &) = delete; private: static std::mutex mutex; - static CryptoPP::AutoSeededRandomPool pool; + static CryptoPP::AutoSeededRandomPool & get_pool (); + + template + friend void random_pool_shuffle (Iter begin, Iter end); }; } \ No newline at end of file diff --git a/nano/crypto_lib/random_pool_shuffle.hpp b/nano/crypto_lib/random_pool_shuffle.hpp new file mode 100644 index 0000000000..848127efee --- /dev/null +++ b/nano/crypto_lib/random_pool_shuffle.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +namespace nano +{ +template +void random_pool_shuffle (Iter begin, Iter end) +{ + std::lock_guard guard (random_pool::mutex); + random_pool::get_pool ().Shuffle (begin, end); +} +} diff --git a/nano/fuzzer_test/CMakeLists.txt b/nano/fuzzer_test/CMakeLists.txt new file mode 100644 index 0000000000..19d2018749 --- /dev/null +++ b/nano/fuzzer_test/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(fuzz_buffer fuzz_buffer.cpp) +target_compile_options(fuzz_buffer PUBLIC -fsanitize=fuzzer) +target_link_libraries(fuzz_buffer PRIVATE -fsanitize=fuzzer node gtest) + +add_executable(fuzz_bignum fuzz_bignum.cpp) +target_compile_options(fuzz_bignum PUBLIC -fsanitize=fuzzer) +target_link_libraries(fuzz_bignum PRIVATE -fsanitize=fuzzer node gtest) + +add_executable(fuzz_endpoint_parsing fuzz_endpoint_parsing.cpp) +target_compile_options(fuzz_endpoint_parsing PUBLIC -fsanitize=fuzzer) +target_link_libraries(fuzz_endpoint_parsing PRIVATE -fsanitize=fuzzer node gtest) diff --git a/nano/fuzzer_test/fuzz_bignum.cpp b/nano/fuzzer_test/fuzz_bignum.cpp new file mode 100644 index 0000000000..5ead68e282 --- /dev/null +++ b/nano/fuzzer_test/fuzz_bignum.cpp @@ -0,0 +1,36 @@ +#include + +/** Fuzz decimal, hex and account parsing */ +void fuzz_bignum_parsers (const uint8_t * Data, size_t Size) +{ + try + { + auto data (std::string (reinterpret_cast (const_cast (Data)), Size)); + nano::uint128_union u128; + u128.decode_dec (data); + u128.decode_hex (data); + + nano::uint256_union u256; + u256.decode_dec (data); + u256.decode_hex (data); + + nano::uint512_union u512; + u512.decode_hex (data); + + nano::public_key pkey; + pkey.decode_account (data); + + uint64_t out; + nano::from_string_hex (data, out); + } + catch (std::out_of_range const &) + { + } +} + +/** Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput (const uint8_t * Data, size_t Size) +{ + fuzz_bignum_parsers (Data, Size); + return 0; +} diff --git a/nano/fuzzer_test/fuzz_buffer.cpp b/nano/fuzzer_test/fuzz_buffer.cpp new file mode 100644 index 0000000000..d5bc22bd25 --- /dev/null +++ b/nano/fuzzer_test/fuzz_buffer.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nano +{ +void force_nano_test_network (); +} +namespace +{ +std::shared_ptr system0; +std::shared_ptr node0; + +class fuzz_visitor : public nano::message_visitor +{ +public: + virtual void keepalive (nano::keepalive const &) override + { + } + virtual void publish (nano::publish const &) override + { + } + virtual void confirm_req (nano::confirm_req const &) override + { + } + virtual void confirm_ack (nano::confirm_ack const &) override + { + } + virtual void bulk_pull (nano::bulk_pull const &) override + { + } + virtual void bulk_pull_account (nano::bulk_pull_account const &) override + { + } + virtual void bulk_push (nano::bulk_push const &) override + { + } + virtual void frontier_req (nano::frontier_req const &) override + { + } + virtual void node_id_handshake (nano::node_id_handshake const &) override + { + } + virtual void telemetry_req (nano::telemetry_req const &) override + { + } + virtual void telemetry_ack (nano::telemetry_ack const &) override + { + } +}; +} + +/** Fuzz live message parsing. This covers parsing and block/vote uniquing. */ +void fuzz_message_parser (const uint8_t * Data, size_t Size) +{ + static bool initialized = false; + if (!initialized) + { + nano::force_nano_test_network (); + initialized = true; + system0 = std::make_shared (1); + node0 = system0->nodes[0]; + } + + fuzz_visitor visitor; + nano::message_parser parser (node0->network.publish_filter, node0->block_uniquer, node0->vote_uniquer, visitor, node0->work); + parser.deserialize_buffer (Data, Size); +} + +/** Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput (const uint8_t * Data, size_t Size) +{ + fuzz_message_parser (Data, Size); + return 0; +} diff --git a/nano/fuzzer_test/fuzz_endpoint_parsing.cpp b/nano/fuzzer_test/fuzz_endpoint_parsing.cpp new file mode 100644 index 0000000000..0308787d1f --- /dev/null +++ b/nano/fuzzer_test/fuzz_endpoint_parsing.cpp @@ -0,0 +1,18 @@ +#include + +/** Fuzz endpoint parsing */ +void fuzz_endpoint_parsing (const uint8_t * Data, size_t Size) +{ + auto data (std::string (reinterpret_cast (const_cast (Data)), Size)); + nano::endpoint endpoint; + nano::parse_endpoint (data, endpoint); + nano::tcp_endpoint tcp_endpoint; + nano::parse_tcp_endpoint (data, tcp_endpoint); +} + +/** Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput (const uint8_t * Data, size_t Size) +{ + fuzz_endpoint_parsing (Data, Size); + return 0; +} diff --git a/nano/ipc_flatbuffers_lib/CMakeLists.txt b/nano/ipc_flatbuffers_lib/CMakeLists.txt new file mode 100644 index 0000000000..b4b5a6e5a0 --- /dev/null +++ b/nano/ipc_flatbuffers_lib/CMakeLists.txt @@ -0,0 +1,51 @@ +# Build flatc from the Flatbuffers submodule +set (FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "") +set (FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "") +mark_as_advanced ( + FLATBUFFERS_BUILD_CPP17 + FLATBUFFERS_BUILD_FLATC + FLATBUFFERS_BUILD_FLATHASH + FLATBUFFERS_BUILD_FLATLIB + FLATBUFFERS_BUILD_GRPCTEST + FLATBUFFERS_BUILD_LEGACY + FLATBUFFERS_BUILD_SHAREDLIB + FLATBUFFERS_BUILD_TESTS + FLATBUFFERS_CODE_COVERAGE + FLATBUFFERS_CODE_SANITIZE + FLATBUFFERS_INSTALL + FLATBUFFERS_LIBCXX_WITH_CLANG + FLATBUFFERS_PACKAGE_DEBIAN + FLATBUFFERS_PACKAGE_REDHAT + FLATBUFFERS_STATIC_FLATC) +add_subdirectory(../../flatbuffers ${CMAKE_CURRENT_BINARY_DIR}/flatbuffers-build EXCLUDE_FROM_ALL) + +# Generate Flatbuffers files into the ipc_flatbuffers_lib library, which will be rebuilt +# whenever any of the fbs files change. Note that while this supports multiple fbs files, +# we currently only use one, to avoid include-file issues with certain language bindings. +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/generated/flatbuffers) +if (APPLE) + install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/ DESTINATION Nano.app/Contents/MacOS/api/flatbuffers) +elseif(LINUX) + install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/ DESTINATION ./bin/api/flatbuffers) +else() + install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/ DESTINATION ./api/flatbuffers) +endif() + +file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/nanoapi*.fbs") +foreach(file ${files}) + get_filename_component(flatbuffers_filename ${file} NAME_WE) + message("Generating flatbuffers code for: ${flatbuffers_filename} into ${CMAKE_CURRENT_SOURCE_DIR}/generated/flatbuffers") + add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/generated/flatbuffers/${flatbuffers_filename}_generated.h + COMMAND "$" --force-empty-vectors --reflect-names --gen-mutable --gen-name-strings --gen-object-api --strict-json --cpp -o ${CMAKE_CURRENT_SOURCE_DIR}/generated/flatbuffers ${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/${flatbuffers_filename}.fbs + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../api/flatbuffers/${flatbuffers_filename}.fbs + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endforeach() + +add_library (ipc_flatbuffers_lib + generated/flatbuffers/nanoapi_generated.h + flatbuffer_producer.hpp + flatbuffer_producer.cpp +) + +target_link_libraries (ipc_flatbuffers_lib flatbuffers) diff --git a/nano/ipc_flatbuffers_lib/flatbuffer_producer.cpp b/nano/ipc_flatbuffers_lib/flatbuffer_producer.cpp new file mode 100644 index 0000000000..9a8692fe51 --- /dev/null +++ b/nano/ipc_flatbuffers_lib/flatbuffer_producer.cpp @@ -0,0 +1,35 @@ +#include + +nano::ipc::flatbuffer_producer::flatbuffer_producer () +{ + fbb = std::make_shared (); +} + +nano::ipc::flatbuffer_producer::flatbuffer_producer (std::shared_ptr const & builder_a) : +fbb (builder_a) +{ +} + +void nano::ipc::flatbuffer_producer::make_error (int code, std::string const & message) +{ + auto msg = fbb->CreateString (message); + nanoapi::ErrorBuilder builder (*fbb); + builder.add_code (code); + builder.add_message (msg); + create_builder_response (builder); +} + +void nano::ipc::flatbuffer_producer::set_correlation_id (std::string const & correlation_id_a) +{ + correlation_id = correlation_id_a; +} + +void nano::ipc::flatbuffer_producer::set_credentials (std::string const & credentials_a) +{ + credentials = credentials_a; +} + +std::shared_ptr nano::ipc::flatbuffer_producer::get_shared_flatbuffer () const +{ + return fbb; +} diff --git a/nano/ipc_flatbuffers_lib/flatbuffer_producer.hpp b/nano/ipc_flatbuffers_lib/flatbuffer_producer.hpp new file mode 100644 index 0000000000..b7a9c3d05c --- /dev/null +++ b/nano/ipc_flatbuffers_lib/flatbuffer_producer.hpp @@ -0,0 +1,91 @@ +#pragma once +#include + +#include +#include + +#include + +namespace nano +{ +namespace ipc +{ + /** Produces Nano API compliant Flatbuffers from objects and builders */ + class flatbuffer_producer + { + public: + flatbuffer_producer (); + flatbuffer_producer (std::shared_ptr const & builder_a); + + template + static std::shared_ptr make_buffer (T & object_a, std::string const & correlation_id_a = {}, std::string const & credentials_a = {}) + { + nano::ipc::flatbuffer_producer producer; + producer.set_correlation_id (correlation_id_a); + producer.set_credentials (credentials_a); + producer.create_response (object_a); + return producer.fbb; + } + + void make_error (int code, std::string const & message); + + /** Every message is put in an envelope, which contains the message type and other sideband information */ + template + auto make_envelope (flatbuffers::Offset obj) + { + auto correlation_id_string = fbb->CreateString (correlation_id); + auto credentials_string = fbb->CreateString (credentials); + nanoapi::EnvelopeBuilder envelope_builder (*fbb); + envelope_builder.add_time (std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()).count ()); + envelope_builder.add_message_type (nanoapi::MessageTraits::enum_value); + envelope_builder.add_message (obj.Union ()); + + if (!correlation_id.empty ()) + { + envelope_builder.add_correlation_id (correlation_id_string); + } + if (!credentials.empty ()) + { + envelope_builder.add_credentials (credentials_string); + } + return envelope_builder.Finish (); + } + + template + void create_response (flatbuffers::Offset offset) + { + auto root = make_envelope (offset); + fbb->Finish (root); + } + + template ::value>> + void create_response (T const & obj) + { + create_response (T::TableType::Pack (*fbb, &obj)); + } + + template + void create_builder_response (T builder) + { + auto offset = builder.Finish (); + auto root = make_envelope (offset); + fbb->Finish (root); + } + + /** Set the correlation id. This will be added to the envelope. */ + void set_correlation_id (std::string const & correlation_id_a); + /** Set the credentials. This will be added to the envelope. */ + void set_credentials (std::string const & credentials_a); + /** Returns the flatbuffer */ + std::shared_ptr get_shared_flatbuffer () const; + + private: + /** The builder managed by this instance */ + std::shared_ptr fbb; + /** Correlation id, if available */ + std::string correlation_id; + /** Credentials, if available */ + std::string credentials; + }; +} +} diff --git a/nano/ipc_flatbuffers_test/CMakeLists.txt b/nano/ipc_flatbuffers_test/CMakeLists.txt new file mode 100644 index 0000000000..8e62516a82 --- /dev/null +++ b/nano/ipc_flatbuffers_test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable (ipc_flatbuffers_test_client + entry.cpp) + +target_link_libraries (ipc_flatbuffers_test_client + nano_lib + Boost::filesystem + Boost::log_setup + Boost::log + Boost::program_options + Boost::system + Boost::thread + Boost::boost) diff --git a/nano/ipc_flatbuffers_test/entry.cpp b/nano/ipc_flatbuffers_test/entry.cpp new file mode 100644 index 0000000000..1b3c7e1e81 --- /dev/null +++ b/nano/ipc_flatbuffers_test/entry.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +namespace +{ +void read_message_loop (std::shared_ptr const & connection) +{ + auto buffer (std::make_shared> ()); + connection->async_read_message (buffer, std::chrono::seconds::max (), [buffer, connection](nano::error error_a, size_t size_a) { + if (!error_a) + { + auto verifier (flatbuffers::Verifier (buffer->data (), buffer->size ())); + if (!nanoapi::VerifyEnvelopeBuffer (verifier)) + { + std::cerr << "Invalid message" << std::endl; + } + else + { + auto envelope (nanoapi::GetEnvelope (buffer->data ())); + if (envelope->message_type () == nanoapi::Message_EventConfirmation) + { + std::cout << "Confirmation at " << envelope->time () << std::endl; + auto conf (envelope->message_as_EventConfirmation ()); + std::cout << " Account : " << conf->account ()->str () << std::endl; + std::cout << " Amount : " << conf->amount ()->str () << std::endl; + std::cout << " Block type : " << nanoapi::EnumNamesBlock ()[conf->block_type ()] << std::endl; + auto state_block = conf->block_as_BlockState (); + if (state_block) + { + std::cout << " Balance : " << state_block->balance ()->str () << std::endl; + } + } + + read_message_loop (connection); + } + } + }); +} +} + +/** A sample IPC/flatbuffers client that subscribes to confirmations from a local node */ +int main (int argc, char * const * argv) +{ + boost::asio::io_context io_ctx; + auto connection (std::make_shared (io_ctx)); + // The client only connects to a local live node for now; the test will + // be improved later to handle various options, including port and address. + std::string ipc_address = "::1"; + uint16_t ipc_port = 7077; + connection->async_connect (ipc_address, ipc_port, [connection](nano::error err) { + if (!err) + { + nanoapi::TopicConfirmationT conf; + connection->async_write (nano::ipc::shared_buffer_from (conf), [connection](nano::error err, size_t size) { + if (!err) + { + std::cout << "Awaiting confirmations..." << std::endl; + read_message_loop (connection); + } + }); + } + else + { + std::cerr << err.get_message () << std::endl; + } + }); + + io_ctx.run (); + return 0; +} diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index 71f0d699f7..a741371e1b 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -25,6 +25,8 @@ add_library (nano_lib configbase.hpp diagnosticsconfig.hpp diagnosticsconfig.cpp + epoch.hpp + epoch.cpp errors.hpp errors.cpp ipc.hpp @@ -33,6 +35,9 @@ add_library (nano_lib ipc_client.cpp json_error_response.hpp jsonconfig.hpp + jsonconfig.cpp + lmdbconfig.hpp + lmdbconfig.cpp locks.hpp locks.cpp logger_mt.hpp @@ -40,6 +45,9 @@ add_library (nano_lib memory.cpp numbers.hpp numbers.cpp + optional_ptr.hpp + rate_limiting.hpp + rate_limiting.cpp rep_weights.hpp rep_weights.cpp rocksdbconfig.hpp @@ -49,19 +57,27 @@ add_library (nano_lib rpcconfig.cpp stats.hpp stats.cpp + stream.hpp + threading.hpp + threading.cpp timer.hpp + timer.cpp tomlconfig.hpp + tomlconfig.cpp utility.hpp utility.cpp walletconfig.hpp walletconfig.cpp work.hpp - work.cpp) + work.cpp + worker.hpp + worker.cpp) target_link_libraries (nano_lib ed25519 crypto_lib blake2 + ipc_flatbuffers_lib ${CRYPTOPP_LIBRARY} ${CMAKE_DL_LIBS} Boost::boost) @@ -71,6 +87,12 @@ if (NANO_STACKTRACE_BACKTRACE) endif () target_compile_definitions(nano_lib + PRIVATE + -DMAJOR_VERSION_STRING=${CPACK_PACKAGE_VERSION_MAJOR} + -DMINOR_VERSION_STRING=${CPACK_PACKAGE_VERSION_MINOR} + -DPATCH_VERSION_STRING=${CPACK_PACKAGE_VERSION_PATCH} + -DPRE_RELEASE_VERSION_STRING=${CPACK_PACKAGE_VERSION_PRE_RELEASE} + -DCI=${CI_TEST} PUBLIC -DACTIVE_NETWORK=${ACTIVE_NETWORK} ) diff --git a/nano/lib/alarm.cpp b/nano/lib/alarm.cpp index 18ffd09e1c..82aa4b1744 100644 --- a/nano/lib/alarm.cpp +++ b/nano/lib/alarm.cpp @@ -1,5 +1,5 @@ #include -#include +#include bool nano::operation::operator> (nano::operation const & other_a) const { @@ -64,18 +64,15 @@ void nano::alarm::add (std::chrono::steady_clock::time_point const & wakeup_a, s condition.notify_all (); } -namespace nano +std::unique_ptr nano::collect_container_info (alarm & alarm, const std::string & name) { -std::unique_ptr collect_seq_con_info (alarm & alarm, const std::string & name) -{ - auto composite = std::make_unique (name); - size_t count = 0; + size_t count; { nano::lock_guard guard (alarm.mutex); count = alarm.operations.size (); } auto sizeof_element = sizeof (decltype (alarm.operations)::value_type); - composite->add_component (std::make_unique (seq_con_info{ "operations", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "operations", count, sizeof_element })); return composite; } -} diff --git a/nano/lib/alarm.hpp b/nano/lib/alarm.hpp index 3e289442d3..b2c1a736e0 100644 --- a/nano/lib/alarm.hpp +++ b/nano/lib/alarm.hpp @@ -1,14 +1,21 @@ #pragma once +#include #include -#include -#include - #include #include #include #include +#include + +namespace boost +{ +namespace asio +{ + class io_context; +} +} namespace nano { @@ -33,8 +40,8 @@ class alarm final std::mutex mutex; nano::condition_variable condition; std::priority_queue, std::greater> operations; - boost::thread thread; + std::thread thread; }; -class seq_con_info_component; -std::unique_ptr collect_seq_con_info (alarm & alarm, const std::string & name); +class container_info_component; +std::unique_ptr collect_container_info (alarm & alarm, const std::string & name); } diff --git a/nano/lib/asio.hpp b/nano/lib/asio.hpp index f25c2d3daf..211a18ed46 100644 --- a/nano/lib/asio.hpp +++ b/nano/lib/asio.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace nano { @@ -34,4 +34,16 @@ async_write (AsyncWriteStream & s, nano::shared_const_buffer const & buffer, Wri { return boost::asio::async_write (s, buffer, std::move (handler)); } -} \ No newline at end of file + +/** + * Alternative to nano::async_write where scatter/gather is desired for best performance, and where + * the buffer originates from Flatbuffers. + * @warning The buffers must be captured in the handler to ensure their lifetimes are properly extended. + */ +template +BOOST_ASIO_INITFN_RESULT_TYPE (WriteHandler, void(boost::system::error_code, std::size_t)) +unsafe_async_write (AsyncWriteStream & s, BufferType && buffer, WriteHandler && handler) +{ + return boost::asio::async_write (s, buffer, std::move (handler)); +} +} diff --git a/nano/lib/blockbuilders.cpp b/nano/lib/blockbuilders.cpp index 61ef2562d9..0a87b843c6 100644 --- a/nano/lib/blockbuilders.cpp +++ b/nano/lib/blockbuilders.cpp @@ -186,7 +186,7 @@ std::error_code check_fields_set (uint8_t block_all_flags, uint8_t build_state) { // Convert the first bit set to a field mask and look up the error code. auto build_flags_mask = static_cast (ffs_mask (res)); - assert (ec_map.find (build_flags_mask) != ec_map.end ()); + debug_assert (ec_map.find (build_flags_mask) != ec_map.end ()); ec = ec_map[build_flags_mask]; } return ec; @@ -650,3 +650,64 @@ nano::receive_block_builder & nano::receive_block_builder::source_hex (std::stri build_state |= build_flags::link_present; return *this; } + +template +std::unique_ptr nano::abstract_builder::build () +{ + if (!ec) + { + static_cast (this)->validate (); + } + debug_assert (!ec); + return std::move (block); +} + +template +std::unique_ptr nano::abstract_builder::build (std::error_code & ec) +{ + if (!this->ec) + { + static_cast (this)->validate (); + } + ec = this->ec; + return std::move (block); +} + +template +nano::abstract_builder & nano::abstract_builder::work (uint64_t work) +{ + block->work = work; + build_state |= build_flags::work_present; + return *this; +} + +template +nano::abstract_builder & nano::abstract_builder::sign (nano::raw_key const & private_key, nano::public_key const & public_key) +{ + block->signature = nano::sign_message (private_key, public_key, block->hash ()); + build_state |= build_flags::signature_present; + return *this; +} + +template +nano::abstract_builder & nano::abstract_builder::sign_zero () +{ + block->signature.clear (); + build_state |= build_flags::signature_present; + return *this; +} + +template +void nano::abstract_builder::construct_block () +{ + block = std::make_unique (); + ec.clear (); + build_state = 0; +} + +// Explicit instantiations +template class nano::abstract_builder; +template class nano::abstract_builder; +template class nano::abstract_builder; +template class nano::abstract_builder; +template class nano::abstract_builder; diff --git a/nano/lib/blockbuilders.hpp b/nano/lib/blockbuilders.hpp index 246bea6bfc..d57975f708 100644 --- a/nano/lib/blockbuilders.hpp +++ b/nano/lib/blockbuilders.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include @@ -46,63 +45,21 @@ class abstract_builder { public: /** Returns the built block as a unique_ptr */ - inline std::unique_ptr build () - { - if (!ec) - { - static_cast (this)->validate (); - } - assert (!ec); - return std::move (block); - } - + std::unique_ptr build (); /** Returns the built block as a unique_ptr. Any errors are placed in \p ec */ - inline std::unique_ptr build (std::error_code & ec) - { - if (!this->ec) - { - static_cast (this)->validate (); - } - ec = this->ec; - return std::move (block); - } - + std::unique_ptr build (std::error_code & ec); /** Set work value */ - inline abstract_builder & work (uint64_t work) - { - block->work = work; - build_state |= build_flags::work_present; - return *this; - } - + abstract_builder & work (uint64_t work); /** Sign the block using the \p private_key and \p public_key */ - inline abstract_builder & sign (nano::raw_key const & private_key, nano::public_key const & public_key) - { - block->signature = nano::sign_message (private_key, public_key, block->hash ()); - build_state |= build_flags::signature_present; - return *this; - } - + abstract_builder & sign (nano::raw_key const & private_key, nano::public_key const & public_key); /** Set signature to zero to pass build() validation, allowing block to be signed at a later point. This is mostly useful for tests. */ - inline abstract_builder & sign_zero () - { - block->signature.clear (); - build_state |= build_flags::signature_present; - return *this; - } + abstract_builder & sign_zero (); protected: - abstract_builder () - { - } + abstract_builder () = default; /** Create a new block and resets the internal builder state */ - inline void construct_block () - { - block = std::make_unique (); - ec.clear (); - build_state = 0; - } + void construct_block (); /** The block we're building. Clients can convert this to shared_ptr as needed. */ std::unique_ptr block; @@ -290,35 +247,35 @@ class block_builder { public: /** Prepares a new state block and returns a block builder */ - inline nano::state_block_builder & state () + nano::state_block_builder & state () { state_builder.make_block (); return state_builder; } /** Prepares a new open block and returns a block builder */ - inline nano::open_block_builder & open () + nano::open_block_builder & open () { open_builder.make_block (); return open_builder; } /** Prepares a new change block and returns a block builder */ - inline nano::change_block_builder & change () + nano::change_block_builder & change () { change_builder.make_block (); return change_builder; } /** Prepares a new send block and returns a block builder */ - inline nano::send_block_builder & send () + nano::send_block_builder & send () { send_builder.make_block (); return send_builder; } /** Prepares a new receive block and returns a block builder */ - inline nano::receive_block_builder & receive () + nano::receive_block_builder & receive () { receive_builder.make_block (); return receive_builder; diff --git a/nano/lib/blocks.cpp b/nano/lib/blocks.cpp index d8c1f2ea7f..ff7673ffe2 100644 --- a/nano/lib/blocks.cpp +++ b/nano/lib/blocks.cpp @@ -2,10 +2,14 @@ #include #include #include -#include +#include + +#include #include -#include +#include + +#include /** Compare blocks, first by type, then content. This is an optimization over dynamic_cast, which is very slow on some platforms. */ namespace @@ -53,7 +57,7 @@ size_t nano::block::size (nano::block_type type_a) { case nano::block_type::invalid: case nano::block_type::not_a_block: - assert (false); + debug_assert (false); break; case nano::block_type::send: result = nano::send_block::size; @@ -74,18 +78,52 @@ size_t nano::block::size (nano::block_type type_a) return result; } -nano::block_hash nano::block::hash () const +nano::work_version nano::block::work_version () const +{ + return nano::work_version::work_1; +} + +uint64_t nano::block::difficulty () const +{ + return nano::work_difficulty (this->work_version (), this->root (), this->block_work ()); +} + +nano::block_hash nano::block::generate_hash () const { nano::block_hash result; blake2b_state hash_l; auto status (blake2b_init (&hash_l, sizeof (result.bytes))); - assert (status == 0); + debug_assert (status == 0); hash (hash_l); status = blake2b_final (&hash_l, result.bytes.data (), sizeof (result.bytes)); - assert (status == 0); + debug_assert (status == 0); return result; } +void nano::block::refresh () +{ + if (!cached_hash.is_zero ()) + { + cached_hash = generate_hash (); + } +} + +nano::block_hash const & nano::block::hash () const +{ + if (!cached_hash.is_zero ()) + { + // Once a block is created, it should not be modified (unless using refresh ()) + // This would invalidate the cache; check it hasn't changed. + debug_assert (cached_hash == generate_hash ()); + } + else + { + cached_hash = generate_hash (); + } + + return cached_hash; +} + nano::block_hash nano::block::full_hash () const { nano::block_hash result; @@ -100,6 +138,22 @@ nano::block_hash nano::block::full_hash () const return result; } +nano::block_sideband const & nano::block::sideband () const +{ + debug_assert (sideband_m.is_initialized ()); + return *sideband_m; +} + +void nano::block::sideband_set (nano::block_sideband const & sideband_a) +{ + sideband_m = sideband_a; +} + +bool nano::block::has_sideband () const +{ + return sideband_m.is_initialized (); +} + nano::account const & nano::block::representative () const { static nano::account rep{ 0 }; @@ -140,6 +194,11 @@ void nano::send_block::visit (nano::block_visitor & visitor_a) const visitor_a.send_block (*this); } +void nano::send_block::visit (nano::mutable_block_visitor & visitor_a) +{ + visitor_a.send_block (*this); +} + void nano::send_block::hash (blake2b_state & hash_a) const { hashables.hash (hash_a); @@ -202,11 +261,11 @@ nano::send_hashables::send_hashables (bool & error_a, boost::property_tree::ptre void nano::send_hashables::hash (blake2b_state & hash_a) const { auto status (blake2b_update (&hash_a, previous.bytes.data (), sizeof (previous.bytes))); - assert (status == 0); + debug_assert (status == 0); status = blake2b_update (&hash_a, destination.bytes.data (), sizeof (destination.bytes)); - assert (status == 0); + debug_assert (status == 0); status = blake2b_update (&hash_a, balance.bytes.data (), sizeof (balance.bytes)); - assert (status == 0); + debug_assert (status == 0); } void nano::send_block::serialize (nano::stream & stream_a) const @@ -267,7 +326,7 @@ bool nano::send_block::deserialize_json (boost::property_tree::ptree const & tre auto error (false); try { - assert (tree_a.get ("type") == "send"); + debug_assert (tree_a.get ("type") == "send"); auto previous_l (tree_a.get ("previous")); auto destination_l (tree_a.get ("destination")); auto balance_l (tree_a.get ("balance")); @@ -459,7 +518,7 @@ hashables (source_a, representative_a, account_a), signature (nano::sign_message (prv_a, pub_a, hash ())), work (work_a) { - assert (!representative_a.is_zero ()); + debug_assert (!representative_a.is_zero ()); } nano::open_block::open_block (nano::block_hash const & source_a, nano::account const & representative_a, nano::account const & account_a, std::nullptr_t) : @@ -588,7 +647,7 @@ bool nano::open_block::deserialize_json (boost::property_tree::ptree const & tre auto error (false); try { - assert (tree_a.get ("type") == "open"); + debug_assert (tree_a.get ("type") == "open"); auto source_l (tree_a.get ("source")); auto representative_l (tree_a.get ("representative")); auto account_l (tree_a.get ("account")); @@ -624,6 +683,11 @@ void nano::open_block::visit (nano::block_visitor & visitor_a) const visitor_a.open_block (*this); } +void nano::open_block::visit (nano::mutable_block_visitor & visitor_a) +{ + visitor_a.open_block (*this); +} + nano::block_type nano::open_block::type () const { return nano::block_type::open; @@ -829,7 +893,7 @@ bool nano::change_block::deserialize_json (boost::property_tree::ptree const & t auto error (false); try { - assert (tree_a.get ("type") == "change"); + debug_assert (tree_a.get ("type") == "change"); auto previous_l (tree_a.get ("previous")); auto representative_l (tree_a.get ("representative")); auto work_l (tree_a.get ("work")); @@ -860,6 +924,11 @@ void nano::change_block::visit (nano::block_visitor & visitor_a) const visitor_a.change_block (*this); } +void nano::change_block::visit (nano::mutable_block_visitor & visitor_a) +{ + visitor_a.change_block (*this); +} + nano::block_type nano::change_block::type () const { return nano::block_type::change; @@ -1121,7 +1190,7 @@ bool nano::state_block::deserialize_json (boost::property_tree::ptree const & tr auto error (false); try { - assert (tree_a.get ("type") == "state"); + debug_assert (tree_a.get ("type") == "state"); auto account_l (tree_a.get ("account")); auto previous_l (tree_a.get ("previous")); auto representative_l (tree_a.get ("representative")); @@ -1167,6 +1236,11 @@ void nano::state_block::visit (nano::block_visitor & visitor_a) const visitor_a.state_block (*this); } +void nano::state_block::visit (nano::mutable_block_visitor & visitor_a) +{ + visitor_a.state_block (*this); +} + nano::block_type nano::state_block::type () const { return nano::block_type::state; @@ -1230,50 +1304,32 @@ std::shared_ptr nano::deserialize_block_json (boost::property_tree: try { auto type (tree_a.get ("type")); + bool error (false); + std::unique_ptr obj; if (type == "receive") { - bool error (false); - std::unique_ptr obj (new nano::receive_block (error, tree_a)); - if (!error) - { - result = std::move (obj); - } + obj = std::make_unique (error, tree_a); } else if (type == "send") { - bool error (false); - std::unique_ptr obj (new nano::send_block (error, tree_a)); - if (!error) - { - result = std::move (obj); - } + obj = std::make_unique (error, tree_a); } else if (type == "open") { - bool error (false); - std::unique_ptr obj (new nano::open_block (error, tree_a)); - if (!error) - { - result = std::move (obj); - } + obj = std::make_unique (error, tree_a); } else if (type == "change") { - bool error (false); - std::unique_ptr obj (new nano::change_block (error, tree_a)); - if (!error) - { - result = std::move (obj); - } + obj = std::make_unique (error, tree_a); } else if (type == "state") { - bool error (false); - std::unique_ptr obj (new nano::state_block (error, tree_a)); - if (!error) - { - result = std::move (obj); - } + obj = std::make_unique (error, tree_a); + } + + if (!error) + { + result = std::move (obj); } } catch (std::runtime_error const &) @@ -1329,7 +1385,9 @@ std::shared_ptr nano::deserialize_block (nano::stream & stream_a, n break; } default: - assert (false); +#ifndef NANO_FUZZER_TEST + debug_assert (false); +#endif break; } if (uniquer_a != nullptr) @@ -1344,6 +1402,11 @@ void nano::receive_block::visit (nano::block_visitor & visitor_a) const visitor_a.receive_block (*this); } +void nano::receive_block::visit (nano::mutable_block_visitor & visitor_a) +{ + visitor_a.receive_block (*this); +} + bool nano::receive_block::operator== (nano::receive_block const & other_a) const { auto result (hashables.previous == other_a.hashables.previous && hashables.source == other_a.hashables.source && work == other_a.work && signature == other_a.signature); @@ -1405,7 +1468,7 @@ bool nano::receive_block::deserialize_json (boost::property_tree::ptree const & auto error (false); try { - assert (tree_a.get ("type") == "receive"); + debug_assert (tree_a.get ("type") == "receive"); auto previous_l (tree_a.get ("previous")); auto source_l (tree_a.get ("source")); auto work_l (tree_a.get ("work")); @@ -1588,6 +1651,188 @@ void nano::receive_hashables::hash (blake2b_state & hash_a) const blake2b_update (&hash_a, source.bytes.data (), sizeof (source.bytes)); } +nano::block_details::block_details (nano::epoch const epoch_a, bool const is_send_a, bool const is_receive_a, bool const is_epoch_a) : +epoch (epoch_a), is_send (is_send_a), is_receive (is_receive_a), is_epoch (is_epoch_a) +{ +} + +constexpr size_t nano::block_details::size () +{ + return 1; +} + +bool nano::block_details::operator== (nano::block_details const & other_a) const +{ + return epoch == other_a.epoch && is_send == other_a.is_send && is_receive == other_a.is_receive && is_epoch == other_a.is_epoch; +} + +uint8_t nano::block_details::packed () const +{ + std::bitset<8> result (static_cast (epoch)); + result.set (7, is_send); + result.set (6, is_receive); + result.set (5, is_epoch); + return static_cast (result.to_ulong ()); +} + +void nano::block_details::unpack (uint8_t details_a) +{ + constexpr std::bitset<8> epoch_mask{ 0b00011111 }; + auto as_bitset = static_cast> (details_a); + is_send = as_bitset.test (7); + is_receive = as_bitset.test (6); + is_epoch = as_bitset.test (5); + epoch = static_cast ((as_bitset & epoch_mask).to_ulong ()); +} + +void nano::block_details::serialize (nano::stream & stream_a) const +{ + nano::write (stream_a, packed ()); +} + +bool nano::block_details::deserialize (nano::stream & stream_a) +{ + bool result (false); + try + { + uint8_t packed{ 0 }; + nano::read (stream_a, packed); + unpack (packed); + } + catch (std::runtime_error &) + { + result = true; + } + + return result; +} + +std::string nano::state_subtype (nano::block_details const details_a) +{ + debug_assert (details_a.is_epoch + details_a.is_receive + details_a.is_send <= 1); + if (details_a.is_send) + { + return "send"; + } + else if (details_a.is_receive) + { + return "receive"; + } + else if (details_a.is_epoch) + { + return "epoch"; + } + else + { + return "change"; + } +} + +nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t height_a, uint64_t timestamp_a, nano::block_details const & details_a) : +successor (successor_a), +account (account_a), +balance (balance_a), +height (height_a), +timestamp (timestamp_a), +details (details_a) +{ +} + +nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t height_a, uint64_t timestamp_a, nano::epoch epoch_a, bool is_send, bool is_receive, bool is_epoch) : +successor (successor_a), +account (account_a), +balance (balance_a), +height (height_a), +timestamp (timestamp_a), +details (epoch_a, is_send, is_receive, is_epoch) +{ +} + +size_t nano::block_sideband::size (nano::block_type type_a) +{ + size_t result (0); + result += sizeof (successor); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + result += sizeof (account); + } + if (type_a != nano::block_type::open) + { + result += sizeof (height); + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + result += sizeof (balance); + } + result += sizeof (timestamp); + if (type_a == nano::block_type::state) + { + static_assert (sizeof (nano::epoch) == nano::block_details::size (), "block_details is larger than the epoch enum"); + result += nano::block_details::size (); + } + return result; +} + +void nano::block_sideband::serialize (nano::stream & stream_a, nano::block_type type_a) const +{ + nano::write (stream_a, successor.bytes); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + nano::write (stream_a, account.bytes); + } + if (type_a != nano::block_type::open) + { + nano::write (stream_a, boost::endian::native_to_big (height)); + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + nano::write (stream_a, balance.bytes); + } + nano::write (stream_a, boost::endian::native_to_big (timestamp)); + if (type_a == nano::block_type::state) + { + details.serialize (stream_a); + } +} + +bool nano::block_sideband::deserialize (nano::stream & stream_a, nano::block_type type_a) +{ + bool result (false); + try + { + nano::read (stream_a, successor.bytes); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + nano::read (stream_a, account.bytes); + } + if (type_a != nano::block_type::open) + { + nano::read (stream_a, height); + boost::endian::big_to_native_inplace (height); + } + else + { + height = 1; + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + nano::read (stream_a, balance.bytes); + } + nano::read (stream_a, timestamp); + boost::endian::big_to_native_inplace (timestamp); + if (type_a == nano::block_type::state) + { + result = details.deserialize (stream_a); + } + } + catch (std::runtime_error &) + { + result = true; + } + + return result; +} + std::shared_ptr nano::block_uniquer::unique (std::shared_ptr block_a) { auto result (block_a); @@ -1635,14 +1880,11 @@ size_t nano::block_uniquer::size () return blocks.size (); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (block_uniquer & block_uniquer, const std::string & name) +std::unique_ptr nano::collect_container_info (block_uniquer & block_uniquer, const std::string & name) { auto count = block_uniquer.size (); auto sizeof_element = sizeof (block_uniquer::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "blocks", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "blocks", count, sizeof_element })); return composite; } -} diff --git a/nano/lib/blocks.hpp b/nano/lib/blocks.hpp index b4af2962e3..ba86e5edd8 100644 --- a/nano/lib/blocks.hpp +++ b/nano/lib/blocks.hpp @@ -1,48 +1,22 @@ #pragma once #include +#include #include #include +#include +#include #include +#include -#include +#include -#include -#include #include namespace nano { -// We operate on streams of uint8_t by convention -using stream = std::basic_streambuf; -// Read a raw byte stream the size of `T' and fill value. Returns true if there was an error, false otherwise -template -bool try_read (nano::stream & stream_a, T & value) -{ - static_assert (std::is_standard_layout::value, "Can't stream read non-standard layout types"); - auto amount_read (stream_a.sgetn (reinterpret_cast (&value), sizeof (value))); - return amount_read != sizeof (value); -} -// A wrapper of try_read which throws if there is an error -template -void read (nano::stream & stream_a, T & value) -{ - auto error = try_read (stream_a, value); - if (error) - { - throw std::runtime_error ("Failed to read type"); - } -} - -template -void write (nano::stream & stream_a, T const & value) -{ - static_assert (std::is_standard_layout::value, "Can't stream write non-standard layout types"); - auto amount_written (stream_a.sputn (reinterpret_cast (&value), sizeof (value))); - (void)amount_written; - assert (amount_written == sizeof (value)); -} class block_visitor; +class mutable_block_visitor; enum class block_type : uint8_t { invalid = 0, @@ -53,13 +27,56 @@ enum class block_type : uint8_t change = 5, state = 6 }; +class block_details +{ + static_assert (std::is_same::type, uint8_t> (), "Epoch enum is not the proper type"); + static_assert (static_cast (nano::epoch::max) < (1 << 5), "Epoch max is too large for the sideband"); + +public: + block_details () = default; + block_details (nano::epoch const epoch_a, bool const is_send_a, bool const is_receive_a, bool const is_epoch_a); + static constexpr size_t size (); + bool operator== (block_details const & other_a) const; + void serialize (nano::stream &) const; + bool deserialize (nano::stream &); + nano::epoch epoch{ nano::epoch::epoch_0 }; + bool is_send{ false }; + bool is_receive{ false }; + bool is_epoch{ false }; + +private: + uint8_t packed () const; + void unpack (uint8_t); +}; + +std::string state_subtype (nano::block_details const); + +class block_sideband final +{ +public: + block_sideband () = default; + block_sideband (nano::account const &, nano::block_hash const &, nano::amount const &, uint64_t, uint64_t, nano::block_details const &); + block_sideband (nano::account const &, nano::block_hash const &, nano::amount const &, uint64_t, uint64_t, nano::epoch, bool is_send, bool is_receive, bool is_epoch); + void serialize (nano::stream &, nano::block_type) const; + bool deserialize (nano::stream &, nano::block_type); + static size_t size (nano::block_type); + nano::block_hash successor{ 0 }; + nano::account account{ 0 }; + nano::amount balance{ 0 }; + uint64_t height{ 0 }; + uint64_t timestamp{ 0 }; + nano::block_details details; +}; class block { public: // Return a digest of the hashables in this block. - nano::block_hash hash () const; + nano::block_hash const & hash () const; // Return a digest of hashables and non-hashables in this block. nano::block_hash full_hash () const; + nano::block_sideband const & sideband () const; + void sideband_set (nano::block_sideband const &); + bool has_sideband () const; std::string to_json () const; virtual void hash (blake2b_state &) const = 0; virtual uint64_t block_work () const = 0; @@ -81,6 +98,7 @@ class block virtual void serialize_json (std::string &, bool = false) const = 0; virtual void serialize_json (boost::property_tree::ptree &) const = 0; virtual void visit (nano::block_visitor &) const = 0; + virtual void visit (nano::mutable_block_visitor &) = 0; virtual bool operator== (nano::block const &) const = 0; virtual nano::block_type type () const = 0; virtual nano::signature const & block_signature () const = 0; @@ -88,6 +106,22 @@ class block virtual ~block () = default; virtual bool valid_predecessor (nano::block const &) const = 0; static size_t size (nano::block_type); + virtual nano::work_version work_version () const; + uint64_t difficulty () const; + // If there are any changes to the hashables, call this to update the cached hash + void refresh (); + +protected: + mutable nano::block_hash cached_hash{ 0 }; + /** + * Contextual details about a block, some fields may or may not be set depending on block type. + * This field is set via sideband_set in ledger processing or deserializing blocks from the database. + * Otherwise it may be null (for example, an old block or fork). + */ + nano::optional_ptr sideband_m; + +private: + nano::block_hash generate_hash () const; }; class send_hashables { @@ -123,6 +157,7 @@ class send_block : public nano::block void serialize_json (boost::property_tree::ptree &) const override; bool deserialize_json (boost::property_tree::ptree const &); void visit (nano::block_visitor &) const override; + void visit (nano::mutable_block_visitor &) override; nano::block_type type () const override; nano::signature const & block_signature () const override; void signature_set (nano::signature const &) override; @@ -167,6 +202,7 @@ class receive_block : public nano::block void serialize_json (boost::property_tree::ptree &) const override; bool deserialize_json (boost::property_tree::ptree const &); void visit (nano::block_visitor &) const override; + void visit (nano::mutable_block_visitor &) override; nano::block_type type () const override; nano::signature const & block_signature () const override; void signature_set (nano::signature const &) override; @@ -215,6 +251,7 @@ class open_block : public nano::block void serialize_json (boost::property_tree::ptree &) const override; bool deserialize_json (boost::property_tree::ptree const &); void visit (nano::block_visitor &) const override; + void visit (nano::mutable_block_visitor &) override; nano::block_type type () const override; nano::signature const & block_signature () const override; void signature_set (nano::signature const &) override; @@ -259,6 +296,7 @@ class change_block : public nano::block void serialize_json (boost::property_tree::ptree &) const override; bool deserialize_json (boost::property_tree::ptree const &); void visit (nano::block_visitor &) const override; + void visit (nano::mutable_block_visitor &) override; nano::block_type type () const override; nano::signature const & block_signature () const override; void signature_set (nano::signature const &) override; @@ -319,6 +357,7 @@ class state_block : public nano::block void serialize_json (boost::property_tree::ptree &) const override; bool deserialize_json (boost::property_tree::ptree const &); void visit (nano::block_visitor &) const override; + void visit (nano::mutable_block_visitor &) override; nano::block_type type () const override; nano::signature const & block_signature () const override; void signature_set (nano::signature const &) override; @@ -340,6 +379,16 @@ class block_visitor virtual void state_block (nano::state_block const &) = 0; virtual ~block_visitor () = default; }; +class mutable_block_visitor +{ +public: + virtual void send_block (nano::send_block &) = 0; + virtual void receive_block (nano::receive_block &) = 0; + virtual void open_block (nano::open_block &) = 0; + virtual void change_block (nano::change_block &) = 0; + virtual void state_block (nano::state_block &) = 0; + virtual ~mutable_block_visitor () = default; +}; /** * This class serves to find and return unique variants of a block in order to minimize memory usage */ @@ -357,7 +406,7 @@ class block_uniquer static unsigned constexpr cleanup_count = 2; }; -std::unique_ptr collect_seq_con_info (block_uniquer & block_uniquer, const std::string & name); +std::unique_ptr collect_container_info (block_uniquer & block_uniquer, const std::string & name); std::shared_ptr deserialize_block (nano::stream &); std::shared_ptr deserialize_block (nano::stream &, nano::block_type, nano::block_uniquer * = nullptr); diff --git a/nano/lib/config.cpp b/nano/lib/config.cpp index ab66295c90..7d462981d8 100644 --- a/nano/lib/config.cpp +++ b/nano/lib/config.cpp @@ -1,9 +1,49 @@ #include +#include +#include + #include namespace nano { +work_thresholds const network_constants::publish_full ( +0xffffffc000000000, +0xfffffff800000000, // 8x higher than epoch_1 +0xfffffe0000000000 // 8x lower than epoch_1 +); + +work_thresholds const network_constants::publish_beta ( +0xfffff00000000000, // 64x lower than publish_full.epoch_1 +0xfffff80000000000, // 2x higher than epoch_1 +0xffffe00000000000 // 2x lower than epoch_1 +); + +work_thresholds const network_constants::publish_test ( +0xfe00000000000000, // Very low for tests +0xffc0000000000000, // 8x higher than epoch_1 +0xf000000000000000 // 8x lower than epoch_1 +); + +const char * network_constants::active_network_err_msg = "Invalid network. Valid values are live, beta and test."; + +uint8_t get_major_node_version () +{ + return boost::numeric_cast (boost::lexical_cast (NANO_MAJOR_VERSION_STRING)); +} +uint8_t get_minor_node_version () +{ + return boost::numeric_cast (boost::lexical_cast (NANO_MINOR_VERSION_STRING)); +} +uint8_t get_patch_node_version () +{ + return boost::numeric_cast (boost::lexical_cast (NANO_PATCH_VERSION_STRING)); +} +uint8_t get_pre_release_node_version () +{ + return boost::numeric_cast (boost::lexical_cast (NANO_PRE_RELEASE_VERSION_STRING)); +} + void force_nano_test_network () { nano::network_constants::set_active_network (nano::nano_networks::nano_test_network); @@ -13,4 +53,33 @@ bool running_within_valgrind () { return (RUNNING_ON_VALGRIND > 0); } + +std::string get_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "config.json").string (); +} + +std::string get_rpc_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "rpc_config.json").string (); +} + +std::string get_node_toml_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "config-node.toml").string (); +} + +std::string get_rpc_toml_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "config-rpc.toml").string (); +} + +std::string get_qtwallet_toml_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "config-qtwallet.toml").string (); +} +std::string get_access_toml_config_path (boost::filesystem::path const & data_path) +{ + return (data_path / "config-access.toml").string (); +} } diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index 4c29c3a1a8..43c2597337 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -1,37 +1,54 @@ #pragma once -#include -#include +#include +#include -#include - -#include -#include +#include #include +namespace boost +{ +namespace filesystem +{ + class path; +} +} + #define xstr(a) ver_str (a) #define ver_str(a) #a /** * Returns build version information */ -static const char * NANO_VERSION_STRING = xstr (TAG_VERSION_STRING); +const char * const NANO_VERSION_STRING = xstr (TAG_VERSION_STRING); +const char * const NANO_MAJOR_VERSION_STRING = xstr (MAJOR_VERSION_STRING); +const char * const NANO_MINOR_VERSION_STRING = xstr (MINOR_VERSION_STRING); +const char * const NANO_PATCH_VERSION_STRING = xstr (PATCH_VERSION_STRING); +const char * const NANO_PRE_RELEASE_VERSION_STRING = xstr (PRE_RELEASE_VERSION_STRING); -static const char * BUILD_INFO = xstr (GIT_COMMIT_HASH BOOST_COMPILER) " \"BOOST " xstr (BOOST_VERSION) "\" BUILT " xstr (__DATE__); +const char * const BUILD_INFO = xstr (GIT_COMMIT_HASH BOOST_COMPILER) " \"BOOST " xstr (BOOST_VERSION) "\" BUILT " xstr (__DATE__); /** Is TSAN/ASAN test build */ #if defined(__has_feature) #if __has_feature(thread_sanitizer) || __has_feature(address_sanitizer) -static const bool is_sanitizer_build = true; +const bool is_sanitizer_build = true; #else -static const bool is_sanitizer_build = false; +const bool is_sanitizer_build = false; #endif +// GCC builds +#elif defined(__SANITIZE_THREAD__) || defined(__SANITIZE_ADDRESS__) +const bool is_sanitizer_build = true; #else -static const bool is_sanitizer_build = false; +const bool is_sanitizer_build = false; #endif namespace nano { +uint8_t get_major_node_version (); +uint8_t get_minor_node_version (); +uint8_t get_patch_node_version (); +uint8_t get_pre_release_node_version (); + /** * Network variants with different genesis blocks and network parameters * @warning Enum values are used in integral comparisons; do not change. @@ -49,6 +66,31 @@ enum class nano_networks rai_live_network = 2, }; +struct work_thresholds +{ + uint64_t const epoch_1; + uint64_t const epoch_2; + uint64_t const epoch_2_receive; + + // Automatically calculated. The base threshold is the maximum of all thresholds and is used for all work multiplier calculations + uint64_t const base; + + // Automatically calculated. The entry threshold is the minimum of all thresholds and defines the required work to enter the node, but does not guarantee a block is processed + uint64_t const entry; + + constexpr work_thresholds (uint64_t epoch_1_a, uint64_t epoch_2_a, uint64_t epoch_2_receive_a) : + epoch_1 (epoch_1_a), epoch_2 (epoch_2_a), epoch_2_receive (epoch_2_receive_a), + base (std::max ({ epoch_1, epoch_2, epoch_2_receive })), + entry (std::min ({ epoch_1, epoch_2, epoch_2_receive })) + { + } + work_thresholds () = delete; + work_thresholds operator= (nano::work_thresholds const & other_a) + { + return other_a; + } +}; + class network_constants { public: @@ -58,10 +100,9 @@ class network_constants } network_constants (nano_networks network_a) : - current_network (network_a) + current_network (network_a), + publish_thresholds (is_live_network () ? publish_full : is_beta_network () ? publish_beta : publish_test) { - publish_threshold = is_test_network () ? publish_test_threshold : is_beta_network () ? publish_beta_threshold : publish_full_threshold; - // A representative is classified as principal based on its weight and this factor principal_weight_factor = 1000; // 0.1% @@ -69,17 +110,21 @@ class network_constants default_rpc_port = is_live_network () ? 7076 : is_beta_network () ? 55000 : 45000; default_ipc_port = is_live_network () ? 7077 : is_beta_network () ? 56000 : 46000; default_websocket_port = is_live_network () ? 7078 : is_beta_network () ? 57000 : 47000; - request_interval_ms = is_test_network () ? (is_sanitizer_build ? 100 : 20) : 500; + request_interval_ms = is_test_network () ? 20 : 500; } - /** Network work thresholds. ~5 seconds of work for the live network */ - static uint64_t const publish_full_threshold{ 0xffffffc000000000 }; - static uint64_t const publish_beta_threshold{ 0xfffffc0000000000 }; // 16x lower than full - static uint64_t const publish_test_threshold{ 0xff00000000000000 }; // very low for tests + /** Network work thresholds. Define these inline as constexpr when moving to cpp17. */ + static const nano::work_thresholds publish_full; + static const nano::work_thresholds publish_beta; + static const nano::work_thresholds publish_test; + + /** Error message when an invalid network is specified */ + static const char * active_network_err_msg; /** The network this param object represents. This may differ from the global active network; this is needed for certain --debug... commands */ - nano_networks current_network; - uint64_t publish_threshold; + nano_networks current_network{ nano::network_constants::active_network }; + nano::work_thresholds publish_thresholds; + unsigned principal_weight_factor; uint16_t default_node_port; uint16_t default_rpc_port; @@ -108,9 +153,9 @@ class network_constants * If not called, the compile-time option will be used. * @param network_a The new active network. Valid values are "live", "beta" and "test" */ - static nano::error set_active_network (std::string network_a) + static bool set_active_network (std::string network_a) { - nano::error err; + auto error{ false }; if (network_a == "live") { active_network = nano::nano_networks::nano_live_network; @@ -125,9 +170,9 @@ class network_constants } else { - err = "Invalid network. Valid values are live, beta and test."; + error = true; } - return err; + return error; } const char * get_current_network_as_string () const @@ -152,34 +197,16 @@ class network_constants static nano::nano_networks active_network; }; -inline boost::filesystem::path get_config_path (boost::filesystem::path const & data_path) -{ - return data_path / "config.json"; -} - -inline boost::filesystem::path get_rpc_config_path (boost::filesystem::path const & data_path) -{ - return data_path / "rpc_config.json"; -} - -inline boost::filesystem::path get_node_toml_config_path (boost::filesystem::path const & data_path) -{ - return data_path / "config-node.toml"; -} - -inline boost::filesystem::path get_rpc_toml_config_path (boost::filesystem::path const & data_path) -{ - return data_path / "config-rpc.toml"; -} - -inline boost::filesystem::path get_qtwallet_toml_config_path (boost::filesystem::path const & data_path) -{ - return data_path / "config-qtwallet.toml"; -} +std::string get_config_path (boost::filesystem::path const & data_path); +std::string get_rpc_config_path (boost::filesystem::path const & data_path); +std::string get_node_toml_config_path (boost::filesystem::path const & data_path); +std::string get_rpc_toml_config_path (boost::filesystem::path const & data_path); +std::string get_access_toml_config_path (boost::filesystem::path const & data_path); +std::string get_qtwallet_toml_config_path (boost::filesystem::path const & data_path); /** Called by gtest_main to enforce test network */ void force_nano_test_network (); /** Checks if we are running inside a valgrind instance */ bool running_within_valgrind (); -} +} \ No newline at end of file diff --git a/nano/lib/configbase.hpp b/nano/lib/configbase.hpp index 78bcc76b97..f1eb10792f 100644 --- a/nano/lib/configbase.hpp +++ b/nano/lib/configbase.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -35,7 +34,6 @@ template <> inline std::string type_desc (void) { return "a double preci template <> inline std::string type_desc (void) { return "a character"; } template <> inline std::string type_desc (void) { return "a string"; } template <> inline std::string type_desc (void) { return "a boolean"; } -template <> inline std::string type_desc (void) { return "an IP address"; } // clang-format on /** Base type for configuration wrappers */ diff --git a/nano/secure/epoch.cpp b/nano/lib/epoch.cpp similarity index 88% rename from nano/secure/epoch.cpp rename to nano/lib/epoch.cpp index 29938d09a7..226d1bb41c 100644 --- a/nano/secure/epoch.cpp +++ b/nano/lib/epoch.cpp @@ -1,4 +1,5 @@ -#include +#include +#include nano::link const & nano::epochs::link (nano::epoch epoch_a) const { @@ -18,13 +19,13 @@ nano::public_key const & nano::epochs::signer (nano::epoch epoch_a) const nano::epoch nano::epochs::epoch (nano::link const & link_a) const { auto existing (std::find_if (epochs_m.begin (), epochs_m.end (), [&link_a](auto const & item_a) { return item_a.second.link == link_a; })); - assert (existing != epochs_m.end ()); + debug_assert (existing != epochs_m.end ()); return existing->first; } void nano::epochs::add (nano::epoch epoch_a, nano::public_key const & signer_a, nano::link const & link_a) { - assert (epochs_m.find (epoch_a) == epochs_m.end ()); + debug_assert (epochs_m.find (epoch_a) == epochs_m.end ()); epochs_m[epoch_a] = { signer_a, link_a }; } @@ -40,6 +41,6 @@ std::underlying_type_t nano::normalized_epoch (nano::epoch epoch_a) // Currently assumes that the epoch versions in the enum are sequential. auto start = std::underlying_type_t (nano::epoch::epoch_0); auto end = std::underlying_type_t (epoch_a); - assert (end >= start); + debug_assert (end >= start); return end - start; } diff --git a/nano/secure/epoch.hpp b/nano/lib/epoch.hpp similarity index 100% rename from nano/secure/epoch.hpp rename to nano/lib/epoch.hpp diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 8518552969..29aac08de1 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -1,4 +1,7 @@ -#include "nano/lib/errors.hpp" +#include +#include + +#include std::string nano::error_common_messages::message (int ev) const { @@ -6,6 +9,8 @@ std::string nano::error_common_messages::message (int ev) const { case nano::error_common::generic: return "Unknown error"; + case nano::error_common::access_denied: + return "Access denied"; case nano::error_common::missing_account: return "Missing account"; case nano::error_common::missing_balance: @@ -140,6 +145,8 @@ std::string nano::error_rpc_messages::message (int ev) const return "Bad source"; case nano::error_rpc::bad_timeout: return "Bad timeout number"; + case nano::error_rpc::bad_work_version: + return "Bad work version"; case nano::error_rpc::block_create_balance_mismatch: return "Balance mismatch for previous block"; case nano::error_rpc::block_create_key_required: @@ -156,6 +163,12 @@ std::string nano::error_rpc_messages::message (int ev) const return "Representative account and previous hash required"; case nano::error_rpc::block_create_requirements_send: return "Destination account, previous hash, current balance and amount required"; + case nano::error_rpc::block_root_mismatch: + return "Root mismatch for block"; + case nano::error_rpc::block_work_enough: + return "Provided work is already enough for given difficulty"; + case nano::error_rpc::block_work_version_mismatch: + return "Work version mismatch for block"; case nano::error_rpc::confirmation_height_not_processing: return "There are no blocks currently being processed for adding confirmation height"; case nano::error_rpc::confirmation_not_found: @@ -192,10 +205,16 @@ std::string nano::error_rpc_messages::message (int ev) const return "Invalid previous block for given subtype"; case nano::error_rpc::invalid_timestamp: return "Invalid timestamp"; + case nano::error_rpc::invalid_threads_count: + return "Invalid threads count"; case nano::error_rpc::payment_account_balance: return "Account has non-zero balance"; case nano::error_rpc::payment_unable_create_account: return "Unable to create transaction account"; + case nano::error_rpc::peer_not_found: + return "Peer not found"; + case nano::error_rpc::requires_port_and_address: + return "Both port and address required"; case nano::error_rpc::rpc_control_disabled: return "RPC control is disabled"; case nano::error_rpc::sign_hash_disabled: @@ -233,6 +252,8 @@ std::string nano::error_process_messages::message (int ev) const return "Balance and amount delta do not match"; case nano::error_process::block_position: return "This block cannot follow the previous block"; + case nano::error_process::insufficient_work: + return "Block work is insufficient"; case nano::error_process::other: return "Error processing block"; } @@ -256,3 +277,216 @@ std::string nano::error_config_messages::message (int ev) const return "Invalid error code"; } + +const char * nano::error_conversion::detail::generic_category::name () const noexcept +{ + return boost::system::generic_category ().name (); +} +std::string nano::error_conversion::detail::generic_category::message (int value) const +{ + return boost::system::generic_category ().message (value); +} + +const std::error_category & nano::error_conversion::generic_category () +{ + static detail::generic_category instance; + return instance; +} + +std::error_code nano::error_conversion::convert (const boost::system::error_code & error) +{ + if (error.category () == boost::system::generic_category ()) + { + return std::error_code (error.value (), + nano::error_conversion::generic_category ()); + } + debug_assert (false); + + return nano::error_common::invalid_type_conversion; +} + +nano::error::error (std::error_code code_a) +{ + code = code_a; +} + +nano::error::error (boost::system::error_code const & code_a) +{ + code = std::make_error_code (static_cast (code_a.value ())); +} + +nano::error::error (std::string message_a) +{ + code = nano::error_common::generic; + message = std::move (message_a); +} + +nano::error::error (std::exception const & exception_a) +{ + code = nano::error_common::exception; + message = exception_a.what (); +} + +nano::error & nano::error::operator= (nano::error const & err_a) +{ + code = err_a.code; + message = err_a.message; + return *this; +} + +nano::error & nano::error::operator= (nano::error && err_a) +{ + code = err_a.code; + message = std::move (err_a.message); + return *this; +} + +/** Assign error code */ +nano::error & nano::error::operator= (const std::error_code code_a) +{ + code = code_a; + message.clear (); + return *this; +} + +/** Assign boost error code (as converted to std::error_code) */ +nano::error & nano::error::operator= (const boost::system::error_code & code_a) +{ + code = nano::error_conversion::convert (code_a); + message.clear (); + return *this; +} + +/** Assign boost error code (as converted to std::error_code) */ +nano::error & nano::error::operator= (const boost::system::errc::errc_t & code_a) +{ + code = nano::error_conversion::convert (boost::system::errc::make_error_code (code_a)); + message.clear (); + return *this; +} + +/** Set the error to nano::error_common::generic and the error message to \p message_a */ +nano::error & nano::error::operator= (const std::string message_a) +{ + code = nano::error_common::generic; + message = std::move (message_a); + return *this; +} + +/** Sets the error to nano::error_common::exception and adopts the exception error message. */ +nano::error & nano::error::operator= (std::exception const & exception_a) +{ + code = nano::error_common::exception; + message = exception_a.what (); + return *this; +} + +/** Return true if this#error_code equals the parameter */ +bool nano::error::operator== (const std::error_code code_a) const +{ + return code == code_a; +} + +/** Return true if this#error_code equals the parameter */ +bool nano::error::operator== (const boost::system::error_code code_a) const +{ + return code.value () == code_a.value (); +} + +/** Call the function iff the current error is zero */ +nano::error & nano::error::then (std::function next) +{ + return code ? *this : next (); +} + +/** Implicit error_code conversion */ +nano::error::operator std::error_code () const +{ + return code; +} + +int nano::error::error_code_as_int () const +{ + return code.value (); +} + +/** Implicit bool conversion; true if there's an error */ +nano::error::operator bool () const +{ + return code.value () != 0; +} + +/** Implicit string conversion; returns the error message or an empty string. */ +nano::error::operator std::string () const +{ + return get_message (); +} + +/** + * Get error message, or an empty string if there's no error. If a custom error message is set, + * that will be returned, otherwise the error_code#message() is returned. + */ +std::string nano::error::get_message () const +{ + std::string res = message; + if (code && res.empty ()) + { + res = code.message (); + } + return res; +} + +/** Set an error message, but only if the error code is already set */ +nano::error & nano::error::on_error (std::string message_a) +{ + if (code) + { + message = std::move (message_a); + } + return *this; +} + +/** Set an error message if the current error code matches \p code_a */ +nano::error & nano::error::on_error (std::error_code code_a, std::string message_a) +{ + if (code == code_a) + { + message = std::move (message_a); + } + return *this; +} + +/** Set an error message and an error code */ +nano::error & nano::error::set (std::string message_a, std::error_code code_a) +{ + message = message_a; + code = code_a; + return *this; +} + +/** Set a custom error message. If the error code is not set, it will be set to nano::error_common::generic. */ +nano::error & nano::error::set_message (std::string message_a) +{ + if (!code) + { + code = nano::error_common::generic; + } + message = std::move (message_a); + return *this; +} + +/** Clear an errors */ +nano::error & nano::error::clear () +{ + code.clear (); + message.clear (); + return *this; +} + +namespace std +{ +std::error_code make_error_code (boost::system::errc::errc_t const & e) +{ + return std::error_code (static_cast (e), ::nano::error_conversion::generic_category ()); +} +} diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 5be9663d3f..6a78034d1d 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -1,9 +1,10 @@ #pragma once +#include #include #include -#include +#include #include #include #include @@ -16,6 +17,7 @@ enum class error_common { generic = 1, exception, + access_denied, account_not_found, account_not_found_wallet, account_exists, @@ -83,6 +85,7 @@ enum class error_rpc bad_representative_number, bad_source, bad_timeout, + bad_work_version, block_create_balance_mismatch, block_create_key_required, block_create_public_key_mismatch, @@ -91,6 +94,9 @@ enum class error_rpc block_create_requirements_receive, block_create_requirements_change, block_create_requirements_send, + block_root_mismatch, + block_work_enough, + block_work_version_mismatch, confirmation_height_not_processing, confirmation_not_found, difficulty_limit, @@ -109,8 +115,11 @@ enum class error_rpc invalid_subtype_epoch_link, invalid_subtype_previous, invalid_timestamp, + invalid_threads_count, payment_account_balance, payment_unable_create_account, + peer_not_found, + requires_port_and_address, rpc_control_disabled, sign_hash_disabled, source_not_found @@ -130,6 +139,7 @@ enum class error_process opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. balance_mismatch, // Balance and amount delta don't match block_position, // This block cannot follow the previous block + insufficient_work, // Insufficient work for this block, even though it passed the minimal validation other }; @@ -202,11 +212,7 @@ struct is_error_code_enum { }; -inline std::error_code make_error_code (boost::system::errc::errc_t e) -{ - return std::error_code (static_cast (e), - ::nano::error_conversion::generic_category ()); -} +std::error_code make_error_code (boost::system::errc::errc_t const & e); } namespace nano { @@ -217,33 +223,12 @@ namespace error_conversion class generic_category : public std::error_category { public: - virtual const char * name () const noexcept override - { - return boost::system::generic_category ().name (); - } - virtual std::string message (int value) const override - { - return boost::system::generic_category ().message (value); - } + const char * name () const noexcept override; + std::string message (int value) const override; }; } - inline const std::error_category & generic_category () - { - static detail::generic_category instance; - return instance; - } - - inline std::error_code convert (const boost::system::error_code & error) - { - if (error.category () == boost::system::generic_category ()) - { - return std::error_code (error.value (), - nano::error_conversion::generic_category ()); - } - assert (false); - - return nano::error_common::invalid_type_conversion; - } + const std::error_category & generic_category (); + std::error_code convert (const boost::system::error_code & error); } } @@ -257,101 +242,20 @@ class error error (nano::error const & error_a) = default; error (nano::error && error_a) = default; - error (std::error_code code_a) - { - code = code_a; - } - - error (boost::system::error_code code_a) - { - code = std::make_error_code (static_cast (code_a.value ())); - } - - error (std::string message_a) - { - code = nano::error_common::generic; - message = std::move (message_a); - } - - error (std::exception const & exception_a) - { - code = nano::error_common::exception; - message = exception_a.what (); - } - - error & operator= (nano::error const & err_a) - { - code = err_a.code; - message = err_a.message; - return *this; - } - - error & operator= (nano::error && err_a) - { - code = err_a.code; - message = std::move (err_a.message); - return *this; - } - - /** Assign error code */ - error & operator= (const std::error_code code_a) - { - code = code_a; - message.clear (); - return *this; - } - - /** Assign boost error code (as converted to std::error_code) */ - error & operator= (const boost::system::error_code & code_a) - { - code = nano::error_conversion::convert (code_a); - message.clear (); - return *this; - } - - /** Assign boost error code (as converted to std::error_code) */ - error & operator= (const boost::system::errc::errc_t & code_a) - { - code = nano::error_conversion::convert (boost::system::errc::make_error_code (code_a)); - message.clear (); - return *this; - } - - /** Set the error to nano::error_common::generic and the error message to \p message_a */ - error & operator= (const std::string message_a) - { - code = nano::error_common::generic; - message = std::move (message_a); - return *this; - } - - /** Sets the error to nano::error_common::exception and adopts the exception error message. */ - error & operator= (std::exception const & exception_a) - { - code = nano::error_common::exception; - message = exception_a.what (); - return *this; - } - - /** Return true if this#error_code equals the parameter */ - bool operator== (const std::error_code code_a) - { - return code == code_a; - } - - /** Return true if this#error_code equals the parameter */ - bool operator== (const boost::system::error_code code_a) - { - return code.value () == code_a.value (); - } - - /** Call the function iff the current error is zero */ - error & then (std::function next) - { - return code ? *this : next (); - } - - /** If the current error is one of the listed codes, reset the error code */ + error (std::error_code code_a); + error (boost::system::error_code const & code_a); + error (std::string message_a); + error (std::exception const & exception_a); + error & operator= (nano::error const & err_a); + error & operator= (nano::error && err_a); + error & operator= (const std::error_code code_a); + error & operator= (const boost::system::error_code & code_a); + error & operator= (const boost::system::errc::errc_t & code_a); + error & operator= (const std::string message_a); + error & operator= (std::exception const & exception_a); + bool operator== (const std::error_code code_a) const; + bool operator== (const boost::system::error_code code_a) const; + error & then (std::function next); template error & accept (ErrorCode... err) { @@ -365,84 +269,20 @@ class error return *this; } - /** Implicit error_code conversion */ - explicit operator std::error_code () const - { - return code; - } - - /** Implicit bool conversion; true if there's an error */ - explicit operator bool () const - { - return code.value () != 0; - } - - /** Implicit string conversion; returns the error message or an empty string. */ - explicit operator std::string () const - { - return get_message (); - } - + explicit operator std::error_code () const; + explicit operator bool () const; + explicit operator std::string () const; + std::string get_message () const; /** - * Get error message, or an empty string if there's no error. If a custom error message is set, - * that will be returned, otherwise the error_code#message() is returned. + * The error code as an integer. Note that some error codes have platform dependent values. + * A return value of 0 signifies there is no error. */ - std::string get_message () const - { - std::string res = message; - if (code && res.empty ()) - { - res = code.message (); - } - return res; - } - - /** Set an error message, but only if the error code is already set */ - error & on_error (std::string message_a) - { - if (code) - { - message = std::move (message_a); - } - return *this; - } - - /** Set an error message if the current error code matches \p code_a */ - error & on_error (std::error_code code_a, std::string message_a) - { - if (code == code_a) - { - message = std::move (message_a); - } - return *this; - } - - /** Set an error message and an error code */ - error & set (std::string message_a, std::error_code code_a = nano::error_common::generic) - { - message = message_a; - code = code_a; - return *this; - } - - /** Set a custom error message. If the error code is not set, it will be set to nano::error_common::generic. */ - error & set_message (std::string message_a) - { - if (!code) - { - code = nano::error_common::generic; - } - message = std::move (message_a); - return *this; - } - - /** Clear an errors */ - error & clear () - { - code.clear (); - message.clear (); - return *this; - } + int error_code_as_int () const; + error & on_error (std::string message_a); + error & on_error (std::error_code code_a, std::string message_a); + error & set (std::string message_a, std::error_code code_a = nano::error_common::generic); + error & set_message (std::string message_a); + error & clear (); private: std::error_code code; diff --git a/nano/lib/ipc.cpp b/nano/lib/ipc.cpp index dd109b1b65..41ae3cb5ca 100644 --- a/nano/lib/ipc.cpp +++ b/nano/lib/ipc.cpp @@ -1,4 +1,5 @@ #include +#include nano::ipc::socket_base::socket_base (boost::asio::io_context & io_ctx_a) : io_timer (io_ctx_a) @@ -28,7 +29,7 @@ void nano::ipc::socket_base::timer_cancel () { boost::system::error_code ec; io_timer.cancel (ec); - assert (!ec); + debug_assert (!ec); } nano::ipc::dsock_file_remover::dsock_file_remover (std::string const & file_a) : diff --git a/nano/lib/ipc.hpp b/nano/lib/ipc.hpp index c89cde78e7..b9c6918735 100644 --- a/nano/lib/ipc.hpp +++ b/nano/lib/ipc.hpp @@ -1,10 +1,7 @@ #pragma once -#include +#include -#include - -#include #include namespace nano @@ -52,7 +49,7 @@ namespace ipc }; /** - * Payload encodings; add protobuf, flatbuffers and so on as needed. + * Payload encodings. */ enum class payload_encoding : uint8_t { @@ -60,9 +57,20 @@ namespace ipc * Request is preamble followed by 32-bit BE payload length and payload bytes. * Response is 32-bit BE payload length followed by payload bytes. */ - json_legacy = 0x1, - /** Request/response is same as json_legacy and exposes unsafe RPC's */ - json_unsafe = 0x2 + json_v1 = 0x1, + + /** Request/response is same as json_v1, but exposes unsafe RPC's */ + json_v1_unsafe = 0x2, + + /** + * Request is preamble followed by 32-bit BE payload length and payload bytes. + * Response is 32-bit BE payload length followed by payload bytes. + * Payloads must be flatbuffer encoded. + */ + flatbuffers = 0x3, + + /** JSON -> Flatbuffers -> JSON */ + flatbuffers_json = 0x4 }; /** IPC transport interface */ diff --git a/nano/lib/ipc_client.cpp b/nano/lib/ipc_client.cpp index df07d33fee..8f706a994b 100644 --- a/nano/lib/ipc_client.cpp +++ b/nano/lib/ipc_client.cpp @@ -1,3 +1,8 @@ +#include +#include +#include +#include +#include #include #include #include @@ -5,14 +10,27 @@ #include #include +#include +#include + namespace { /** Socket agnostic IO interface */ class channel { public: - virtual void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) = 0; + virtual void async_read (std::shared_ptr> const & buffer_a, size_t size_a, std::function callback_a) = 0; virtual void async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a) = 0; + + /** + * Read a length-prefixed message asynchronously using the given timeout. This is suitable for full duplex scenarios where it may + * take an arbitrarily long time for the node to send messages for a given subscription. + * Received length must be a big endian 32-bit unsigned integer. + * @param buffer_a Receives the payload + * @param timeout_a How long to await message data. In some scenarios, such as waiting for data on subscriptions, specifying std::chrono::seconds::max() makes sense. + * @param callback_a If called without errors, the payload buffer is successfully populated + */ + virtual void async_read_message (std::shared_ptr> const & buffer_a, std::chrono::seconds timeout_a, std::function callback_a) = 0; }; /* Boost v1.70 introduced breaking changes; the conditional compilation allows 1.6x to be supported as well. */ @@ -24,7 +42,7 @@ using socket_type = boost::asio::basic_stream_socket -class socket_client : public nano::ipc::socket_base, public channel +class socket_client : public nano::ipc::socket_base, public channel, public std::enable_shared_from_this> { public: socket_client (boost::asio::io_context & io_ctx_a, ENDPOINT_TYPE endpoint_a) : @@ -34,13 +52,14 @@ class socket_client : public nano::ipc::socket_base, public channel void async_resolve (std::string const & host_a, uint16_t port_a, std::function callback_a) { - this->timer_start (io_timeout); - resolver.async_resolve (boost::asio::ip::tcp::resolver::query (host_a, std::to_string (port_a)), [this, callback_a](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator_a) { - this->timer_cancel (); + auto this_l (this->shared_from_this ()); + this_l->timer_start (io_timeout); + resolver.async_resolve (boost::asio::ip::tcp::resolver::query (host_a, std::to_string (port_a)), [this_l, callback_a](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator_a) { + this_l->timer_cancel (); boost::asio::ip::tcp::resolver::iterator end; if (!ec && endpoint_iterator_a != end) { - endpoint = *endpoint_iterator_a; + this_l->endpoint = *endpoint_iterator_a; callback_a (ec, *endpoint_iterator_a); } else @@ -52,40 +71,117 @@ class socket_client : public nano::ipc::socket_base, public channel void async_connect (std::function callback_a) { - this->timer_start (io_timeout); - socket.async_connect (endpoint, boost::asio::bind_executor (strand, [this, callback_a](boost::system::error_code const & ec) { - this->timer_cancel (); + auto this_l (this->shared_from_this ()); + this_l->timer_start (io_timeout); + socket.async_connect (endpoint, boost::asio::bind_executor (strand, [this_l, callback_a](boost::system::error_code const & ec) { + this_l->timer_cancel (); callback_a (ec); })); } - void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) override + void async_read (std::shared_ptr> const & buffer_a, size_t size_a, std::function callback_a) override { - this->timer_start (io_timeout); + auto this_l (this->shared_from_this ()); + this_l->timer_start (io_timeout); buffer_a->resize (size_a); - boost::asio::async_read (socket, boost::asio::buffer (buffer_a->data (), size_a), boost::asio::bind_executor (this->strand, [this, callback_a](boost::system::error_code const & ec, size_t size_a) { - this->timer_cancel (); + boost::asio::async_read (socket, boost::asio::buffer (buffer_a->data (), size_a), boost::asio::bind_executor (this_l->strand, [this_l, buffer_a, callback_a](boost::system::error_code const & ec, size_t size_a) { + this_l->timer_cancel (); callback_a (ec, size_a); })); } void async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a) override { - this->timer_start (io_timeout); - nano::async_write (socket, buffer_a, boost::asio::bind_executor (this->strand, [this, callback_a](boost::system::error_code const & ec, size_t size_a) { - this->timer_cancel (); - callback_a (ec, size_a); + auto this_l (this->shared_from_this ()); + boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, this_l]() { + bool write_in_progress = !this_l->send_queue.empty (); + auto queue_size = this_l->send_queue.size (); + if (queue_size < this_l->queue_size_max) + { + this_l->send_queue.emplace_back (buffer_a, callback_a); + } + if (!write_in_progress) + { + this_l->write_queued_messages (); + } + })); + } + + void write_queued_messages () + { + auto this_l (this->shared_from_this ()); + auto msg (send_queue.front ()); + this_l->timer_start (io_timeout); + nano::async_write (socket, msg.buffer, + boost::asio::bind_executor (strand, + [msg, this_l](boost::system::error_code ec, std::size_t size_a) { + this_l->timer_cancel (); + + if (msg.callback) + { + msg.callback (ec, size_a); + } + + this_l->send_queue.pop_front (); + if (!ec && !this_l->send_queue.empty ()) + { + this_l->write_queued_messages (); + } + })); + } + + void async_read_message (std::shared_ptr> const & buffer_a, std::chrono::seconds timeout_a, std::function callback_a) override + { + auto this_l (this->shared_from_this ()); + this_l->timer_start (timeout_a); + buffer_a->resize (4); + // Read 32 bit big-endian length + boost::asio::async_read (socket, boost::asio::buffer (buffer_a->data (), 4), boost::asio::bind_executor (this_l->strand, [this_l, timeout_a, buffer_a, callback_a](boost::system::error_code const & ec, size_t size_a) { + this_l->timer_cancel (); + if (!ec) + { + uint32_t payload_size_l = boost::endian::big_to_native (*reinterpret_cast (buffer_a->data ())); + buffer_a->resize (payload_size_l); + // Read payload + this_l->timer_start (timeout_a); + this_l->async_read (buffer_a, payload_size_l, [this_l, buffer_a, callback_a](boost::system::error_code const & ec_a, size_t size_a) { + this_l->timer_cancel (); + callback_a (ec_a, size_a); + }); + } + else + { + callback_a (ec, size_a); + } })); } /** Shut down and close socket */ void close () override { - socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both); - socket.close (); + auto this_l (this->shared_from_this ()); + boost::asio::post (strand, boost::asio::bind_executor (strand, [this_l]() { + this_l->socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both); + this_l->socket.close (); + })); } private: + /** Holds the buffer and callback for queued writes */ + class queue_item + { + public: + queue_item (nano::shared_const_buffer const & buffer_a, std::function callback_a) : + buffer (buffer_a), callback (callback_a) + { + } + nano::shared_const_buffer buffer; + std::function callback; + }; + size_t const queue_size_max = 64 * 1024; + /** The send queue is protected by always being accessed through the strand */ + std::deque send_queue; + ENDPOINT_TYPE endpoint; SOCKET_TYPE socket; boost::asio::ip::tcp::resolver resolver; @@ -187,24 +283,50 @@ void nano::ipc::ipc_client::async_write (nano::shared_const_buffer const & buffe }); } -void nano::ipc::ipc_client::async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) +void nano::ipc::ipc_client::async_read (std::shared_ptr> const & buffer_a, size_t size_a, std::function callback_a) +{ + auto client (boost::polymorphic_downcast (impl.get ())); + client->get_channel ().async_read (buffer_a, size_a, [callback_a, buffer_a](const boost::system::error_code & ec_a, size_t bytes_read_a) { + callback_a (nano::error (ec_a), bytes_read_a); + }); +} + +/** Read a length-prefixed message asynchronously. Received length must be a big endian 32-bit unsigned integer. */ +void nano::ipc::ipc_client::async_read_message (std::shared_ptr> const & buffer_a, std::chrono::seconds timeout_a, std::function callback_a) { auto client (boost::polymorphic_downcast (impl.get ())); - client->get_channel ().async_read (buffer_a, size_a, [callback_a](const boost::system::error_code & ec_a, size_t bytes_read_a) { + client->get_channel ().async_read_message (buffer_a, timeout_a, [callback_a, buffer_a](const boost::system::error_code & ec_a, size_t bytes_read_a) { callback_a (nano::error (ec_a), bytes_read_a); }); } +std::vector nano::ipc::get_preamble (nano::ipc::payload_encoding encoding_a) +{ + std::vector buffer_l; + buffer_l.push_back ('N'); + buffer_l.push_back (static_cast (encoding_a)); + buffer_l.push_back (0); + buffer_l.push_back (0); + return buffer_l; +} + +nano::shared_const_buffer nano::ipc::prepare_flatbuffers_request (std::shared_ptr const & flatbuffer_a) +{ + auto buffer_l (get_preamble (nano::ipc::payload_encoding::flatbuffers)); + auto payload_length = static_cast (flatbuffer_a->GetSize ()); + uint32_t be = boost::endian::native_to_big (payload_length); + char * chars = reinterpret_cast (&be); + buffer_l.insert (buffer_l.end (), chars, chars + sizeof (uint32_t)); + buffer_l.insert (buffer_l.end (), flatbuffer_a->GetBufferPointer (), flatbuffer_a->GetBufferPointer () + flatbuffer_a->GetSize ()); + return nano::shared_const_buffer{ std::move (buffer_l) }; +} + nano::shared_const_buffer nano::ipc::prepare_request (nano::ipc::payload_encoding encoding_a, std::string const & payload_a) { std::vector buffer_l; - if (encoding_a == nano::ipc::payload_encoding::json_legacy) + if (encoding_a == nano::ipc::payload_encoding::json_v1 || encoding_a == nano::ipc::payload_encoding::flatbuffers_json) { - buffer_l.push_back ('N'); - buffer_l.push_back (static_cast (encoding_a)); - buffer_l.push_back (0); - buffer_l.push_back (0); - + buffer_l = get_preamble (encoding_a); auto payload_length = static_cast (payload_a.size ()); uint32_t be = boost::endian::native_to_big (payload_length); char * chars = reinterpret_cast (&be); @@ -214,13 +336,12 @@ nano::shared_const_buffer nano::ipc::prepare_request (nano::ipc::payload_encodin return nano::shared_const_buffer{ std::move (buffer_l) }; } -std::string nano::ipc::request (nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a) +std::string nano::ipc::request (nano::ipc::payload_encoding encoding_a, nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a) { - auto req (prepare_request (nano::ipc::payload_encoding::json_legacy, rpc_action_a)); + auto req (prepare_request (encoding_a, rpc_action_a)); auto res (std::make_shared> ()); std::promise result_l; - // clang-format off ipc_client.async_write (req, [&ipc_client, &res, &result_l](nano::error err_a, size_t size_a) { // Read length ipc_client.async_read (res, sizeof (uint32_t), [&ipc_client, &res, &result_l](nano::error err_read_a, size_t size_read_a) { @@ -231,7 +352,6 @@ std::string nano::ipc::request (nano::ipc::ipc_client & ipc_client, std::string }); }); }); - // clang-format on return result_l.get_future ().get (); } diff --git a/nano/lib/ipc_client.hpp b/nano/lib/ipc_client.hpp index af406c4ca2..6fde3cc7d1 100644 --- a/nano/lib/ipc_client.hpp +++ b/nano/lib/ipc_client.hpp @@ -1,16 +1,17 @@ #pragma once -#include +#include +#include #include #include #include -#include - -#include +#include #include #include +#include + namespace nano { class shared_const_buffer; @@ -43,7 +44,17 @@ namespace ipc void async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a); /** Read \p size_a bytes asynchronously */ - void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a); + void async_read (std::shared_ptr> const & buffer_a, size_t size_a, std::function callback_a); + + /** + * Read a length-prefixed message asynchronously using the given timeout. This is suitable for full duplex scenarios where it may + * take an arbitrarily long time for the node to send messages for a given subscription. + * Received length must be a big endian 32-bit unsigned integer. + * @param buffer_a Receives the payload + * @param timeout_a How long to await message data. In some scenarios, such as waiting for data on subscriptions, specifying std::chrono::seconds::max() makes sense. + * @param callback_a If called without errors, the payload buffer is successfully populated + */ + void async_read_message (std::shared_ptr> const & buffer_a, std::chrono::seconds timeout_a, std::function callback_a); private: boost::asio::io_context & io_ctx; @@ -53,7 +64,24 @@ namespace ipc }; /** Convenience function for making synchronous IPC calls. The client must be connected */ - std::string request (nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a); + std::string request (nano::ipc::payload_encoding encoding_a, nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a); + + /** + * Returns a buffer with an IPC preamble for the given \p encoding_a + */ + std::vector get_preamble (nano::ipc::payload_encoding encoding_a); + + /** + * Returns a buffer with an IPC preamble, followed by 32-bit BE lenght, followed by payload + */ + nano::shared_const_buffer prepare_flatbuffers_request (std::shared_ptr const & flatbuffer_a); + + template + nano::shared_const_buffer shared_buffer_from (T & object_a, std::string const & correlation_id_a = {}, std::string const & credentials_a = {}) + { + auto buffer_l (nano::ipc::flatbuffer_producer::make_buffer (object_a, correlation_id_a, credentials_a)); + return nano::ipc::prepare_flatbuffers_request (buffer_l); + } /** * Returns a buffer with an IPC preamble for the given \p encoding_a followed by the payload. Depending on encoding, diff --git a/nano/lib/jsonconfig.cpp b/nano/lib/jsonconfig.cpp new file mode 100644 index 0000000000..95ca65f2ca --- /dev/null +++ b/nano/lib/jsonconfig.cpp @@ -0,0 +1,261 @@ +#include +#include + +#include +#include + +#include + +nano::jsonconfig::jsonconfig () : +tree (tree_default) +{ + error = std::make_shared (); +} + +nano::jsonconfig::jsonconfig (boost::property_tree::ptree & tree_a, std::shared_ptr error_a) : +nano::configbase (error_a), tree (tree_a) +{ + if (!error) + { + error = std::make_shared (); + } +} + +/** + * Reads a json object from the stream + * @return nano::error&, including a descriptive error message if the config file is malformed. + */ +nano::error & nano::jsonconfig::read (boost::filesystem::path const & path_a) +{ + std::fstream stream; + open_or_create (stream, path_a.string ()); + if (!stream.fail ()) + { + try + { + boost::property_tree::read_json (stream, tree); + } + catch (std::runtime_error const & ex) + { + auto pos (stream.tellg ()); + if (pos != std::streampos (0)) + { + *error = ex; + } + } + stream.close (); + } + return *error; +} + +void nano::jsonconfig::write (boost::filesystem::path const & path_a) +{ + std::fstream stream; + open_or_create (stream, path_a.string ()); + write (stream); +} + +void nano::jsonconfig::write (std::ostream & stream_a) const +{ + boost::property_tree::write_json (stream_a, tree); +} + +void nano::jsonconfig::read (std::istream & stream_a) +{ + boost::property_tree::read_json (stream_a, tree); +} + +/** Open configuration file, create if necessary */ +void nano::jsonconfig::open_or_create (std::fstream & stream_a, std::string const & path_a) +{ + if (!boost::filesystem::exists (path_a)) + { + // Create temp stream to first create the file + std::ofstream stream (path_a); + + // Set permissions before opening otherwise Windows only has read permissions + nano::set_secure_perm_file (path_a); + } + + stream_a.open (path_a); +} + +/** Takes a filepath, appends '_backup_' to the end (but before any extension) and saves that file in the same directory */ +void nano::jsonconfig::create_backup_file (boost::filesystem::path const & filepath_a) +{ + auto extension = filepath_a.extension (); + auto filename_without_extension = filepath_a.filename ().replace_extension (""); + auto orig_filepath = filepath_a; + auto & backup_path = orig_filepath.remove_filename (); + auto backup_filename = filename_without_extension; + backup_filename += "_backup_"; + backup_filename += std::to_string (std::chrono::system_clock::now ().time_since_epoch ().count ()); + backup_filename += extension; + auto backup_filepath = backup_path / backup_filename; + + boost::filesystem::copy_file (filepath_a, backup_filepath); +} + +/** Returns the boost property node managed by this instance */ +boost::property_tree::ptree const & nano::jsonconfig::get_tree () +{ + return tree; +} + +/** Returns true if the property tree node is empty */ +bool nano::jsonconfig::empty () const +{ + return tree.empty (); +} + +boost::optional nano::jsonconfig::get_optional_child (std::string const & key_a) +{ + boost::optional child_config; + auto child = tree.get_child_optional (key_a); + if (child) + { + return jsonconfig (child.get (), error); + } + return child_config; +} + +nano::jsonconfig nano::jsonconfig::get_required_child (std::string const & key_a) +{ + auto child = tree.get_child_optional (key_a); + if (!child) + { + *error = nano::error_config::missing_value; + error->set_message ("Missing configuration node: " + key_a); + } + return child ? jsonconfig (child.get (), error) : *this; +} + +nano::jsonconfig & nano::jsonconfig::put_child (std::string const & key_a, nano::jsonconfig & conf_a) +{ + tree.add_child (key_a, conf_a.get_tree ()); + return *this; +} + +nano::jsonconfig & nano::jsonconfig::replace_child (std::string const & key_a, nano::jsonconfig & conf_a) +{ + tree.erase (key_a); + put_child (key_a, conf_a); + return *this; +} + +/** Returns true if \p key_a is present */ +bool nano::jsonconfig::has_key (std::string const & key_a) +{ + return tree.find (key_a) != tree.not_found (); +} + +/** Erase the property of given key */ +nano::jsonconfig & nano::jsonconfig::erase (std::string const & key_a) +{ + tree.erase (key_a); + return *this; +} + +// boost's lexical cast doesn't handle (u)int8_t +nano::jsonconfig & nano::jsonconfig::get_config (bool optional, std::string key, uint8_t & target, uint8_t default_value) +{ + int64_t tmp; + try + { + auto val (tree.get (key)); + if (!boost::conversion::try_lexical_convert (val, tmp) || tmp < 0 || tmp > 255) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + else + { + target = static_cast (tmp); + } + } + catch (boost::property_tree::ptree_bad_path const &) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; +} + +nano::jsonconfig & nano::jsonconfig::get_config (bool optional, std::string key, bool & target, bool default_value) +{ + auto bool_conv = [this, &target, &key, optional](std::string val) { + if (val == "true") + { + target = true; + } + else if (val == "false") + { + target = false; + } + else if (!*error) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + }; + try + { + auto val (tree.get (key)); + bool_conv (val); + } + catch (boost::property_tree::ptree_bad_path const &) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; +} + +nano::jsonconfig & nano::jsonconfig::get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value) +{ + try + { + auto address_l (tree.get (key)); + boost::system::error_code bec; + target = boost::asio::ip::make_address_v6 (address_l, bec); + if (bec) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + } + catch (boost::property_tree::ptree_bad_path const &) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + return *this; +} + +void nano::jsonconfig::write_json (std::fstream & stream) +{ + boost::property_tree::write_json (stream, tree); +} diff --git a/nano/lib/jsonconfig.hpp b/nano/lib/jsonconfig.hpp index ce9d47abe4..2c8fab683e 100644 --- a/nano/lib/jsonconfig.hpp +++ b/nano/lib/jsonconfig.hpp @@ -1,63 +1,35 @@ #pragma once -#include #include #include #include -#include +#include #include -#include +#include #include +namespace boost +{ +namespace asio +{ + namespace ip + { + class address_v6; + } +} +} + namespace nano { /** Manages a node in a boost configuration tree. */ class jsonconfig : public nano::configbase { public: - jsonconfig () : - tree (tree_default) - { - error = std::make_shared (); - } - - jsonconfig (boost::property_tree::ptree & tree_a, std::shared_ptr error_a = nullptr) : - nano::configbase (error_a), tree (tree_a) - { - if (!error) - { - error = std::make_shared (); - } - } - - /** - * Reads a json object from the stream - * @return nano::error&, including a descriptive error message if the config file is malformed. - */ - nano::error & read (boost::filesystem::path const & path_a) - { - std::fstream stream; - open_or_create (stream, path_a.string ()); - if (!stream.fail ()) - { - try - { - boost::property_tree::read_json (stream, tree); - } - catch (std::runtime_error const & ex) - { - auto pos (stream.tellg ()); - if (pos != std::streampos (0)) - { - *error = ex; - } - } - stream.close (); - } - return *error; - } + jsonconfig (); + jsonconfig (boost::property_tree::ptree & tree_a, std::shared_ptr error_a = nullptr); + nano::error & read (boost::filesystem::path const & path_a); /** * Reads a json object from the stream and if it was changed, write the object back to the stream. @@ -83,7 +55,7 @@ class jsonconfig : public nano::configbase stream.open (path_a.string (), std::ios_base::out | std::ios_base::trunc); try { - boost::property_tree::write_json (stream, tree); + write_json (stream); } catch (std::runtime_error const & ex) { @@ -95,100 +67,19 @@ class jsonconfig : public nano::configbase return *error; } - void write (boost::filesystem::path const & path_a) - { - std::fstream stream; - open_or_create (stream, path_a.string ()); - write (stream); - } - - void write (std::ostream & stream_a) const - { - boost::property_tree::write_json (stream_a, tree); - } - - void read (std::istream & stream_a) - { - boost::property_tree::read_json (stream_a, tree); - } - - /** Open configuration file, create if necessary */ - void open_or_create (std::fstream & stream_a, std::string const & path_a) - { - if (!boost::filesystem::exists (path_a)) - { - // Create temp stream to first create the file - std::ofstream stream (path_a); - - // Set permissions before opening otherwise Windows only has read permissions - nano::set_secure_perm_file (path_a); - } - - stream_a.open (path_a); - } - - /** Takes a filepath, appends '_backup_' to the end (but before any extension) and saves that file in the same directory */ - void create_backup_file (boost::filesystem::path const & filepath_a) - { - auto extension = filepath_a.extension (); - auto filename_without_extension = filepath_a.filename ().replace_extension (""); - auto orig_filepath = filepath_a; - auto & backup_path = orig_filepath.remove_filename (); - auto backup_filename = filename_without_extension; - backup_filename += "_backup_"; - backup_filename += std::to_string (std::chrono::system_clock::now ().time_since_epoch ().count ()); - backup_filename += extension; - auto backup_filepath = backup_path / backup_filename; - - boost::filesystem::copy_file (filepath_a, backup_filepath); - } - - /** Returns the boost property node managed by this instance */ - boost::property_tree::ptree const & get_tree () - { - return tree; - } - - /** Returns true if the property tree node is empty */ - bool empty () const - { - return tree.empty (); - } - - boost::optional get_optional_child (std::string const & key_a) - { - boost::optional child_config; - auto child = tree.get_child_optional (key_a); - if (child) - { - return jsonconfig (child.get (), error); - } - return child_config; - } - - jsonconfig get_required_child (std::string const & key_a) - { - auto child = tree.get_child_optional (key_a); - if (!child) - { - *error = nano::error_config::missing_value; - error->set_message ("Missing configuration node: " + key_a); - } - return child ? jsonconfig (child.get (), error) : *this; - } - - jsonconfig & put_child (std::string const & key_a, nano::jsonconfig & conf_a) - { - tree.add_child (key_a, conf_a.get_tree ()); - return *this; - } - - jsonconfig & replace_child (std::string const & key_a, nano::jsonconfig & conf_a) - { - tree.erase (key_a); - put_child (key_a, conf_a); - return *this; - } + void write (boost::filesystem::path const & path_a); + void write (std::ostream & stream_a) const; + void read (std::istream & stream_a); + void open_or_create (std::fstream & stream_a, std::string const & path_a); + void create_backup_file (boost::filesystem::path const & filepath_a); + boost::property_tree::ptree const & get_tree (); + bool empty () const; + boost::optional get_optional_child (std::string const & key_a); + jsonconfig get_required_child (std::string const & key_a); + jsonconfig & put_child (std::string const & key_a, nano::jsonconfig & conf_a); + jsonconfig & replace_child (std::string const & key_a, nano::jsonconfig & conf_a); + bool has_key (std::string const & key_a); + jsonconfig & erase (std::string const & key_a); /** Set value for the given key. Any existing value will be overwritten. */ template @@ -208,19 +99,6 @@ class jsonconfig : public nano::configbase return *this; } - /** Returns true if \p key_a is present */ - bool has_key (std::string const & key_a) - { - return tree.find (key_a) != tree.not_found (); - } - - /** Erase the property of given key */ - jsonconfig & erase (std::string const & key_a) - { - tree.erase (key_a); - return *this; - } - /** Iterate array entries */ template jsonconfig & array_entries (std::function callback) @@ -236,7 +114,7 @@ class jsonconfig : public nano::configbase template jsonconfig & get_optional (std::string const & key, T & target, T default_value) { - get_config (true, key, target, default_value); + get_config (true, key, target, default_value); return *this; } @@ -247,7 +125,7 @@ class jsonconfig : public nano::configbase template jsonconfig & get_optional (std::string const & key, T & target) { - get_config (true, key, target, target); + get_config (true, key, target, target); return *this; } @@ -259,7 +137,7 @@ class jsonconfig : public nano::configbase if (has_key (key)) { T target{}; - get_config (true, key, target, target); + get_config (true, key, target, target); res = target; } return res; @@ -269,7 +147,7 @@ class jsonconfig : public nano::configbase template jsonconfig & get (std::string const & key, T & target) { - get_config (true, key, target, target); + get_config (true, key, target, target); return *this; } @@ -280,7 +158,7 @@ class jsonconfig : public nano::configbase T get (std::string const & key) { T target{}; - get_config (true, key, target, target); + get_config (true, key, target, target); return target; } @@ -291,7 +169,14 @@ class jsonconfig : public nano::configbase template jsonconfig & get_required (std::string const & key, T & target) { - get_config (false, key, target); + get_config (false, key, target); + return *this; + } + + template + jsonconfig & get_required (std::string const & key, T & target, T const & default_value) + { + get_config (false, key, target, default_value); return *this; } @@ -326,110 +211,15 @@ class jsonconfig : public nano::configbase } // boost's lexical cast doesn't handle (u)int8_t - template ::value>> - jsonconfig & get_config (bool optional, std::string key, uint8_t & target, uint8_t default_value = T ()) - { - int64_t tmp; - try - { - auto val (tree.get (key)); - if (!boost::conversion::try_lexical_convert (val, tmp) || tmp < 0 || tmp > 255) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - else - { - target = static_cast (tmp); - } - } - catch (boost::property_tree::ptree_bad_path const &) - { - if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - catch (std::runtime_error & ex) - { - conditionally_set_error (ex, optional, key); - } - return *this; - } - - template ::value>> - jsonconfig & get_config (bool optional, std::string key, bool & target, bool default_value = false) - { - auto bool_conv = [this, &target, &key, optional](std::string val) { - if (val == "true") - { - target = true; - } - else if (val == "false") - { - target = false; - } - else if (!*error) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - }; - try - { - auto val (tree.get (key)); - bool_conv (val); - } - catch (boost::property_tree::ptree_bad_path const &) - { - if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - catch (std::runtime_error & ex) - { - conditionally_set_error (ex, optional, key); - } - return *this; - } - - template ::value>> - jsonconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 default_value = T ()) - { - try - { - auto address_l (tree.get (key)); - boost::system::error_code bec; - target = boost::asio::ip::address_v6::from_string (address_l, bec); - if (bec) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - } - catch (boost::property_tree::ptree_bad_path const &) - { - if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - return *this; - } + jsonconfig & get_config (bool optional, std::string key, uint8_t & target, uint8_t default_value = uint8_t ()); + jsonconfig & get_config (bool optional, std::string key, bool & target, bool default_value = false); + jsonconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value); private: /** The property node being managed */ boost::property_tree::ptree & tree; boost::property_tree::ptree tree_default; + + void write_json (std::fstream & stream); }; } diff --git a/nano/lib/lmdbconfig.cpp b/nano/lib/lmdbconfig.cpp new file mode 100644 index 0000000000..664ddabfe7 --- /dev/null +++ b/nano/lib/lmdbconfig.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +#include + +nano::error nano::lmdb_config::serialize_toml (nano::tomlconfig & toml) const +{ + std::string sync_string; + switch (sync) + { + case nano::lmdb_config::sync_strategy::always: + sync_string = "always"; + break; + case nano::lmdb_config::sync_strategy::nosync_safe: + sync_string = "nosync_safe"; + break; + case nano::lmdb_config::sync_strategy::nosync_unsafe: + sync_string = "nosync_unsafe"; + break; + case nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory: + sync_string = "nosync_unsafe_large_memory"; + break; + } + + toml.put ("sync", sync_string, "Sync strategy for flushing commits to the ledger database. This does not affect the wallet database.\ntype:string,{always, nosync_safe, nosync_unsafe, nosync_unsafe_large_memory}"); + toml.put ("max_databases", max_databases, "Maximum open lmdb databases. Increase default if more than 100 wallets is required.\nNote: external management is recommended when a large amounts of wallets are required (see https://docs.nano.org/integration-guides/key-management/).\ntype:uin32"); + toml.put ("map_size", map_size, "Maximum ledger database map size in bytes.\ntype:uint64"); + return toml.get_error (); +} + +nano::error nano::lmdb_config::deserialize_toml (nano::tomlconfig & toml, bool is_deprecated_lmdb_dbs_used) +{ + static nano::network_params params; + auto default_max_databases = max_databases; + toml.get_optional ("max_databases", max_databases); + toml.get_optional ("map_size", map_size); + + // For now we accept either setting, but not both + if (!params.network.is_test_network () && is_deprecated_lmdb_dbs_used && default_max_databases != max_databases) + { + toml.get_error ().set ("Both the deprecated node.lmdb_max_dbs and the new node.lmdb.max_databases setting are used. Please use max_databases only."); + } + + if (!toml.get_error ()) + { + std::string sync_string = "always"; + toml.get_optional ("sync", sync_string); + if (sync_string == "always") + { + sync = nano::lmdb_config::sync_strategy::always; + } + else if (sync_string == "nosync_safe") + { + sync = nano::lmdb_config::sync_strategy::nosync_safe; + } + else if (sync_string == "nosync_unsafe") + { + sync = nano::lmdb_config::sync_strategy::nosync_unsafe; + } + else if (sync_string == "nosync_unsafe_large_memory") + { + sync = nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory; + } + else + { + toml.get_error ().set (sync_string + " is not a valid sync option"); + } + } + + return toml.get_error (); +} diff --git a/nano/lib/lmdbconfig.hpp b/nano/lib/lmdbconfig.hpp new file mode 100644 index 0000000000..342c03d126 --- /dev/null +++ b/nano/lib/lmdbconfig.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include + +namespace nano +{ +class tomlconfig; + +/** Configuration options for LMDB */ +class lmdb_config final +{ +public: + /** + * Dictates how lmdb flushes to disk on commit. + * These options only apply to the ledger database; the wallet database + * always flush. + */ + enum sync_strategy + { + /** Always flush to disk on commit. This is default. */ + always, + + /** Do not flush meta data eagerly. This may cause loss of transactions, but maintains integrity. */ + nosync_safe, + + /** + * Let the OS decide when to flush to disk. On filesystems with write ordering, this has the same + * guarantees as nosync_safe, otherwise corruption may occur on system crash. + */ + nosync_unsafe, + /** + * Use a writeable memory map. Let the OS decide when to flush to disk, and make the request asynchronous. + * This may give better performance on systems where the database fits entirely in memory, otherwise is + * may be slower. + * @warning Do not use this option if external processes uses the database concurrently. + */ + nosync_unsafe_large_memory + }; + + nano::error serialize_toml (nano::tomlconfig & toml_a) const; + nano::error deserialize_toml (nano::tomlconfig & toml_a, bool is_deprecated_lmdb_dbs_used); + + /** Sync strategy for the ledger database */ + sync_strategy sync{ always }; + uint32_t max_databases{ 128 }; + size_t map_size{ 128ULL * 1024 * 1024 * 1024 }; +}; +} diff --git a/nano/lib/locks.cpp b/nano/lib/locks.cpp index 0758972774..e38a4e174a 100644 --- a/nano/lib/locks.cpp +++ b/nano/lib/locks.cpp @@ -1,6 +1,8 @@ #include #include +#include + #if NANO_TIMED_LOCKS > 0 namespace { diff --git a/nano/lib/locks.hpp b/nano/lib/locks.hpp index 3d22dff4a3..f0b34bdb6d 100644 --- a/nano/lib/locks.hpp +++ b/nano/lib/locks.hpp @@ -79,4 +79,74 @@ using unique_lock = std::unique_lock; // For consistency wrapping the less well known _any variant which can be used with any lockable type using condition_variable = std::condition_variable_any; + +/** A general purpose monitor template */ +template +class locked +{ +public: + template + locked (Args &&... args) : + obj (std::forward (args)...) + { + } + + struct scoped_lock final + { + scoped_lock (locked * owner_a) : + owner (owner_a) + { + owner->mutex.lock (); + } + + ~scoped_lock () + { + owner->mutex.unlock (); + } + + T * operator-> () + { + return &owner->obj; + } + + T & get () const + { + return owner->obj; + } + + T & operator* () const + { + return get (); + } + + locked * owner{ nullptr }; + }; + + scoped_lock operator-> () + { + return scoped_lock (this); + } + + T & operator= (T const & other) + { + nano::unique_lock lk (mutex); + obj = other; + return obj; + } + + operator T () const + { + return obj; + } + + /** Returns a scoped lock wrapper, allowing multiple calls to the underlying object under the same lock */ + scoped_lock lock () + { + return scoped_lock (this); + } + +private: + T obj; + std::mutex mutex; +}; } diff --git a/nano/lib/logger_mt.hpp b/nano/lib/logger_mt.hpp index b2746c6e5b..5f7744a4fe 100644 --- a/nano/lib/logger_mt.hpp +++ b/nano/lib/logger_mt.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -31,7 +32,7 @@ inline boost::log::formatting_ostream & operator<< (boost::log::formatting_ostre }; nano::severity_level level = manip.get (); - assert (static_cast (level) < strings.size ()); + debug_assert (static_cast (level) < strings.size ()); strm << strings[static_cast (level)]; return strm; } diff --git a/nano/lib/numbers.cpp b/nano/lib/numbers.cpp index dc4346443c..bffd37e396 100644 --- a/nano/lib/numbers.cpp +++ b/nano/lib/numbers.cpp @@ -14,14 +14,14 @@ char const * account_lookup ("13456789abcdefghijkmnopqrstuwxyz"); char const * account_reverse ("~0~1234567~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~89:;<=>?@AB~CDEFGHIJK~LMNO~~~~~"); char account_encode (uint8_t value) { - assert (value < 32); + debug_assert (value < 32); auto result (account_lookup[value]); return result; } uint8_t account_decode (char value) { - assert (value >= '0'); - assert (value <= '~'); + debug_assert (value >= '0'); + debug_assert (value <= '~'); auto result (account_reverse[value - 0x30]); if (result != '~') { @@ -33,7 +33,7 @@ uint8_t account_decode (char value) void nano::public_key::encode_account (std::string & destination_a) const { - assert (destination_a.empty ()); + debug_assert (destination_a.empty ()); destination_a.reserve (65); uint64_t check (0); blake2b_state hash; @@ -200,7 +200,7 @@ nano::uint256_t nano::uint256_union::number () const void nano::uint256_union::encode_hex (std::string & text) const { - assert (text.empty ()); + debug_assert (text.empty ()); std::stringstream stream; stream << std::hex << std::uppercase << std::noshowbase << std::setw (64) << std::setfill ('0'); stream << number (); @@ -238,7 +238,7 @@ bool nano::uint256_union::decode_hex (std::string const & text) void nano::uint256_union::encode_dec (std::string & text) const { - assert (text.empty ()); + debug_assert (text.empty ()); std::stringstream stream; stream << std::dec << std::noshowbase; stream << number (); @@ -317,7 +317,7 @@ nano::uint512_t nano::uint512_union::number () const void nano::uint512_union::encode_hex (std::string & text) const { - assert (text.empty ()); + debug_assert (text.empty ()); std::stringstream stream; stream << std::hex << std::uppercase << std::noshowbase << std::setw (128) << std::setfill ('0'); stream << number (); @@ -396,13 +396,6 @@ nano::private_key const & nano::raw_key::as_private_key () const return reinterpret_cast (data); } -nano::signature nano::sign_message (nano::raw_key const & private_key, nano::public_key const & public_key, nano::uint256_union const & message) -{ - nano::signature result; - ed25519_sign (message.bytes.data (), sizeof (message.bytes), private_key.data.bytes.data (), public_key.bytes.data (), result.bytes.data ()); - return result; -} - nano::private_key nano::deterministic_key (nano::raw_key const & seed_a, uint32_t index_a) { nano::private_key prv_key; @@ -422,16 +415,35 @@ nano::public_key nano::pub_key (nano::private_key const & privatekey_a) return result; } -bool nano::validate_message (nano::public_key const & public_key, nano::uint256_union const & message, nano::signature const & signature) +nano::signature nano::sign_message (nano::raw_key const & private_key, nano::public_key const & public_key, uint8_t const * data, size_t size) { - auto result (0 != ed25519_sign_open (message.bytes.data (), sizeof (message.bytes), public_key.bytes.data (), signature.bytes.data ())); + nano::signature result; + ed25519_sign (data, size, private_key.data.bytes.data (), public_key.bytes.data (), result.bytes.data ()); return result; } +nano::signature nano::sign_message (nano::raw_key const & private_key, nano::public_key const & public_key, nano::uint256_union const & message) +{ + return nano::sign_message (private_key, public_key, message.bytes.data (), sizeof (message.bytes)); +} + +bool nano::validate_message (nano::public_key const & public_key, uint8_t const * data, size_t size, nano::signature const & signature) +{ + return 0 != ed25519_sign_open (data, size, public_key.bytes.data (), signature.bytes.data ()); +} + +bool nano::validate_message (nano::public_key const & public_key, nano::uint256_union const & message, nano::signature const & signature) +{ + return validate_message (public_key, message.bytes.data (), sizeof (message.bytes), signature); +} + bool nano::validate_message_batch (const unsigned char ** m, size_t * mlen, const unsigned char ** pk, const unsigned char ** RS, size_t num, int * valid) { - bool result (0 == ed25519_sign_open_batch (m, mlen, pk, RS, num, valid)); - return result; + for (size_t i{ 0 }; i < num; ++i) + { + valid[i] = (0 == ed25519_sign_open (m[i], mlen[i], pk[i], RS[i])); + } + return true; } nano::uint128_union::uint128_union (std::string const & string_a) @@ -481,7 +493,7 @@ nano::uint128_t nano::uint128_union::number () const void nano::uint128_union::encode_hex (std::string & text) const { - assert (text.empty ()); + debug_assert (text.empty ()); std::stringstream stream; stream << std::hex << std::uppercase << std::noshowbase << std::setw (32) << std::setfill ('0'); stream << number (); @@ -515,7 +527,7 @@ bool nano::uint128_union::decode_hex (std::string const & text) void nano::uint128_union::encode_dec (std::string & text) const { - assert (text.empty ()); + debug_assert (text.empty ()); std::stringstream stream; stream << std::dec << std::noshowbase; stream << number (); @@ -737,7 +749,7 @@ std::string format_balance (nano::uint128_t balance, nano::uint128_t scale, int return stream.str (); } -std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits) +std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits) const { auto thousands_sep = std::use_facet> (std::locale ()).thousands_sep (); auto decimal_point = std::use_facet> (std::locale ()).decimal_point (); @@ -745,7 +757,7 @@ std::string nano::uint128_union::format_balance (nano::uint128_t scale, int prec return ::format_balance (number (), scale, precision, group_digits, thousands_sep, decimal_point, grouping); } -std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale) +std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale) const { auto thousands_sep = std::use_facet> (locale).thousands_sep (); auto decimal_point = std::use_facet> (locale).decimal_point (); @@ -894,7 +906,7 @@ std::string nano::to_string (double const value_a, int const precision_a) uint64_t nano::difficulty::from_multiplier (double const multiplier_a, uint64_t const base_difficulty_a) { - assert (multiplier_a > 0.); + debug_assert (multiplier_a > 0.); nano::uint128_t reverse_difficulty ((-base_difficulty_a) / multiplier_a); if (reverse_difficulty > std::numeric_limits::max ()) { @@ -912,7 +924,7 @@ uint64_t nano::difficulty::from_multiplier (double const multiplier_a, uint64_t double nano::difficulty::to_multiplier (uint64_t const difficulty_a, uint64_t const base_difficulty_a) { - assert (difficulty_a > 0); + debug_assert (difficulty_a > 0); return static_cast (-base_difficulty_a) / (-difficulty_a); } diff --git a/nano/lib/numbers.hpp b/nano/lib/numbers.hpp index 4735af1bba..1be15455c2 100644 --- a/nano/lib/numbers.hpp +++ b/nano/lib/numbers.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include namespace nano @@ -36,8 +34,8 @@ class uint128_union void encode_dec (std::string &) const; bool decode_dec (std::string const &, bool = false); bool decode_dec (std::string const &, nano::uint128_t); - std::string format_balance (nano::uint128_t scale, int precision, bool group_digits); - std::string format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale); + std::string format_balance (nano::uint128_t scale, int precision, bool group_digits) const; + std::string format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale) const; nano::uint128_t number () const; void clear (); bool is_zero () const; @@ -245,8 +243,10 @@ class qualified_root : public uint512_union }; nano::signature sign_message (nano::raw_key const &, nano::public_key const &, nano::uint256_union const &); +nano::signature sign_message (nano::raw_key const &, nano::public_key const &, uint8_t const *, size_t); bool validate_message (nano::public_key const &, nano::uint256_union const &, nano::signature const &); -bool validate_message_batch (const unsigned char **, size_t *, const unsigned char **, const unsigned char **, size_t, int *); +bool validate_message (nano::public_key const &, uint8_t const *, size_t, nano::signature const &); +bool validate_message_batch (unsigned const char **, size_t *, unsigned const char **, unsigned const char **, size_t, int *); nano::private_key deterministic_key (nano::raw_key const &, uint32_t); nano::public_key pub_key (nano::private_key const &); @@ -274,7 +274,7 @@ struct hash<::nano::uint256_union> { size_t operator() (::nano::uint256_union const & data_a) const { - return *reinterpret_cast (data_a.bytes.data ()); + return data_a.qwords[0] + data_a.qwords[1] + data_a.qwords[2] + data_a.qwords[3]; } }; template <> @@ -330,7 +330,7 @@ struct hash<::nano::uint512_union> { size_t operator() (::nano::uint512_union const & data_a) const { - return *reinterpret_cast (data_a.bytes.data ()); + return hash<::nano::uint256_union> () (data_a.uint256s[0]) + hash<::nano::uint256_union> () (data_a.uint256s[1]); } }; template <> @@ -338,7 +338,7 @@ struct hash<::nano::qualified_root> { size_t operator() (::nano::qualified_root const & data_a) const { - return *reinterpret_cast (data_a.bytes.data ()); + return hash<::nano::uint512_union> () (data_a); } }; } diff --git a/nano/lib/optional_ptr.hpp b/nano/lib/optional_ptr.hpp new file mode 100644 index 0000000000..7c7ab903c8 --- /dev/null +++ b/nano/lib/optional_ptr.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include +#include + +namespace nano +{ +/** + * A space efficient optional which does heap allocation when needed. + * This is an alternative to boost/std::optional when the value type is + * large and often not present. + * + * optional_ptr is similar to using std::unique_ptr as an optional, with the + * main difference being that it's copyable. + */ +template +class optional_ptr +{ + static_assert (sizeof (T) > alignof (std::max_align_t), "Use [std|boost]::optional"); + +public: + optional_ptr () = default; + + optional_ptr (T const & value) : + ptr (new T{ value }) + { + } + + optional_ptr (optional_ptr const & other) + { + if (other && other.ptr) + { + ptr = std::make_unique (*other.ptr); + } + } + + optional_ptr & operator= (optional_ptr const & other) + { + if (other && other.ptr) + { + ptr = std::make_unique (*other.ptr); + } + return *this; + } + + T & operator* () + { + return *ptr; + } + + T const & operator* () const + { + return *ptr; + } + + T * const operator-> () + { + return ptr.operator-> (); + } + + T const * const operator-> () const + { + return ptr.operator-> (); + } + + T const * const get () const + { + debug_assert (is_initialized ()); + return ptr.get (); + } + + T * const get () + { + debug_assert (is_initialized ()); + return ptr.get (); + } + + explicit operator bool () const + { + return static_cast (ptr); + } + + bool is_initialized () const + { + return static_cast (ptr); + } + +private: + std::unique_ptr ptr{ nullptr }; +}; +} diff --git a/nano/lib/plat/darwin/thread_role.cpp b/nano/lib/plat/darwin/thread_role.cpp index fb0f3f021a..dd9bdc6199 100644 --- a/nano/lib/plat/darwin/thread_role.cpp +++ b/nano/lib/plat/darwin/thread_role.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/nano/lib/plat/freebsd/thread_role.cpp b/nano/lib/plat/freebsd/thread_role.cpp index 7be32ab242..44699ae882 100644 --- a/nano/lib/plat/freebsd/thread_role.cpp +++ b/nano/lib/plat/freebsd/thread_role.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/nano/lib/plat/linux/debugging.cpp b/nano/lib/plat/linux/debugging.cpp index 41b068636c..f0a596cf68 100644 --- a/nano/lib/plat/linux/debugging.cpp +++ b/nano/lib/plat/linux/debugging.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -12,7 +14,7 @@ namespace int create_load_memory_address_file (dl_phdr_info * info, size_t, void *) { static int counter = 0; - assert (counter <= 99); + debug_assert (counter <= 99); // Create filename const char file_prefix[] = "nano_node_crash_load_address_dump_"; // Holds the filename prefix, a unique (max 2 digits) number and extension (null terminator is included in file_prefix size) @@ -27,7 +29,7 @@ int create_load_memory_address_file (dl_phdr_info * info, size_t, void *) 0 #endif ); - assert (file_descriptor); + debug_assert (file_descriptor); if (file_descriptor) { // Write the name of shared library (can be empty for the executable) diff --git a/nano/lib/plat/linux/thread_role.cpp b/nano/lib/plat/linux/thread_role.cpp index af0a492a5a..46d2952250 100644 --- a/nano/lib/plat/linux/thread_role.cpp +++ b/nano/lib/plat/linux/thread_role.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/nano/lib/plat/windows/perms.cpp b/nano/lib/plat/windows/perms.cpp index 7bb9b1886b..1fc093b539 100644 --- a/nano/lib/plat/windows/perms.cpp +++ b/nano/lib/plat/windows/perms.cpp @@ -2,19 +2,20 @@ #include -#include - +// clang-format off +// Keep windows.h header at the top +#include #include #include #include -#include +// clang-format on void nano::set_umask () { int oldMode; auto result (_umask_s (_S_IWRITE | _S_IREAD, &oldMode)); - assert (result == 0); + debug_assert (result == 0); } void nano::set_secure_perm_directory (boost::filesystem::path const & path) diff --git a/nano/lib/plat/windows/thread_role.cpp b/nano/lib/plat/windows/thread_role.cpp index ad1913a191..a604e67baa 100644 --- a/nano/lib/plat/windows/thread_role.cpp +++ b/nano/lib/plat/windows/thread_role.cpp @@ -1,7 +1,6 @@ -#include +#include #include -#include void nano::thread_role::set_os_name (std::string const & thread_name) { diff --git a/nano/lib/rate_limiting.cpp b/nano/lib/rate_limiting.cpp new file mode 100644 index 0000000000..c77261d625 --- /dev/null +++ b/nano/lib/rate_limiting.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +#include + +nano::rate::token_bucket::token_bucket (size_t max_token_count_a, size_t refill_rate_a) +{ + // A token count of 0 indicates unlimited capacity. We use 1e9 as + // a sentinel, allowing largest burst to still be computed. + if (max_token_count_a == 0 || refill_rate_a == 0) + { + refill_rate_a = max_token_count_a = 1e9; + } + max_token_count = smallest_size = current_size = max_token_count_a; + refill_rate = refill_rate_a; + last_refill = std::chrono::steady_clock::now (); +} + +bool nano::rate::token_bucket::try_consume (unsigned tokens_required_a) +{ + debug_assert (tokens_required_a <= 1e9); + nano::lock_guard lk (bucket_mutex); + refill (); + bool possible = current_size >= tokens_required_a; + if (possible) + { + current_size -= tokens_required_a; + } + else if (tokens_required_a == 1e9) + { + current_size = 0; + } + + // Keep track of smallest observed bucket size so burst size can be computed (for tests and stats) + smallest_size = std::min (smallest_size, current_size); + + return possible || refill_rate == 1e9; +} + +void nano::rate::token_bucket::refill () +{ + auto now (std::chrono::steady_clock::now ()); + double tokens_to_add = std::chrono::duration_cast (now - last_refill).count () / 1e9 * refill_rate; + current_size = std::min (current_size + tokens_to_add, static_cast (max_token_count)); + last_refill = std::chrono::steady_clock::now (); +} + +size_t nano::rate::token_bucket::largest_burst () const +{ + nano::lock_guard lk (bucket_mutex); + return max_token_count - smallest_size; +} diff --git a/nano/lib/rate_limiting.hpp b/nano/lib/rate_limiting.hpp new file mode 100644 index 0000000000..9271535fbe --- /dev/null +++ b/nano/lib/rate_limiting.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +namespace nano +{ +/* Namespace for shaping (egress) and policing (ingress) rate limiting algorithms */ +namespace rate +{ + /** + * Token bucket based rate limiting. This is suitable for rate limiting ipc/api calls + * and network traffic, while allowing short bursts. + * + * Tokens are refilled at N tokens per second and there's a bucket capacity to limit + * bursts. + * + * A bucket has low overhead and can be instantiated for various purposes, such as one + * bucket per session, or one for bandwidth limiting. A token can represent bytes, + * messages, or the cost of API invocations. + */ + class token_bucket + { + public: + /** + * Set up a token bucket. + * @param max_token_count_a Maximum number of tokens in this bucket, which limits bursts. + * @param refill_rate_a Token refill rate, which limits the long term rate (tokens per seconds) + */ + token_bucket (size_t max_token_count_a, size_t refill_rate_a); + + /** + * Determine if an operation of cost \p tokens_required_a is possible, and deduct from the + * bucket if that's the case. + * The default cost is 1 token, but resource intensive operations may request + * more tokens to be available. + */ + bool try_consume (unsigned tokens_required_a = 1); + + /** Returns the largest burst observed */ + size_t largest_burst () const; + + private: + void refill (); + size_t max_token_count; + size_t refill_rate; + size_t current_size{ 0 }; + /** The minimum observed bucket size, from which the largest burst can be derived */ + size_t smallest_size{ 0 }; + std::chrono::steady_clock::time_point last_refill; + mutable std::mutex bucket_mutex; + }; +} +} diff --git a/nano/lib/rep_weights.cpp b/nano/lib/rep_weights.cpp index fd92702486..24167ae701 100644 --- a/nano/lib/rep_weights.cpp +++ b/nano/lib/rep_weights.cpp @@ -54,16 +54,16 @@ nano::uint128_t nano::rep_weights::get (nano::account const & account_a) } } -std::unique_ptr nano::collect_seq_con_info (nano::rep_weights & rep_weights, const std::string & name) +std::unique_ptr nano::collect_container_info (nano::rep_weights & rep_weights, const std::string & name) { - size_t rep_amounts_count = 0; + size_t rep_amounts_count; { nano::lock_guard guard (rep_weights.mutex); rep_amounts_count = rep_weights.rep_amounts.size (); } auto sizeof_element = sizeof (decltype (rep_weights.rep_amounts)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "rep_amounts", rep_amounts_count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "rep_amounts", rep_amounts_count, sizeof_element })); return composite; } diff --git a/nano/lib/rep_weights.hpp b/nano/lib/rep_weights.hpp index b7ad4d7c09..818c5f4e17 100644 --- a/nano/lib/rep_weights.hpp +++ b/nano/lib/rep_weights.hpp @@ -26,8 +26,8 @@ class rep_weights void put (nano::account const & account_a, nano::uint128_union const & representation_a); nano::uint128_t get (nano::account const & account_a); - friend std::unique_ptr collect_seq_con_info (rep_weights &, const std::string &); + friend std::unique_ptr collect_container_info (rep_weights &, const std::string &); }; -std::unique_ptr collect_seq_con_info (rep_weights &, const std::string &); +std::unique_ptr collect_container_info (rep_weights &, const std::string &); } diff --git a/nano/lib/rpc_handler_interface.hpp b/nano/lib/rpc_handler_interface.hpp index 4a0011ea4b..fa25f5602f 100644 --- a/nano/lib/rpc_handler_interface.hpp +++ b/nano/lib/rpc_handler_interface.hpp @@ -1,17 +1,65 @@ #pragma once #include +#include +#include #include namespace nano { class rpc; +/** Keeps information about http requests, and for v2+ includes path and header values of interest */ +class rpc_handler_request_params final +{ +public: + int rpc_version{ 1 }; + std::string path; + std::string credentials; + std::string correlation_id; + + /** + * If the path is non-empty, this wraps the body inside an IPC API compliant envelope. + * Otherwise the input string is returned unchanged. + * This allows HTTP clients to use a simplified request format by omitting the envelope. + * Envelope fields may still be specified through corresponding nano- header fields. + */ + std::string json_envelope (std::string const & body_a) const + { + std::string body_l; + if (!path.empty ()) + { + std::ostringstream json; + json << "{"; + if (!credentials.empty ()) + { + json << "\"credentials\": \"" << credentials << "\", "; + } + if (!correlation_id.empty ()) + { + json << "\"correlation_id\": \"" << correlation_id << "\", "; + } + json << "\"message_type\": \"" << path << "\", "; + json << "\"message\": " << body_a; + json << "}"; + body_l = json.str (); + } + else + { + body_l = body_a; + } + return body_l; + } +}; + class rpc_handler_interface { public: virtual ~rpc_handler_interface () = default; + /** Process RPC 1.0 request. */ virtual void process_request (std::string const & action, std::string const & body, std::function response) = 0; + /** Process RPC 2.0 request. This is called via the IPC API */ + virtual void process_request_v2 (rpc_handler_request_params const & params_a, std::string const & body, std::function)> response) = 0; virtual void stop () = 0; virtual void rpc_instance (nano::rpc & rpc) = 0; }; diff --git a/nano/lib/rpcconfig.cpp b/nano/lib/rpcconfig.cpp index 8529413298..262c9f61e7 100644 --- a/nano/lib/rpcconfig.cpp +++ b/nano/lib/rpcconfig.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -53,7 +54,14 @@ nano::error nano::rpc_secure_config::deserialize_toml (nano::tomlconfig & toml) return toml.get_error (); } -nano::rpc_config::rpc_config (bool enable_control_a) : +nano::rpc_config::rpc_config () : +address (boost::asio::ip::address_v6::loopback ().to_string ()) +{ +} + +nano::rpc_config::rpc_config (uint16_t port_a, bool enable_control_a) : +address (boost::asio::ip::address_v6::loopback ().to_string ()), +port (port_a), enable_control (enable_control_a) { } @@ -61,7 +69,7 @@ enable_control (enable_control_a) nano::error nano::rpc_config::serialize_json (nano::jsonconfig & json) const { json.put ("version", json_version ()); - json.put ("address", address.to_string ()); + json.put ("address", address); json.put ("port", port); json.put ("enable_control", enable_control); json.put ("max_json_depth", max_json_depth); @@ -106,7 +114,9 @@ nano::error nano::rpc_config::deserialize_json (bool & upgraded_a, nano::jsoncon secure.deserialize_json (*rpc_secure_l); } - json.get_required ("address", address); + boost::asio::ip::address_v6 address_l; + json.get_required ("address", address_l, boost::asio::ip::address_v6::loopback ()); + address = address_l.to_string (); json.get_optional ("port", port); json.get_optional ("enable_control", enable_control); json.get_optional ("max_json_depth", max_json_depth); @@ -126,7 +136,9 @@ nano::error nano::rpc_config::deserialize_json (bool & upgraded_a, nano::jsoncon rpc_process_l->get_optional ("io_threads", rpc_process.io_threads); rpc_process_l->get_optional ("ipc_port", rpc_process.ipc_port); - rpc_process_l->get_optional ("ipc_address", rpc_process.ipc_address); + boost::asio::ip::address_v6 ipc_address_l; + rpc_process_l->get_optional ("ipc_address", ipc_address_l); + rpc_process.ipc_address = ipc_address_l.to_string (); rpc_process_l->get_optional ("num_ipc_connections", rpc_process.num_ipc_connections); } } @@ -141,7 +153,7 @@ nano::error nano::rpc_config::deserialize_json (bool & upgraded_a, nano::jsoncon nano::error nano::rpc_config::serialize_toml (nano::tomlconfig & toml) const { - toml.put ("address", address.to_string (), "Bind address for the RPC server.\ntype:string,ip"); + toml.put ("address", address, "Bind address for the RPC server.\ntype:string,ip"); toml.put ("port", port, "Listening port for the RPC server.\ntype:uint16"); toml.put ("enable_control", enable_control, "Enable or disable control-level requests.\nWARNING: Enabling this gives anyone with RPC access the ability to stop the node and access wallet funds.\ntype:bool"); toml.put ("max_json_depth", max_json_depth, "Maximum number of levels in JSON requests.\ntype:uint8"); @@ -149,10 +161,14 @@ nano::error nano::rpc_config::serialize_toml (nano::tomlconfig & toml) const nano::tomlconfig rpc_process_l; rpc_process_l.put ("io_threads", rpc_process.io_threads, "Number of threads used to serve IO.\ntype:uint32"); - rpc_process_l.put ("ipc_address", rpc_process.ipc_address.to_string (), "Address of IPC server.\ntype:string,ip"); + rpc_process_l.put ("ipc_address", rpc_process.ipc_address, "Address of IPC server.\ntype:string,ip"); rpc_process_l.put ("ipc_port", rpc_process.ipc_port, "Listening port of IPC server.\ntype:uint16"); rpc_process_l.put ("num_ipc_connections", rpc_process.num_ipc_connections, "Number of IPC connections to establish.\ntype:uint32"); toml.put_child ("process", rpc_process_l); + + nano::tomlconfig rpc_logging_l; + rpc_logging_l.put ("log_rpc", rpc_logging.log_rpc, "Whether to log RPC calls.\ntype:bool"); + toml.put_child ("logging", rpc_logging_l); return toml.get_error (); } @@ -166,18 +182,28 @@ nano::error nano::rpc_config::deserialize_toml (nano::tomlconfig & toml) secure.deserialize_toml (*rpc_secure_l); } - toml.get_optional ("address", address); + boost::asio::ip::address_v6 address_l; + toml.get_optional ("address", address_l, boost::asio::ip::address_v6::loopback ()); + address = address_l.to_string (); toml.get_optional ("port", port); toml.get_optional ("enable_control", enable_control); toml.get_optional ("max_json_depth", max_json_depth); toml.get_optional ("max_request_size", max_request_size); + auto rpc_logging_l (toml.get_optional_child ("logging")); + if (rpc_logging_l) + { + rpc_logging_l->get_optional ("log_rpc", rpc_logging.log_rpc); + } + auto rpc_process_l (toml.get_optional_child ("process")); if (rpc_process_l) { rpc_process_l->get_optional ("io_threads", rpc_process.io_threads); rpc_process_l->get_optional ("ipc_port", rpc_process.ipc_port); - rpc_process_l->get_optional ("ipc_address", rpc_process.ipc_address); + boost::asio::ip::address_v6 ipc_address_l; + rpc_process_l->get_optional ("ipc_address", ipc_address_l, boost::asio::ip::address_v6::loopback ()); + rpc_process.ipc_address = address_l.to_string (); rpc_process_l->get_optional ("num_ipc_connections", rpc_process.num_ipc_connections); } } @@ -185,6 +211,11 @@ nano::error nano::rpc_config::deserialize_toml (nano::tomlconfig & toml) return toml.get_error (); } +nano::rpc_process_config::rpc_process_config () : +ipc_address (boost::asio::ip::address_v6::loopback ().to_string ()) +{ +} + namespace nano { nano::error read_rpc_config_toml (boost::filesystem::path const & data_path_a, nano::rpc_config & config_a, std::vector const & config_overrides) @@ -249,7 +280,7 @@ nano::error read_rpc_config_toml (boost::filesystem::path const & data_path_a, n } else { - toml.read (config_overrides_stream); + error = toml.read (config_overrides_stream); } } diff --git a/nano/lib/rpcconfig.hpp b/nano/lib/rpcconfig.hpp index af0bdf81fa..018e1e0a76 100644 --- a/nano/lib/rpcconfig.hpp +++ b/nano/lib/rpcconfig.hpp @@ -1,15 +1,20 @@ #pragma once -#include #include #include -#include -#include - #include +#include #include +namespace boost +{ +namespace filesystem +{ + class path; +} +} + namespace nano { class jsonconfig; @@ -43,9 +48,10 @@ class rpc_secure_config final class rpc_process_config final { public: + rpc_process_config (); nano::network_constants network_constants; - unsigned io_threads{ std::max (4, boost::thread::hardware_concurrency ()) }; - boost::asio::ip::address_v6 ipc_address{ boost::asio::ip::address_v6::loopback () }; + unsigned io_threads{ (4 < std::thread::hardware_concurrency ()) ? std::thread::hardware_concurrency () : 4 }; + std::string ipc_address; uint16_t ipc_port{ network_constants.default_ipc_port }; unsigned num_ipc_connections{ network_constants.is_live_network () ? 8u : network_constants.is_beta_network () ? 4u : 1u }; static unsigned json_version () @@ -54,22 +60,30 @@ class rpc_process_config final } }; +class rpc_logging_config final +{ +public: + bool log_rpc{ true }; +}; + class rpc_config final { public: - explicit rpc_config (bool = false); + rpc_config (); + explicit rpc_config (uint16_t, bool); nano::error serialize_json (nano::jsonconfig &) const; nano::error deserialize_json (bool & upgraded_a, nano::jsonconfig &); nano::error serialize_toml (nano::tomlconfig &) const; nano::error deserialize_toml (nano::tomlconfig &); nano::rpc_process_config rpc_process; - boost::asio::ip::address_v6 address{ boost::asio::ip::address_v6::loopback () }; + std::string address; uint16_t port{ rpc_process.network_constants.default_rpc_port }; - bool enable_control; + bool enable_control{ false }; rpc_secure_config secure; uint8_t max_json_depth{ 20 }; uint64_t max_request_size{ 32 * 1024 * 1024 }; + nano::rpc_logging_config rpc_logging; static unsigned json_version () { return 1; diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index 9a6f44fe03..cb7770d41e 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include @@ -7,9 +8,7 @@ #include #include -#include #include -#include nano::error nano::stat_config::deserialize_json (nano::jsonconfig & json) { @@ -214,7 +213,7 @@ std::shared_ptr nano::stat::get_entry_impl (uint32_t key, size auto entry = entries.find (key); if (entry == entries.end ()) { - res = entries.insert (std::make_pair (key, std::make_shared (capacity, interval))).first->second; + res = entries.emplace (key, std::make_shared (capacity, interval)).first->second; } else { @@ -430,7 +429,7 @@ std::string nano::stat::type_to_string (uint32_t key) case nano::stat::type::message: res = "message"; break; - case nano::stat::type::observer: + case nano::stat::type::confirmation_observer: res = "observer"; break; case nano::stat::type::confirmation_height: @@ -438,6 +437,19 @@ std::string nano::stat::type_to_string (uint32_t key) break; case nano::stat::type::drop: res = "drop"; + break; + case nano::stat::type::aggregator: + res = "aggregator"; + break; + case nano::stat::type::requests: + res = "requests"; + break; + case nano::stat::type::filter: + res = "filter"; + break; + case nano::stat::type::telemetry: + res = "telemetry"; + break; } return res; } @@ -478,13 +490,13 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::bulk_push: res = "bulk_push"; break; - case nano::stat::detail::observer_confirmation_active_quorum: + case nano::stat::detail::active_quorum: res = "observer_confirmation_active_quorum"; break; - case nano::stat::detail::observer_confirmation_active_conf_height: + case nano::stat::detail::active_conf_height: res = "observer_confirmation_active_conf_height"; break; - case nano::stat::detail::observer_confirmation_inactive: + case nano::stat::detail::inactive_conf_height: res = "observer_confirmation_inactive"; break; case nano::stat::detail::error_socket_close: @@ -505,6 +517,15 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::fork: res = "fork"; break; + case nano::stat::detail::old: + res = "old"; + break; + case nano::stat::detail::gap_previous: + res = "gap_previous"; + break; + case nano::stat::detail::gap_source: + res = "gap_source"; + break; case nano::stat::detail::frontier_confirmation_failed: res = "frontier_confirmation_failed"; break; @@ -553,6 +574,12 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::send: res = "send"; break; + case nano::stat::detail::telemetry_req: + res = "telemetry_req"; + break; + case nano::stat::detail::telemetry_ack: + res = "telemetry_ack"; + break; case nano::stat::detail::state_block: res = "state_block"; break; @@ -565,6 +592,9 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::vote_replay: res = "vote_replay"; break; + case nano::stat::detail::vote_indeterminate: + res = "vote_indeterminate"; + break; case nano::stat::detail::vote_invalid: res = "vote_invalid"; break; @@ -583,6 +613,24 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::late_block_seconds: res = "late_block_seconds"; break; + case nano::stat::detail::election_non_priority: + res = "election_non_priority"; + break; + case nano::stat::detail::election_priority: + res = "election_priority"; + break; + case nano::stat::detail::election_block_conflict: + res = "election_block_conflict"; + break; + case nano::stat::detail::election_difficulty_update: + res = "election_difficulty_update"; + break; + case nano::stat::detail::election_drop: + res = "election_drop"; + break; + case nano::stat::detail::election_restart: + res = "election_restart"; + break; case nano::stat::detail::blocking: res = "blocking"; break; @@ -598,6 +646,12 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::tcp_write_drop: res = "tcp_write_drop"; break; + case nano::stat::detail::tcp_write_no_socket_drop: + res = "tcp_write_no_socket_drop"; + break; + case nano::stat::detail::tcp_excluded: + res = "tcp_excluded"; + break; case nano::stat::detail::unreachable_host: res = "unreachable_host"; break; @@ -628,14 +682,72 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::invalid_node_id_handshake_message: res = "invalid_node_id_handshake_message"; break; + case nano::stat::detail::invalid_telemetry_req_message: + res = "invalid_telemetry_req_message"; + break; + case nano::stat::detail::invalid_telemetry_ack_message: + res = "invalid_telemetry_ack_message"; + break; case nano::stat::detail::outdated_version: res = "outdated_version"; break; - case nano::stat::detail::invalid_block: - res = "invalid_block"; - break; case nano::stat::detail::blocks_confirmed: res = "blocks_confirmed"; + break; + case nano::stat::detail::blocks_confirmed_unbounded: + res = "blocks_confirmed_unbounded"; + break; + case nano::stat::detail::blocks_confirmed_bounded: + res = "blocks_confirmed_bounded"; + break; + case nano::stat::detail::aggregator_accepted: + res = "aggregator_accepted"; + break; + case nano::stat::detail::aggregator_dropped: + res = "aggregator_dropped"; + break; + case nano::stat::detail::requests_cached_hashes: + res = "requests_cached_hashes"; + break; + case nano::stat::detail::requests_generated_hashes: + res = "requests_generated_hashes"; + break; + case nano::stat::detail::requests_cached_votes: + res = "requests_cached_votes"; + break; + case nano::stat::detail::requests_generated_votes: + res = "requests_generated_votes"; + break; + case nano::stat::detail::requests_cannot_vote: + res = "requests_cannot_vote"; + break; + case nano::stat::detail::requests_unknown: + res = "requests_unknown"; + break; + case nano::stat::detail::duplicate_publish: + res = "duplicate_publish"; + break; + case nano::stat::detail::different_genesis_hash: + res = "different_genesis_hash"; + break; + case nano::stat::detail::invalid_signature: + res = "invalid_signature"; + break; + case nano::stat::detail::node_id_mismatch: + res = "node_id_mismatch"; + break; + case nano::stat::detail::request_within_protection_cache_zone: + res = "request_within_protection_cache_zone"; + break; + case nano::stat::detail::no_response_received: + res = "no_response_received"; + break; + case nano::stat::detail::unsolicited_telemetry_ack: + res = "unsolicited_telemetry_ack"; + break; + case nano::stat::detail::failed_send_telemetry_req: + res = "failed_send_telemetry_req"; + break; } return res; } @@ -655,3 +767,53 @@ std::string nano::stat::dir_to_string (uint32_t key) } return res; } + +nano::stat_datapoint::stat_datapoint (stat_datapoint const & other_a) +{ + nano::lock_guard lock (other_a.datapoint_mutex); + value = other_a.value; + timestamp = other_a.timestamp; +} + +nano::stat_datapoint & nano::stat_datapoint::operator= (stat_datapoint const & other_a) +{ + nano::lock_guard lock (other_a.datapoint_mutex); + value = other_a.value; + timestamp = other_a.timestamp; + return *this; +} + +uint64_t nano::stat_datapoint::get_value () const +{ + nano::lock_guard lock (datapoint_mutex); + return value; +} + +void nano::stat_datapoint::set_value (uint64_t value_a) +{ + nano::lock_guard lock (datapoint_mutex); + value = value_a; +} + +std::chrono::system_clock::time_point nano::stat_datapoint::get_timestamp () const +{ + nano::lock_guard lock (datapoint_mutex); + return timestamp; +} + +void nano::stat_datapoint::set_timestamp (std::chrono::system_clock::time_point timestamp_a) +{ + nano::lock_guard lock (datapoint_mutex); + timestamp = timestamp_a; +} + +/** Add \addend to the current value and optionally update the timestamp */ +void nano::stat_datapoint::add (uint64_t addend, bool update_timestamp) +{ + nano::lock_guard lock (datapoint_mutex); + value += addend; + if (update_timestamp) + { + timestamp = std::chrono::system_clock::now (); + } +} diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index 47fd49b9d4..54ae0119e2 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -1,24 +1,21 @@ #pragma once #include -#include #include #include -#include -#include #include #include #include #include #include -#include namespace nano { class node; class tomlconfig; +class jsonconfig; /** * Serialize and deserialize the 'statistics' node from config.json * All configuration values have defaults. In particular, file logging of statistics @@ -65,50 +62,13 @@ class stat_datapoint final { public: stat_datapoint () = default; - stat_datapoint (stat_datapoint const & other_a) - { - nano::lock_guard lock (other_a.datapoint_mutex); - value = other_a.value; - timestamp = other_a.timestamp; - } - stat_datapoint & operator= (stat_datapoint const & other_a) - { - nano::lock_guard lock (other_a.datapoint_mutex); - value = other_a.value; - timestamp = other_a.timestamp; - return *this; - } - - uint64_t get_value () - { - nano::lock_guard lock (datapoint_mutex); - return value; - } - void set_value (uint64_t value_a) - { - nano::lock_guard lock (datapoint_mutex); - value = value_a; - } - std::chrono::system_clock::time_point get_timestamp () - { - nano::lock_guard lock (datapoint_mutex); - return timestamp; - } - void set_timestamp (std::chrono::system_clock::time_point timestamp_a) - { - nano::lock_guard lock (datapoint_mutex); - timestamp = timestamp_a; - } - /** Add \addend to the current value and optionally update the timestamp */ - void add (uint64_t addend, bool update_timestamp = true) - { - nano::lock_guard lock (datapoint_mutex); - value += addend; - if (update_timestamp) - { - timestamp = std::chrono::system_clock::now (); - } - } + stat_datapoint (stat_datapoint const & other_a); + stat_datapoint & operator= (stat_datapoint const & other_a); + uint64_t get_value () const; + void set_value (uint64_t value_a); + std::chrono::system_clock::time_point get_timestamp () const; + void set_timestamp (std::chrono::system_clock::time_point timestamp_a); + void add (uint64_t addend, bool update_timestamp = true); private: mutable std::mutex datapoint_mutex; @@ -235,9 +195,13 @@ class stat final ipc, tcp, udp, - observer, confirmation_height, - drop + confirmation_observer, + drop, + aggregator, + requests, + filter, + telemetry, }; /** Optional detail type */ @@ -251,10 +215,10 @@ class stat final http_callback, unreachable_host, - // observer specific - observer_confirmation_active_quorum, - observer_confirmation_active_conf_height, - observer_confirmation_inactive, + // confirmation_observer specific + active_quorum, + active_conf_height, + inactive_conf_height, // ledger, block, bootstrap send, @@ -264,6 +228,9 @@ class stat final state_block, epoch_block, fork, + old, + gap_previous, + gap_source, // message specific keepalive, @@ -272,6 +239,8 @@ class stat final confirm_req, confirm_ack, node_id_handshake, + telemetry_req, + telemetry_ack, // bootstrap, callback initiate, @@ -295,6 +264,7 @@ class stat final // vote specific vote_valid, vote_replay, + vote_indeterminate, vote_invalid, vote_overflow, @@ -303,6 +273,12 @@ class stat final vote_cached, late_block, late_block_seconds, + election_non_priority, + election_priority, + election_block_conflict, + election_difficulty_update, + election_drop, + election_restart, // udp blocking, @@ -316,12 +292,16 @@ class stat final invalid_confirm_req_message, invalid_confirm_ack_message, invalid_node_id_handshake_message, + invalid_telemetry_req_message, + invalid_telemetry_ack_message, outdated_version, // tcp tcp_accept_success, tcp_accept_failure, tcp_write_drop, + tcp_write_no_socket_drop, + tcp_excluded, // ipc invocations, @@ -331,7 +311,32 @@ class stat final // confirmation height blocks_confirmed, - invalid_block + blocks_confirmed_unbounded, + blocks_confirmed_bounded, + + // [request] aggregator + aggregator_accepted, + aggregator_dropped, + + // requests + requests_cached_hashes, + requests_generated_hashes, + requests_cached_votes, + requests_generated_votes, + requests_cannot_vote, + requests_unknown, + + // duplicate + duplicate_publish, + + // telemetry + invalid_signature, + different_genesis_hash, + node_id_mismatch, + request_within_protection_cache_zone, + no_response_received, + unsolicited_telemetry_ack, + failed_send_telemetry_req }; /** Direction of the stat. If the direction is irrelevant, use in */ diff --git a/nano/lib/stream.hpp b/nano/lib/stream.hpp new file mode 100644 index 0000000000..c5c537ae96 --- /dev/null +++ b/nano/lib/stream.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include + +namespace nano +{ +// We operate on streams of uint8_t by convention +using stream = std::basic_streambuf; +// Read a raw byte stream the size of `T' and fill value. Returns true if there was an error, false otherwise +template +bool try_read (nano::stream & stream_a, T & value) +{ + static_assert (std::is_standard_layout::value, "Can't stream read non-standard layout types"); + auto amount_read (stream_a.sgetn (reinterpret_cast (&value), sizeof (value))); + return amount_read != sizeof (value); +} +// A wrapper of try_read which throws if there is an error +template +void read (nano::stream & stream_a, T & value) +{ + auto error = try_read (stream_a, value); + if (error) + { + throw std::runtime_error ("Failed to read type"); + } +} + +template +void write (nano::stream & stream_a, T const & value) +{ + static_assert (std::is_standard_layout::value, "Can't stream write non-standard layout types"); + auto amount_written (stream_a.sputn (reinterpret_cast (&value), sizeof (value))); + (void)amount_written; + debug_assert (amount_written == sizeof (value)); +} +} diff --git a/nano/lib/threading.cpp b/nano/lib/threading.cpp new file mode 100644 index 0000000000..0b0319bd3b --- /dev/null +++ b/nano/lib/threading.cpp @@ -0,0 +1,171 @@ +#include + +#include + +namespace +{ +thread_local nano::thread_role::name current_thread_role = nano::thread_role::name::unknown; +} + +nano::thread_role::name nano::thread_role::get () +{ + return current_thread_role; +} + +std::string nano::thread_role::get_string (nano::thread_role::name role) +{ + std::string thread_role_name_string; + + switch (role) + { + case nano::thread_role::name::unknown: + thread_role_name_string = ""; + break; + case nano::thread_role::name::io: + thread_role_name_string = "I/O"; + break; + case nano::thread_role::name::work: + thread_role_name_string = "Work pool"; + break; + case nano::thread_role::name::packet_processing: + thread_role_name_string = "Pkt processing"; + break; + case nano::thread_role::name::alarm: + thread_role_name_string = "Alarm"; + break; + case nano::thread_role::name::vote_processing: + thread_role_name_string = "Vote processing"; + break; + case nano::thread_role::name::block_processing: + thread_role_name_string = "Blck processing"; + break; + case nano::thread_role::name::request_loop: + thread_role_name_string = "Request loop"; + break; + case nano::thread_role::name::wallet_actions: + thread_role_name_string = "Wallet actions"; + break; + case nano::thread_role::name::work_watcher: + thread_role_name_string = "Work watcher"; + break; + case nano::thread_role::name::bootstrap_initiator: + thread_role_name_string = "Bootstrap init"; + break; + case nano::thread_role::name::bootstrap_connections: + thread_role_name_string = "Bootstrap conn"; + break; + case nano::thread_role::name::voting: + thread_role_name_string = "Voting"; + break; + case nano::thread_role::name::signature_checking: + thread_role_name_string = "Signature check"; + break; + case nano::thread_role::name::rpc_request_processor: + thread_role_name_string = "RPC processor"; + break; + case nano::thread_role::name::rpc_process_container: + thread_role_name_string = "RPC process"; + break; + case nano::thread_role::name::confirmation_height_processing: + thread_role_name_string = "Conf height"; + break; + case nano::thread_role::name::worker: + thread_role_name_string = "Worker"; + break; + case nano::thread_role::name::request_aggregator: + thread_role_name_string = "Req aggregator"; + break; + case nano::thread_role::name::state_block_signature_verification: + thread_role_name_string = "State block sig"; + break; + case nano::thread_role::name::epoch_upgrader: + thread_role_name_string = "Epoch upgrader"; + break; + } + + /* + * We want to constrain the thread names to 15 + * characters, since this is the smallest maximum + * length supported by the platforms we support + * (specifically, Linux) + */ + debug_assert (thread_role_name_string.size () < 16); + return (thread_role_name_string); +} + +std::string nano::thread_role::get_string () +{ + return get_string (current_thread_role); +} + +void nano::thread_role::set (nano::thread_role::name role) +{ + auto thread_role_name_string (get_string (role)); + + nano::thread_role::set_os_name (thread_role_name_string); + + current_thread_role = role; +} + +void nano::thread_attributes::set (boost::thread::attributes & attrs) +{ + auto attrs_l (&attrs); + attrs_l->set_stack_size (8000000); //8MB +} + +nano::thread_runner::thread_runner (boost::asio::io_context & io_ctx_a, unsigned service_threads_a) : +io_guard (boost::asio::make_work_guard (io_ctx_a)) +{ + boost::thread::attributes attrs; + nano::thread_attributes::set (attrs); + for (auto i (0u); i < service_threads_a; ++i) + { + threads.emplace_back (attrs, [&io_ctx_a]() { + nano::thread_role::set (nano::thread_role::name::io); + try + { + io_ctx_a.run (); + } + catch (std::exception const & ex) + { + std::cerr << ex.what () << std::endl; +#ifndef NDEBUG + throw; +#endif + } + catch (...) + { +#ifndef NDEBUG + /* + * In a release build, catch and swallow the + * io_context exception, in debug mode pass it + * on + */ + throw; +#endif + } + }); + } +} + +nano::thread_runner::~thread_runner () +{ + join (); +} + +void nano::thread_runner::join () +{ + io_guard.reset (); + for (auto & i : threads) + { + if (i.joinable ()) + { + i.join (); + } + } +} + +void nano::thread_runner::stop_event_processing () +{ + io_guard.get_executor ().context ().stop (); +} diff --git a/nano/lib/threading.hpp b/nano/lib/threading.hpp new file mode 100644 index 0000000000..f6ab4e187d --- /dev/null +++ b/nano/lib/threading.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include + +#include + +namespace nano +{ +/* + * Functions for understanding the role of the current thread + */ +namespace thread_role +{ + enum class name + { + unknown, + io, + work, + packet_processing, + alarm, + vote_processing, + block_processing, + request_loop, + wallet_actions, + bootstrap_initiator, + bootstrap_connections, + voting, + signature_checking, + rpc_request_processor, + rpc_process_container, + work_watcher, + confirmation_height_processing, + worker, + request_aggregator, + state_block_signature_verification, + epoch_upgrader + }; + /* + * Get/Set the identifier for the current thread + */ + nano::thread_role::name get (); + void set (nano::thread_role::name); + + /* + * Get the thread name as a string from enum + */ + std::string get_string (nano::thread_role::name); + + /* + * Get the current thread's role as a string + */ + std::string get_string (); + + /* + * Internal only, should not be called directly + */ + void set_os_name (std::string const &); +} + +namespace thread_attributes +{ + void set (boost::thread::attributes &); +} + +class thread_runner final +{ +public: + thread_runner (boost::asio::io_context &, unsigned); + ~thread_runner (); + /** Tells the IO context to stop processing events.*/ + void stop_event_processing (); + /** Wait for IO threads to complete */ + void join (); + std::vector threads; + boost::asio::executor_work_guard io_guard; +}; + +/* Default memory order of normal std::atomic operations is std::memory_order_seq_cst which provides + a total global ordering of atomic operations are well as synchronization between threads. Weaker memory + ordering can provide benefits in some circumstances, such like in dumb counters where no other data is + dependent on the ordering of these operations. This assumes T is a type of integer, not bool or char. */ +template ::value>> +class relaxed_atomic_integral +{ +public: + relaxed_atomic_integral () noexcept = default; + constexpr relaxed_atomic_integral (T desired) noexcept : + atomic (desired) + { + } + + T operator= (T desired) noexcept + { + store (desired); + return atomic; + } + + relaxed_atomic_integral (relaxed_atomic_integral const &) = delete; + relaxed_atomic_integral & operator= (relaxed_atomic_integral const &) = delete; + + void store (T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + atomic.store (desired, order); + } + + T load (std::memory_order order = std::memory_order_relaxed) const noexcept + { + return atomic.load (std::memory_order_relaxed); + } + + operator T () const noexcept + { + return load (); + } + + bool compare_exchange_weak (T & expected, T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.compare_exchange_weak (expected, desired, order); + } + + bool compare_exchange_strong (T & expected, T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.compare_exchange_strong (expected, desired, order); + } + + T fetch_add (T arg, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.fetch_add (arg, order); + } + + T fetch_sub (T arg, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.fetch_sub (arg, order); + } + + T operator++ () noexcept + { + return fetch_add (1) + 1; + } + + T operator++ (int) noexcept + { + return fetch_add (1); + } + + T operator-- () noexcept + { + return fetch_sub (1) - 1; + } + + T operator-- (int) noexcept + { + return fetch_sub (1); + } + +private: + std::atomic atomic; +}; +} diff --git a/nano/lib/timer.cpp b/nano/lib/timer.cpp new file mode 100644 index 0000000000..2acd18819a --- /dev/null +++ b/nano/lib/timer.cpp @@ -0,0 +1,227 @@ +#include +#include + +#include +#include + +namespace +{ +template ::value> * = nullptr> +std::string typed_unit () +{ + return "nanoseconds"; +} + +template ::value> * = nullptr> +std::string typed_unit () +{ + return "microseconds"; +} + +template ::value> * = nullptr> +std::string typed_unit () +{ + return "milliseconds"; +} + +template ::value> * = nullptr> +std::string typed_unit () +{ + return "seconds"; +} + +template ::value> * = nullptr> +std::string typed_unit () +{ + return "minutes"; +} + +template ::value> * = nullptr> +std::string typed_unit () +{ + return "hours"; +} +} + +template +nano::timer::timer (nano::timer_state state_a, std::string const & description_a) : +desc (description_a) +{ + if (state_a == nano::timer_state::started) + { + start (); + } +} + +template +nano::timer::timer (std::string const & description_a) : +desc (description_a) +{ +} + +template +nano::timer::timer (std::string const & description_a, nano::timer * parent_a) : +parent (parent_a), +desc (description_a) +{ +} + +template +nano::timer & nano::timer::set_minimum (UNIT minimum_a) +{ + minimum = minimum_a; + return *this; +} + +template +nano::timer & nano::timer::child (std::string const & description_a) +{ + children.emplace_back (description_a, this); + return children.back (); +} + +template +nano::timer & nano::timer::start_child (std::string const & description_a) +{ + auto & child_timer = child (description_a); + child_timer.start (); + return child_timer; +} + +template +void nano::timer::start () +{ + debug_assert (state == nano::timer_state::stopped); + state = nano::timer_state::started; + begin = CLOCK::now (); +} + +template +UNIT nano::timer::restart () +{ + auto current = ticks; + state = nano::timer_state::started; + begin = CLOCK::now (); + ticks = UNIT::zero (); + measurements = 0; + return current; +} + +template +UNIT nano::timer::pause () +{ + ++measurements; + return stop (); +} + +template +void nano::timer::update_ticks () +{ + auto end = CLOCK::now (); + ticks += std::chrono::duration_cast (end - begin); +} + +template +UNIT nano::timer::stop () +{ + debug_assert (state == nano::timer_state::started); + state = nano::timer_state::stopped; + + update_ticks (); + return ticks; +} + +template +UNIT nano::timer::value () +{ + update_ticks (); + return ticks; +} + +template +UNIT nano::timer::since_start () const +{ + auto end = CLOCK::now (); + return std::chrono::duration_cast (end - begin); +} + +template +bool nano::timer::after_deadline (UNIT duration_a) +{ + auto end = CLOCK::now (); + return std::chrono::duration_cast (end - begin) > duration_a; +} + +template +bool nano::timer::before_deadline (UNIT duration_a) +{ + auto end = CLOCK::now (); + return std::chrono::duration_cast (end - begin) < duration_a; +} + +template +void nano::timer::stop (std::ostream & stream_a) +{ + stop (); + print (stream_a); +} + +template +void nano::timer::stop (std::string & output_a) +{ + std::ostringstream stream; + stop (stream); + output_a = stream.str (); +} + +template +void nano::timer::print (std::ostream & stream_a) +{ + if (ticks >= minimum) + { + // Print cumulative children first. Non-cumulative children prints directly. + for (auto & child : children) + { + if (child.measurements > 0) + { + child.print (stream_a); + } + } + + auto current_parent = parent; + while (current_parent) + { + stream_a << parent->desc << "."; + current_parent = current_parent->parent; + } + + stream_a << desc << ": " << ticks.count () << ' ' << unit (); + if (measurements > 0) + { + stream_a << " (" << measurements << " measurements, " << std::setprecision (2) << std::fixed << static_cast (ticks.count ()) / static_cast (measurements) << ' ' << unit () << " avg)"; + } + stream_a << std::endl; + } +} + +template +std::string nano::timer::unit () const +{ + return typed_unit (); +} + +template +nano::timer_state nano::timer::current_state () const +{ + return state; +} + +// Explicitly instantiate all realistically used timers +template class nano::timer; +template class nano::timer; +template class nano::timer; +template class nano::timer; +template class nano::timer; +template class nano::timer; +template class nano::timer; +template class nano::timer; diff --git a/nano/lib/timer.hpp b/nano/lib/timer.hpp index 68790d1380..423d6f6f73 100644 --- a/nano/lib/timer.hpp +++ b/nano/lib/timer.hpp @@ -1,10 +1,7 @@ #pragma once -#include #include -#include -#include -#include +#include #include #include @@ -22,179 +19,72 @@ class timer { public: timer () = default; - - timer (nano::timer_state state_a, std::string description_a = "timer") : - desc (description_a) - { - if (state_a == nano::timer_state::started) - { - start (); - } - } - - timer (std::string description_a) : - desc (description_a) - { - } - - timer (std::string description_a, timer * parent_a) : - parent (parent_a), - desc (description_a) - { - } + timer (nano::timer_state state_a, std::string const & description_a = "timer"); + timer (std::string const & description_a); + timer (std::string const & description_a, timer * parent_a); /** Do not output if measured time is below the time units threshold in \p minimum_a */ - timer & set_minimum (UNIT minimum_a) - { - minimum = minimum_a; - return *this; - } + timer & set_minimum (UNIT minimum_a); /** * Create a child timer without starting it. * Since the timing API needs to have low overhead, this function * does not check if a timer with the same name already exists. */ - timer & child (std::string description_a = "child timer") - { - children.emplace_back (description_a, this); - return children.back (); - } + timer & child (std::string const & description_a = "child timer"); /** Create and start a child timer */ - timer & start_child (std::string description_a = "child timer") - { - auto & child_timer = child (description_a); - child_timer.start (); - return child_timer; - } + timer & start_child (std::string const & description_a = "child timer"); /** Start the timer. This will assert if the timer is already started. */ - void start () - { - assert (state == nano::timer_state::stopped); - state = nano::timer_state::started; - begin = CLOCK::now (); - } + void start (); - /** Restarts the timer */ - void restart () - { - state = nano::timer_state::started; - begin = CLOCK::now (); - ticks = UNIT::zero (); - measurements = 0; - } + /** + * Restarts the timer by setting start time to current time and resetting tick count. + * This can be called in any timer state. + * @return duration + */ + UNIT restart (); /** * Stops the timer and increases the measurement count. A timer can be started and paused * multiple times (e.g. in a loop). * @return duration */ - UNIT pause () - { - ++measurements; - return stop (); - } + UNIT pause (); /** - * Stop timer + * Stop timer. This will assert if the timer is not in a started state. * @return duration */ - UNIT stop () - { - assert (state == nano::timer_state::started); - state = nano::timer_state::stopped; - - auto end = CLOCK::now (); - ticks += std::chrono::duration_cast (end - begin); - return ticks; - } + UNIT stop (); /** - * Return current units. + * Updates and returns current tick count. */ - UNIT value () - { - return ticks; - } + UNIT value (); /** Returns the duration in UNIT since the timer was last started. */ - UNIT since_start () const - { - auto end = CLOCK::now (); - return std::chrono::duration_cast (end - begin); - } + UNIT since_start () const; /** Returns true if the timer was last started longer than \p duration_a units ago*/ - bool after_deadline (UNIT duration_a) - { - auto end = CLOCK::now (); - return std::chrono::duration_cast (end - begin) > duration_a; - } + bool after_deadline (UNIT duration_a); /** Returns true if the timer was last started shorter than \p duration_a units ago*/ - bool before_deadline (UNIT duration_a) - { - auto end = CLOCK::now (); - return std::chrono::duration_cast (end - begin) < duration_a; - } - - /** Stop timer and write measurements to \p output_a */ - void stop (std::string & output_a) - { - std::ostringstream stream; - stop (stream); - output_a = stream.str (); - } + bool before_deadline (UNIT duration_a); /** Stop timer and write measurements to \p stream_a */ - void stop (std::ostream & stream_a) - { - stop (); - print (stream_a); - } - - /** Print measurements to the \p stream_a */ - void print (std::ostream & stream_a) - { - if (ticks >= minimum) - { - // Print cumulative children first. Non-cumulative children prints directly. - for (auto & child : children) - { - if (child.measurements > 0) - { - child.print (stream_a); - } - } + void stop (std::ostream & stream_a); - auto current_parent = parent; - while (current_parent) - { - stream_a << parent->desc << "."; - current_parent = current_parent->parent; - } + /** Stop timer and write measurements to \p output_a */ + void stop (std::string & output_a); - stream_a << desc << ": " << ticks.count () << ' ' << unit (); - if (measurements > 0) - { - stream_a << " (" << measurements << " measurements, " << std::setprecision (2) << std::fixed << static_cast (ticks.count ()) / static_cast (measurements) << ' ' << unit () << " avg)"; - } - stream_a << std::endl; - } - } + /** Print measurements to the \p stream_a */ + void print (std::ostream & stream_a); /** Returns the SI unit string */ - std::string unit () const - { - return typed_unit (); - } - - nano::timer_state current_state () const - { - return state; - } + std::string unit () const; + nano::timer_state current_state () const; private: timer * parent{ nullptr }; @@ -205,62 +95,6 @@ class timer UNIT ticks{ 0 }; UNIT minimum{ UNIT::zero () }; unsigned measurements{ 0 }; - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "nanoseconds"; - } - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "microseconds"; - } - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "milliseconds"; - } - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "seconds"; - } - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "minutes"; - } - - template ::value> * = nullptr> - std::string typed_unit () const - { - return "hours"; - } -}; - -/** - * The autotimer starts on construction, and stops and prints on destruction. - */ -template -class autotimer : public nano::timer -{ -public: - autotimer (std::string description_a, std::ostream & stream_a = std::cout) : - nano::timer (description_a), stream (stream_a) - { - nano::timer::start (); - } - ~autotimer () - { - nano::timer::stop (stream); - } - -private: - std::ostream & stream; + void update_ticks (); }; } diff --git a/nano/lib/tomlconfig.cpp b/nano/lib/tomlconfig.cpp new file mode 100644 index 0000000000..b574939878 --- /dev/null +++ b/nano/lib/tomlconfig.cpp @@ -0,0 +1,377 @@ +#include +#include + +#include + +nano::tomlconfig::tomlconfig () : +tree (cpptoml::make_table ()) +{ + error = std::make_shared (); +} + +nano::tomlconfig::tomlconfig (std::shared_ptr const & tree_a, std::shared_ptr const & error_a) : +nano::configbase (error_a), tree (tree_a) +{ + if (!error) + { + error = std::make_shared (); + } +} + +void nano::tomlconfig::doc (std::string const & key, std::string const & doc) +{ + tree->document (key, doc); +} + +nano::error & nano::tomlconfig::read (boost::filesystem::path const & path_a) +{ + std::stringstream stream_override_empty; + stream_override_empty << std::endl; + return read (stream_override_empty, path_a); +} + +nano::error & nano::tomlconfig::read (std::istream & stream_overrides, boost::filesystem::path const & path_a) +{ + std::fstream stream; + open_or_create (stream, path_a.string ()); + if (!stream.fail ()) + { + read (stream_overrides, stream); + } + return *error; +} + +nano::error & nano::tomlconfig::read (std::istream & stream_a) +{ + std::stringstream stream_override_empty; + stream_override_empty << std::endl; + return read (stream_override_empty, stream_a); +} + +/** Read from two streams where keys in the first will take precedence over those in the second stream. */ +nano::error & nano::tomlconfig::read (std::istream & stream_first_a, std::istream & stream_second_a) +{ + try + { + tree = cpptoml::parse_base_and_override_files (stream_first_a, stream_second_a, cpptoml::parser::merge_type::ignore, true); + } + catch (std::runtime_error const & ex) + { + *error = ex; + } + return *error; +} + +void nano::tomlconfig::write (boost::filesystem::path const & path_a) +{ + std::fstream stream; + open_or_create (stream, path_a.string ()); + write (stream); +} + +void nano::tomlconfig::write (std::ostream & stream_a) const +{ + cpptoml::toml_writer writer{ stream_a, "" }; + tree->accept (writer); +} + +/** Open configuration file, create if necessary */ +void nano::tomlconfig::open_or_create (std::fstream & stream_a, std::string const & path_a) +{ + if (!boost::filesystem::exists (path_a)) + { + // Create temp stream to first create the file + std::ofstream stream (path_a); + + // Set permissions before opening otherwise Windows only has read permissions + nano::set_secure_perm_file (path_a); + } + + stream_a.open (path_a); +} + +/** Returns the table managed by this instance */ +std::shared_ptr nano::tomlconfig::get_tree () +{ + return tree; +} + +/** Returns true if the toml table is empty */ +bool nano::tomlconfig::empty () const +{ + return tree->empty (); +} + +boost::optional nano::tomlconfig::get_optional_child (std::string const & key_a) +{ + boost::optional child_config; + if (tree->contains (key_a)) + { + return tomlconfig (tree->get_table (key_a), error); + } + return child_config; +} + +nano::tomlconfig nano::tomlconfig::get_required_child (std::string const & key_a) +{ + if (!tree->contains (key_a)) + { + *error = nano::error_config::missing_value; + error->set_message ("Missing configuration node: " + key_a); + return *this; + } + else + { + return tomlconfig (tree->get_table (key_a), error); + } +} + +nano::tomlconfig & nano::tomlconfig::put_child (std::string const & key_a, nano::tomlconfig & conf_a) +{ + tree->insert (key_a, conf_a.get_tree ()); + return *this; +} + +nano::tomlconfig & nano::tomlconfig::replace_child (std::string const & key_a, nano::tomlconfig & conf_a) +{ + tree->erase (key_a); + put_child (key_a, conf_a); + return *this; +} + +/** Returns true if \p key_a is present */ +bool nano::tomlconfig::has_key (std::string const & key_a) +{ + return tree->contains (key_a); +} + +/** Erase the property of given key */ +nano::tomlconfig & nano::tomlconfig::erase (std::string const & key_a) +{ + tree->erase (key_a); + return *this; +} + +std::shared_ptr nano::tomlconfig::create_array (std::string const & key, boost::optional documentation_a) +{ + if (!has_key (key)) + { + auto arr = cpptoml::make_array (); + tree->insert (key, arr); + if (documentation_a) + { + doc (key, *documentation_a); + } + } + + return tree->get_qualified (key)->as_array (); +} + +/** + * Erase keys whose values are equal to the one in \p defaults + */ +void nano::tomlconfig::erase_default_values (tomlconfig & defaults_a) +{ + std::shared_ptr clone = std::dynamic_pointer_cast (tree->clone ()); + tomlconfig self (clone); + + // The toml library doesn't offer a general way to compare values, so let the diff run on a stringified parse + std::stringstream ss_self; + write (ss_self); + self.read (ss_self); + + tomlconfig defaults_l; + std::stringstream ss; + defaults_a.write (ss); + defaults_l.read (ss); + + erase_defaults (defaults_l.get_tree (), self.get_tree (), get_tree ()); +} + +std::string nano::tomlconfig::to_string () +{ + std::stringstream ss; + cpptoml::toml_writer writer{ ss, "" }; + tree->accept (writer); + return ss.str (); +} + +std::string nano::tomlconfig::to_string_commented_entries () +{ + std::stringstream ss, ss_processed; + cpptoml::toml_writer writer{ ss, "" }; + tree->accept (writer); + std::string line; + while (std::getline (ss, line, '\n')) + { + if (!line.empty () && line[0] != '#' && line[0] != '[') + { + line = "#" + line; + } + ss_processed << line << std::endl; + } + return ss_processed.str (); +} + +// boost's lexical cast doesn't handle (u)int8_t +nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string const & key, uint8_t & target, uint8_t default_value) +{ + try + { + if (tree->contains_qualified (key)) + { + int64_t tmp; + auto val (tree->get_qualified_as (key)); + if (!boost::conversion::try_lexical_convert (*val, tmp) || tmp < 0 || tmp > 255) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + else + { + target = static_cast (tmp); + } + } + else if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + + return *this; +} + +nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string const & key, bool & target, bool default_value) +{ + auto bool_conv = [this, &target, &key, optional](std::string val) { + if (val == "true") + { + target = true; + } + else if (val == "false") + { + target = false; + } + else if (!*error) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + }; + try + { + if (tree->contains_qualified (key)) + { + auto val (tree->get_qualified_as (key)); + bool_conv (*val); + } + else if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; +} + +/** Compare two stringified configs, remove keys where values are equal */ +void nano::tomlconfig::erase_defaults (std::shared_ptr base, std::shared_ptr other, std::shared_ptr update_target) +{ + std::vector erased; + debug_assert (other != nullptr); + for (auto & item : *other) + { + std::string const & key = item.first; + if (other->contains (key) && base->contains (key)) + { + auto value = item.second; + if (value->is_table ()) + { + auto child_base = base->get_table (key); + auto child_other = other->get_table (key); + auto child_target = update_target->get_table (key); + erase_defaults (child_base, child_other, child_target); + if (child_target->empty ()) + { + erased.push_back (key); + } + } + else if (value->is_array ()) + { + auto arr_other = other->get_array (key)->get (); + auto arr_base = base->get_array (key)->get (); + + if (arr_other.size () == arr_base.size ()) + { + bool equal = std::equal (arr_other.begin (), arr_other.end (), arr_base.begin (), + [](auto const & item1, auto const & item2) -> bool { + return (item1->template as ()->get () == item2->template as ()->get ()); + }); + + if (equal) + { + erased.push_back (key); + } + } + } + else if (value->is_value ()) + { + auto val_other = std::dynamic_pointer_cast> (other->get (key)); + auto val_base = std::dynamic_pointer_cast> (base->get (key)); + + if (val_other->get () == val_base->get ()) + { + erased.push_back (key); + } + } + } + } + for (auto & key : erased) + { + update_target->erase (key); + } +} + +nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value) +{ + try + { + if (tree->contains_qualified (key)) + { + auto address_l (tree->get_qualified_as (key)); + boost::system::error_code bec; + target = boost::asio::ip::make_address_v6 (address_l.value_or (""), bec); + if (bec) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + } + else if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + + return *this; +} \ No newline at end of file diff --git a/nano/lib/tomlconfig.hpp b/nano/lib/tomlconfig.hpp index 8e61092903..7f34944962 100644 --- a/nano/lib/tomlconfig.hpp +++ b/nano/lib/tomlconfig.hpp @@ -1,169 +1,55 @@ #pragma once -#include #include -#include #include -#include +#include #include #include -#include -#include -#include - #include -namespace nano +namespace boost { -/** Manages a table in a toml configuration table hierarchy */ -class tomlconfig : public nano::configbase +namespace asio { -public: - tomlconfig () : - tree (cpptoml::make_table ()) - { - error = std::make_shared (); - } - - tomlconfig (std::shared_ptr const & tree_a, std::shared_ptr const & error_a = nullptr) : - nano::configbase (error_a), tree (tree_a) - { - if (!error) - { - error = std::make_shared (); - } - } - - void doc (std::string const & key, std::string const & doc) - { - tree->document (key, doc); - } - - /** - * Reads a json object from the stream - * @return nano::error&, including a descriptive error message if the config file is malformed. - */ - nano::error & read (boost::filesystem::path const & path_a) - { - std::stringstream stream_override_empty; - stream_override_empty << std::endl; - return read (stream_override_empty, path_a); - } - - nano::error & read (std::istream & stream_overrides, boost::filesystem::path const & path_a) - { - std::fstream stream; - open_or_create (stream, path_a.string ()); - if (!stream.fail ()) - { - try - { - read (stream_overrides, stream); - } - catch (std::runtime_error const & ex) - { - auto pos (stream.tellg ()); - if (pos != std::streampos (0)) - { - *error = ex; - } - } - stream.close (); - } - return *error; - } - - /** Read from two streams where keys in the first will take precedence over those in the second stream. */ - void read (std::istream & stream_first_a, std::istream & stream_second_a) - { - tree = cpptoml::parse_base_and_override_files (stream_first_a, stream_second_a, cpptoml::parser::merge_type::ignore, true); - } - - void read (std::istream & stream_a) - { - std::stringstream stream_override_empty; - stream_override_empty << std::endl; - tree = cpptoml::parse_base_and_override_files (stream_override_empty, stream_a, cpptoml::parser::merge_type::ignore, true); - } - - void write (boost::filesystem::path const & path_a) - { - std::fstream stream; - open_or_create (stream, path_a.string ()); - write (stream); - } - - void write (std::ostream & stream_a) const - { - cpptoml::toml_writer writer{ stream_a, "" }; - tree->accept (writer); - } - - /** Open configuration file, create if necessary */ - void open_or_create (std::fstream & stream_a, std::string const & path_a) - { - if (!boost::filesystem::exists (path_a)) - { - // Create temp stream to first create the file - std::ofstream stream (path_a); - - // Set permissions before opening otherwise Windows only has read permissions - nano::set_secure_perm_file (path_a); - } - - stream_a.open (path_a); - } - - /** Returns the table managed by this instance */ - std::shared_ptr get_tree () - { - return tree; - } - - /** Returns true if the toml table is empty */ - bool empty () const + namespace ip { - return tree->empty (); - } - - boost::optional get_optional_child (std::string const & key_a) - { - boost::optional child_config; - if (tree->contains (key_a)) - { - return tomlconfig (tree->get_table (key_a), error); - } - return child_config; - } - - tomlconfig get_required_child (std::string const & key_a) - { - if (!tree->contains (key_a)) - { - *error = nano::error_config::missing_value; - error->set_message ("Missing configuration node: " + key_a); - return *this; - } - else - { - return tomlconfig (tree->get_table (key_a), error); - } + class address_v6; } +} +} - tomlconfig & put_child (std::string const & key_a, nano::tomlconfig & conf_a) - { - tree->insert (key_a, conf_a.get_tree ()); - return *this; - } +namespace nano +{ +class error; - tomlconfig & replace_child (std::string const & key_a, nano::tomlconfig & conf_a) - { - tree->erase (key_a); - put_child (key_a, conf_a); - return *this; - } +/** Manages a table in a toml configuration table hierarchy */ +class tomlconfig : public nano::configbase +{ +public: + tomlconfig (); + tomlconfig (std::shared_ptr const & tree_a, std::shared_ptr const & error_a = nullptr); + void doc (std::string const & key, std::string const & doc); + nano::error & read (boost::filesystem::path const & path_a); + nano::error & read (std::istream & stream_overrides, boost::filesystem::path const & path_a); + nano::error & read (std::istream & stream_a); + nano::error & read (std::istream & stream_first_a, std::istream & stream_second_a); + void write (boost::filesystem::path const & path_a); + void write (std::ostream & stream_a) const; + void open_or_create (std::fstream & stream_a, std::string const & path_a); + std::shared_ptr get_tree (); + bool empty () const; + boost::optional get_optional_child (std::string const & key_a); + tomlconfig get_required_child (std::string const & key_a); + tomlconfig & put_child (std::string const & key_a, nano::tomlconfig & conf_a); + tomlconfig & replace_child (std::string const & key_a, nano::tomlconfig & conf_a); + bool has_key (std::string const & key_a); + tomlconfig & erase (std::string const & key_a); + std::shared_ptr create_array (std::string const & key, boost::optional documentation_a); + void erase_default_values (tomlconfig & defaults_a); + std::string to_string (); + std::string to_string_commented_entries (); /** Set value for the given key. Any existing value will be overwritten. */ template @@ -177,19 +63,6 @@ class tomlconfig : public nano::configbase return *this; } - /** Returns true if \p key_a is present */ - bool has_key (std::string const & key_a) - { - return tree->contains (key_a); - } - - /** Erase the property of given key */ - tomlconfig & erase (std::string const & key_a) - { - tree->erase (key_a); - return *this; - } - /** * Push array element * @param key Array element key. Qualified (dotted) keys are not supported for arrays so this must be called on the correct tomlconfig node. @@ -207,21 +80,6 @@ class tomlconfig : public nano::configbase return *this; } - auto create_array (std::string const & key, boost::optional documentation_a) - { - if (!has_key (key)) - { - auto arr = cpptoml::make_array (); - tree->insert (key, arr); - if (documentation_a) - { - doc (key, *documentation_a); - } - } - - return tree->get_qualified (key)->as_array (); - } - /** * Iterate array entries. * @param key Array element key. Qualified (dotted) keys are not supported for arrays so this must be called on the correct tomlconfig node. @@ -248,7 +106,7 @@ class tomlconfig : public nano::configbase template tomlconfig & get_optional (std::string const & key, T & target, T default_value) { - get_config (true, key, target, default_value); + get_config (true, key, target, default_value); return *this; } @@ -259,7 +117,7 @@ class tomlconfig : public nano::configbase template tomlconfig & get_optional (std::string const & key, T & target) { - get_config (true, key, target, target); + get_config (true, key, target, target); return *this; } @@ -271,7 +129,7 @@ class tomlconfig : public nano::configbase if (has_key (key)) { T target{}; - get_config (true, key, target, target); + get_config (true, key, target, target); res = target; } return res; @@ -281,7 +139,7 @@ class tomlconfig : public nano::configbase template tomlconfig & get (std::string const & key, T & target) { - get_config (true, key, target, target); + get_config (true, key, target, target); return *this; } @@ -292,7 +150,7 @@ class tomlconfig : public nano::configbase T get (std::string const & key) { T target{}; - get_config (true, key, target, target); + get_config (true, key, target, target); return target; } @@ -303,54 +161,15 @@ class tomlconfig : public nano::configbase template tomlconfig & get_required (std::string const & key, T & target) { - get_config (false, key, target); + get_config (false, key, target); return *this; } - /** - * Erase keys whose values are equal to the one in \p defaults - */ - void erase_default_values (tomlconfig & defaults_a) - { - std::shared_ptr clone = std::dynamic_pointer_cast (tree->clone ()); - tomlconfig self (clone); - - // The toml library doesn't offer a general way to compare values, so let the diff run on a stringified parse - std::stringstream ss_self; - write (ss_self); - self.read (ss_self); - - tomlconfig defaults_l; - std::stringstream ss; - defaults_a.write (ss); - defaults_l.read (ss); - - erase_defaults (defaults_l.get_tree (), self.get_tree (), get_tree ()); - } - - std::string to_string () - { - std::stringstream ss; - cpptoml::toml_writer writer{ ss, "" }; - tree->accept (writer); - return ss.str (); - } - - std::string to_string_commented_entries () + template + tomlconfig & get_required (std::string const & key, T & target, T const & default_value) { - std::stringstream ss, ss_processed; - cpptoml::toml_writer writer{ ss, "" }; - tree->accept (writer); - std::string line; - while (std::getline (ss, line, '\n')) - { - if (!line.empty () && line[0] != '#' && line[0] != '[') - { - line = "#" + line; - } - ss_processed << line << std::endl; - } - return ss_processed.str (); + get_config (false, key, target, default_value); + return *this; } protected: @@ -384,174 +203,15 @@ class tomlconfig : public nano::configbase return *this; } - // boost's lexical cast doesn't handle (u)int8_t - template ::value>> - tomlconfig & get_config (bool optional, std::string const & key, uint8_t & target, uint8_t default_value = T ()) - { - try - { - if (tree->contains_qualified (key)) - { - int64_t tmp; - auto val (tree->get_qualified_as (key)); - if (!boost::conversion::try_lexical_convert (*val, tmp) || tmp < 0 || tmp > 255) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - else - { - target = static_cast (tmp); - } - } - else if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - catch (std::runtime_error & ex) - { - conditionally_set_error (ex, optional, key); - } - - return *this; - } - - template ::value>> - tomlconfig & get_config (bool optional, std::string const & key, bool & target, bool default_value = false) - { - auto bool_conv = [this, &target, &key, optional](std::string val) { - if (val == "true") - { - target = true; - } - else if (val == "false") - { - target = false; - } - else if (!*error) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - }; - try - { - if (tree->contains_qualified (key)) - { - auto val (tree->get_qualified_as (key)); - bool_conv (*val); - } - else if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - catch (std::runtime_error & ex) - { - conditionally_set_error (ex, optional, key); - } - return *this; - } - - template ::value>> - tomlconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 default_value = T ()) - { - try - { - if (tree->contains_qualified (key)) - { - auto address_l (tree->get_qualified_as (key)); - boost::system::error_code bec; - target = boost::asio::ip::address_v6::from_string (address_l.value_or (""), bec); - if (bec) - { - conditionally_set_error (nano::error_config::invalid_value, optional, key); - } - } - else if (!optional) - { - conditionally_set_error (nano::error_config::missing_value, optional, key); - } - else - { - target = default_value; - } - } - catch (std::runtime_error & ex) - { - conditionally_set_error (ex, optional, key); - } - - return *this; - } + tomlconfig & get_config (bool optional, std::string const & key, uint8_t & target, uint8_t default_value = uint8_t ()); + tomlconfig & get_config (bool optional, std::string const & key, bool & target, bool default_value = false); + tomlconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value); private: /** The config node being managed */ std::shared_ptr tree; /** Compare two stringified configs, remove keys where values are equal */ - void erase_defaults (std::shared_ptr base, std::shared_ptr other, std::shared_ptr update_target) - { - std::vector erased; - assert (other != nullptr); - for (auto & item : *other) - { - std::string const & key = item.first; - if (other->contains (key) && base->contains (key)) - { - auto value = item.second; - if (value->is_table ()) - { - auto child_base = base->get_table (key); - auto child_other = other->get_table (key); - auto child_target = update_target->get_table (key); - erase_defaults (child_base, child_other, child_target); - if (child_target->empty ()) - { - erased.push_back (key); - } - } - else if (value->is_array ()) - { - auto arr_other = other->get_array (key)->get (); - auto arr_base = base->get_array (key)->get (); - - if (arr_other.size () == arr_base.size ()) - { - bool equal = std::equal (arr_other.begin (), arr_other.end (), arr_base.begin (), - [](auto const & item1, auto const & item2) -> bool { - return (item1->template as ()->get () == item2->template as ()->get ()); - }); - - if (equal) - { - erased.push_back (key); - } - } - } - else if (value->is_value ()) - { - auto val_other = std::dynamic_pointer_cast> (other->get (key)); - auto val_base = std::dynamic_pointer_cast> (base->get (key)); - - if (val_other->get () == val_base->get ()) - { - erased.push_back (key); - } - } - } - } - for (auto & key : erased) - { - update_target->erase (key); - } - } + void erase_defaults (std::shared_ptr base, std::shared_ptr other, std::shared_ptr update_target); }; } diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index ede99219b3..44b61c3f40 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -1,8 +1,11 @@ #include #include +#include #include +#include +#include // Some builds (mac) fail due to "Boost.Stacktrace requires `_Unwind_Backtrace` function". #ifndef _WIN32 @@ -25,54 +28,52 @@ #endif #endif -namespace nano -{ -seq_con_info_composite::seq_con_info_composite (const std::string & name) : +nano::container_info_composite::container_info_composite (const std::string & name) : name (name) { } -bool seq_con_info_composite::is_composite () const +bool nano::container_info_composite::is_composite () const { return true; } -void seq_con_info_composite::add_component (std::unique_ptr child) +void nano::container_info_composite::add_component (std::unique_ptr child) { children.push_back (std::move (child)); } -const std::vector> & seq_con_info_composite::get_children () const +const std::vector> & nano::container_info_composite::get_children () const { return children; } -const std::string & seq_con_info_composite::get_name () const +const std::string & nano::container_info_composite::get_name () const { return name; } -seq_con_info_leaf::seq_con_info_leaf (const seq_con_info & info) : +nano::container_info_leaf::container_info_leaf (const container_info & info) : info (info) { } -bool seq_con_info_leaf::is_composite () const +bool nano::container_info_leaf::is_composite () const { return false; } -const seq_con_info & seq_con_info_leaf::get_info () const +const nano::container_info & nano::container_info_leaf::get_info () const { return info; } -void dump_crash_stacktrace () +void nano::dump_crash_stacktrace () { boost::stacktrace::safe_dump_to ("nano_node_backtrace.dump"); } -std::string generate_stacktrace () +std::string nano::generate_stacktrace () { auto stacktrace = boost::stacktrace::stacktrace (); std::stringstream ss; @@ -80,244 +81,6 @@ std::string generate_stacktrace () return ss.str (); } -namespace thread_role -{ - /* - * nano::thread_role namespace - * - * Manage thread role - */ - static thread_local nano::thread_role::name current_thread_role = nano::thread_role::name::unknown; - nano::thread_role::name get () - { - return current_thread_role; - } - - std::string get_string (nano::thread_role::name role) - { - std::string thread_role_name_string; - - switch (role) - { - case nano::thread_role::name::unknown: - thread_role_name_string = ""; - break; - case nano::thread_role::name::io: - thread_role_name_string = "I/O"; - break; - case nano::thread_role::name::work: - thread_role_name_string = "Work pool"; - break; - case nano::thread_role::name::packet_processing: - thread_role_name_string = "Pkt processing"; - break; - case nano::thread_role::name::alarm: - thread_role_name_string = "Alarm"; - break; - case nano::thread_role::name::vote_processing: - thread_role_name_string = "Vote processing"; - break; - case nano::thread_role::name::block_processing: - thread_role_name_string = "Blck processing"; - break; - case nano::thread_role::name::request_loop: - thread_role_name_string = "Request loop"; - break; - case nano::thread_role::name::wallet_actions: - thread_role_name_string = "Wallet actions"; - break; - case nano::thread_role::name::work_watcher: - thread_role_name_string = "Work watcher"; - break; - case nano::thread_role::name::bootstrap_initiator: - thread_role_name_string = "Bootstrap init"; - break; - case nano::thread_role::name::voting: - thread_role_name_string = "Voting"; - break; - case nano::thread_role::name::signature_checking: - thread_role_name_string = "Signature check"; - break; - case nano::thread_role::name::rpc_request_processor: - thread_role_name_string = "RPC processor"; - break; - case nano::thread_role::name::rpc_process_container: - thread_role_name_string = "RPC process"; - break; - case nano::thread_role::name::confirmation_height_processing: - thread_role_name_string = "Conf height"; - break; - case nano::thread_role::name::worker: - thread_role_name_string = "Worker"; - break; - } - - /* - * We want to constrain the thread names to 15 - * characters, since this is the smallest maximum - * length supported by the platforms we support - * (specifically, Linux) - */ - assert (thread_role_name_string.size () < 16); - return (thread_role_name_string); - } - - std::string get_string () - { - return get_string (current_thread_role); - } - - void set (nano::thread_role::name role) - { - auto thread_role_name_string (get_string (role)); - - nano::thread_role::set_os_name (thread_role_name_string); - - nano::thread_role::current_thread_role = role; - } -} -} - -void nano::thread_attributes::set (boost::thread::attributes & attrs) -{ - auto attrs_l (&attrs); - attrs_l->set_stack_size (8000000); //8MB -} - -nano::thread_runner::thread_runner (boost::asio::io_context & io_ctx_a, unsigned service_threads_a) : -io_guard (boost::asio::make_work_guard (io_ctx_a)) -{ - boost::thread::attributes attrs; - nano::thread_attributes::set (attrs); - for (auto i (0u); i < service_threads_a; ++i) - { - threads.push_back (boost::thread (attrs, [&io_ctx_a]() { - nano::thread_role::set (nano::thread_role::name::io); - try - { - io_ctx_a.run (); - } - catch (std::exception const & ex) - { - std::cerr << ex.what () << std::endl; -#ifndef NDEBUG - throw; -#endif - } - catch (...) - { -#ifndef NDEBUG - /* - * In a release build, catch and swallow the - * io_context exception, in debug mode pass it - * on - */ - throw; -#endif - } - })); - } -} - -nano::thread_runner::~thread_runner () -{ - join (); -} - -void nano::thread_runner::join () -{ - io_guard.reset (); - for (auto & i : threads) - { - if (i.joinable ()) - { - i.join (); - } - } -} - -void nano::thread_runner::stop_event_processing () -{ - io_guard.get_executor ().context ().stop (); -} - -nano::worker::worker () : -thread ([this]() { - nano::thread_role::set (nano::thread_role::name::worker); - this->run (); -}) -{ -} - -void nano::worker::run () -{ - nano::unique_lock lk (mutex); - while (!stopped) - { - if (!queue.empty ()) - { - auto func = queue.front (); - queue.pop_front (); - lk.unlock (); - func (); - // So that we reduce locking for anything being pushed as that will - // most likely be on an io-thread - std::this_thread::yield (); - lk.lock (); - } - else - { - cv.wait (lk); - } - } -} - -nano::worker::~worker () -{ - stop (); -} - -void nano::worker::push_task (std::function func_a) -{ - { - nano::lock_guard guard (mutex); - if (!stopped) - { - queue.emplace_back (func_a); - } - } - - cv.notify_one (); -} - -void nano::worker::stop () -{ - { - nano::unique_lock lk (mutex); - stopped = true; - queue.clear (); - } - cv.notify_one (); - if (thread.joinable ()) - { - thread.join (); - } -} - -std::unique_ptr nano::collect_seq_con_info (nano::worker & worker, const std::string & name) -{ - auto composite = std::make_unique (name); - - size_t count = 0; - { - nano::lock_guard guard (worker.mutex); - count = worker.queue.size (); - } - auto sizeof_element = sizeof (decltype (worker.queue)::value_type); - composite->add_component (std::make_unique (nano::seq_con_info{ "queue", count, sizeof_element })); - return composite; -} - void nano::remove_all_files_in_dir (boost::filesystem::path const & dir) { for (auto & p : boost::filesystem::directory_iterator (dir)) @@ -343,29 +106,26 @@ void nano::move_all_files_to_dir (boost::filesystem::path const & from, boost::f } /* - * Backing code for "release_assert", which is itself a macro + * Backing code for "release_assert" & "debug_assert", which are macros */ -void release_assert_internal (bool check, const char * check_expr, const char * file, unsigned int line) +void assert_internal (const char * check_expr, const char * func, const char * file, unsigned int line, bool is_release_assert) { - if (check) - { - return; - } - - std::cerr << "Assertion (" << check_expr << ") failed " << file << ":" << line << "\n\n"; + std::cerr << "Assertion (" << check_expr << ") failed\n" + << func << "\n" + << file << ":" << line << "\n\n"; // Output stack trace to cerr auto backtrace_str = nano::generate_stacktrace (); std::cerr << backtrace_str << std::endl; // "abort" at the end of this function will go into any signal handlers (the daemon ones will generate a stack trace and load memory address files on non-Windows systems). - // As there is no async-signal-safe way to generate stacktraces on Windows so must be done before aborting + // As there is no async-signal-safe way to generate stacktraces on Windows it must be done before aborting #ifdef _WIN32 { // Try construct the stacktrace dump in the same folder as the the running executable, otherwise use the current directory. boost::system::error_code err; auto running_executable_filepath = boost::dll::program_location (err); - std::string filename = "nano_node_backtrace_release_assert.txt"; + std::string filename = is_release_assert ? "nano_node_backtrace_release_assert.txt" : "nano_node_backtrace_assert.txt"; std::string filepath = filename; if (!err) { @@ -377,5 +137,6 @@ void release_assert_internal (bool check, const char * check_expr, const char * file << backtrace_str; } #endif + abort (); } diff --git a/nano/lib/utility.hpp b/nano/lib/utility.hpp index 10e49b9db9..1bd9e18fe9 100644 --- a/nano/lib/utility.hpp +++ b/nano/lib/utility.hpp @@ -1,60 +1,79 @@ #pragma once -#include #include -#include -#include -#include +#include +#include #include #include -#include #include +namespace boost +{ +namespace filesystem +{ + class path; +} + +namespace system +{ + class error_code; +} +} + +void assert_internal (const char * check_expr, const char * func, const char * file, unsigned int line, bool is_release_assert); +#define release_assert(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, true) + +#ifdef NDEBUG +#define debug_assert(check) (void)0 +#else +#define debug_assert(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, false) +#endif + namespace nano { /* These containers are used to collect information about sequence containers. * It makes use of the composite design pattern to collect information * from sequence containers and sequence containers inside member variables. */ -struct seq_con_info +struct container_info { std::string name; size_t count; size_t sizeof_element; }; -class seq_con_info_component +class container_info_component { public: - virtual ~seq_con_info_component () = default; + virtual ~container_info_component () = default; virtual bool is_composite () const = 0; }; -class seq_con_info_composite : public seq_con_info_component +class container_info_composite : public container_info_component { public: - seq_con_info_composite (const std::string & name); + container_info_composite (const std::string & name); bool is_composite () const override; - void add_component (std::unique_ptr child); - const std::vector> & get_children () const; + void add_component (std::unique_ptr child); + const std::vector> & get_children () const; const std::string & get_name () const; private: std::string name; - std::vector> children; + std::vector> children; }; -class seq_con_info_leaf : public seq_con_info_component +class container_info_leaf : public container_info_component { public: - seq_con_info_leaf (const seq_con_info & info); + container_info_leaf (container_info const & info); bool is_composite () const override; - const seq_con_info & get_info () const; + const container_info & get_info () const; private: - seq_con_info info; + container_info info; }; // Lower priority of calling work generating thread @@ -94,92 +113,6 @@ void dump_crash_stacktrace (); */ std::string generate_stacktrace (); -/* - * Functions for understanding the role of the current thread - */ -namespace thread_role -{ - enum class name - { - unknown, - io, - work, - packet_processing, - alarm, - vote_processing, - block_processing, - request_loop, - wallet_actions, - bootstrap_initiator, - voting, - signature_checking, - rpc_request_processor, - rpc_process_container, - work_watcher, - confirmation_height_processing, - worker - }; - /* - * Get/Set the identifier for the current thread - */ - nano::thread_role::name get (); - void set (nano::thread_role::name); - - /* - * Get the thread name as a string from enum - */ - std::string get_string (nano::thread_role::name); - - /* - * Get the current thread's role as a string - */ - std::string get_string (); - - /* - * Internal only, should not be called directly - */ - void set_os_name (std::string const &); -} - -namespace thread_attributes -{ - void set (boost::thread::attributes &); -} - -class thread_runner final -{ -public: - thread_runner (boost::asio::io_context &, unsigned); - ~thread_runner (); - /** Tells the IO context to stop processing events.*/ - void stop_event_processing (); - /** Wait for IO threads to complete */ - void join (); - std::vector threads; - boost::asio::executor_work_guard io_guard; -}; - -class worker final -{ -public: - worker (); - ~worker (); - void run (); - void push_task (std::function func); - void stop (); - -private: - nano::condition_variable cv; - std::deque> queue; - std::mutex mutex; - bool stopped{ false }; - std::thread thread; - - friend std::unique_ptr collect_seq_con_info (worker &, const std::string &); -}; - -std::unique_ptr collect_seq_con_info (worker & worker, const std::string & name); - /** * Returns seconds passed since unix epoch (posix time) */ @@ -210,7 +143,7 @@ class observer_set final }; template -std::unique_ptr collect_seq_con_info (observer_set & observer_set, const std::string & name) +std::unique_ptr collect_container_info (observer_set & observer_set, const std::string & name) { size_t count = 0; { @@ -219,15 +152,34 @@ std::unique_ptr collect_seq_con_info (observer_set } auto sizeof_element = sizeof (typename decltype (observer_set.observers)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "observers", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "observers", count, sizeof_element })); return composite; } void remove_all_files_in_dir (boost::filesystem::path const & dir); void move_all_files_to_dir (boost::filesystem::path const & from, boost::filesystem::path const & to); + +template +void transform_if (InputIt first, InputIt last, OutputIt dest, Pred pred, Func transform) +{ + while (first != last) + { + if (pred (*first)) + { + *dest++ = transform (*first); + } + + ++first; + } } -// Have our own async_write which we must use? -void release_assert_internal (bool check, const char * check_expr, const char * file, unsigned int line); -#define release_assert(check) release_assert_internal (check, #check, __FILE__, __LINE__) +/** Safe narrowing cast which silences warnings and asserts on data loss in debug builds. This is optimized away. */ +template +constexpr TARGET_TYPE narrow_cast (SOURCE_TYPE const & val) +{ + auto res (static_cast (val)); + debug_assert (val == static_cast (res)); + return res; +} +} diff --git a/nano/lib/walletconfig.cpp b/nano/lib/walletconfig.cpp index e3f91a6405..885bd54936 100644 --- a/nano/lib/walletconfig.cpp +++ b/nano/lib/walletconfig.cpp @@ -5,7 +5,7 @@ nano::wallet_config::wallet_config () { nano::random_pool::generate_block (wallet.bytes.data (), wallet.bytes.size ()); - assert (!wallet.is_zero ()); + debug_assert (!wallet.is_zero ()); } nano::error nano::wallet_config::parse (std::string const & wallet_a, std::string const & account_a) diff --git a/nano/lib/work.cpp b/nano/lib/work.cpp index d78e2aec36..8ef291a9db 100644 --- a/nano/lib/work.cpp +++ b/nano/lib/work.cpp @@ -1,27 +1,128 @@ #include #include +#include +#include #include #include #include -bool nano::work_validate (nano::root const & root_a, uint64_t work_a, uint64_t * difficulty_a) +std::string nano::to_string (nano::work_version const version_a) { - static nano::network_constants network_constants; - auto value (nano::work_value (root_a, work_a)); - if (difficulty_a != nullptr) + std::string result ("invalid"); + switch (version_a) + { + case nano::work_version::work_1: + result = "work_1"; + break; + case nano::work_version::unspecified: + result = "unspecified"; + break; + } + return result; +} + +bool nano::work_validate_entry (nano::block const & block_a) +{ + return block_a.difficulty () < nano::work_threshold_entry (block_a.work_version ()); +} + +bool nano::work_validate_entry (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) +{ + return nano::work_difficulty (version_a, root_a, work_a) < nano::work_threshold_entry (version_a); +} + +uint64_t nano::work_difficulty (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) +{ + uint64_t result{ 0 }; + switch (version_a) + { + case nano::work_version::work_1: + result = nano::work_v1::value (root_a, work_a); + break; + default: + debug_assert (false && "Invalid version specified to work_difficulty"); + } + return result; +} + +uint64_t nano::work_threshold_base (nano::work_version const version_a) +{ + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) { - *difficulty_a = value; + case nano::work_version::work_1: + result = nano::work_v1::threshold_base (); + break; + default: + debug_assert (false && "Invalid version specified to work_threshold_base"); } - return value < network_constants.publish_threshold; + return result; +} + +uint64_t nano::work_threshold_entry (nano::work_version const version_a) +{ + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) + { + case nano::work_version::work_1: + result = nano::work_v1::threshold_entry (); + break; + default: + debug_assert (false && "Invalid version specified to work_threshold_entry"); + } + return result; } -bool nano::work_validate (nano::block const & block_a, uint64_t * difficulty_a) +uint64_t nano::work_threshold (nano::work_version const version_a, nano::block_details const details_a) { - return work_validate (block_a.root (), block_a.block_work (), difficulty_a); + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) + { + case nano::work_version::work_1: + result = nano::work_v1::threshold (details_a); + break; + default: + debug_assert (false && "Invalid version specified to ledger work_threshold"); + } + return result; } -uint64_t nano::work_value (nano::root const & root_a, uint64_t work_a) +uint64_t nano::work_v1::threshold_base () +{ + static nano::network_constants network_constants; + return network_constants.publish_thresholds.base; +} + +uint64_t nano::work_v1::threshold_entry () +{ + static nano::network_constants network_constants; + return network_constants.publish_thresholds.entry; +} + +uint64_t nano::work_v1::threshold (nano::block_details const details_a) +{ + static_assert (nano::epoch::max == nano::epoch::epoch_2, "work_v1::threshold is ill-defined"); + static nano::network_constants network_constants; + + uint64_t result{ std::numeric_limits::max () }; + switch (details_a.epoch) + { + case nano::epoch::epoch_2: + result = (details_a.is_receive || details_a.is_epoch) ? network_constants.publish_thresholds.epoch_2_receive : network_constants.publish_thresholds.epoch_2; + break; + case nano::epoch::epoch_1: + case nano::epoch::epoch_0: + result = network_constants.publish_thresholds.epoch_1; + break; + default: + debug_assert (false && "Invalid epoch specified to work_v1 ledger work_threshold"); + } + return result; +} + +#ifndef NANO_FUZZER_TEST +uint64_t nano::work_v1::value (nano::root const & root_a, uint64_t work_a) { uint64_t result; blake2b_state hash; @@ -31,8 +132,65 @@ uint64_t nano::work_value (nano::root const & root_a, uint64_t work_a) blake2b_final (&hash, reinterpret_cast (&result), sizeof (result)); return result; } +#else +uint64_t nano::work_v1::value (nano::root const & root_a, uint64_t work_a) +{ + static nano::network_constants network_constants; + if (!network_constants.is_test_network ()) + { + debug_assert (false); + std::exit (1); + } + return network_constants.publish_thresholds.base + 1; +} +#endif -nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function (nano::root const &, uint64_t, std::atomic &)> opencl_a) : +double nano::normalized_multiplier (double const multiplier_a, uint64_t const threshold_a) +{ + static nano::network_constants network_constants; + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + /* Normalization rules + ratio = multiplier of max work threshold (send epoch 2) from given threshold + i.e. max = 0xfe00000000000000, given = 0xf000000000000000, ratio = 8.0 + normalized = (multiplier + (ratio - 1)) / ratio; + Epoch 1 + multiplier | normalized + 1.0 | 1.0 + 9.0 | 2.0 + 25.0 | 4.0 + Epoch 2 (receive / epoch subtypes) + multiplier | normalized + 1.0 | 1.0 + 65.0 | 2.0 + 241.0 | 4.0 + */ + if (threshold_a == network_constants.publish_thresholds.epoch_1 || threshold_a == network_constants.publish_thresholds.epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (network_constants.publish_thresholds.epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = (multiplier + (ratio - 1.0)) / ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + +double nano::denormalized_multiplier (double const multiplier_a, uint64_t const threshold_a) +{ + static nano::network_constants network_constants; + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + if (threshold_a == network_constants.publish_thresholds.epoch_1 || threshold_a == network_constants.publish_thresholds.epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (network_constants.publish_thresholds.epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = multiplier * ratio + 1.0 - ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + +nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> opencl_a) : ticket (0), done (false), pow_rate_limiter (pow_rate_limiter_a), @@ -49,12 +207,11 @@ opencl (opencl_a) } for (auto i (0u); i < count; ++i) { - auto thread (boost::thread (attrs, [this, i]() { + threads.emplace_back (attrs, [this, i]() { nano::thread_role::set (nano::thread_role::name::work); nano::work_thread_reprioritize (); loop (i); - })); - threads.push_back (std::move (thread)); + }); } } @@ -95,12 +252,12 @@ void nano::work_pool::loop (uint64_t thread) boost::optional opt_work; if (thread == 0 && opencl) { - opt_work = opencl (current_l.item, current_l.difficulty, ticket); + opt_work = opencl (current_l.version, current_l.item, current_l.difficulty, ticket); } if (opt_work.is_initialized ()) { work = *opt_work; - output = work_value (current_l.item, work); + output = nano::work_v1::value (current_l.item, work); } else { @@ -132,8 +289,8 @@ void nano::work_pool::loop (uint64_t thread) if (ticket == ticket_l) { // If the ticket matches what we started with, we're the ones that found the solution - assert (output >= current_l.difficulty); - assert (current_l.difficulty == 0 || work_value (current_l.item, work) == output); + debug_assert (output >= current_l.difficulty); + debug_assert (current_l.difficulty == 0 || nano::work_v1::value (current_l.item, work) == output); // Signal other threads to stop their work next time they check ticket ++ticket; pending.pop_front (); @@ -191,19 +348,14 @@ void nano::work_pool::stop () producer_condition.notify_all (); } -void nano::work_pool::generate (nano::root const & root_a, std::function const &)> callback_a) -{ - generate (root_a, callback_a, network_constants.publish_threshold); -} - -void nano::work_pool::generate (nano::root const & root_a, std::function const &)> callback_a, uint64_t difficulty_a) +void nano::work_pool::generate (nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::function const &)> callback_a) { - assert (!root_a.is_zero ()); + debug_assert (!root_a.is_zero ()); if (!threads.empty ()) { { nano::lock_guard lock (mutex); - pending.push_back ({ root_a, callback_a, difficulty_a }); + pending.emplace_back (version_a, root_a, difficulty_a, callback_a); } producer_condition.notify_all (); } @@ -215,22 +367,28 @@ void nano::work_pool::generate (nano::root const & root_a, std::function nano::work_pool::generate (nano::root const & root_a) { - return generate (root_a, network_constants.publish_threshold); + static nano::network_constants network_constants; + debug_assert (network_constants.is_test_network ()); + return generate (nano::work_version::work_1, root_a, network_constants.publish_thresholds.base); } boost::optional nano::work_pool::generate (nano::root const & root_a, uint64_t difficulty_a) +{ + static nano::network_constants network_constants; + debug_assert (network_constants.is_test_network ()); + return generate (nano::work_version::work_1, root_a, difficulty_a); +} + +boost::optional nano::work_pool::generate (nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a) { boost::optional result; if (!threads.empty ()) { std::promise> work; std::future> future = work.get_future (); - // clang-format off - generate (root_a, [&work](boost::optional work_a) { + generate (version_a, root_a, difficulty_a, [&work](boost::optional work_a) { work.set_value (work_a); - }, - difficulty_a); - // clang-format on + }); result = future.get ().value (); } return result; @@ -242,20 +400,16 @@ size_t nano::work_pool::size () return pending.size (); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (work_pool & work_pool, const std::string & name) +std::unique_ptr nano::collect_container_info (work_pool & work_pool, const std::string & name) { - auto composite = std::make_unique (name); - - size_t count = 0; + size_t count; { nano::lock_guard guard (work_pool.mutex); count = work_pool.pending.size (); } auto sizeof_element = sizeof (decltype (work_pool.pending)::value_type); - composite->add_component (std::make_unique (seq_con_info{ "pending", count, sizeof_element })); - composite->add_component (collect_seq_con_info (work_pool.work_observers, "work_observers")); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "pending", count, sizeof_element })); + composite->add_component (collect_container_info (work_pool.work_observers, "work_observers")); return composite; } -} diff --git a/nano/lib/work.hpp b/nano/lib/work.hpp index 7c61bda455..f65261cfa3 100644 --- a/nano/lib/work.hpp +++ b/nano/lib/work.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -8,39 +9,63 @@ #include #include -#include #include -#include namespace nano { +enum class work_version +{ + unspecified, + work_1 +}; +std::string to_string (nano::work_version const version_a); + class block; -bool work_validate (nano::root const &, uint64_t, uint64_t * = nullptr); -bool work_validate (nano::block const &, uint64_t * = nullptr); -uint64_t work_value (nano::root const &, uint64_t); +class block_details; +bool work_validate_entry (nano::block const &); +bool work_validate_entry (nano::work_version const, nano::root const &, uint64_t const); + +uint64_t work_difficulty (nano::work_version const, nano::root const &, uint64_t const); + +uint64_t work_threshold_base (nano::work_version const); +uint64_t work_threshold_entry (nano::work_version const); +// Ledger threshold +uint64_t work_threshold (nano::work_version const, nano::block_details const); + +namespace work_v1 +{ + uint64_t value (nano::root const & root_a, uint64_t work_a); + uint64_t threshold_base (); + uint64_t threshold_entry (); + uint64_t threshold (nano::block_details const); +} + +double normalized_multiplier (double const, uint64_t const); +double denormalized_multiplier (double const, uint64_t const); class opencl_work; class work_item final { public: - work_item (nano::root const & item_a, std::function const &)> const & callback_a, uint64_t difficulty_a) : - item (item_a), callback (callback_a), difficulty (difficulty_a) + work_item (nano::work_version const version_a, nano::root const & item_a, uint64_t difficulty_a, std::function const &)> const & callback_a) : + version (version_a), item (item_a), difficulty (difficulty_a), callback (callback_a) { } - - nano::root item; - std::function const &)> callback; - uint64_t difficulty; + nano::work_version const version; + nano::root const item; + uint64_t const difficulty; + std::function const &)> const callback; }; class work_pool final { public: - work_pool (unsigned, std::chrono::nanoseconds = std::chrono::nanoseconds (0), std::function (nano::root const &, uint64_t, std::atomic &)> = nullptr); + work_pool (unsigned, std::chrono::nanoseconds = std::chrono::nanoseconds (0), std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> = nullptr); ~work_pool (); void loop (uint64_t); void stop (); void cancel (nano::root const &); - void generate (nano::root const &, std::function const &)>); - void generate (nano::root const &, std::function const &)>, uint64_t); + void generate (nano::work_version const, nano::root const &, uint64_t, std::function const &)>); + boost::optional generate (nano::work_version const, nano::root const &, uint64_t); + // For tests only boost::optional generate (nano::root const &); boost::optional generate (nano::root const &, uint64_t); size_t size (); @@ -52,9 +77,9 @@ class work_pool final std::mutex mutex; nano::condition_variable producer_condition; std::chrono::nanoseconds pow_rate_limiter; - std::function (nano::root const &, uint64_t, std::atomic &)> opencl; + std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> opencl; nano::observer_set work_observers; }; -std::unique_ptr collect_seq_con_info (work_pool & work_pool, const std::string & name); +std::unique_ptr collect_container_info (work_pool & work_pool, const std::string & name); } diff --git a/nano/lib/worker.cpp b/nano/lib/worker.cpp new file mode 100644 index 0000000000..b8974d6a41 --- /dev/null +++ b/nano/lib/worker.cpp @@ -0,0 +1,78 @@ +#include +#include + +nano::worker::worker () : +thread ([this]() { + nano::thread_role::set (nano::thread_role::name::worker); + this->run (); +}) +{ +} + +void nano::worker::run () +{ + nano::unique_lock lk (mutex); + while (!stopped) + { + if (!queue.empty ()) + { + auto func = queue.front (); + queue.pop_front (); + lk.unlock (); + func (); + // So that we reduce locking for anything being pushed as that will + // most likely be on an io-thread + std::this_thread::yield (); + lk.lock (); + } + else + { + cv.wait (lk); + } + } +} + +nano::worker::~worker () +{ + stop (); +} + +void nano::worker::push_task (std::function func_a) +{ + { + nano::lock_guard guard (mutex); + if (!stopped) + { + queue.emplace_back (func_a); + } + } + + cv.notify_one (); +} + +void nano::worker::stop () +{ + { + nano::unique_lock lk (mutex); + stopped = true; + queue.clear (); + } + cv.notify_one (); + if (thread.joinable ()) + { + thread.join (); + } +} + +std::unique_ptr nano::collect_container_info (nano::worker & worker, const std::string & name) +{ + size_t count; + { + nano::lock_guard guard (worker.mutex); + count = worker.queue.size (); + } + auto sizeof_element = sizeof (decltype (worker.queue)::value_type); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (nano::container_info{ "queue", count, sizeof_element })); + return composite; +} diff --git a/nano/lib/worker.hpp b/nano/lib/worker.hpp new file mode 100644 index 0000000000..e9a8d5898e --- /dev/null +++ b/nano/lib/worker.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace nano +{ +class worker final +{ +public: + worker (); + ~worker (); + void run (); + void push_task (std::function func); + void stop (); + +private: + nano::condition_variable cv; + std::deque> queue; + std::mutex mutex; + bool stopped{ false }; + std::thread thread; + + friend std::unique_ptr collect_container_info (worker &, const std::string &); +}; + +std::unique_ptr collect_container_info (worker & worker, const std::string & name); +} \ No newline at end of file diff --git a/nano/load_test/entry.cpp b/nano/load_test/entry.cpp index 47247708fa..8299d7bbda 100644 --- a/nano/load_test/entry.cpp +++ b/nano/load_test/entry.cpp @@ -1,7 +1,11 @@ -#include -#include -#include +#include +#include +#include +#include +#include +#define IGNORE_GTEST_INCL #include +#include #include #include #include @@ -9,8 +13,10 @@ #include #include +#include #include +#include #include #include @@ -248,37 +254,93 @@ class send_session final : public std::enable_shared_from_this tcp::resolver::results_type const & results; }; -boost::property_tree::ptree rpc_request (boost::property_tree::ptree const & request, boost::asio::io_context & ioc, tcp::resolver::results_type const & results) +class rpc_session final : public std::enable_shared_from_this { - tcp::socket socket{ ioc }; - boost::asio::connect (socket, results.begin (), results.end ()); +public: + rpc_session (boost::property_tree::ptree const & request, boost::asio::io_context & ioc, tcp::resolver::results_type const & results, std::function callback) : + io_ctx (ioc), + socket (ioc), + request (request), + results (results), + callback (callback) + { + } - std::stringstream ostream; - boost::property_tree::write_json (ostream, request); - auto request_string = ostream.str (); + void run () + { + auto this_l (shared_from_this ()); + boost::asio::async_connect (this_l->socket, this_l->results.cbegin (), this_l->results.cend (), [this_l](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator) { + if (ec) + { + fail (ec, "connect"); + } - http::request req{ http::verb::post, "/", 11, request_string }; - req.prepare_payload (); + std::stringstream ostream; + boost::property_tree::write_json (ostream, this_l->request); - http::write (socket, req); - boost::beast::flat_buffer buffer; + this_l->req.method (http::verb::post); + this_l->req.version (11); + this_l->req.target ("/"); + this_l->req.body () = ostream.str (); + this_l->req.prepare_payload (); + + http::async_write (this_l->socket, this_l->req, [this_l](boost::system::error_code ec, std::size_t) { + if (ec) + { + fail (ec, "write"); + } + + http::async_read (this_l->socket, this_l->buffer, this_l->res, [this_l](boost::system::error_code ec, std::size_t) { + if (ec) + { + fail (ec, "read"); + } - http::response res; - http::read (socket, buffer, res); + boost::property_tree::ptree json; + std::stringstream body (this_l->res.body ()); + boost::property_tree::read_json (body, json); - boost::property_tree::ptree json; - std::stringstream body (res.body ()); - boost::property_tree::read_json (body, json); + this_l->socket.shutdown (tcp::socket::shutdown_both, ec); + if (ec && ec != boost::system::errc::not_connected) + { + fail (ec, "shutdown"); + } + else + { + return this_l->callback (json); + } + }); + }); + }); + } - // Gracefully close the socket - boost::system::error_code ec; - socket.shutdown (tcp::socket::shutdown_both, ec); +private: + boost::asio::io_context & io_ctx; + socket_type socket; + boost::beast::flat_buffer buffer; + http::request req; + http::response res; + boost::property_tree::ptree request; + tcp::resolver::results_type const & results; + std::function callback; +}; - if (ec && ec != boost::system::errc::not_connected) +boost::property_tree::ptree rpc_request (boost::property_tree::ptree const & request, boost::asio::io_context & ioc, tcp::resolver::results_type const & results) +{ + debug_assert (results.size () == 1); + std::promise> promise; + auto rpc_session (std::make_shared (request, ioc, results, [&promise](auto const & response_a) { + promise.set_value (response_a); + })); + rpc_session->run (); + auto future = promise.get_future (); + if (future.wait_for (std::chrono::seconds (5)) != std::future_status::ready) { - throw boost::system::system_error{ ec }; + throw std::runtime_error ("RPC request timed out"); } - return json; + auto response = future.get (); + debug_assert (response.is_initialized ()); + return response.value_or (decltype (response)::argument_type{}); } void keepalive_rpc (boost::asio::io_context & ioc, tcp::resolver::results_type const & results, uint16_t port) @@ -407,6 +469,11 @@ int main (int argc, char * const * argv) } node_path = node_filepath.string (); } + if (!boost::filesystem::exists (node_path)) + { + std::cerr << "nano_node executable could not be found in " << node_path << std::endl; + return 1; + } auto rpc_path_it (vm.find ("rpc_path")); std::string rpc_path; @@ -423,6 +490,11 @@ int main (int argc, char * const * argv) } rpc_path = rpc_filepath.string (); } + if (!boost::filesystem::exists (rpc_path)) + { + std::cerr << "nano_rpc executable could not be found in " << rpc_path << std::endl; + return 1; + } std::vector data_paths; for (auto i = 0; i < node_count; ++i) @@ -446,7 +518,7 @@ int main (int argc, char * const * argv) #else std::thread processes_thread ([&data_paths, &node_path, &rpc_path, ¤t_network]() { auto formatted_command = "%1% --daemon --data_path=%2% --network=%3% %4%"; - assert (!data_paths.empty ()); + ASSERT_TRUE (!data_paths.empty ()); for (int i = 0; i < data_paths.size (); ++i) { auto node_exe_command = boost::str (boost::format (formatted_command) % node_path % data_paths[i].string () % current_network % "&"); @@ -470,7 +542,7 @@ int main (int argc, char * const * argv) boost::asio::io_context ioc; - assert (!nano::signal_handler_impl); + debug_assert (!nano::signal_handler_impl); nano::signal_handler_impl = [&ioc]() { ioc.stop (); }; @@ -481,35 +553,35 @@ int main (int argc, char * const * argv) tcp::resolver resolver{ ioc }; auto const primary_node_results = resolver.resolve ("::1", std::to_string (rpc_port_start)); - for (int i = 0; i < node_count; ++i) - { - keepalive_rpc (ioc, primary_node_results, peering_port_start + i); - } + std::thread t ([send_count, &ioc, &primary_node_results, &resolver, &node_count, &destination_count]() { + for (int i = 0; i < node_count; ++i) + { + keepalive_rpc (ioc, primary_node_results, peering_port_start + i); + } - std::cout << "Beginning tests" << std::endl; + std::cout << "Beginning tests" << std::endl; - // Create keys - std::vector destination_accounts; - for (int i = 0; i < destination_count; ++i) - { - destination_accounts.emplace_back (key_create_rpc (ioc, primary_node_results)); - } + // Create keys + std::vector destination_accounts; + for (int i = 0; i < destination_count; ++i) + { + destination_accounts.emplace_back (key_create_rpc (ioc, primary_node_results)); + } - // Create wallet - std::string wallet = wallet_create_rpc (ioc, primary_node_results); + // Create wallet + std::string wallet = wallet_create_rpc (ioc, primary_node_results); - // Add genesis account to it - wallet_add_rpc (ioc, primary_node_results, wallet, nano::test_genesis_key.prv.data.to_string ()); + // Add genesis account to it + wallet_add_rpc (ioc, primary_node_results, wallet, nano::test_genesis_key.prv.data.to_string ()); - // Add destination accounts - for (auto & account : destination_accounts) - { - wallet_add_rpc (ioc, primary_node_results, wallet, account.private_key); - } + // Add destination accounts + for (auto & account : destination_accounts) + { + wallet_add_rpc (ioc, primary_node_results, wallet, account.private_key); + } - std::cout << "\rPrimary node processing transactions: 00%"; + std::cout << "\rPrimary node processing transactions: 00%"; - std::thread t ([send_count, &destination_accounts, &ioc, &primary_node_results, &wallet, &resolver, &node_count]() { std::random_device rd; std::mt19937 mt (rd ()); std::uniform_int_distribution dist (0, destination_accounts.size () - 1); diff --git a/nano/nano_node/CMakeLists.txt b/nano/nano_node/CMakeLists.txt index 0c276eccb8..ed9782748c 100644 --- a/nano/nano_node/CMakeLists.txt +++ b/nano/nano_node/CMakeLists.txt @@ -33,7 +33,12 @@ add_custom_command(TARGET nano_node COMMAND nano_node --generate_config rpc > ${PROJECT_BINARY_DIR}/config-rpc.toml.sample) if ((NANO_GUI OR RAIBLOCKS_GUI) AND NOT APPLE) - install(TARGETS nano_node - RUNTIME DESTINATION . - ) + if (WIN32) + install(TARGETS nano_node + RUNTIME DESTINATION . + ) + else () + install(TARGETS nano_node + RUNTIME DESTINATION ./bin) + endif() endif() diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index 0e2b3f3d86..e3deaaa65c 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -1,18 +1,16 @@ -#include +#include +#include #include #include #include -#include +#include #include #include #include #include #include -#include - #include -#include #include namespace @@ -49,23 +47,31 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: nano::logger_mt logger{ config.node.logging.min_time_between_log_output }; boost::asio::io_context io_ctx; auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, logger)); - nano::work_pool opencl_work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::root const & root_a, uint64_t difficulty_a, std::atomic & ticket_a) { - return opencl->generate_work (root_a, difficulty_a, ticket_a); + nano::work_pool opencl_work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::atomic & ticket_a) { + return opencl->generate_work (version_a, root_a, difficulty_a, ticket_a); } - : std::function (nano::root const &, uint64_t, std::atomic &)> (nullptr)); + : std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> (nullptr)); nano::alarm alarm (io_ctx); try { + // This avoid a blank prompt during any node initialization delays + auto initialization_text = "Starting up Nano node..."; + std::cout << initialization_text << std::endl; + logger.always_log (initialization_text); + auto node (std::make_shared (io_ctx, data_path, alarm, config.node, opencl_work, flags)); if (!node->init_error ()) { - auto database_backend = dynamic_cast (node->store_impl.get ()) ? "LMDB" : "RocksDB"; auto network_label = node->network_params.network.get_current_network_as_string (); std::cout << "Network: " << network_label << ", version: " << NANO_VERSION_STRING << "\n" << "Path: " << node->application_path.string () << "\n" << "Build Info: " << BUILD_INFO << "\n" - << "Database backend: " << database_backend << std::endl; - + << "Database backend: " << node->store.vendor_get () << std::endl; + auto voting (node->wallets.reps ().voting); + if (voting > 1) + { + std::cout << "Voting with more than one representative can limit performance: " << voting << " representatives are configured" << std::endl; + } node->start (); nano::ipc::ipc_server ipc_server (*node, config.rpc); #if BOOST_PROCESS_SUPPORTED @@ -73,7 +79,7 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: std::unique_ptr nano_pow_server_process; #endif - if (config.pow_server.enable) + /*if (config.pow_server.enable) { if (!boost::filesystem::exists (config.pow_server.pow_server_path)) { @@ -82,13 +88,12 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: } #if BOOST_PROCESS_SUPPORTED - auto network = node->network_params.network.get_current_network_as_string (); nano_pow_server_process = std::make_unique (config.pow_server.pow_server_path, "--config_path", data_path / "config-nano-pow-server.toml"); #else std::cerr << "nano_pow_server is configured to start as a child process, but this is not supported on this system. Disable startup and start the server manually." << std::endl; std::exit (1); #endif - } + }*/ std::unique_ptr rpc_process_thread; std::unique_ptr rpc; @@ -105,7 +110,7 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: std::cout << error.get_message () << std::endl; std::exit (1); } - rpc_handler = std::make_unique (*node, config.rpc, [&ipc_server, &alarm, &io_ctx]() { + rpc_handler = std::make_unique (*node, ipc_server, config.rpc, [&ipc_server, &alarm, &io_ctx]() { ipc_server.stop (); alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (3), [&io_ctx]() { io_ctx.stop (); @@ -127,18 +132,16 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: rpc_process = std::make_unique (config.rpc.child_process.rpc_path, "--daemon", "--data_path", data_path, "--network", network); #else auto rpc_exe_command = boost::str (boost::format ("%1% --daemon --data_path=%2% --network=%3%") % config.rpc.child_process.rpc_path % data_path % network); - // clang-format off rpc_process_thread = std::make_unique ([rpc_exe_command, &logger = node->logger]() { nano::thread_role::set (nano::thread_role::name::rpc_process_container); std::system (rpc_exe_command.c_str ()); logger.always_log ("RPC server has stopped"); }); - // clang-format on #endif } } - assert (!nano::signal_handler_impl); + debug_assert (!nano::signal_handler_impl); nano::signal_handler_impl = [&io_ctx]() { io_ctx.stop (); sig_int_or_term = 1; diff --git a/nano/nano_node/daemon.hpp b/nano/nano_node/daemon.hpp index 4301f7c376..acb09e85b6 100644 --- a/nano/nano_node/daemon.hpp +++ b/nano/nano_node/daemon.hpp @@ -1,4 +1,10 @@ -#include +namespace boost +{ +namespace filesystem +{ + class path; +} +} namespace nano { diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 5182ff9a1b..0b2f8649fc 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -2,15 +2,20 @@ #include #include #include -#include +#include +#include #include #include -#include #include +#include +#include +#include #include #include +#include +#include #include #include @@ -34,6 +39,27 @@ #endif #endif +namespace +{ +class uint64_from_hex // For use with boost::lexical_cast to read hexadecimal strings +{ +public: + uint64_t value; +}; +std::istream & operator>> (std::istream & in, uint64_from_hex & out_val); + +class address_library_pair +{ +public: + uint64_t address; + std::string library; + + address_library_pair (uint64_t address, std::string library); + bool operator< (const address_library_pair & other) const; + bool operator== (const address_library_pair & other) const; +}; +} + int main (int argc, char * const * argv) { nano::set_umask (); @@ -45,18 +71,20 @@ int main (int argc, char * const * argv) ("version", "Prints out version") ("config", boost::program_options::value>()->multitoken(), "Pass node configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.") ("daemon", "Start node daemon") + ("compare_rep_weights", "Display a summarized comparison between the hardcoded bootstrap weights and representative weights from the ledger. Full comparison is output to logs") ("debug_block_count", "Display the number of block") ("debug_bootstrap_generate", "Generate bootstrap sequence of blocks") ("debug_dump_frontier_unchecked_dependents", "Dump frontiers which have matching unchecked keys") ("debug_dump_online_weight", "Dump online_weights table") ("debug_dump_representatives", "List representatives and weights") ("debug_account_count", "Display the number of accounts") - ("debug_mass_activity", "Generates fake debug activity") + ("debug_mass_activity", "(Deprecated) Generates fake debug activity. Can use slow_test's generate_mass_activity test for the same behavior.") ("debug_profile_generate", "Profile work generation") ("debug_profile_validate", "Profile work validation") ("debug_opencl", "OpenCL work generation") ("debug_profile_kdf", "Profile kdf function") ("debug_output_last_backtrace_dump", "Displays the contents of the latest backtrace in the event of a nano_node crash") + ("debug_generate_crash_report", "Consolidates the nano_node_backtrace.dump file. Requires addr2line installed on Linux") ("debug_sys_logging", "Test the system logger") ("debug_verify_profile", "Profile signature verification") ("debug_verify_profile_batch", "Profile batch signature verification") @@ -64,18 +92,23 @@ int main (int argc, char * const * argv) ("debug_profile_sign", "Profile signature generation") ("debug_profile_process", "Profile active blocks processing (only for nano_test_network)") ("debug_profile_votes", "Profile votes processing (only for nano_test_network)") + ("debug_profile_frontiers_confirmation", "Profile frontiers confirmation speed (only for nano_test_network)") ("debug_random_feed", "Generates output to RNG test suites") ("debug_rpc", "Read an RPC command from stdin and invoke it. Network operations will have no effect.") - ("debug_validate_blocks", "Check all blocks for correct hash, signature, work value") ("debug_peers", "Display peer IPv6:port connections") ("debug_cemented_block_count", "Displays the number of cemented (confirmed) blocks") ("debug_stacktrace", "Display an example stacktrace") ("debug_account_versions", "Display the total counts of each version for all accounts (including unpocketed)") + ("validate_blocks,debug_validate_blocks", "Check all blocks for correct hash, signature, work value") ("platform", boost::program_options::value (), "Defines the for OpenCL commands") ("device", boost::program_options::value (), "Defines for OpenCL command") - ("threads", boost::program_options::value (), "Defines count for OpenCL command") + ("threads", boost::program_options::value (), "Defines count for various commands") ("difficulty", boost::program_options::value (), "Defines for OpenCL command, HEX") - ("pow_sleep_interval", boost::program_options::value (), "Defines the amount to sleep inbetween each pow calculation attempt"); + ("multiplier", boost::program_options::value (), "Defines for work generation. Overrides ") + ("count", boost::program_options::value (), "Defines for various commands") + ("pow_sleep_interval", boost::program_options::value (), "Defines the amount to sleep inbetween each pow calculation attempt") + ("address_column", boost::program_options::value (), "Defines which column the addresses are located, 0 indexed (check --debug_output_last_backtrace_dump output)") + ("silent", "Silent command execution"); // clang-format on nano::add_node_options (description); nano::add_node_flag_options (description); @@ -98,7 +131,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - std::cerr << err.get_message () << std::endl; + std::cerr << nano::network_constants::active_network_err_msg << std::endl; std::exit (1); } } @@ -126,21 +159,162 @@ int main (int argc, char * const * argv) auto flags_ec = nano::update_flags (flags, vm); if (flags_ec) { - std::cerr << flags_ec.message (); + std::cerr << flags_ec.message () << std::endl; std::exit (1); } - auto config (vm.find ("config")); - if (config != vm.end ()) + daemon.run (data_path, flags); + } + else if (vm.count ("compare_rep_weights")) + { + if (!nano::network_constants ().is_test_network ()) { - flags.config_overrides = config->second.as> (); + auto node_flags = nano::inactive_node_flag_defaults (); + nano::update_flags (node_flags, vm); + node_flags.generate_cache.reps = true; + nano::inactive_node inactive_node (data_path, node_flags); + auto node = inactive_node.node; + + auto const hardcoded = node->get_bootstrap_weights ().second; + auto const ledger_unfiltered = node->ledger.cache.rep_weights.get_rep_amounts (); + + auto get_total = [](decltype (hardcoded) const & reps) -> nano::uint128_union { + return std::accumulate (reps.begin (), reps.end (), nano::uint128_t{ 0 }, [](auto sum, auto const & rep) { return sum + rep.second; }); + }; + + // Hardcoded weights are filtered to a cummulative weight of 99%, need to do the same for ledger weights + std::remove_const_t ledger; + { + std::vector> sorted; + sorted.reserve (ledger_unfiltered.size ()); + std::copy (ledger_unfiltered.begin (), ledger_unfiltered.end (), std::back_inserter (sorted)); + std::sort (sorted.begin (), sorted.end (), [](auto const & left, auto const & right) { return left.second > right.second; }); + auto const total_unfiltered = get_total (ledger_unfiltered); + nano::uint128_t sum{ 0 }; + auto target = (total_unfiltered.number () / 100) * 99; + for (auto i (sorted.begin ()), n (sorted.end ()); i != n && sum <= target; sum += i->second, ++i) + { + ledger.insert (*i); + } + } + + auto const total_ledger = get_total (ledger); + auto const total_hardcoded = get_total (hardcoded); + + struct mismatched_t + { + nano::account rep; + nano::uint128_union hardcoded; + nano::uint128_union ledger; + nano::uint128_union diff; + std::string get_entry () const + { + return boost::str (boost::format ("representative %1% hardcoded %2% ledger %3% mismatch %4%") + % rep.to_account () % hardcoded.format_balance (nano::Mxrb_ratio, 0, true) % ledger.format_balance (nano::Mxrb_ratio, 0, true) % diff.format_balance (nano::Mxrb_ratio, 0, true)); + } + }; + + std::vector mismatched; + mismatched.reserve (hardcoded.size ()); + std::transform (hardcoded.begin (), hardcoded.end (), std::back_inserter (mismatched), [&ledger](auto const & rep) { + auto ledger_rep (ledger.find (rep.first)); + nano::uint128_t ledger_weight = (ledger_rep == ledger.end () ? 0 : ledger_rep->second); + auto absolute = ledger_weight > rep.second ? ledger_weight - rep.second : rep.second - ledger_weight; + return mismatched_t{ rep.first, rep.second, ledger_weight, absolute }; + }); + + // Sort by descending difference + std::sort (mismatched.begin (), mismatched.end (), [](mismatched_t const & left, mismatched_t const & right) { return left.diff > right.diff; }); + + nano::uint128_union const mismatch_total = std::accumulate (mismatched.begin (), mismatched.end (), nano::uint128_t{ 0 }, [](auto sum, mismatched_t const & sample) { return sum + sample.diff.number (); }); + nano::uint128_union const mismatch_mean = mismatch_total.number () / mismatched.size (); + + nano::uint512_union mismatch_variance = std::accumulate (mismatched.begin (), mismatched.end (), nano::uint512_t (0), [M = mismatch_mean.number (), N = mismatched.size ()](nano::uint512_t sum, mismatched_t const & sample) { + auto x = sample.diff.number (); + nano::uint512_t const mean_diff = x > M ? x - M : M - x; + nano::uint512_t const sqr = mean_diff * mean_diff; + return sum + sqr; + }) + / mismatched.size (); + + nano::uint128_union const mismatch_stddev = nano::narrow_cast (boost::multiprecision::sqrt (mismatch_variance.number ())); + + auto const outlier_threshold = std::max (nano::Gxrb_ratio, mismatch_mean.number () + 1 * mismatch_stddev.number ()); + decltype (mismatched) outliers; + std::copy_if (mismatched.begin (), mismatched.end (), std::back_inserter (outliers), [outlier_threshold](mismatched_t const & sample) { + return sample.diff > outlier_threshold; + }); + + auto const newcomer_threshold = std::max (nano::Gxrb_ratio, mismatch_mean.number ()); + std::vector> newcomers; + std::copy_if (ledger.begin (), ledger.end (), std::back_inserter (newcomers), [&hardcoded](auto const & rep) { + return !hardcoded.count (rep.first) && rep.second; + }); + + // Sort by descending weight + std::sort (newcomers.begin (), newcomers.end (), [](auto const & left, auto const & right) { return left.second > right.second; }); + + auto newcomer_entry = [](auto const & rep) { + return boost::str (boost::format ("representative %1% hardcoded --- ledger %2%") % rep.first.to_account () % nano::uint128_union (rep.second).format_balance (nano::Mxrb_ratio, 0, true)); + }; + + std::cout << boost::str (boost::format ("hardcoded weight %1% Mnano\nledger weight %2% Mnano\nmismatched\n\tsamples %3%\n\ttotal %4% Mnano\n\tmean %5% Mnano\n\tsigma %6% Mnano\n") + % total_hardcoded.format_balance (nano::Mxrb_ratio, 0, true) + % total_ledger.format_balance (nano::Mxrb_ratio, 0, true) + % mismatched.size () + % mismatch_total.format_balance (nano::Mxrb_ratio, 0, true) + % mismatch_mean.format_balance (nano::Mxrb_ratio, 0, true) + % mismatch_stddev.format_balance (nano::Mxrb_ratio, 0, true)); + + if (!outliers.empty ()) + { + std::cout << "outliers\n"; + for (auto const & outlier : outliers) + { + std::cout << '\t' << outlier.get_entry () << '\n'; + } + } + + if (!newcomers.empty ()) + { + std::cout << "newcomers\n"; + for (auto const & newcomer : newcomers) + { + if (newcomer.second > newcomer_threshold) + { + std::cout << '\t' << newcomer_entry (newcomer) << '\n'; + } + } + } + + // Log more data + auto const log_threshold = nano::Gxrb_ratio; + for (auto const & sample : mismatched) + { + if (sample.diff > log_threshold) + { + node->logger.always_log (sample.get_entry ()); + } + } + for (auto const & newcomer : newcomers) + { + if (newcomer.second > log_threshold) + { + node->logger.always_log (newcomer_entry (newcomer)); + } + } + } + else + { + std::cout << "Not available for the test network" << std::endl; + result = -1; } - daemon.run (data_path, flags); } else if (vm.count ("debug_block_count")) { - nano::inactive_node node (data_path); - auto transaction (node.node->store.tx_begin_read ()); - std::cout << boost::str (boost::format ("Block count: %1%\n") % node.node->store.block_count (transaction).sum ()); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto transaction (node->store.tx_begin_read ()); + std::cout << boost::str (boost::format ("Block count: %1%\n") % node->store.block_count (transaction).sum ()); } else if (vm.count ("debug_bootstrap_generate")) { @@ -166,8 +340,9 @@ int main (int argc, char * const * argv) << "Public: " << rep.pub.to_string () << "\n" << "Account: " << rep.pub.to_account () << "\n"; } + nano::network_constants network_constants; nano::uint128_t balance (std::numeric_limits::max ()); - nano::open_block genesis_block (reinterpret_cast (genesis.pub), genesis.pub, genesis.pub, genesis.prv, genesis.pub, *work.generate (genesis.pub)); + nano::open_block genesis_block (reinterpret_cast (genesis.pub), genesis.pub, genesis.pub, genesis.prv, genesis.pub, *work.generate (nano::work_version::work_1, genesis.pub, network_constants.publish_thresholds.epoch_1)); std::cout << genesis_block.to_json (); std::cout.flush (); nano::block_hash previous (genesis_block.hash ()); @@ -177,9 +352,9 @@ int main (int argc, char * const * argv) auto weekly_distribution (yearly_distribution / 52); for (auto j (0); j != 52; ++j) { - assert (balance > weekly_distribution); + debug_assert (balance > weekly_distribution); balance = balance < (weekly_distribution * 2) ? 0 : balance - weekly_distribution; - nano::send_block send (previous, landing.pub, balance, genesis.prv, genesis.pub, *work.generate (previous)); + nano::send_block send (previous, landing.pub, balance, genesis.prv, genesis.pub, *work.generate (nano::work_version::work_1, previous, network_constants.publish_thresholds.epoch_1)); previous = send.hash (); std::cout << send.to_json (); std::cout.flush (); @@ -200,11 +375,12 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_dump_online_weight")) { - nano::inactive_node node (data_path); - auto current (node.node->online_reps.online_stake ()); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto current (node->online_reps.online_stake ()); std::cout << boost::str (boost::format ("Online Weight %1%\n") % current); - auto transaction (node.node->store.tx_begin_read ()); - for (auto i (node.node->store.online_weight_begin (transaction)), n (node.node->store.online_weight_end ()); i != n; ++i) + auto transaction (node->store.tx_begin_read ()); + for (auto i (node->store.online_weight_begin (transaction)), n (node->store.online_weight_end ()); i != n; ++i) { using time_point = std::chrono::system_clock::time_point; time_point ts (std::chrono::duration_cast (std::chrono::nanoseconds (i->first))); @@ -217,11 +393,13 @@ int main (int argc, char * const * argv) else if (vm.count ("debug_dump_representatives")) { auto node_flags = nano::inactive_node_flag_defaults (); - node_flags.cache_representative_weights_from_frontiers = true; - nano::inactive_node node (data_path, 24000, node_flags); - auto transaction (node.node->store.tx_begin_read ()); + nano::update_flags (node_flags, vm); + node_flags.generate_cache.reps = true; + nano::inactive_node inactive_node (data_path, node_flags); + auto node = inactive_node.node; + auto transaction (node->store.tx_begin_read ()); nano::uint128_t total; - auto rep_amounts = node.node->ledger.rep_weights.get_rep_amounts (); + auto rep_amounts = node->ledger.cache.rep_weights.get_rep_amounts (); std::map ordered_reps (rep_amounts.begin (), rep_amounts.end ()); for (auto const & rep : ordered_reps) { @@ -231,19 +409,20 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_dump_frontier_unchecked_dependents")) { - nano::inactive_node node (data_path); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; std::cout << "Outputting any frontier hashes which have associated key hashes in the unchecked table (may take some time)...\n"; // Cache the account heads to make searching quicker against unchecked keys. - auto transaction (node.node->store.tx_begin_read ()); + auto transaction (node->store.tx_begin_read ()); std::unordered_set frontier_hashes; - for (auto i (node.node->store.latest_begin (transaction)), n (node.node->store.latest_end ()); i != n; ++i) + for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) { frontier_hashes.insert (i->second.head); } // Check all unchecked keys for matching frontier hashes. Indicates an issue with process_batch algorithm - for (auto i (node.node->store.unchecked_begin (transaction)), n (node.node->store.unchecked_end ()); i != n; ++i) + for (auto i (node->store.unchecked_begin (transaction)), n (node->store.unchecked_end ()); i != n; ++i) { auto it = frontier_hashes.find (i->first.key ()); if (it != frontier_hashes.cend ()) @@ -254,13 +433,15 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_account_count")) { - nano::inactive_node node (data_path); - auto transaction (node.node->store.tx_begin_read ()); - std::cout << boost::str (boost::format ("Frontier count: %1%\n") % node.node->store.account_count (transaction)); + auto node_flags = nano::inactive_node_flag_defaults (); + nano::update_flags (node_flags, vm); + node_flags.generate_cache.account_count = true; + nano::inactive_node inactive_node (data_path, node_flags); + std::cout << boost::str (boost::format ("Frontier count: %1%\n") % inactive_node.node->ledger.cache.account_count); } else if (vm.count ("debug_mass_activity")) { - nano::system system (24000, 1); + nano::system system (1); uint32_t count (1000000); system.generate_mass_activity (count, *system.nodes[0]); } @@ -281,6 +462,35 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_profile_generate")) { + nano::network_constants network_constants; + uint64_t difficulty{ network_constants.publish_full.base }; + auto multiplier_it = vm.find ("multiplier"); + if (multiplier_it != vm.end ()) + { + try + { + auto multiplier (boost::lexical_cast (multiplier_it->second.as ())); + difficulty = nano::difficulty::from_multiplier (multiplier, difficulty); + } + catch (boost::bad_lexical_cast &) + { + std::cerr << "Invalid multiplier\n"; + return -1; + } + } + else + { + auto difficulty_it = vm.find ("difficulty"); + if (difficulty_it != vm.end ()) + { + if (nano::from_string_hex (difficulty_it->second.as (), difficulty)) + { + std::cerr << "Invalid difficulty\n"; + return -1; + } + } + } + auto pow_rate_limiter = std::chrono::nanoseconds (0); auto pow_sleep_interval_it = vm.find ("pow_sleep_interval"); if (pow_sleep_interval_it != vm.cend ()) @@ -290,19 +500,22 @@ int main (int argc, char * const * argv) nano::work_pool work (std::numeric_limits::max (), pow_rate_limiter); nano::change_block block (0, 0, nano::keypair ().prv, 0, 0); - std::cerr << "Starting generation profiling\n"; - while (true) + if (!result) { - block.hashables.previous.qwords[0] += 1; - auto begin1 (std::chrono::high_resolution_clock::now ()); - block.block_work_set (*work.generate (block.root ())); - auto end1 (std::chrono::high_resolution_clock::now ()); - std::cerr << boost::str (boost::format ("%|1$ 12d|\n") % std::chrono::duration_cast (end1 - begin1).count ()); + std::cerr << boost::str (boost::format ("Starting generation profiling. Difficulty: %1$#x (%2%x from base difficulty %3$#x)\n") % difficulty % nano::to_string (nano::difficulty::to_multiplier (difficulty, network_constants.publish_full.base), 4) % network_constants.publish_full.base); + while (!result) + { + block.hashables.previous.qwords[0] += 1; + auto begin1 (std::chrono::high_resolution_clock::now ()); + block.block_work_set (*work.generate (nano::work_version::work_1, block.root (), difficulty)); + auto end1 (std::chrono::high_resolution_clock::now ()); + std::cerr << boost::str (boost::format ("%|1$ 12d|\n") % std::chrono::duration_cast (end1 - begin1).count ()); + } } } else if (vm.count ("debug_profile_validate")) { - uint64_t difficulty{ nano::network_constants::publish_full_threshold }; + uint64_t difficulty{ nano::network_constants ().publish_full.base }; std::cerr << "Starting validation profile" << std::endl; auto start (std::chrono::steady_clock::now ()); bool valid{ false }; @@ -310,7 +523,7 @@ int main (int argc, char * const * argv) uint64_t count{ 10000000U }; // 10M for (uint64_t i (0); i < count; ++i) { - valid = nano::work_value (hash, i) > difficulty; + valid = nano::work_v1::value (hash, i) > difficulty; } std::ostringstream oss (valid ? "true" : "false"); // IO forces compiler to not dismiss the variable auto total_time (std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count ()); @@ -336,7 +549,7 @@ int main (int argc, char * const * argv) catch (boost::bad_lexical_cast &) { std::cerr << "Invalid platform id\n"; - result = -1; + return -1; } } unsigned short device (0); @@ -350,7 +563,7 @@ int main (int argc, char * const * argv) catch (boost::bad_lexical_cast &) { std::cerr << "Invalid device id\n"; - result = -1; + return -1; } } unsigned threads (1024 * 1024); @@ -364,22 +577,34 @@ int main (int argc, char * const * argv) catch (boost::bad_lexical_cast &) { std::cerr << "Invalid threads count\n"; - result = -1; + return -1; } } - uint64_t difficulty (network_constants.publish_threshold); - auto difficulty_it = vm.find ("difficulty"); - if (difficulty_it != vm.end ()) + uint64_t difficulty (network_constants.publish_full.base); + auto multiplier_it = vm.find ("multiplier"); + if (multiplier_it != vm.end ()) { - if (nano::from_string_hex (difficulty_it->second.as (), difficulty)) + try { - std::cerr << "Invalid difficulty\n"; - result = -1; + auto multiplier (boost::lexical_cast (multiplier_it->second.as ())); + difficulty = nano::difficulty::from_multiplier (multiplier, difficulty); } - else if (difficulty < network_constants.publish_threshold) + catch (boost::bad_lexical_cast &) { - std::cerr << "Difficulty below publish threshold\n"; - result = -1; + std::cerr << "Invalid multiplier\n"; + return -1; + } + } + else + { + auto difficulty_it = vm.find ("difficulty"); + if (difficulty_it != vm.end ()) + { + if (nano::from_string_hex (difficulty_it->second.as (), difficulty)) + { + std::cerr << "Invalid difficulty\n"; + return -1; + } } } if (!result) @@ -393,17 +618,17 @@ int main (int argc, char * const * argv) nano::logger_mt logger; nano::opencl_config config (platform, device, threads); auto opencl (nano::opencl_work::create (true, config, logger)); - nano::work_pool work_pool (std::numeric_limits::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::root const & root_a, uint64_t difficulty_a, std::atomic &) { - return opencl->generate_work (root_a, difficulty_a); + nano::work_pool work_pool (std::numeric_limits::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::atomic &) { + return opencl->generate_work (version_a, root_a, difficulty_a); } - : std::function (nano::root const &, uint64_t, std::atomic &)> (nullptr)); + : std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> (nullptr)); nano::change_block block (0, 0, nano::keypair ().prv, 0, 0); - std::cerr << boost::str (boost::format ("Starting OpenCL generation profiling. Platform: %1%. Device: %2%. Threads: %3%. Difficulty: %4$#x\n") % platform % device % threads % difficulty); + std::cerr << boost::str (boost::format ("Starting OpenCL generation profiling. Platform: %1%. Device: %2%. Threads: %3%. Difficulty: %4$#x (%5%x from base difficulty %6$#x)\n") % platform % device % threads % difficulty % nano::to_string (nano::difficulty::to_multiplier (difficulty, network_constants.publish_full.base), 4) % network_constants.publish_full.base); for (uint64_t i (0); true; ++i) { block.hashables.previous.qwords[0] += 1; auto begin1 (std::chrono::high_resolution_clock::now ()); - block.block_work_set (*work_pool.generate (block.root (), difficulty)); + block.block_work_set (*work_pool.generate (nano::work_version::work_1, block.root (), difficulty)); auto end1 (std::chrono::high_resolution_clock::now ()); std::cerr << boost::str (boost::format ("%|1$ 12d|\n") % std::chrono::duration_cast (end1 - begin1).count ()); } @@ -441,6 +666,194 @@ int main (int argc, char * const * argv) << st << std::endl; } } + else if (vm.count ("debug_generate_crash_report")) + { + if (boost::filesystem::exists ("nano_node_backtrace.dump")) + { + // There is a backtrace, so output the contents + std::ifstream ifs ("nano_node_backtrace.dump"); + boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump (ifs); + + std::string crash_report_filename = "nano_node_crash_report.txt"; + +#if defined(_WIN32) || defined(__APPLE__) + // Only linux has load addresses, so just write the dump to a readable file. + // It's the best we can do to keep consistency. + std::ofstream ofs (crash_report_filename); + ofs << st; +#else + // Read all the nano node files + boost::system::error_code err; + auto running_executable_filepath = boost::dll::program_location (err); + if (!err) + { + auto num = 0; + auto format = boost::format ("nano_node_crash_load_address_dump_%1%.txt"); + std::vector base_addresses; + + // The first one only has the load address + uint64_from_hex base_address; + std::string line; + if (boost::filesystem::exists (boost::str (format % num))) + { + std::getline (std::ifstream (boost::str (format % num)), line); + if (boost::conversion::try_lexical_convert (line, base_address)) + { + base_addresses.emplace_back (base_address.value, running_executable_filepath.string ()); + } + } + ++num; + + // Now do the rest of the files + while (boost::filesystem::exists (boost::str (format % num))) + { + std::ifstream ifs_dump_filename (boost::str (format % num)); + + // 2 lines, the path to the dynamic library followed by the load address + std::string dynamic_lib_path; + std::getline (ifs_dump_filename, dynamic_lib_path); + std::getline (ifs_dump_filename, line); + + if (boost::conversion::try_lexical_convert (line, base_address)) + { + base_addresses.emplace_back (base_address.value, dynamic_lib_path); + } + + ++num; + } + + std::sort (base_addresses.begin (), base_addresses.end ()); + + auto address_column_it = vm.find ("address_column"); + auto column = -1; + if (address_column_it != vm.end ()) + { + if (!boost::conversion::try_lexical_convert (address_column_it->second.as (), column)) + { + std::cerr << "Error: Invalid address column\n"; + result = -1; + } + } + + // Extract the addresses from the dump file. + std::stringstream stacktrace_ss; + stacktrace_ss << st; + std::vector backtrace_addresses; + while (std::getline (stacktrace_ss, line)) + { + std::istringstream iss (line); + std::vector results (std::istream_iterator{ iss }, std::istream_iterator ()); + + if (column != -1) + { + if (column < results.size ()) + { + uint64_from_hex address_hex; + if (boost::conversion::try_lexical_convert (results[column], address_hex)) + { + backtrace_addresses.push_back (address_hex.value); + } + else + { + std::cerr << "Error: Address column does not point to valid addresses\n"; + result = -1; + } + } + else + { + std::cerr << "Error: Address column too high\n"; + result = -1; + } + } + else + { + for (const auto & text : results) + { + uint64_from_hex address_hex; + if (boost::conversion::try_lexical_convert (text, address_hex)) + { + backtrace_addresses.push_back (address_hex.value); + break; + } + } + } + } + + // Recreate the crash report with an empty file + boost::filesystem::remove (crash_report_filename); + { + std::ofstream ofs (crash_report_filename); + nano::set_secure_perm_file (crash_report_filename); + } + + // Hold the results from all addr2line calls, if all fail we can assume that addr2line is not installed, + // and inform the user that it needs installing + std::vector system_codes; + + auto run_addr2line = [&backtrace_addresses, &base_addresses, &system_codes, &crash_report_filename](bool use_relative_addresses) { + for (auto backtrace_address : backtrace_addresses) + { + // Find the closest address to it + for (auto base_address : boost::adaptors::reverse (base_addresses)) + { + if (backtrace_address > base_address.address) + { + // Addresses need to be in hex for addr2line to work + auto address = use_relative_addresses ? backtrace_address - base_address.address : backtrace_address; + std::stringstream ss; + ss << std::uppercase << std::hex << address; + + // Call addr2line to convert the address into something readable. + auto res = std::system (boost::str (boost::format ("addr2line -fCi %1% -e %2% >> %3%") % ss.str () % base_address.library % crash_report_filename).c_str ()); + system_codes.push_back (res); + break; + } + } + } + }; + + // First run addr2line using absolute addresses + run_addr2line (false); + { + std::ofstream ofs (crash_report_filename, std::ios_base::out | std::ios_base::app); + ofs << std::endl + << "Using relative addresses:" << std::endl; // Add an empty line to separate the absolute & relative output + } + + // Now run using relative addresses. This will give actual results for other dlls, the results from the nano_node executable. + run_addr2line (true); + + if (std::find (system_codes.begin (), system_codes.end (), 0) == system_codes.end ()) + { + std::cerr << "Error: Check that addr2line is installed and that nano_node_crash_load_address_dump_*.txt files exist." << std::endl; + result = -1; + } + else + { + // Delete the crash dump files. The user won't care about them after this. + num = 0; + while (boost::filesystem::exists (boost::str (format % num))) + { + boost::filesystem::remove (boost::str (format % num)); + ++num; + } + + boost::filesystem::remove ("nano_node_backtrace.dump"); + } + } + else + { + std::cerr << "Error: Could not determine running executable path" << std::endl; + result = -1; + } +#endif + } + else + { + std::cerr << "Error: nano_node_backtrace.dump could not be found"; + result = -1; + } + } else if (vm.count ("debug_verify_profile")) { nano::keypair key; @@ -494,15 +907,17 @@ int main (int argc, char * const * argv) nano::network_params test_params; nano::block_builder builder; size_t num_accounts (100000); - size_t num_interations (5); // 100,000 * 5 * 2 = 1,000,000 blocks - size_t max_blocks (2 * num_accounts * num_interations + num_accounts * 2); // 1,000,000 + 2* 100,000 = 1,200,000 blocks - std::cerr << boost::str (boost::format ("Starting pregenerating %1% blocks\n") % max_blocks); - nano::system system (24000, 1); + size_t num_iterations (5); // 100,000 * 5 * 2 = 1,000,000 blocks + size_t max_blocks (2 * num_accounts * num_iterations + num_accounts * 2); // 1,000,000 + 2 * 100,000 = 1,200,000 blocks + std::cout << boost::str (boost::format ("Starting pregenerating %1% blocks\n") % max_blocks); + nano::system system; nano::work_pool work (std::numeric_limits::max ()); nano::logging logging; auto path (nano::unique_path ()); logging.init (path); - auto node (std::make_shared (system.io_ctx, 24001, path, system.alarm, logging, work)); + nano::node_flags node_flags; + nano::update_flags (node_flags, vm); + auto node (std::make_shared (system.io_ctx, 24001, path, system.alarm, logging, work, node_flags)); nano::block_hash genesis_latest (node->latest (test_params.ledger.test_genesis_key.pub)); nano::uint128_t genesis_balance (std::numeric_limits::max ()); // Generating keys @@ -521,8 +936,8 @@ int main (int argc, char * const * argv) .representative (test_params.ledger.test_genesis_key.pub) .balance (genesis_balance) .link (keys[i].pub) - .sign (keys[i].prv, keys[i].pub) - .work (*work.generate (genesis_latest)) + .sign (test_params.ledger.test_genesis_key.prv, test_params.ledger.test_genesis_key.pub) + .work (*work.generate (nano::work_version::work_1, genesis_latest, node->network_params.network.publish_thresholds.epoch_1)) .build (); genesis_latest = send->hash (); @@ -534,14 +949,14 @@ int main (int argc, char * const * argv) .representative (keys[i].pub) .balance (balances[i]) .link (genesis_latest) - .sign (test_params.ledger.test_genesis_key.prv, test_params.ledger.test_genesis_key.pub) - .work (*work.generate (keys[i].pub)) + .sign (keys[i].prv, keys[i].pub) + .work (*work.generate (nano::work_version::work_1, keys[i].pub, node->network_params.network.publish_thresholds.epoch_1)) .build (); frontiers[i] = open->hash (); blocks.push_back (std::move (open)); } - for (auto i (0); i != num_interations; ++i) + for (auto i (0); i != num_iterations; ++i) { for (auto j (0); j != num_accounts; ++j) { @@ -556,7 +971,7 @@ int main (int argc, char * const * argv) .balance (balances[j]) .link (keys[other].pub) .sign (keys[j].prv, keys[j].pub) - .work (*work.generate (frontiers[j])) + .work (*work.generate (nano::work_version::work_1, frontiers[j], node->network_params.network.publish_thresholds.epoch_1)) .build (); frontiers[j] = send->hash (); @@ -571,7 +986,7 @@ int main (int argc, char * const * argv) .balance (balances[other]) .link (static_cast (frontiers[j])) .sign (keys[other].prv, keys[other].pub) - .work (*work.generate (frontiers[other])) + .work (*work.generate (nano::work_version::work_1, frontiers[other], node->network_params.network.publish_thresholds.epoch_1)) .build (); frontiers[other] = receive->hash (); @@ -579,7 +994,7 @@ int main (int argc, char * const * argv) } } // Processing blocks - std::cerr << boost::str (boost::format ("Starting processing %1% active blocks\n") % max_blocks); + std::cout << boost::str (boost::format ("Starting processing %1% blocks\n") % max_blocks); auto begin (std::chrono::high_resolution_clock::now ()); while (!blocks.empty ()) { @@ -587,17 +1002,29 @@ int main (int argc, char * const * argv) node->process_active (block); blocks.pop_front (); } + nano::timer timer_l (nano::timer_state::started); + while (node->ledger.cache.block_count != max_blocks + 1) + { + std::this_thread::sleep_for (std::chrono::milliseconds (10)); + // Message each 15 seconds + if (timer_l.after_deadline (std::chrono::seconds (15))) + { + timer_l.restart (); + std::cout << boost::str (boost::format ("%1% (%2%) blocks processed (unchecked), %3% remaining") % node->ledger.cache.block_count % node->ledger.cache.unchecked_count % node->block_processor.size ()) << std::endl; + } + } + // Waiting for final transaction commit uint64_t block_count (0); while (block_count < max_blocks + 1) { - std::this_thread::sleep_for (std::chrono::milliseconds (100)); + std::this_thread::sleep_for (std::chrono::milliseconds (10)); auto transaction (node->store.tx_begin_read ()); block_count = node->store.block_count (transaction).sum (); } auto end (std::chrono::high_resolution_clock::now ()); auto time (std::chrono::duration_cast (end - begin).count ()); node->stop (); - std::cerr << boost::str (boost::format ("%|1$ 12d| us \n%2% blocks per second\n") % time % (max_blocks * 1000000 / time)); + std::cout << boost::str (boost::format ("%|1$ 12d| us \n%2% blocks per second\n") % time % (max_blocks * 1000000 / time)); } else if (vm.count ("debug_profile_votes")) { @@ -608,7 +1035,7 @@ int main (int argc, char * const * argv) size_t num_representatives (25); size_t max_votes (num_elections * num_representatives); // 40,000 * 25 = 1,000,000 votes std::cerr << boost::str (boost::format ("Starting pregenerating %1% votes\n") % max_votes); - nano::system system (24000, 1); + nano::system system (1); nano::work_pool work (std::numeric_limits::max ()); nano::logging logging; auto path (nano::unique_path ()); @@ -631,7 +1058,7 @@ int main (int argc, char * const * argv) .balance (genesis_balance) .link (keys[i].pub) .sign (test_params.ledger.test_genesis_key.prv, test_params.ledger.test_genesis_key.pub) - .work (*work.generate (genesis_latest)) + .work (*work.generate (nano::work_version::work_1, genesis_latest, node->network_params.network.publish_thresholds.epoch_1)) .build (); genesis_latest = send->hash (); @@ -644,7 +1071,7 @@ int main (int argc, char * const * argv) .balance (balance) .link (genesis_latest) .sign (keys[i].prv, keys[i].pub) - .work (*work.generate (keys[i].pub)) + .work (*work.generate (nano::work_version::work_1, keys[i].pub, node->network_params.network.publish_thresholds.epoch_1)) .build (); node->ledger.process (transaction, *open); @@ -663,7 +1090,7 @@ int main (int argc, char * const * argv) .balance (genesis_balance) .link (destination.pub) .sign (test_params.ledger.test_genesis_key.prv, test_params.ledger.test_genesis_key.pub) - .work (*work.generate (genesis_latest)) + .work (*work.generate (nano::work_version::work_1, genesis_latest, node->network_params.network.publish_thresholds.epoch_1)) .build (); genesis_latest = send->hash (); @@ -708,6 +1135,192 @@ int main (int argc, char * const * argv) node->stop (); std::cerr << boost::str (boost::format ("%|1$ 12d| us \n%2% votes per second\n") % time % (max_votes * 1000000 / time)); } + else if (vm.count ("debug_profile_frontiers_confirmation")) + { + nano::force_nano_test_network (); + nano::network_params test_params; + nano::block_builder builder; + size_t count (32 * 1024); + auto count_it = vm.find ("count"); + if (count_it != vm.end ()) + { + try + { + count = boost::lexical_cast (count_it->second.as ()); + } + catch (boost::bad_lexical_cast &) + { + std::cerr << "Invalid count\n"; + return -1; + } + } + std::cout << boost::str (boost::format ("Starting generating %1% blocks...\n") % (count * 2)); + boost::asio::io_context io_ctx1; + boost::asio::io_context io_ctx2; + nano::alarm alarm1 (io_ctx1); + nano::alarm alarm2 (io_ctx2); + nano::work_pool work (std::numeric_limits::max ()); + nano::logging logging; + auto path1 (nano::unique_path ()); + auto path2 (nano::unique_path ()); + logging.init (path1); + nano::node_config config1 (24000, logging); + nano::node_flags flags; + flags.disable_lazy_bootstrap = true; + flags.disable_legacy_bootstrap = true; + flags.disable_wallet_bootstrap = true; + flags.disable_bootstrap_listener = true; + auto node1 (std::make_shared (io_ctx1, path1, alarm1, config1, work, flags, 0)); + nano::block_hash genesis_latest (node1->latest (test_params.ledger.test_genesis_key.pub)); + nano::uint128_t genesis_balance (std::numeric_limits::max ()); + // Generating blocks + std::deque> blocks; + for (auto i (0); i != count; ++i) + { + nano::keypair key; + genesis_balance = genesis_balance - 1; + + auto send = builder.state () + .account (test_params.ledger.test_genesis_key.pub) + .previous (genesis_latest) + .representative (test_params.ledger.test_genesis_key.pub) + .balance (genesis_balance) + .link (key.pub) + .sign (test_params.ledger.test_genesis_key.prv, test_params.ledger.test_genesis_key.pub) + .work (*work.generate (nano::work_version::work_1, genesis_latest, test_params.network.publish_thresholds.epoch_1)) + .build (); + + genesis_latest = send->hash (); + + auto open = builder.state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (genesis_latest) + .sign (key.prv, key.pub) + .work (*work.generate (nano::work_version::work_1, key.pub, test_params.network.publish_thresholds.epoch_1)) + .build (); + + blocks.push_back (std::move (send)); + blocks.push_back (std::move (open)); + if (i % 20000 == 0 && i != 0) + { + std::cout << boost::str (boost::format ("%1% blocks generated\n") % (i * 2)); + } + } + node1->start (); + nano::thread_runner runner1 (io_ctx1, node1->config.io_threads); + + std::cout << boost::str (boost::format ("Processing %1% blocks\n") % (count * 2)); + for (auto & block : blocks) + { + node1->block_processor.add (block); + } + node1->block_processor.flush (); + auto iteration (0); + while (node1->ledger.cache.block_count != count * 2 + 1) + { + std::this_thread::sleep_for (std::chrono::milliseconds (500)); + if (++iteration % 60 == 0) + { + std::cout << boost::str (boost::format ("%1% blocks processed\n") % node1->ledger.cache.block_count); + } + } + // Confirm blocks for node1 + for (auto & block : blocks) + { + node1->confirmation_height_processor.add (block->hash ()); + } + while (node1->ledger.cache.cemented_count != node1->ledger.cache.block_count) + { + std::this_thread::sleep_for (std::chrono::milliseconds (500)); + if (++iteration % 60 == 0) + { + std::cout << boost::str (boost::format ("%1% blocks cemented\n") % node1->ledger.cache.cemented_count); + } + } + + // Start new node + nano::node_config config2 (24001, logging); + // Config override + std::vector config_overrides; + auto config (vm.find ("config")); + if (config != vm.end ()) + { + config_overrides = config->second.as> (); + } + if (!config_overrides.empty ()) + { + auto path (nano::unique_path ()); + nano::daemon_config daemon_config (path); + auto error = nano::read_node_config_toml (path, daemon_config, config_overrides); + if (error) + { + std::cerr << "\n" + << error.get_message () << std::endl; + std::exit (1); + } + else + { + config2.frontiers_confirmation = daemon_config.node.frontiers_confirmation; + config2.active_elections_size = daemon_config.node.active_elections_size; + } + } + auto node2 (std::make_shared (io_ctx2, path2, alarm2, config2, work, flags, 1)); + node2->start (); + nano::thread_runner runner2 (io_ctx2, node2->config.io_threads); + std::cout << boost::str (boost::format ("Processing %1% blocks (test node)\n") % (count * 2)); + // Processing block + while (!blocks.empty ()) + { + auto block (blocks.front ()); + node2->block_processor.add (block); + blocks.pop_front (); + } + node2->block_processor.flush (); + while (node2->ledger.cache.block_count != count * 2 + 1) + { + std::this_thread::sleep_for (std::chrono::milliseconds (500)); + if (++iteration % 60 == 0) + { + std::cout << boost::str (boost::format ("%1% blocks processed\n") % node2->ledger.cache.block_count); + } + } + // Insert representative + std::cout << "Initializing representative\n"; + auto wallet (node1->wallets.create (nano::random_wallet_id ())); + wallet->insert_adhoc (test_params.ledger.test_genesis_key.prv); + node2->network.merge_peer (node1->network.endpoint ()); + while (node2->rep_crawler.representative_count () == 0) + { + std::this_thread::sleep_for (std::chrono::milliseconds (10)); + if (++iteration % 500 == 0) + { + std::cout << "Representative initialization iteration...\n"; + } + } + auto begin (std::chrono::high_resolution_clock::now ()); + std::cout << boost::str (boost::format ("Starting confirming %1% frontiers (test node)\n") % (count + 1)); + // Wait for full frontiers confirmation + while (node2->ledger.cache.cemented_count != node2->ledger.cache.block_count) + { + std::this_thread::sleep_for (std::chrono::milliseconds (25)); + if (++iteration % 1200 == 0) + { + std::cout << boost::str (boost::format ("%1% blocks confirmed\n") % node2->ledger.cache.cemented_count); + } + } + auto end (std::chrono::high_resolution_clock::now ()); + auto time (std::chrono::duration_cast (end - begin).count ()); + std::cout << boost::str (boost::format ("%|1$ 12d| us \n%2% frontiers per second\n") % time % ((count + 1) * 1000000 / time)); + io_ctx1.stop (); + io_ctx2.stop (); + runner1.join (); + runner2.join (); + node1->stop (); + node2->stop (); + } else if (vm.count ("debug_random_feed")) { /* @@ -740,78 +1353,134 @@ int main (int argc, char * const * argv) std::exit (0); }); - nano::inactive_node inactive_node_l (data_path); + auto node_flags = nano::inactive_node_flag_defaults (); + nano::update_flags (node_flags, vm); + node_flags.generate_cache.enable_all (); + nano::inactive_node inactive_node_l (data_path, node_flags); + nano::node_rpc_config config; nano::ipc::ipc_server server (*inactive_node_l.node, config); - nano::json_handler handler_l (*inactive_node_l.node, config, command_l.str (), response_handler_l); - handler_l.process_request (); + auto handler_l (std::make_shared (*inactive_node_l.node, config, command_l.str (), response_handler_l)); + handler_l->process_request (); } - else if (vm.count ("debug_validate_blocks")) + else if (vm.count ("validate_blocks") || vm.count ("debug_validate_blocks")) { - nano::inactive_node node (data_path); - auto transaction (node.node->store.tx_begin_read ()); - std::cout << boost::str (boost::format ("Performing blocks hash, signature, work validation...\n")); - size_t count (0); - uint64_t block_count (0); - for (auto i (node.node->store.latest_begin (transaction)), n (node.node->store.latest_end ()); i != n; ++i) + nano::timer timer; + timer.start (); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + bool const silent (vm.count ("silent")); + unsigned threads_count (1); + auto threads_it = vm.find ("threads"); + if (threads_it != vm.end ()) { + if (!boost::conversion::try_lexical_convert (threads_it->second.as (), threads_count)) + { + std::cerr << "Invalid threads count\n"; + return -1; + } + } + threads_count = std::max (1u, threads_count); + std::vector threads; + std::mutex mutex; + nano::condition_variable condition; + std::atomic finished (false); + std::deque> accounts; + std::atomic count (0); + std::atomic block_count (0); + std::atomic errors (0); + + auto print_error_message = [&silent, &errors](std::string const & error_message_a) { + if (!silent) + { + static std::mutex cerr_mutex; + nano::lock_guard lock (cerr_mutex); + std::cerr << error_message_a; + } + ++errors; + }; + + auto start_threads = [node, &threads_count, &threads, &mutex, &condition, &finished](const auto & function_a, auto & deque_a) { + for (auto i (0); i < threads_count; ++i) + { + threads.emplace_back ([&function_a, node, &mutex, &condition, &finished, &deque_a]() { + auto transaction (node->store.tx_begin_read ()); + nano::unique_lock lock (mutex); + while (!deque_a.empty () || !finished) + { + while (deque_a.empty () && !finished) + { + condition.wait (lock); + } + if (!deque_a.empty ()) + { + auto pair (deque_a.front ()); + deque_a.pop_front (); + lock.unlock (); + function_a (node, transaction, pair.first, pair.second); + lock.lock (); + } + } + }); + } + }; + + auto check_account = [&print_error_message, &silent, &count, &block_count](std::shared_ptr const & node, nano::read_transaction const & transaction, nano::account const & account, nano::account_info const & info) { ++count; - if ((count % 20000) == 0) + if (!silent && (count % 20000) == 0) { std::cout << boost::str (boost::format ("%1% accounts validated\n") % count); } - nano::account_info const & info (i->second); - nano::account const & account (i->first); - uint64_t confirmation_height; - node.node->store.confirmation_height_get (transaction, account, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + node->store.confirmation_height_get (transaction, account, confirmation_height_info); - if (confirmation_height > info.block_count) + if (confirmation_height_info.height > info.block_count) { - std::cerr << "Confirmation height " << confirmation_height << " greater than block count " << info.block_count << " for account: " << account.to_account () << std::endl; + print_error_message (boost::str (boost::format ("Confirmation height %1% greater than block count %2% for account: %3%\n") % confirmation_height_info.height % info.block_count % account.to_account ())); } auto hash (info.open_block); nano::block_hash calculated_hash (0); - nano::block_sideband sideband; - auto block (node.node->store.block_get (transaction, hash, &sideband)); // Block data + auto block (node->store.block_get (transaction, hash)); // Block data uint64_t height (0); uint64_t previous_timestamp (0); nano::account calculated_representative (0); while (!hash.is_zero () && block != nullptr) { ++block_count; + auto const & sideband (block->sideband ()); // Check for state & open blocks if account field is correct if (block->type () == nano::block_type::open || block->type () == nano::block_type::state) { if (block->account () != account) { - std::cerr << boost::str (boost::format ("Incorrect account field for block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect account field for block %1%\n") % hash.to_string ())); } } // Check if sideband account is correct else if (sideband.account != account) { - std::cerr << boost::str (boost::format ("Incorrect sideband account for block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect sideband account for block %1%\n") % hash.to_string ())); } // Check if previous field is correct if (calculated_hash != block->previous ()) { - std::cerr << boost::str (boost::format ("Incorrect previous field for block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect previous field for block %1%\n") % hash.to_string ())); } // Check if previous & type for open blocks are correct if (height == 0 && !block->previous ().is_zero ()) { - std::cerr << boost::str (boost::format ("Incorrect previous for open block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect previous for open block %1%\n") % hash.to_string ())); } if (height == 0 && block->type () != nano::block_type::open && block->type () != nano::block_type::state) { - std::cerr << boost::str (boost::format ("Incorrect type for open block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect type for open block %1%\n") % hash.to_string ())); } // Check if block data is correct (calculating hash) calculated_hash = block->hash (); if (calculated_hash != hash) { - std::cerr << boost::str (boost::format ("Invalid data inside block %1% calculated hash: %2%\n") % hash.to_string () % calculated_hash.to_string ()); + print_error_message (boost::str (boost::format ("Invalid data inside block %1% calculated hash: %2%\n") % hash.to_string () % calculated_hash.to_string ())); } // Check if block signature is correct if (validate_message (account, hash, block->block_signature ())) @@ -824,33 +1493,72 @@ int main (int argc, char * const * argv) nano::amount prev_balance (0); if (!state_block.hashables.previous.is_zero ()) { - prev_balance = node.node->ledger.balance (transaction, state_block.hashables.previous); + prev_balance = node->ledger.balance (transaction, state_block.hashables.previous); } - if (node.node->ledger.is_epoch_link (state_block.hashables.link) && state_block.hashables.balance == prev_balance) + if (node->ledger.is_epoch_link (state_block.hashables.link) && state_block.hashables.balance == prev_balance) { - invalid = validate_message (node.node->ledger.epoch_signer (block->link ()), hash, block->block_signature ()); + invalid = validate_message (node->ledger.epoch_signer (block->link ()), hash, block->block_signature ()); } } if (invalid) { - std::cerr << boost::str (boost::format ("Invalid signature for block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Invalid signature for block %1%\n") % hash.to_string ())); + } + } + // Validate block details set in the sideband + bool block_details_error = false; + if (block->type () != nano::block_type::state) + { + // Not state + block_details_error = sideband.details.is_send || sideband.details.is_receive || sideband.details.is_epoch; + } + else + { + auto prev_balance (node->ledger.balance (transaction, block->previous ())); + if (block->balance () < prev_balance) + { + // State send + block_details_error = !sideband.details.is_send || sideband.details.is_receive || sideband.details.is_epoch; + } + else + { + if (block->link ().is_zero ()) + { + // State change + block_details_error = sideband.details.is_send || sideband.details.is_receive || sideband.details.is_epoch; + } + else if (block->balance () == prev_balance && node->ledger.is_epoch_link (block->link ())) + { + // State epoch + block_details_error = !sideband.details.is_epoch || sideband.details.is_send || sideband.details.is_receive; + } + else + { + // State receive + block_details_error = !sideband.details.is_receive || sideband.details.is_send || sideband.details.is_epoch; + block_details_error |= !node->store.source_exists (transaction, block->link ()); + } } } + if (block_details_error) + { + print_error_message (boost::str (boost::format ("Incorrect sideband block details for block %1%\n") % hash.to_string ())); + } // Check if block work value is correct - if (nano::work_validate (*block.get ())) + if (block->difficulty () < nano::work_threshold (block->work_version (), block->sideband ().details)) { - std::cerr << boost::str (boost::format ("Invalid work for block %1% value: %2%\n") % hash.to_string () % nano::to_string_hex (block->block_work ())); + print_error_message (boost::str (boost::format ("Invalid work for block %1% value: %2%\n") % hash.to_string () % nano::to_string_hex (block->block_work ()))); } // Check if sideband height is correct ++height; if (sideband.height != height) { - std::cerr << boost::str (boost::format ("Incorrect sideband height for block %1%. Sideband: %2%. Expected: %3%\n") % hash.to_string () % sideband.height % height); + print_error_message (boost::str (boost::format ("Incorrect sideband height for block %1%. Sideband: %2%. Expected: %3%\n") % hash.to_string () % sideband.height % height)); } // Check if sideband timestamp is after previous timestamp if (sideband.timestamp < previous_timestamp) { - std::cerr << boost::str (boost::format ("Incorrect sideband timestamp for block %1%\n") % hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect sideband timestamp for block %1%\n") % hash.to_string ())); } previous_timestamp = sideband.timestamp; // Calculate representative block @@ -859,57 +1567,95 @@ int main (int argc, char * const * argv) calculated_representative = block->representative (); } // Retrieving successor block hash - hash = node.node->store.block_successor (transaction, hash); + hash = node->store.block_successor (transaction, hash); // Retrieving block data if (!hash.is_zero ()) { - block = node.node->store.block_get (transaction, hash, &sideband); + block = node->store.block_get (transaction, hash); } } // Check if required block exists if (!hash.is_zero () && block == nullptr) { - std::cerr << boost::str (boost::format ("Required block in account %1% chain was not found in ledger: %2%\n") % account.to_account () % hash.to_string ()); + print_error_message (boost::str (boost::format ("Required block in account %1% chain was not found in ledger: %2%\n") % account.to_account () % hash.to_string ())); } // Check account block count if (info.block_count != height) { - std::cerr << boost::str (boost::format ("Incorrect block count for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % height % info.block_count); + print_error_message (boost::str (boost::format ("Incorrect block count for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % height % info.block_count)); } // Check account head block (frontier) if (info.head != calculated_hash) { - std::cerr << boost::str (boost::format ("Incorrect frontier for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % calculated_hash.to_string () % info.head.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect frontier for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % calculated_hash.to_string () % info.head.to_string ())); } // Check account representative block if (info.representative != calculated_representative) { - std::cerr << boost::str (boost::format ("Incorrect representative for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % calculated_representative.to_string () % info.representative.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect representative for account %1%. Actual: %2%. Expected: %3%\n") % account.to_account () % calculated_representative.to_string () % info.representative.to_string ())); + } + }; + + start_threads (check_account, accounts); + + if (!silent) + { + std::cout << boost::str (boost::format ("Performing %1% threads blocks hash, signature, work validation...\n") % threads_count); + } + size_t const accounts_deque_overflow (32 * 1024); + auto transaction (node->store.tx_begin_read ()); + for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) + { + { + nano::unique_lock lock (mutex); + if (accounts.size () > accounts_deque_overflow) + { + auto wait_ms (250 * accounts.size () / accounts_deque_overflow); + const auto wakeup (std::chrono::steady_clock::now () + std::chrono::milliseconds (wait_ms)); + condition.wait_until (lock, wakeup); + } + accounts.emplace_back (i->first, i->second); } + condition.notify_all (); + } + { + nano::lock_guard lock (mutex); + finished = true; + } + condition.notify_all (); + for (auto & thread : threads) + { + thread.join (); + } + threads.clear (); + if (!silent) + { + std::cout << boost::str (boost::format ("%1% accounts validated\n") % count); } - std::cout << boost::str (boost::format ("%1% accounts validated\n") % count); + // Validate total block count - auto ledger_block_count (node.node->store.block_count (transaction).sum ()); + auto ledger_block_count (node->store.block_count (transaction).sum ()); if (block_count != ledger_block_count) { - std::cerr << boost::str (boost::format ("Incorrect total block count. Blocks validated %1%. Block count in database: %2%\n") % block_count % ledger_block_count); + print_error_message (boost::str (boost::format ("Incorrect total block count. Blocks validated %1%. Block count in database: %2%\n") % block_count % ledger_block_count)); } + // Validate pending blocks count = 0; - for (auto i (node.node->store.pending_begin (transaction)), n (node.node->store.pending_end ()); i != n; ++i) - { + finished = false; + std::deque> pending; + + auto check_pending = [&print_error_message, &silent, &count](std::shared_ptr const & node, nano::read_transaction const & transaction, nano::pending_key const & key, nano::pending_info const & info) { ++count; - if ((count % 200000) == 0) + if (!silent && (count % 500000) == 0) { std::cout << boost::str (boost::format ("%1% pending blocks validated\n") % count); } - nano::pending_key const & key (i->first); - nano::pending_info const & info (i->second); // Check block existance - auto block (node.node->store.block_get (transaction, key.hash)); + auto block (node->store.block_get_no_sideband (transaction, key.hash)); if (block == nullptr) { - std::cerr << boost::str (boost::format ("Pending block does not exist %1%\n") % key.hash.to_string ()); + print_error_message (boost::str (boost::format ("Pending block does not exist %1%\n") % key.hash.to_string ())); } else { @@ -917,7 +1663,7 @@ int main (int argc, char * const * argv) nano::account destination (0); if (auto state = dynamic_cast (block.get ())) { - if (node.node->ledger.is_send (transaction, *state)) + if (node->ledger.is_send (transaction, *state)) { destination = state->hashables.link; } @@ -928,42 +1674,85 @@ int main (int argc, char * const * argv) } else { - std::cerr << boost::str (boost::format ("Incorrect type for pending block %1%\n") % key.hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect type for pending block %1%\n") % key.hash.to_string ())); } if (key.account != destination) { - std::cerr << boost::str (boost::format ("Incorrect destination for pending block %1%\n") % key.hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect destination for pending block %1%\n") % key.hash.to_string ())); } // Check if pending source is correct - auto account (node.node->ledger.account (transaction, key.hash)); + auto account (node->ledger.account (transaction, key.hash)); if (info.source != account) { - std::cerr << boost::str (boost::format ("Incorrect source for pending block %1%\n") % key.hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect source for pending block %1%\n") % key.hash.to_string ())); } // Check if pending amount is correct - auto amount (node.node->ledger.amount (transaction, key.hash)); + auto amount (node->ledger.amount (transaction, key.hash)); if (info.amount != amount) { - std::cerr << boost::str (boost::format ("Incorrect amount for pending block %1%\n") % key.hash.to_string ()); + print_error_message (boost::str (boost::format ("Incorrect amount for pending block %1%\n") % key.hash.to_string ())); + } + } + }; + + start_threads (check_pending, pending); + + size_t const pending_deque_overflow (64 * 1024); + for (auto i (node->store.pending_begin (transaction)), n (node->store.pending_end ()); i != n; ++i) + { + { + nano::unique_lock lock (mutex); + if (pending.size () > pending_deque_overflow) + { + auto wait_ms (50 * pending.size () / pending_deque_overflow); + const auto wakeup (std::chrono::steady_clock::now () + std::chrono::milliseconds (wait_ms)); + condition.wait_until (lock, wakeup); } + pending.emplace_back (i->first, i->second); } + condition.notify_all (); + } + { + nano::lock_guard lock (mutex); + finished = true; + } + condition.notify_all (); + for (auto & thread : threads) + { + thread.join (); + } + if (!silent) + { + std::cout << boost::str (boost::format ("%1% pending blocks validated\n") % count); + timer.stop (); + std::cout << boost::str (boost::format ("%1% %2% validation time\n") % timer.value ().count () % timer.unit ()); + } + if (errors == 0) + { + std::cout << "Validation status: Ok\n"; + } + else + { + std::cout << boost::str (boost::format ("Validation status: Failed\n%1% errors found\n") % errors); } - std::cout << boost::str (boost::format ("%1% pending blocks validated\n") % count); } else if (vm.count ("debug_profile_bootstrap")) { - nano::inactive_node node2 (nano::unique_path (), 24001); - nano::update_flags (node2.node->flags, vm); + auto node_flags = nano::inactive_node_flag_defaults (); + node_flags.read_only = false; + nano::update_flags (node_flags, vm); + nano::inactive_node node2 (nano::unique_path (), node_flags); nano::genesis genesis; auto begin (std::chrono::high_resolution_clock::now ()); uint64_t block_count (0); size_t count (0); { - nano::inactive_node node (data_path, 24000); - auto transaction (node.node->store.tx_begin_read ()); - block_count = node.node->store.block_count (transaction).sum (); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto transaction (node->store.tx_begin_read ()); + block_count = node->ledger.cache.block_count; std::cout << boost::str (boost::format ("Performing bootstrap emulation, %1% blocks in ledger...") % block_count) << std::endl; - for (auto i (node.node->store.latest_begin (transaction)), n (node.node->store.latest_end ()); i != n; ++i) + for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) { nano::account const & account (i->first); nano::account_info const & info (i->second); @@ -971,11 +1760,11 @@ int main (int argc, char * const * argv) while (!hash.is_zero ()) { // Retrieving block data - auto block (node.node->store.block_get (transaction, hash)); + auto block (node->store.block_get_no_sideband (transaction, hash)); if (block != nullptr) { ++count; - if ((count % 100000) == 0) + if ((count % 500000) == 0) { std::cout << boost::str (boost::format ("%1% blocks retrieved") % count) << std::endl; } @@ -987,31 +1776,39 @@ int main (int argc, char * const * argv) } } } - count = 0; + nano::timer timer_l (nano::timer_state::started); + while (node2.node->ledger.cache.block_count != block_count) + { + std::this_thread::sleep_for (std::chrono::milliseconds (50)); + // Message each 60 seconds + if (timer_l.after_deadline (std::chrono::seconds (60))) + { + timer_l.restart (); + std::cout << boost::str (boost::format ("%1% (%2%) blocks processed (unchecked)") % node2.node->ledger.cache.block_count % node2.node->ledger.cache.unchecked_count) << std::endl; + } + } + // Waiting for final transaction commit uint64_t block_count_2 (0); while (block_count_2 != block_count) { - std::this_thread::sleep_for (std::chrono::seconds (1)); + std::this_thread::sleep_for (std::chrono::milliseconds (50)); auto transaction_2 (node2.node->store.tx_begin_read ()); block_count_2 = node2.node->store.block_count (transaction_2).sum (); - if ((count % 60) == 0) - { - std::cout << boost::str (boost::format ("%1% (%2%) blocks processed") % block_count_2 % node2.node->store.unchecked_count (transaction_2)) << std::endl; - } - count++; } auto end (std::chrono::high_resolution_clock::now ()); auto time (std::chrono::duration_cast (end - begin).count ()); - auto seconds (time / 1000000); + auto us_in_second (1000000); + auto seconds (time / us_in_second); nano::remove_temporary_directories (); - std::cout << boost::str (boost::format ("%|1$ 12d| seconds \n%2% blocks per second") % seconds % (block_count / seconds)) << std::endl; + std::cout << boost::str (boost::format ("%|1$ 12d| seconds \n%2% blocks per second") % seconds % (block_count * us_in_second / time)) << std::endl; } else if (vm.count ("debug_peers")) { - nano::inactive_node node (data_path); - auto transaction (node.node->store.tx_begin_read ()); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto transaction (node->store.tx_begin_read ()); - for (auto i (node.node->store.peers_begin (transaction)), n (node.node->store.peers_end ()); i != n; ++i) + for (auto i (node->store.peers_begin (transaction)), n (node->store.peers_end ()); i != n; ++i) { std::cout << boost::str (boost::format ("%1%\n") % nano::endpoint (boost::asio::ip::address_v6 (i->first.address_bytes ()), i->first.port ())); } @@ -1019,9 +1816,10 @@ int main (int argc, char * const * argv) else if (vm.count ("debug_cemented_block_count")) { auto node_flags = nano::inactive_node_flag_defaults (); - node_flags.cache_cemented_count_from_frontiers = true; - nano::inactive_node node (data_path, 24000, node_flags); - std::cout << "Total cemented block count: " << node.node->ledger.cemented_count << std::endl; + node_flags.generate_cache.cemented_count = true; + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); + std::cout << "Total cemented block count: " << node.node->ledger.cache.cemented_count << std::endl; } else if (vm.count ("debug_stacktrace")) { @@ -1036,18 +1834,19 @@ int main (int argc, char * const * argv) return 1; } #endif - nano::inactive_node node (data_path); - node.node->logger.always_log (nano::severity_level::error, "Testing system logger"); + auto inactive_node = nano::default_inactive_node (data_path, vm); + inactive_node->node->logger.always_log (nano::severity_level::error, "Testing system logger"); } else if (vm.count ("debug_account_versions")) { - nano::inactive_node node (data_path); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; - auto transaction (node.node->store.tx_begin_read ()); + auto transaction (node->store.tx_begin_read ()); std::vector> opened_account_versions (nano::normalized_epoch (nano::epoch::max)); // Cache the accounts in a collection to make searching quicker against unchecked keys. Group by epoch - for (auto i (node.node->store.latest_begin (transaction)), n (node.node->store.latest_end ()); i != n; ++i) + for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) { auto const & account (i->first); auto const & account_info (i->second); @@ -1059,16 +1858,14 @@ int main (int argc, char * const * argv) // Iterate all pending blocks and collect the highest version for each unopened account std::unordered_map> unopened_highest_pending; - for (auto i (node.node->store.pending_begin (transaction)), n (node.node->store.pending_end ()); i != n; ++i) + for (auto i (node->store.pending_begin (transaction)), n (node->store.pending_end ()); i != n; ++i) { nano::pending_key const & key (i->first); nano::pending_info const & info (i->second); - // clang-format off auto & account = key.account; auto exists = std::any_of (opened_account_versions.begin (), opened_account_versions.end (), [&account](auto const & account_version) { return account_version.find (account) != account_version.end (); }); - // clang-format on if (!exists) { // This is an unopened account, store the highest pending version @@ -1128,3 +1925,27 @@ int main (int argc, char * const * argv) } return result; } + +namespace +{ +std::istream & operator>> (std::istream & in, uint64_from_hex & out_val) +{ + in >> std::hex >> out_val.value; + return in; +} + +address_library_pair::address_library_pair (uint64_t address, std::string library) : +address (address), library (library) +{ +} + +bool address_library_pair::operator< (const address_library_pair & other) const +{ + return address < other.address; +} + +bool address_library_pair::operator== (const address_library_pair & other) const +{ + return address == other.address; +} +} diff --git a/nano/nano_rpc/CMakeLists.txt b/nano/nano_rpc/CMakeLists.txt index b8f40f4334..deb3cc5708 100644 --- a/nano/nano_rpc/CMakeLists.txt +++ b/nano/nano_rpc/CMakeLists.txt @@ -20,7 +20,12 @@ target_compile_definitions(nano_rpc -DGIT_COMMIT_HASH=${GIT_COMMIT_HASH}) if ((NANO_GUI OR RAIBLOCKS_GUI) AND NOT APPLE) - install(TARGETS nano_rpc - RUNTIME DESTINATION . - ) + if (WIN32) + install(TARGETS nano_rpc + RUNTIME DESTINATION . + ) + else () + install(TARGETS nano_rpc + RUNTIME DESTINATION ./bin) + endif() endif() diff --git a/nano/nano_rpc/entry.cpp b/nano/nano_rpc/entry.cpp index 3613da0000..f6e31b47bf 100644 --- a/nano/nano_rpc/entry.cpp +++ b/nano/nano_rpc/entry.cpp @@ -1,15 +1,13 @@ #include -#include +#include #include -#include #include -#include +#include #include #include -#include +#include -#include -#include +#include #include #include #include @@ -54,7 +52,7 @@ void run (boost::filesystem::path const & data_path, std::vector co auto rpc = nano::get_rpc (io_ctx, rpc_config, ipc_rpc_processor); rpc->start (); - assert (!nano::signal_handler_impl); + debug_assert (!nano::signal_handler_impl); nano::signal_handler_impl = [&io_ctx]() { io_ctx.stop (); sig_int_or_term = 1; @@ -117,7 +115,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - std::cerr << err.get_message () << std::endl; + std::cerr << nano::network_constants::active_network_err_msg << std::endl; std::exit (1); } } diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index 90d8b030c0..290808e3d2 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -1,14 +1,15 @@ -#include +#include #include #include #include +#include #include #include #include #include #include #include -#include +#include #include #include #include @@ -38,20 +39,32 @@ void show_help (std::string const & message_a) message.exec (); } -nano::error read_and_update_wallet_config (nano::wallet_config & config_a, boost::filesystem::path const & data_path_a) +nano::error write_wallet_config (nano::wallet_config & config_a, boost::filesystem::path const & data_path_a) { nano::tomlconfig wallet_config_toml; auto wallet_path (nano::get_qtwallet_toml_config_path (data_path_a)); - wallet_config_toml.read (nano::get_qtwallet_toml_config_path (data_path_a)); config_a.serialize_toml (wallet_config_toml); // Write wallet config. If missing, the file is created and permissions are set. wallet_config_toml.write (wallet_path); return wallet_config_toml.get_error (); } + +nano::error read_wallet_config (nano::wallet_config & config_a, boost::filesystem::path const & data_path_a) +{ + nano::tomlconfig wallet_config_toml; + auto wallet_path (nano::get_qtwallet_toml_config_path (data_path_a)); + if (!boost::filesystem::exists (wallet_path)) + { + write_wallet_config (config_a, data_path_a); + } + wallet_config_toml.read (wallet_path); + config_a.deserialize_toml (wallet_config_toml); + return wallet_config_toml.get_error (); +} } -int run_wallet (QApplication & application, int argc, char * const * argv, boost::filesystem::path const & data_path, std::vector const & config_overrides, nano::node_flags const & flags) +int run_wallet (QApplication & application, int argc, char * const * argv, boost::filesystem::path const & data_path, nano::node_flags const & flags) { int result (0); nano_qt::eventloop_processor processor; @@ -68,10 +81,10 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost nano::daemon_config config (data_path); nano::wallet_config wallet_config; - auto error = nano::read_node_config_toml (data_path, config, config_overrides); + auto error = nano::read_node_config_toml (data_path, config, flags.config_overrides); if (!error) { - error = read_and_update_wallet_config (wallet_config, data_path); + error = read_wallet_config (wallet_config, data_path); } #if !NANO_ROCKSDB @@ -95,10 +108,10 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost std::shared_ptr gui; nano::set_application_icon (application); auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, logger)); - nano::work_pool work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::root const & root_a, uint64_t difficulty_a, std::atomic &) { - return opencl->generate_work (root_a, difficulty_a); + nano::work_pool work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::atomic &) { + return opencl->generate_work (version_a, root_a, difficulty_a); } - : std::function (nano::root const &, uint64_t, std::atomic &)> (nullptr)); + : std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> (nullptr)); nano::alarm alarm (io_ctx); node = std::make_shared (io_ctx, data_path, alarm, config.node, work, flags); if (!node->init_error ()) @@ -130,8 +143,8 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost wallet_config.account = wallet->deterministic_insert (transaction); } } - assert (wallet->exists (wallet_config.account)); - read_and_update_wallet_config (wallet_config, data_path); + debug_assert (wallet->exists (wallet_config.account)); + write_wallet_config (wallet_config, data_path); node->start (); nano::ipc::ipc_server ipc (*node, config.rpc); @@ -150,7 +163,6 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost } #if BOOST_PROCESS_SUPPORTED - auto network = node->network_params.network.get_current_network_as_string (); nano_pow_server_process = std::make_unique (config.pow_server.pow_server_path, "--config_path", data_path / "config-nano-pow-server.toml"); #else splash->hide (); @@ -158,7 +170,6 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost std::exit (1); #endif } - std::unique_ptr rpc; std::unique_ptr rpc_handler; if (config.rpc_enable) @@ -172,7 +183,7 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost { show_error (error.get_message ()); } - rpc_handler = std::make_unique (*node, config.rpc); + rpc_handler = std::make_unique (*node, ipc, config.rpc); rpc = nano::get_rpc (io_ctx, rpc_config, *rpc_handler); rpc->start (); } @@ -226,7 +237,7 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost splash->hide (); show_error ("Error initializing node"); } - read_and_update_wallet_config (wallet_config, data_path); + write_wallet_config (wallet_config, data_path); } else { @@ -269,7 +280,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - show_error (err.get_message ()); + show_error (nano::network_constants::active_network_err_msg); std::exit (1); } } @@ -320,12 +331,7 @@ int main (int argc, char * const * argv) { throw std::runtime_error (flags_ec.message ()); } - auto config (vm.find ("config")); - if (config != vm.end ()) - { - flags.config_overrides = config->second.as> (); - } - result = run_wallet (application, argc, argv, data_path, config_overrides, flags); + result = run_wallet (application, argc, argv, data_path, flags); } catch (std::exception const & e) { diff --git a/nano/nano_wallet/entry_com.cpp b/nano/nano_wallet/entry_com.cpp index d62226d1eb..d81ef0ac8c 100644 --- a/nano/nano_wallet/entry_com.cpp +++ b/nano/nano_wallet/entry_com.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include +#include #include #include diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index e2c57a3b69..d710a2eb2b 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -24,15 +24,20 @@ add_library (node ${rocksdb_sources} active_transactions.hpp active_transactions.cpp - blockprocessor.cpp blockprocessor.hpp blockprocessor.cpp + bootstrap/bootstrap_attempt.hpp + bootstrap/bootstrap_attempt.cpp bootstrap/bootstrap_bulk_pull.hpp bootstrap/bootstrap_bulk_pull.cpp bootstrap/bootstrap_bulk_push.hpp bootstrap/bootstrap_bulk_push.cpp + bootstrap/bootstrap_connections.hpp + bootstrap/bootstrap_connections.cpp bootstrap/bootstrap_frontier.hpp bootstrap/bootstrap_frontier.cpp + bootstrap/bootstrap_lazy.hpp + bootstrap/bootstrap_lazy.cpp bootstrap/bootstrap_server.hpp bootstrap/bootstrap_server.cpp bootstrap/bootstrap.hpp @@ -41,20 +46,38 @@ add_library (node cli.cpp common.hpp common.cpp + confirmation_height_bounded.hpp + confirmation_height_bounded.cpp confirmation_height_processor.hpp confirmation_height_processor.cpp + confirmation_height_unbounded.hpp + confirmation_height_unbounded.cpp + confirmation_solicitor.hpp + confirmation_solicitor.cpp daemonconfig.hpp daemonconfig.cpp distributed_work.hpp distributed_work.cpp + distributed_work_factory.hpp + distributed_work_factory.cpp election.hpp election.cpp gap_cache.hpp gap_cache.cpp - ipc.hpp - ipc.cpp - ipcconfig.hpp - ipcconfig.cpp + ipc/action_handler.hpp + ipc/action_handler.cpp + ipc/flatbuffers_handler.hpp + ipc/flatbuffers_handler.cpp + ipc/flatbuffers_util.hpp + ipc/flatbuffers_util.cpp + ipc/ipc_access_config.hpp + ipc/ipc_access_config.cpp + ipc/ipc_broker.hpp + ipc/ipc_broker.cpp + ipc/ipc_config.hpp + ipc/ipc_config.cpp + ipc/ipc_server.hpp + ipc/ipc_server.cpp json_handler.hpp json_handler.cpp json_payment_observer.hpp @@ -88,12 +111,24 @@ add_library (node openclwork.cpp payment_observer_processor.hpp payment_observer_processor.cpp + peer_exclusion.hpp + peer_exclusion.cpp portmapping.hpp portmapping.cpp node_pow_server_config.hpp node_pow_server_config.cpp repcrawler.hpp repcrawler.cpp + request_aggregator.hpp + request_aggregator.cpp + signatures.hpp + signatures.cpp + socket.hpp + socket.cpp + state_block_signature_verification.hpp + state_block_signature_verification.cpp + telemetry.hpp + telemetry.cpp testing.hpp testing.cpp transport/tcp.hpp @@ -102,10 +137,6 @@ add_library (node transport/transport.cpp transport/udp.hpp transport/udp.cpp - signatures.hpp - signatures.cpp - socket.hpp - socket.cpp vote_processor.hpp vote_processor.cpp voting.hpp @@ -147,3 +178,7 @@ target_compile_definitions(node PRIVATE -DTAG_VERSION_STRING=${TAG_VERSION_STRING} -DGIT_COMMIT_HASH=${GIT_COMMIT_HASH}) + +# This ensures that any changes to Flatbuffers source files will cause a +# regeneration of any C++ header files. +add_dependencies(node ipc_flatbuffers_lib) diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index afef7a68e7..0636224bce 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -1,25 +1,44 @@ +#include #include +#include +#include +#include #include +#include +#include -#include +#include +#include #include using namespace std::chrono; -nano::active_transactions::active_transactions (nano::node & node_a) : +nano::active_transactions::active_transactions (nano::node & node_a, nano::confirmation_height_processor & confirmation_height_processor_a) : +recently_dropped (node_a.stats), +confirmation_height_processor (confirmation_height_processor_a), node (node_a), -long_election_threshold (node.network_params.network.is_test_network () ? 2s : 24s), -election_request_delay (node.network_params.network.is_test_network () ? 0s : 1s), -election_time_to_live (node.network_params.network.is_test_network () ? 0s : 10s), multipliers_cb (20, 1.), -trended_active_difficulty (node.network_params.network.publish_threshold), -next_frontier_check (steady_clock::now ()), +trended_active_multiplier (1.0), +generator (node_a.config, node_a.ledger, node_a.wallets, node_a.vote_processor, node_a.votes_cache, node_a.network), +check_all_elections_period (node_a.network_params.network.is_test_network () ? 10ms : 5s), +election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 2s), +prioritized_cutoff (std::max (1, node_a.config.active_elections_size / 10)), thread ([this]() { nano::thread_role::set (nano::thread_role::name::request_loop); request_loop (); }) { + // Register a callback which will get called after a block is cemented + confirmation_height_processor.add_cemented_observer ([this](std::shared_ptr callback_block_a) { + this->block_cemented_callback (callback_block_a); + }); + + // Register a callback which will get called if a block is already cemented + confirmation_height_processor.add_block_already_cemented_observer ([this](nano::block_hash const & hash_a) { + this->block_already_cemented_callback (hash_a); + }); + nano::unique_lock lock (mutex); condition.wait (lock, [& started = started] { return started; }); } @@ -29,27 +48,22 @@ nano::active_transactions::~active_transactions () stop (); } -void nano::active_transactions::search_frontiers (nano::transaction const & transaction_a) +void nano::active_transactions::confirm_prioritized_frontiers (nano::transaction const & transaction_a) { // Limit maximum count of elections to start - bool representative (node.config.enable_voting && node.wallets.reps_count > 0); - bool half_princpal_representative (representative && node.wallets.half_principal_reps_count > 0); + auto rep_counts (node.wallets.reps ()); + bool representative (node.config.enable_voting && rep_counts.voting > 0); + bool half_princpal_representative (representative && rep_counts.half_principal > 0); /* Check less frequently for regular nodes in auto mode */ bool agressive_mode (half_princpal_representative || node.config.frontiers_confirmation == nano::frontiers_confirmation_mode::always); - auto request_interval (std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - auto agressive_factor = request_interval * (agressive_mode ? 20 : 100); - // Decrease check time for test network auto is_test_network = node.network_params.network.is_test_network (); - int test_network_factor = is_test_network ? 1000 : 1; auto roots_size = size (); - nano::unique_lock lk (mutex); auto check_time_exceeded = std::chrono::steady_clock::now () >= next_frontier_check; - lk.unlock (); - auto max_elections = (node.config.active_elections_size / 20); + auto max_elections = 1000ull; auto low_active_elections = roots_size < max_elections; bool wallets_check_required = (!skip_wallets || !priority_wallet_cementable_frontiers.empty ()) && !agressive_mode; - // Minimise dropping real-time transactions, set the number of frontiers added to a factor of the total number of active elections - auto max_active = node.config.active_elections_size / 5; + // Minimise dropping real-time transactions, set the number of frontiers added to a factor of the maximum number of possible active elections + auto max_active = node.config.active_elections_size / 20; if (roots_size <= max_active && (check_time_exceeded || wallets_check_required || (!is_test_network && low_active_elections && agressive_mode))) { // When the number of active elections is low increase max number of elections for setting confirmation height. @@ -58,38 +72,34 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran max_elections = max_active - roots_size; } - // Spend time prioritizing accounts to reduce voting traffic - auto time_spent_prioritizing_ledger_accounts = request_interval / 10; - auto time_spent_prioritizing_wallet_accounts = request_interval / 25; - prioritize_frontiers_for_confirmation (transaction_a, is_test_network ? std::chrono::milliseconds (50) : time_spent_prioritizing_ledger_accounts, time_spent_prioritizing_wallet_accounts); - size_t elections_count (0); - lk.lock (); - auto start_elections_for_prioritized_frontiers = [&transaction_a, &elections_count, max_elections, &lk, &representative, this](prioritize_num_uncemented & cementable_frontiers) { + nano::unique_lock lk (mutex); + auto start_elections_for_prioritized_frontiers = [&transaction_a, &elections_count, max_elections, &lk, this](prioritize_num_uncemented & cementable_frontiers) { while (!cementable_frontiers.empty () && !this->stopped && elections_count < max_elections) { - auto cementable_account_front_it = cementable_frontiers.get<1> ().begin (); + auto cementable_account_front_it = cementable_frontiers.get ().begin (); auto cementable_account = *cementable_account_front_it; - cementable_frontiers.get<1> ().erase (cementable_account_front_it); + cementable_frontiers.get ().erase (cementable_account_front_it); lk.unlock (); nano::account_info info; - auto error = node.store.account_get (transaction_a, cementable_account.account, info); + auto error = this->node.store.account_get (transaction_a, cementable_account.account, info); if (!error) { - uint64_t confirmation_height; - error = node.store.confirmation_height_get (transaction_a, cementable_account.account, confirmation_height); - release_assert (!error); - - if (info.block_count > confirmation_height && !this->node.pending_confirmation_height.is_processing_block (info.head)) + if (!this->confirmation_height_processor.is_processing_block (info.head)) { - auto block (this->node.store.block_get (transaction_a, info.head)); - if (!this->start (block, true)) + nano::confirmation_height_info confirmation_height_info; + error = this->node.store.confirmation_height_get (transaction_a, cementable_account.account, confirmation_height_info); + release_assert (!error); + + if (info.block_count > confirmation_height_info.height) { - ++elections_count; - // Calculate votes for local representatives - if (representative) + auto block (this->node.store.block_get (transaction_a, info.head)); + auto previous_balance (this->node.ledger.balance (transaction_a, block->previous ())); + auto insert_result = this->insert (block, previous_balance); + if (insert_result.inserted) { - this->node.block_processor.generator.add (block->hash ()); + insert_result.election->transition_active (); + ++elections_count; } } } @@ -99,344 +109,294 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran }; start_elections_for_prioritized_frontiers (priority_cementable_frontiers); start_elections_for_prioritized_frontiers (priority_wallet_cementable_frontiers); - next_frontier_check = steady_clock::now () + (agressive_factor / test_network_factor); + + auto request_interval (std::chrono::milliseconds (node.network_params.network.request_interval_ms)); + auto rel_time_next_frontier_check = request_interval * (agressive_mode ? 20 : 60); + // Decrease check time for test network + int test_network_factor = is_test_network ? 1000 : 1; + + next_frontier_check = steady_clock::now () + (rel_time_next_frontier_check / test_network_factor); } } -void nano::active_transactions::post_confirmation_height_set (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a) + +void nano::active_transactions::block_cemented_callback (std::shared_ptr const & block_a) { - if (election_status_type_a == nano::election_status_type::inactive_confirmation_height) + auto transaction = node.store.tx_begin_read (); + + boost::optional election_status_type; + if (!confirmation_height_processor.is_processing_block (block_a->hash ())) { - nano::account account (0); - nano::uint128_t amount (0); - bool is_state_send (false); - nano::account pending_account (0); - node.process_confirmed_data (transaction_a, block_a, block_a->hash (), sideband_a, account, amount, is_state_send, pending_account); - node.observers.blocks.notify (nano::election_status{ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, nano::election_status_type::inactive_confirmation_height }, account, amount, is_state_send); + election_status_type = confirm_block (transaction, block_a); } else { - auto hash (block_a->hash ()); - nano::lock_guard lock (mutex); - auto existing (pending_conf_height.find (hash)); - if (existing != pending_conf_height.end ()) - { - auto election = existing->second; - if (election->confirmed && !election->stopped && election->status.winner->hash () == hash) - { - add_confirmed (existing->second->status, block_a->qualified_root ()); - - node.receive_confirmed (transaction_a, block_a, hash); - nano::account account (0); - nano::uint128_t amount (0); - bool is_state_send (false); - nano::account pending_account (0); - node.process_confirmed_data (transaction_a, block_a, hash, sideband_a, account, amount, is_state_send, pending_account); - election->status.type = election_status_type_a; - election->status.confirmation_request_count = election->confirmation_request_count; - node.observers.blocks.notify (election->status, account, amount, is_state_send); - if (amount > 0) - { - node.observers.account_balance.notify (account, false); - if (!pending_account.is_zero ()) - { - node.observers.account_balance.notify (pending_account, true); - } - } - } - - pending_conf_height.erase (hash); - } + // This block was explicitly added to the confirmation height_processor + election_status_type = nano::election_status_type::active_confirmed_quorum; } -} -void nano::active_transactions::election_escalate (std::shared_ptr & election_l, nano::transaction const & transaction_l, size_t const & roots_size_l) -{ - static unsigned constexpr high_confirmation_request_count{ 128 }; - // Log votes for very long unconfirmed elections - if (election_l->confirmation_request_count % (4 * high_confirmation_request_count) == 1) + if (election_status_type.is_initialized ()) { - auto tally_l (election_l->tally ()); - election_l->log_votes (tally_l); - } - /* - * Escalation for long unconfirmed elections - * Start new elections for previous block & source if there are less than 100 active elections - */ - if (election_l->confirmation_request_count % high_confirmation_request_count == 1 && roots_size_l < 100 && !node.network_params.network.is_test_network ()) - { - bool escalated_l (false); - std::shared_ptr previous_l; - auto previous_hash_l (election_l->status.winner->previous ()); - if (!previous_hash_l.is_zero ()) + if (election_status_type == nano::election_status_type::inactive_confirmation_height) { - previous_l = node.store.block_get (transaction_l, previous_hash_l); - if (previous_l != nullptr && blocks.find (previous_hash_l) == blocks.end () && !node.block_confirmed_or_being_confirmed (transaction_l, previous_hash_l)) - { - add (std::move (previous_l), true); - escalated_l = true; - } + nano::account account (0); + nano::uint128_t amount (0); + bool is_state_send (false); + nano::account pending_account (0); + node.process_confirmed_data (transaction, block_a, block_a->hash (), account, amount, is_state_send, pending_account); + node.observers.blocks.notify (nano::election_status{ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::inactive_confirmation_height }, account, amount, is_state_send); } - /* If previous block not existing/not commited yet, block_source can cause segfault for state blocks - So source check can be done only if previous != nullptr or previous is 0 (open account) */ - if (previous_hash_l.is_zero () || previous_l != nullptr) + else { - auto source_hash_l (node.ledger.block_source (transaction_l, *election_l->status.winner)); - if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && blocks.find (source_hash_l) == blocks.end ()) + auto hash (block_a->hash ()); + nano::unique_lock election_winners_lk (election_winner_details_mutex); + auto existing (election_winner_details.find (hash)); + if (existing != election_winner_details.end ()) { - auto source_l (node.store.block_get (transaction_l, source_hash_l)); - if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction_l, source_hash_l)) + auto election = existing->second; + election_winner_details.erase (hash); + election_winners_lk.unlock (); + nano::unique_lock lk (mutex); + if (election->confirmed () && election->status.winner->hash () == hash) { - add (std::move (source_l), true); - escalated_l = true; + add_recently_cemented (election->status); + lk.unlock (); + node.receive_confirmed (transaction, block_a, hash); + nano::account account (0); + nano::uint128_t amount (0); + bool is_state_send (false); + nano::account pending_account (0); + node.process_confirmed_data (transaction, block_a, hash, account, amount, is_state_send, pending_account); + lk.lock (); + election->status.type = *election_status_type; + election->status.confirmation_request_count = election->confirmation_request_count; + auto status (election->status); + lk.unlock (); + node.observers.blocks.notify (status, account, amount, is_state_send); + if (amount > 0) + { + node.observers.account_balance.notify (account, false); + if (!pending_account.is_zero ()) + { + node.observers.account_balance.notify (pending_account, true); + } + } } } } - if (escalated_l) + + // Start or vote for the next unconfirmed block in this account + auto const & account (!block_a->account ().is_zero () ? block_a->account () : block_a->sideband ().account); + debug_assert (!account.is_zero ()); + activate (account); + + // Start or vote for the next unconfirmed block in the destination account + auto const & destination (node.ledger.block_destination (transaction, *block_a)); + if (!destination.is_zero ()) { - election_l->update_dependent (); + activate (destination); } } } -void nano::active_transactions::election_broadcast (std::shared_ptr & election_l, nano::transaction const & transaction_l, std::deque> & blocks_bundle_l, std::unordered_set & inactive_l, nano::qualified_root & root_l) +void nano::active_transactions::add_election_winner_details (nano::block_hash const & hash_a, std::shared_ptr const & election_a) { - if (node.ledger.could_fit (transaction_l, *election_l->status.winner)) - { - // Broadcast current winner - if (blocks_bundle_l.size () < max_block_broadcasts) - { - blocks_bundle_l.push_back (election_l->status.winner); - } - } - else if (election_l->confirmation_request_count != 0) - { - election_l->stop (); - inactive_l.insert (root_l); - } + nano::lock_guard guard (election_winner_details_mutex); + election_winner_details.emplace (hash_a, election_a); } -bool nano::active_transactions::election_request_confirm (std::shared_ptr & election_l, std::vector const & representatives_l, size_t const & roots_size_l, -std::deque, std::shared_ptr>>>> & single_confirm_req_bundle_l, -std::unordered_map, std::deque>> & batched_confirm_req_bundle_l) +void nano::active_transactions::remove_election_winner_details (nano::block_hash const & hash_a) { - bool inserted_into_any_bundle{ false }; - std::vector> rep_channels_missing_vote_l; - // Add all rep endpoints that haven't already voted - for (const auto & rep : representatives_l) - { - if (election_l->last_votes.find (rep.account) == election_l->last_votes.end ()) - { - rep_channels_missing_vote_l.push_back (rep.channel); + nano::lock_guard guard (election_winner_details_mutex); + election_winner_details.erase (hash_a); +} - if (node.config.logging.vote_logging () && election_l->confirmation_request_count > 0) - { - node.logger.try_log ("Representative did not respond to confirm_req, retrying: ", rep.account.to_account ()); - } - } - } - // Unique channels as there can be multiple reps per channel - rep_channels_missing_vote_l.erase (std::unique (rep_channels_missing_vote_l.begin (), rep_channels_missing_vote_l.end ()), rep_channels_missing_vote_l.end ()); - bool low_reps_weight (rep_channels_missing_vote_l.empty () || node.rep_crawler.total_weight () < node.config.online_weight_minimum.number ()); - if (low_reps_weight && roots_size_l <= 5 && !node.network_params.network.is_test_network ()) - { - // Spam mode - auto deque_l (node.network.udp_channels.random_set (100)); - auto vec (std::make_shared>> ()); - for (auto i : deque_l) - { - vec->push_back (i); - } - single_confirm_req_bundle_l.push_back (std::make_pair (election_l->status.winner, vec)); - inserted_into_any_bundle = true; - } - else - { - auto single_confirm_req_channels_l (std::make_shared>> ()); - for (auto & rep : rep_channels_missing_vote_l) - { - if (rep->get_network_version () >= node.network_params.protocol.tcp_realtime_protocol_version_min) - { - // Send batch request to peers supporting confirm_req by hash + root - auto rep_request_l (batched_confirm_req_bundle_l.find (rep)); - auto block_l (election_l->status.winner); - auto root_hash_l (std::make_pair (block_l->hash (), block_l->root ())); - if (rep_request_l == batched_confirm_req_bundle_l.end ()) - { - // Maximum number of representatives - if (batched_confirm_req_bundle_l.size () < max_confirm_representatives) - { - std::deque> insert_root_hash = { root_hash_l }; - batched_confirm_req_bundle_l.insert (std::make_pair (rep, insert_root_hash)); - inserted_into_any_bundle = true; - } - } - // Maximum number of hashes - else if (rep_request_l->second.size () < max_confirm_req_batches * nano::network::confirm_req_hashes_max) - { - rep_request_l->second.push_back (root_hash_l); - inserted_into_any_bundle = true; - } - } - else - { - single_confirm_req_channels_l->push_back (rep); - } - } - // broadcast_confirm_req_base modifies reps, so we clone it once to avoid aliasing - if (single_confirm_req_bundle_l.size () < max_confirm_req && !single_confirm_req_channels_l->empty ()) - { - single_confirm_req_bundle_l.push_back (std::make_pair (election_l->status.winner, single_confirm_req_channels_l)); - inserted_into_any_bundle = true; - } - } - return inserted_into_any_bundle; +void nano::active_transactions::block_already_cemented_callback (nano::block_hash const & hash_a) +{ + // Depending on timing there is a situation where the election_winner_details is not reset. + // This can happen when a block wins an election, and the block is confirmed + observer + // called before the block hash gets added to election_winner_details. If the block is confirmed + // callbacks have already been done, so we can safely just remove it. + remove_election_winner_details (hash_a); } void nano::active_transactions::request_confirm (nano::unique_lock & lock_a) { - assert (!mutex.try_lock ()); - auto transaction_l (node.store.tx_begin_read ()); - std::unordered_set inactive_l; - std::deque> blocks_bundle_l; - std::unordered_map, std::deque>> batched_confirm_req_bundle_l; - std::deque, std::shared_ptr>>>> single_confirm_req_bundle_l; - - /* - * Confirm frontiers when there aren't many confirmations already pending and node finished initial bootstrap - * In auto mode start confirm only if node contains almost principal representative (half of required for principal weight) - */ - - // Due to the confirmation height processor working asynchronously and compressing several roots into one frontier, probably_unconfirmed_frontiers can be wrong - { - auto pending_confirmation_height_size (node.pending_confirmation_height.size ()); - bool probably_unconfirmed_frontiers (node.ledger.block_count_cache > node.ledger.cemented_count + roots.size () + pending_confirmation_height_size); - bool bootstrap_weight_reached (node.ledger.block_count_cache >= node.ledger.bootstrap_weight_max_blocks); - if (node.config.frontiers_confirmation != nano::frontiers_confirmation_mode::disabled && bootstrap_weight_reached && probably_unconfirmed_frontiers && pending_confirmation_height_size < confirmed_frontiers_max_pending_cut_off) - { - lock_a.unlock (); - search_frontiers (transaction_l); - lock_a.lock (); - } - } + debug_assert (!mutex.try_lock ()); - // Any new election started from process_live only gets requests after at least 1 second - auto cutoff_l (std::chrono::steady_clock::now () - election_request_delay); - // Elections taking too long get escalated - auto long_election_cutoff_l (std::chrono::steady_clock::now () - long_election_threshold); - // The lowest PoW difficulty elections have a maximum time to live if they are beyond the soft threshold size for the container - auto election_ttl_cutoff_l (std::chrono::steady_clock::now () - election_time_to_live); + // Only representatives ready to receive batched confirm_req + nano::confirmation_solicitor solicitor (node.network, node.network_params.network); + solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits::max ())); - auto const representatives_l (node.rep_crawler.representatives (std::numeric_limits::max ())); - auto roots_size_l (roots.size ()); - auto & sorted_roots_l = roots.get<1> (); - size_t count_l{ 0 }; + nano::vote_generator_session generator_session (generator); + auto & sorted_roots_l (roots.get ()); + auto const election_ttl_cutoff_l (std::chrono::steady_clock::now () - election_time_to_live); + bool const check_all_elections_l (std::chrono::steady_clock::now () - last_check_all_elections > check_all_elections_period); + size_t const this_loop_target_l (check_all_elections_l ? sorted_roots_l.size () : prioritized_cutoff); + size_t unconfirmed_count_l (0); + nano::timer elapsed (nano::timer_state::started); /* * Loop through active elections in descending order of proof-of-work difficulty, requesting confirmation * * Only up to a certain amount of elections are queued for confirmation request and block rebroadcasting. The remaining elections can still be confirmed if votes arrive - * We avoid selecting the same elections repeatedly in the next loops, through a modulo on confirmation_request_count - * An election only gets confirmation_request_count increased after the first confirm_req; after that it is increased every loop unless they don't fit in the queues * Elections extending the soft config.active_elections_size limit are flushed after a certain time-to-live cutoff * Flushed elections are later re-activated via frontier confirmation */ - for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n; ++i, ++count_l) + for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n && unconfirmed_count_l < this_loop_target_l;) { - auto election_l (i->election); - auto root_l (i->root); - // Erase finished elections - if ((election_l->confirmed || election_l->stopped)) + auto & election_l (i->election); + bool const confirmed_l (election_l->confirmed ()); + + if (!election_l->prioritized () && unconfirmed_count_l < prioritized_cutoff) { - inactive_l.insert (root_l); + election_l->prioritize_election (generator_session); } - // Drop elections - else if (count_l >= node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (root_l)) + + unconfirmed_count_l += !confirmed_l; + bool const overflow_l (unconfirmed_count_l > node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (i->root)); + if (overflow_l || election_l->transition_time (solicitor)) { - election_l->stop (); - inactive_l.insert (root_l); - add_dropped_elections_cache (root_l); + election_l->cleanup (); + i = sorted_roots_l.erase (i); } - // Broadcast and request confirmation - else if (election_l->skip_delay || election_l->election_start < cutoff_l) + else { - bool increment_counter_l{ true }; - // Escalate long election after a certain time and number of requests performed - if (election_l->confirmation_request_count > 4 && election_l->election_start < long_election_cutoff_l) - { - election_escalate (election_l, transaction_l, roots_size_l); - } - // Block broadcasting - if (election_l->confirmation_request_count % 8 == 1 || node.network_params.network.is_test_network ()) - { - election_broadcast (election_l, transaction_l, blocks_bundle_l, inactive_l, root_l); - } - // Confirmation requesting - else if (election_l->confirmation_request_count % 4 == 0) - { - // If failed to insert into any of the bundles (capped), don't increment the counter so that the same root is sent for confirmation in the next loop - if (!election_request_confirm (election_l, representatives_l, roots_size_l, single_confirm_req_bundle_l, batched_confirm_req_bundle_l)) - { - increment_counter_l = false; - } - } - if (increment_counter_l) - { - ++election_l->confirmation_request_count; - } + ++i; } } - ongoing_broadcasts = !blocks_bundle_l.empty () + !batched_confirm_req_bundle_l.empty () + !single_confirm_req_bundle_l.empty (); lock_a.unlock (); + solicitor.flush (); + generator_session.flush (); + lock_a.lock (); + activate_dependencies (lock_a); - // Rebroadcast unconfirmed blocks - if (!blocks_bundle_l.empty ()) + // This is updated after the loop to ensure slow machines don't do the full check often + if (check_all_elections_l) { - node.network.flood_block_many ( - std::move (blocks_bundle_l), [this]() { - { - nano::lock_guard guard_l (this->mutex); - --this->ongoing_broadcasts; - } - this->condition.notify_all (); - }, - 10); // 10ms/block * 30blocks = 300ms < 500ms + last_check_all_elections = std::chrono::steady_clock::now (); + if (node.config.logging.timing_logging () && this_loop_target_l > prioritized_cutoff) + { + node.logger.try_log (boost::str (boost::format ("Processed %1% elections (%2% were already confirmed) in %3% %4%") % this_loop_target_l % (this_loop_target_l - unconfirmed_count_l) % elapsed.value ().count () % elapsed.unit ())); + } } - // Batch confirmation request - if (!batched_confirm_req_bundle_l.empty ()) +} + +void nano::active_transactions::frontiers_confirmation (nano::unique_lock & lock_a) +{ + /* + * Confirm frontiers when there aren't many confirmations already pending and node finished initial bootstrap + */ + auto pending_confirmation_height_size (confirmation_height_processor.awaiting_processing_size ()); + auto bootstrap_weight_reached (node.ledger.cache.block_count >= node.ledger.bootstrap_weight_max_blocks); + auto disabled_confirmation_mode = (node.config.frontiers_confirmation == nano::frontiers_confirmation_mode::disabled); + auto conf_height_capacity_reached = pending_confirmation_height_size > confirmed_frontiers_max_pending_size; + auto all_cemented = node.ledger.cache.block_count == node.ledger.cache.cemented_count; + if (!disabled_confirmation_mode && bootstrap_weight_reached && !conf_height_capacity_reached && !all_cemented) { - node.network.broadcast_confirm_req_batched_many ( - batched_confirm_req_bundle_l, [this]() { - { - nano::lock_guard guard_l (this->mutex); - --this->ongoing_broadcasts; - } - this->condition.notify_all (); - }, - 15); // 15ms/batch * 20batches = 300ms < 500ms + // Spend some time prioritizing accounts with the most uncemented blocks to reduce voting traffic + auto request_interval = std::chrono::milliseconds (node.network_params.network.request_interval_ms); + // Spend longer searching ledger accounts when there is a low amount of elections going on + auto low_active = roots.size () < 1000; + auto time_spent_prioritizing_ledger_accounts = request_interval / (low_active ? 20 : 100); + auto time_spent_prioritizing_wallet_accounts = request_interval / 250; + lock_a.unlock (); + auto transaction = node.store.tx_begin_read (); + prioritize_frontiers_for_confirmation (transaction, node.network_params.network.is_test_network () ? std::chrono::milliseconds (50) : time_spent_prioritizing_ledger_accounts, time_spent_prioritizing_wallet_accounts); + confirm_prioritized_frontiers (transaction); + lock_a.lock (); } - // Single confirmation requests - if (!single_confirm_req_bundle_l.empty ()) +} + +void nano::active_transactions::activate_dependencies (nano::unique_lock & lock_a) +{ + debug_assert (lock_a.owns_lock ()); + decltype (pending_dependencies) pending_l; + pending_l.swap (pending_dependencies); + lock_a.unlock (); + + auto first_unconfirmed = [this](nano::transaction const & transaction_a, nano::account const & account_a, nano::block_hash const & confirmed_frontier_a) { + if (!confirmed_frontier_a.is_zero ()) + { + return this->node.store.block_successor (transaction_a, confirmed_frontier_a); + } + else + { + nano::account_info account_info_l; + auto error = node.store.account_get (transaction_a, account_a, account_info_l); + (void)error; + debug_assert (!error); + return account_info_l.open_block; + } + }; + + // Store blocks to activate when the lock is re-acquired, adding the hash of the original election as a dependency + std::vector, nano::block_hash>> activate_l; { - node.network.broadcast_confirm_req_many ( - single_confirm_req_bundle_l, [this]() { + auto transaction = node.store.tx_begin_read (); + for (auto const & entry_l : pending_l) + { + auto const & hash_l (entry_l.first); + auto const block_l (node.store.block_get (transaction, hash_l)); + if (block_l) { - nano::lock_guard guard_l (this->mutex); - --this->ongoing_broadcasts; + auto const height_l (entry_l.second); + auto const previous_hash_l (block_l->previous ()); + if (!previous_hash_l.is_zero ()) + { + /* Insert first unconfirmed block (pessimistic) and bisect the chain (likelihood) */ + auto const account (node.store.block_account_calculated (*block_l)); + nano::confirmation_height_info conf_info_l; + if (!node.store.confirmation_height_get (transaction, account, conf_info_l)) + { + if (height_l > conf_info_l.height + 1) + { + auto const successor_hash_l = first_unconfirmed (transaction, account, conf_info_l.frontier); + if (!confirmation_height_processor.is_processing_block (successor_hash_l)) + { + auto const successor_l = node.store.block_get (transaction, successor_hash_l); + debug_assert (successor_l != nullptr); + if (successor_l != nullptr) + { + activate_l.emplace_back (successor_l, hash_l); + } + } + } + if (height_l > conf_info_l.height + 2) + { + auto const jumps_l = std::min (128, (height_l - conf_info_l.height) / 2); + auto const backtracked_l (node.ledger.backtrack (transaction, block_l, jumps_l)); + if (backtracked_l != nullptr) + { + activate_l.emplace_back (backtracked_l, hash_l); + } + } + } + } + /* If previous block not existing/not commited yet, block_source can cause segfault for state blocks + So source check can be done only if previous != nullptr or previous is 0 (open account) */ + if (previous_hash_l.is_zero () || node.ledger.block_exists (previous_hash_l)) + { + auto source_hash_l (node.ledger.block_source (transaction, *block_l)); + if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && blocks.find (source_hash_l) == blocks.end ()) + { + auto source_l (node.store.block_get (transaction, source_hash_l)); + if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, source_hash_l)) + { + activate_l.emplace_back (source_l, hash_l); + } + } + } } - this->condition.notify_all (); - }, - 30); // 30~60ms/req * 5 reqs = 150~300ms < 500ms + } } lock_a.lock (); - // Erase inactive elections - for (auto i (inactive_l.begin ()), n (inactive_l.end ()); i != n; ++i) + for (auto const & entry_l : activate_l) { - auto root_it (roots.find (*i)); - if (root_it != roots.end ()) + auto election = insert_impl (entry_l.first); + if (election.inserted) { - root_it->election->clear_blocks (); - root_it->election->clear_dependent (); - roots.erase (root_it); + election.election->transition_active (); + election.election->dependent_blocks.insert (entry_l.second); } } } @@ -454,73 +414,70 @@ void nano::active_transactions::request_loop () lock.lock (); - while (!stopped) + while (!stopped && !node.flags.disable_request_loop) { // Account for the time spent in request_confirm by defining the wakeup point beforehand const auto wakeup_l (std::chrono::steady_clock::now () + std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - update_active_difficulty (lock); + update_adjusted_multiplier (); + // frontiers_confirmation should be above update_active_multiplier to ensure new sorted roots are updated + frontiers_confirmation (lock); + update_active_multiplier (lock); request_confirm (lock); // Sleep until all broadcasts are done, plus the remaining loop time - while (!stopped && ongoing_broadcasts) - { - condition.wait (lock); - } if (!stopped) { - // clang-format off condition.wait_until (lock, wakeup_l, [&wakeup_l, &stopped = stopped] { return stopped || std::chrono::steady_clock::now () >= wakeup_l; }); - // clang-format on } } } void nano::active_transactions::prioritize_account_for_confirmation (nano::active_transactions::prioritize_num_uncemented & cementable_frontiers_a, size_t & cementable_frontiers_size_a, nano::account const & account_a, nano::account_info const & info_a, uint64_t confirmation_height) { - if (info_a.block_count > confirmation_height && !node.pending_confirmation_height.is_processing_block (info_a.head)) + if (info_a.block_count > confirmation_height && !confirmation_height_processor.is_processing_block (info_a.head)) { auto num_uncemented = info_a.block_count - confirmation_height; nano::lock_guard guard (mutex); - auto it = cementable_frontiers_a.find (account_a); - if (it != cementable_frontiers_a.end ()) + auto it = cementable_frontiers_a.get ().find (account_a); + if (it != cementable_frontiers_a.get ().end ()) { if (it->blocks_uncemented != num_uncemented) { // Account already exists and there is now a different uncemented block count so update it in the container - cementable_frontiers_a.modify (it, [num_uncemented](nano::cementable_account & info) { + cementable_frontiers_a.get ().modify (it, [num_uncemented](nano::cementable_account & info) { info.blocks_uncemented = num_uncemented; }); } } else { - assert (cementable_frontiers_size_a <= max_priority_cementable_frontiers); + debug_assert (cementable_frontiers_size_a <= max_priority_cementable_frontiers); if (cementable_frontiers_size_a == max_priority_cementable_frontiers) { // The maximum amount of frontiers stored has been reached. Check if the current frontier // has more uncemented blocks than the lowest uncemented frontier in the collection if so replace it. - auto least_uncemented_frontier_it = cementable_frontiers_a.get<1> ().end (); + auto least_uncemented_frontier_it = cementable_frontiers_a.get ().end (); --least_uncemented_frontier_it; if (num_uncemented > least_uncemented_frontier_it->blocks_uncemented) { - cementable_frontiers_a.get<1> ().erase (least_uncemented_frontier_it); - cementable_frontiers_a.emplace (account_a, num_uncemented); + cementable_frontiers_a.get ().erase (least_uncemented_frontier_it); + cementable_frontiers_a.get ().emplace (account_a, num_uncemented); } } else { - cementable_frontiers_a.emplace (account_a, num_uncemented); + cementable_frontiers_a.get ().emplace (account_a, num_uncemented); } } cementable_frontiers_size_a = cementable_frontiers_a.size (); } } -void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::transaction const & transaction_a, std::chrono::milliseconds ledger_accounts_time_a, std::chrono::milliseconds wallet_account_time_a) +void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::transaction const & transaction_a, std::chrono::milliseconds ledger_account_traversal_max_time_a, std::chrono::milliseconds wallet_account_traversal_max_time_a) { // Don't try to prioritize when there are a large number of pending confirmation heights as blocks can be cemented in the meantime, making the prioritization less reliable - if (node.pending_confirmation_height.size () < confirmed_frontiers_max_pending_cut_off) + if (confirmation_height_processor.awaiting_processing_size () < confirmed_frontiers_max_pending_size) { size_t priority_cementable_frontiers_size; size_t priority_wallet_cementable_frontiers_size; @@ -559,11 +516,11 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra auto i (wallet->store.begin (wallet_transaction, next_wallet_frontier_account)); auto n (wallet->store.end ()); - uint64_t confirmation_height = 0; + nano::confirmation_height_info confirmation_height_info; for (; i != n; ++i) { auto const & account (i->first); - if (!node.store.account_get (transaction_a, account, info) && !node.store.confirmation_height_get (transaction_a, account, confirmation_height)) + if (!node.store.account_get (transaction_a, account, info) && !node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) { // If it exists in normal priority collection delete from there. auto it = priority_cementable_frontiers.find (account); @@ -574,9 +531,9 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra priority_cementable_frontiers_size = priority_cementable_frontiers.size (); } - prioritize_account_for_confirmation (priority_wallet_cementable_frontiers, priority_wallet_cementable_frontiers_size, account, info, confirmation_height); + prioritize_account_for_confirmation (priority_wallet_cementable_frontiers, priority_wallet_cementable_frontiers_size, account, info, confirmation_height_info.height); - if (wallet_account_timer.since_start () >= wallet_account_time_a) + if (wallet_account_timer.since_start () >= wallet_account_traversal_max_time_a) { break; } @@ -605,20 +562,20 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra auto i (node.store.latest_begin (transaction_a, next_frontier_account)); auto n (node.store.latest_end ()); - uint64_t confirmation_height = 0; + nano::confirmation_height_info confirmation_height_info; for (; i != n && !stopped; ++i) { auto const & account (i->first); auto const & info (i->second); if (priority_wallet_cementable_frontiers.find (account) == priority_wallet_cementable_frontiers.end ()) { - if (!node.store.confirmation_height_get (transaction_a, account, confirmation_height)) + if (!node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) { - prioritize_account_for_confirmation (priority_cementable_frontiers, priority_cementable_frontiers_size, account, info, confirmation_height); + prioritize_account_for_confirmation (priority_cementable_frontiers, priority_cementable_frontiers_size, account, info, confirmation_height_info.height); } } next_frontier_account = account.number () + 1; - if (timer.since_start () >= ledger_accounts_time_a) + if (timer.since_start () >= ledger_account_traversal_max_time_a) { break; } @@ -647,95 +604,143 @@ void nano::active_transactions::stop () { thread.join (); } + generator.stop (); lock.lock (); roots.clear (); } -bool nano::active_transactions::start (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) -{ - nano::lock_guard lock (mutex); - return add (block_a, skip_delay_a, confirmation_action_a); -} - -bool nano::active_transactions::add (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) +nano::election_insertion_result nano::active_transactions::insert_impl (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, std::function)> const & confirmation_action_a) { - auto error (true); + debug_assert (block_a->has_sideband ()); + nano::election_insertion_result result; if (!stopped) { auto root (block_a->qualified_root ()); - auto existing (roots.find (root)); - if (existing == roots.end () && confirmed_set.get<1> ().find (root) == confirmed_set.get<1> ().end ()) + auto existing (roots.get ().find (root)); + if (existing == roots.get ().end ()) { - auto hash (block_a->hash ()); - auto election (nano::make_shared (node, block_a, skip_delay_a, confirmation_action_a)); - uint64_t difficulty (0); - error = nano::work_validate (*block_a, &difficulty); - release_assert (!error); - roots.insert (nano::conflict_info{ root, difficulty, difficulty, election }); - blocks.insert (std::make_pair (hash, election)); - adjust_difficulty (hash); - election->insert_inactive_votes_cache (); + if (recently_confirmed.get ().find (root) == recently_confirmed.get ().end ()) + { + result.inserted = true; + auto hash (block_a->hash ()); + auto epoch (block_a->sideband ().details.epoch); + nano::uint128_t previous_balance (previous_balance_a.value_or (0)); + debug_assert (!(previous_balance_a.value_or (0) > 0 && block_a->previous ().is_zero ())); + if (!previous_balance_a.is_initialized () && !block_a->previous ().is_zero ()) + { + auto transaction (node.store.tx_begin_read ()); + if (node.ledger.block_exists (block_a->previous ())) + { + previous_balance = node.ledger.balance (transaction, block_a->previous ()); + } + } + double multiplier (normalized_multiplier (*block_a)); + bool prioritized = roots.size () < prioritized_cutoff || multiplier > last_prioritized_multiplier.value_or (0); + result.election = nano::make_shared (node, block_a, confirmation_action_a, prioritized); + roots.get ().emplace (nano::active_transactions::conflict_info{ root, multiplier, multiplier, result.election, epoch, previous_balance }); + blocks.emplace (hash, result.election); + add_adjust_difficulty (hash); + result.election->insert_inactive_votes_cache (hash); + node.stats.inc (nano::stat::type::election, prioritized ? nano::stat::detail::election_priority : nano::stat::detail::election_non_priority); + } + } + else + { + result.election = existing->election; } } - return error; + return result; +} + +nano::election_insertion_result nano::active_transactions::insert (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, std::function)> const & confirmation_action_a) +{ + nano::lock_guard lock (mutex); + return insert_impl (block_a, previous_balance_a, confirmation_action_a); } // Validate a vote and apply it to the current election if one exists -bool nano::active_transactions::vote (std::shared_ptr vote_a, bool single_lock) +nano::vote_code nano::active_transactions::vote (std::shared_ptr vote_a) { - std::shared_ptr election; + // If none of the hashes are active, votes are not republished + bool at_least_one (false); + // If all hashes were recently confirmed then it is a replay + unsigned recently_confirmed_counter (0); bool replay (false); bool processed (false); { - nano::unique_lock lock; - if (!single_lock) - { - lock = nano::unique_lock (mutex); - } + nano::lock_guard lock (mutex); for (auto vote_block : vote_a->blocks) { nano::election_vote_result result; + auto & recently_confirmed_by_hash (recently_confirmed.get ()); if (vote_block.which ()) { auto block_hash (boost::get (vote_block)); auto existing (blocks.find (block_hash)); if (existing != blocks.end ()) { + at_least_one = true; result = existing->second->vote (vote_a->account, vote_a->sequence, block_hash); } - else + else if (recently_confirmed_by_hash.count (block_hash) == 0) { add_inactive_votes_cache (block_hash, vote_a->account); } + else + { + ++recently_confirmed_counter; + } } else { auto block (boost::get> (vote_block)); - auto existing (roots.find (block->qualified_root ())); - if (existing != roots.end ()) + auto existing (roots.get ().find (block->qualified_root ())); + if (existing != roots.get ().end ()) { + at_least_one = true; result = existing->election->vote (vote_a->account, vote_a->sequence, block->hash ()); } - else + else if (recently_confirmed_by_hash.count (block->hash ()) == 0) { add_inactive_votes_cache (block->hash (), vote_a->account); } + else + { + ++recently_confirmed_counter; + } } - replay = replay || result.replay; processed = processed || result.processed; + replay = replay || result.replay; } } - if (processed) + + if (at_least_one) { - node.network.flood_vote (vote_a); + // Republish vote if it is new and the node does not host a principal representative (or close to) + if (processed) + { + auto const reps (node.wallets.reps ()); + if (!reps.have_half_rep () && !reps.exists (vote_a->account)) + { + node.network.flood_vote (vote_a, 0.5f); + } + } + return replay ? nano::vote_code::replay : nano::vote_code::vote; + } + else if (recently_confirmed_counter == vote_a->blocks.size ()) + { + return nano::vote_code::replay; + } + else + { + return nano::vote_code::indeterminate; } - return replay; } bool nano::active_transactions::active (nano::qualified_root const & root_a) { nano::lock_guard lock (mutex); - return roots.find (root_a) != roots.end (); + return roots.get ().find (root_a) != roots.get ().end (); } bool nano::active_transactions::active (nano::block const & block_a) @@ -743,245 +748,382 @@ bool nano::active_transactions::active (nano::block const & block_a) return active (block_a.qualified_root ()); } -void nano::active_transactions::update_difficulty (std::shared_ptr block_a, boost::optional opt_transaction_a) +std::shared_ptr nano::active_transactions::election (nano::qualified_root const & root_a) const { - nano::unique_lock lock (mutex); - auto existing_election (roots.find (block_a->qualified_root ())); - if (existing_election != roots.end ()) + std::shared_ptr result; + nano::lock_guard lock (mutex); + auto existing = roots.get ().find (root_a); + if (existing != roots.get ().end ()) { - uint64_t difficulty; - auto error (nano::work_validate (*block_a, &difficulty)); - (void)error; - assert (!error); - if (difficulty > existing_election->difficulty) + result = existing->election; + } + return result; +} + +std::shared_ptr nano::active_transactions::winner (nano::block_hash const & hash_a) const +{ + std::shared_ptr result; + nano::lock_guard lock (mutex); + auto existing = blocks.find (hash_a); + if (existing != blocks.end ()) + { + result = existing->second->status.winner; + } + return result; +} + +nano::election_insertion_result nano::active_transactions::activate (nano::account const & account_a) +{ + nano::election_insertion_result result; + auto transaction (node.store.tx_begin_read ()); + nano::account_info account_info; + if (!node.store.account_get (transaction, account_a, account_info)) + { + nano::confirmation_height_info conf_info; + auto error = node.store.confirmation_height_get (transaction, account_a, conf_info); + debug_assert (!error); + if (!error && conf_info.height < account_info.block_count) { - if (node.config.logging.active_update_logging ()) + debug_assert (conf_info.frontier != account_info.head); + auto hash = conf_info.height == 0 ? account_info.open_block : node.store.block_successor (transaction, conf_info.frontier); + auto block = node.store.block_get (transaction, hash); + release_assert (block != nullptr); + if (node.ledger.can_vote (transaction, *block)) { - node.logger.try_log (boost::str (boost::format ("Block %1% was updated from difficulty %2% to %3%") % block_a->hash ().to_string () % nano::to_string_hex (existing_election->difficulty) % nano::to_string_hex (difficulty))); + result = insert (block); + if (result.election) + { + if (result.inserted) + { + result.election->transition_active (); + } + else if (result.election->prioritized ()) + { + // Generate vote for ongoing election + result.election->generate_votes (block->hash ()); + } + } } - roots.modify (existing_election, [difficulty](nano::conflict_info & info_a) { - info_a.difficulty = difficulty; - }); - adjust_difficulty (block_a->hash ()); } } - else if (opt_transaction_a.is_initialized ()) + return result; +} + +bool nano::active_transactions::update_difficulty (nano::block const & block_a) +{ + nano::lock_guard guard (mutex); + auto existing_election (roots.get ().find (block_a.qualified_root ())); + bool error = existing_election == roots.get ().end () || update_difficulty_impl (existing_election, block_a); + return error; +} + +bool nano::active_transactions::update_difficulty_impl (nano::active_transactions::roots_iterator const & root_it_a, nano::block const & block_a) +{ + debug_assert (!mutex.try_lock ()); + double multiplier (normalized_multiplier (block_a, root_it_a)); + bool error = multiplier <= root_it_a->multiplier; + if (!error) { - // Only guaranteed to immediately restart the election if the new block is received within 60s of dropping it - static constexpr std::chrono::seconds recently_dropped_cutoff{ 60s }; - if (find_dropped_elections_cache (block_a->qualified_root ()) > std::chrono::steady_clock::now () - recently_dropped_cutoff) + if (node.config.logging.active_update_logging ()) { - lock.unlock (); - nano::block_sideband existing_sideband; - auto hash (block_a->hash ()); - auto existing_block (node.store.block_get (*opt_transaction_a, hash, &existing_sideband)); - release_assert (existing_block != nullptr); - uint64_t confirmation_height; - release_assert (!node.store.confirmation_height_get (*opt_transaction_a, node.store.block_account (*opt_transaction_a, hash), confirmation_height)); - bool confirmed = (confirmation_height >= existing_sideband.height); - if (!confirmed && existing_block->block_work () != block_a->block_work ()) + node.logger.try_log (boost::str (boost::format ("Election %1% difficulty updated with block %2% from multiplier %3% to %4%") % root_it_a->root.to_string () % block_a.hash ().to_string () % root_it_a->multiplier % multiplier)); + } + roots.get ().modify (root_it_a, [multiplier](nano::active_transactions::conflict_info & info_a) { + info_a.multiplier = multiplier; + }); + add_adjust_difficulty (block_a.hash ()); + node.stats.inc (nano::stat::type::election, nano::stat::detail::election_difficulty_update); + } + return error; +} + +bool nano::active_transactions::restart (std::shared_ptr const & block_a, nano::write_transaction const & transaction_a) +{ + // Only guaranteed to restart the election if the new block is received within 2 minutes of its election being dropped + constexpr std::chrono::minutes recently_dropped_cutoff{ 2 }; + bool error = true; + if (recently_dropped.find (block_a->qualified_root ()) > std::chrono::steady_clock::now () - recently_dropped_cutoff) + { + auto hash (block_a->hash ()); + auto ledger_block (node.store.block_get (transaction_a, hash)); + if (ledger_block != nullptr && ledger_block->block_work () != block_a->block_work () && !node.block_confirmed_or_being_confirmed (transaction_a, hash)) + { + if (block_a->difficulty () > ledger_block->difficulty ()) { - uint64_t existing_difficulty; - uint64_t new_difficulty; - if (!nano::work_validate (*block_a, &new_difficulty) && !nano::work_validate (*existing_block, &existing_difficulty)) + // Re-writing the block is necessary to avoid the same work being received later to force restarting the election + // The existing block is re-written, not the arriving block, as that one might not have gone through a full signature check + ledger_block->block_work_set (block_a->block_work ()); + + auto block_count = node.ledger.cache.block_count.load (); + node.store.block_put (transaction_a, hash, *ledger_block); + debug_assert (node.ledger.cache.block_count.load () == block_count); + + // Restart election for the upgraded block, previously dropped from elections + auto previous_balance = node.ledger.balance (transaction_a, ledger_block->previous ()); + auto insert_result = insert (ledger_block, previous_balance); + if (insert_result.inserted) { - if (new_difficulty > existing_difficulty) - { - // Re-writing the block is necessary to avoid the same work being received later to force restarting the election - // The existing block is re-written, not the arriving block, as that one might not have gone through a full signature check - existing_block->block_work_set (block_a->block_work ()); - node.store.block_put (*opt_transaction_a, hash, *existing_block, existing_sideband); - - // Restart election for the upgraded block, previously dropped from elections - lock.lock (); - add (existing_block); - } + error = false; + insert_result.election->transition_active (); + recently_dropped.erase (ledger_block->qualified_root ()); + node.stats.inc (nano::stat::type::election, nano::stat::detail::election_restart); } } } } + return error; } -void nano::active_transactions::adjust_difficulty (nano::block_hash const & hash_a) +double nano::active_transactions::normalized_multiplier (nano::block const & block_a, boost::optional const & root_it_a) const { - assert (!mutex.try_lock ()); - std::deque> remaining_blocks; - remaining_blocks.emplace_back (hash_a, 0); + debug_assert (!mutex.try_lock ()); + auto difficulty (block_a.difficulty ()); + uint64_t threshold (0); + bool sideband_not_found (false); + if (block_a.has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), block_a.sideband ().details); + } + else if (root_it_a.is_initialized ()) + { + auto election (*root_it_a); + debug_assert (election != roots.end ()); + auto find_block (election->election->blocks.find (block_a.hash ())); + if (find_block != election->election->blocks.end () && find_block->second->has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), find_block->second->sideband ().details); + } + else + { + // This can have incorrect results during an epoch upgrade, but it only affects prioritization + bool is_send = election->previous_balance > block_a.balance ().number (); + bool is_receive = election->previous_balance < block_a.balance ().number (); + nano::block_details details (election->epoch, is_send, is_receive, false); + + threshold = nano::work_threshold (block_a.work_version (), details); + sideband_not_found = true; + } + } + double multiplier (nano::difficulty::to_multiplier (difficulty, threshold)); + debug_assert (multiplier >= 1 || sideband_not_found); + if (multiplier >= 1) + { + multiplier = nano::normalized_multiplier (multiplier, threshold); + } + else + { + // Inferred threshold was incorrect + multiplier = 1; + } + return multiplier; +} + +void nano::active_transactions::add_adjust_difficulty (nano::block_hash const & hash_a) +{ + debug_assert (!mutex.try_lock ()); + adjust_difficulty_list.push_back (hash_a); +} + +void nano::active_transactions::update_adjusted_multiplier () +{ + debug_assert (!mutex.try_lock ()); std::unordered_set processed_blocks; - std::vector> elections_list; - double sum (0.); - int64_t highest_level (0); - int64_t lowest_level (0); - while (!remaining_blocks.empty ()) + while (!adjust_difficulty_list.empty ()) { - auto const & item (remaining_blocks.front ()); - auto hash (item.first); - auto level (item.second); - if (processed_blocks.find (hash) == processed_blocks.end ()) + auto const & adjust_difficulty_item (adjust_difficulty_list.front ()); + std::deque> remaining_blocks; + remaining_blocks.emplace_back (adjust_difficulty_item, 0); + adjust_difficulty_list.pop_front (); + std::vector> elections_list; + double sum (0.); + int64_t highest_level (0); + int64_t lowest_level (0); + while (!remaining_blocks.empty ()) { - auto existing (blocks.find (hash)); - if (existing != blocks.end () && !existing->second->confirmed && !existing->second->stopped && existing->second->status.winner->hash () == hash) + auto const & item (remaining_blocks.front ()); + auto hash (item.first); + auto level (item.second); + if (processed_blocks.find (hash) == processed_blocks.end ()) { - auto previous (existing->second->status.winner->previous ()); - if (!previous.is_zero ()) + auto existing (blocks.find (hash)); + if (existing != blocks.end () && !existing->second->confirmed () && existing->second->status.winner->hash () == hash) { - remaining_blocks.emplace_back (previous, level + 1); - } - auto source (existing->second->status.winner->source ()); - if (!source.is_zero () && source != previous) - { - remaining_blocks.emplace_back (source, level + 1); - } - auto link (existing->second->status.winner->link ()); - if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous) - { - remaining_blocks.emplace_back (link, level + 1); - } - for (auto & dependent_block : existing->second->dependent_blocks) - { - remaining_blocks.emplace_back (dependent_block, level - 1); - } - processed_blocks.insert (hash); - nano::qualified_root root (previous, existing->second->status.winner->root ()); - auto existing_root (roots.find (root)); - if (existing_root != roots.end ()) - { - sum += nano::difficulty::to_multiplier (existing_root->difficulty, node.network_params.network.publish_threshold); - elections_list.emplace_back (root, level); - if (level > highest_level) + auto previous (existing->second->status.winner->previous ()); + if (!previous.is_zero ()) + { + remaining_blocks.emplace_back (previous, level + 1); + } + auto source (existing->second->status.winner->source ()); + if (!source.is_zero () && source != previous) + { + remaining_blocks.emplace_back (source, level + 1); + } + auto link (existing->second->status.winner->link ()); + if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous) { - highest_level = level; + remaining_blocks.emplace_back (link, level + 1); } - else if (level < lowest_level) + for (auto & dependent_block : existing->second->dependent_blocks) { - lowest_level = level; + remaining_blocks.emplace_back (dependent_block, level - 1); + } + processed_blocks.insert (hash); + nano::qualified_root root (previous, existing->second->status.winner->root ()); + auto existing_root (roots.get ().find (root)); + if (existing_root != roots.get ().end ()) + { + sum += existing_root->multiplier; + elections_list.emplace_back (root, level); + if (level > highest_level) + { + highest_level = level; + } + else if (level < lowest_level) + { + lowest_level = level; + } } } } + remaining_blocks.pop_front (); } - remaining_blocks.pop_front (); - } - if (!elections_list.empty ()) - { - double multiplier = sum / elections_list.size (); - uint64_t average = nano::difficulty::from_multiplier (multiplier, node.network_params.network.publish_threshold); - // Prevent overflow - int64_t limiter (0); - if (std::numeric_limits::max () - average < static_cast (highest_level)) - { - // Highest adjusted difficulty value should be std::numeric_limits::max () - limiter = std::numeric_limits::max () - average + highest_level; - assert (std::numeric_limits::max () == average + highest_level - limiter); - } - else if (average < std::numeric_limits::min () - lowest_level) + if (!elections_list.empty ()) { - // Lowest adjusted difficulty value should be std::numeric_limits::min () - limiter = std::numeric_limits::min () - average + lowest_level; - assert (std::numeric_limits::min () == average + lowest_level - limiter); - } + double avg_multiplier = sum / elections_list.size (); + double min_unit = 32.0 * avg_multiplier * std::numeric_limits::epsilon (); + debug_assert (min_unit > 0); - // Set adjusted difficulty - for (auto & item : elections_list) - { - auto existing_root (roots.find (item.first)); - uint64_t difficulty_a = average + item.second - limiter; - roots.modify (existing_root, [difficulty_a](nano::conflict_info & info_a) { - info_a.adjusted_difficulty = difficulty_a; - }); + // Set adjusted multiplier + for (auto & item : elections_list) + { + auto existing_root (roots.get ().find (item.first)); + double multiplier_a = avg_multiplier + (double)item.second * min_unit; + if (existing_root->adjusted_multiplier != multiplier_a) + { + roots.get ().modify (existing_root, [multiplier_a](nano::active_transactions::conflict_info & info_a) { + info_a.adjusted_multiplier = multiplier_a; + }); + } + } } } } -void nano::active_transactions::update_active_difficulty (nano::unique_lock & lock_a) +void nano::active_transactions::update_active_multiplier (nano::unique_lock & lock_a) { - assert (!mutex.try_lock ()); + debug_assert (!mutex.try_lock ()); + last_prioritized_multiplier.reset (); double multiplier (1.); - if (!roots.empty ()) + // Heurestic to filter out non-saturated network and frontier confirmation + if (roots.size () >= prioritized_cutoff || (node.network_params.network.is_test_network () && !roots.empty ())) { - auto & sorted_roots = roots.get<1> (); - std::vector active_root_difficulties; - active_root_difficulties.reserve (std::min (roots.size (), node.config.active_elections_size)); - size_t count (0); - auto cutoff (std::chrono::steady_clock::now () - election_request_delay - 1s); - for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && count++ < node.config.active_elections_size; ++it) + auto & sorted_roots = roots.get (); + std::vector prioritized; + prioritized.reserve (std::min (sorted_roots.size (), prioritized_cutoff)); + for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && prioritized.size () < prioritized_cutoff; ++it) { - if (!it->election->confirmed && !it->election->stopped && it->election->election_start < cutoff) + if (!it->election->confirmed ()) { - active_root_difficulties.push_back (it->adjusted_difficulty); + prioritized.push_back (it->adjusted_multiplier); } } - if (active_root_difficulties.size () > 10 || (!active_root_difficulties.empty () && node.network_params.network.is_test_network ())) + if (prioritized.size () > 10 || (node.network_params.network.is_test_network () && !prioritized.empty ())) { - multiplier = nano::difficulty::to_multiplier (active_root_difficulties[active_root_difficulties.size () / 2], node.network_params.network.publish_threshold); + multiplier = prioritized[prioritized.size () / 2]; + } + if (!prioritized.empty ()) + { + last_prioritized_multiplier = prioritized.back (); } } - assert (multiplier >= 1); + debug_assert (multiplier >= nano::difficulty::to_multiplier (node.network_params.network.publish_thresholds.entry, node.network_params.network.publish_thresholds.base)); multipliers_cb.push_front (multiplier); auto sum (std::accumulate (multipliers_cb.begin (), multipliers_cb.end (), double(0))); - auto difficulty = nano::difficulty::from_multiplier (sum / multipliers_cb.size (), node.network_params.network.publish_threshold); - assert (difficulty >= node.network_params.network.publish_threshold); + double avg_multiplier (sum / multipliers_cb.size ()); + auto difficulty = nano::difficulty::from_multiplier (avg_multiplier, node.default_difficulty (nano::work_version::work_1)); + debug_assert (difficulty >= node.network_params.network.publish_thresholds.entry); - trended_active_difficulty = difficulty; - node.observers.difficulty.notify (trended_active_difficulty); + trended_active_multiplier = avg_multiplier; + node.observers.difficulty.notify (difficulty); } uint64_t nano::active_transactions::active_difficulty () { - nano::lock_guard lock (mutex); - return trended_active_difficulty; + return nano::difficulty::from_multiplier (active_multiplier (), node.default_difficulty (nano::work_version::work_1)); +} + +uint64_t nano::active_transactions::limited_active_difficulty (nano::block const & block_a) +{ + uint64_t threshold (0); + if (block_a.has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), block_a.sideband ().details); + } + else + { + threshold = node.default_difficulty (block_a.work_version ()); + } + return limited_active_difficulty (block_a.work_version (), threshold); +} + +uint64_t nano::active_transactions::limited_active_difficulty (nano::work_version const version_a, uint64_t const threshold_a) +{ + auto difficulty (nano::difficulty::from_multiplier (nano::denormalized_multiplier (active_multiplier (), threshold_a), threshold_a)); + return std::min (difficulty, node.max_work_generate_difficulty (version_a)); } -uint64_t nano::active_transactions::limited_active_difficulty () +double nano::active_transactions::active_multiplier () { - return std::min (active_difficulty (), node.config.max_work_generate_difficulty); + nano::lock_guard lock (mutex); + return trended_active_multiplier; } // List of active blocks in elections -std::deque> nano::active_transactions::list_blocks (bool single_lock) +std::deque> nano::active_transactions::list_blocks () { std::deque> result; - nano::unique_lock lock; - if (!single_lock) - { - lock = nano::unique_lock (mutex); - } - for (auto i (roots.begin ()), n (roots.end ()); i != n; ++i) + nano::lock_guard lock (mutex); + for (auto & root : roots) { - result.push_back (i->election->status.winner); + result.push_back (root.election->status.winner); } return result; } -std::deque nano::active_transactions::list_confirmed () +std::deque nano::active_transactions::list_recently_cemented () { nano::lock_guard lock (mutex); - return confirmed; + return recently_cemented; } -void nano::active_transactions::add_confirmed (nano::election_status const & status_a, nano::qualified_root const & root_a) +void nano::active_transactions::add_recently_cemented (nano::election_status const & status_a) { - confirmed.push_back (status_a); - auto inserted (confirmed_set.insert (nano::election_timepoint{ std::chrono::steady_clock::now (), root_a })); - if (confirmed.size () > node.config.confirmation_history_size) + recently_cemented.push_back (status_a); + if (recently_cemented.size () > node.config.confirmation_history_size) { - confirmed.pop_front (); - if (inserted.second) - { - confirmed_set.erase (confirmed_set.begin ()); - } + recently_cemented.pop_front (); + } +} + +void nano::active_transactions::add_recently_confirmed (nano::qualified_root const & root_a, nano::block_hash const & hash_a) +{ + recently_confirmed.get ().emplace_back (root_a, hash_a); + if (recently_confirmed.size () > recently_confirmed_size) + { + recently_confirmed.get ().pop_front (); } } void nano::active_transactions::erase (nano::block const & block_a) { - nano::lock_guard lock (mutex); - auto root_it (roots.find (block_a.qualified_root ())); - if (root_it != roots.end ()) + nano::unique_lock lock (mutex); + auto root_it (roots.get ().find (block_a.qualified_root ())); + if (root_it != roots.get ().end ()) { - root_it->election->stop (); - root_it->election->clear_blocks (); - root_it->election->clear_dependent (); - roots.erase (root_it); + root_it->election->cleanup (); + root_it->election->adjust_dependent_difficulty (); + roots.get ().erase (root_it); + lock.unlock (); node.logger.try_log (boost::str (boost::format ("Election erased for block block %1% root %2%") % block_a.hash ().to_string () % block_a.root ().to_string ())); } } @@ -1001,48 +1143,58 @@ size_t nano::active_transactions::size () bool nano::active_transactions::publish (std::shared_ptr block_a) { nano::lock_guard lock (mutex); - auto existing (roots.find (block_a->qualified_root ())); + auto existing (roots.get ().find (block_a->qualified_root ())); auto result (true); - if (existing != roots.end ()) + if (existing != roots.get ().end ()) { + update_difficulty_impl (existing, *block_a); auto election (existing->election); result = election->publish (block_a); - if (!result && !election->confirmed) + if (!result) { - blocks.insert (std::make_pair (block_a->hash (), election)); + blocks.emplace (block_a->hash (), election); + node.stats.inc (nano::stat::type::election, nano::stat::detail::election_block_conflict); } } return result; } -void nano::active_transactions::clear_block (nano::block_hash const & hash_a) -{ - nano::lock_guard guard (mutex); - pending_conf_height.erase (hash_a); -} - // Returns the type of election status requiring callbacks calling later boost::optional nano::active_transactions::confirm_block (nano::transaction const & transaction_a, std::shared_ptr block_a) { auto hash (block_a->hash ()); nano::unique_lock lock (mutex); auto existing (blocks.find (hash)); + boost::optional status_type; if (existing != blocks.end ()) { - if (!existing->second->confirmed && !existing->second->stopped && existing->second->status.winner->hash () == hash) + if (existing->second->status.winner && existing->second->status.winner->hash () == hash) { - existing->second->confirm_once (nano::election_status_type::active_confirmation_height); - return nano::election_status_type::active_confirmation_height; + if (!existing->second->confirmed ()) + { + existing->second->confirm_once (nano::election_status_type::active_confirmation_height); + status_type = nano::election_status_type::active_confirmation_height; + } + else + { +#ifndef NDEBUG + nano::unique_lock election_winners_lk (election_winner_details_mutex); + debug_assert (election_winner_details.find (hash) != election_winner_details.cend ()); +#endif + status_type = nano::election_status_type::active_confirmed_quorum; + } } else { - return boost::optional{}; + status_type = boost::optional{}; } } else { - return nano::election_status_type::inactive_confirmation_height; + status_type = nano::election_status_type::inactive_confirmation_height; } + + return status_type; } size_t nano::active_transactions::priority_cementable_frontiers_size () @@ -1074,82 +1226,121 @@ void nano::active_transactions::add_inactive_votes_cache (nano::block_hash const // Check principal representative status if (node.ledger.weight (representative_a) > node.minimum_principal_weight ()) { - auto existing (inactive_votes_cache.get<1> ().find (hash_a)); - if (existing != inactive_votes_cache.get<1> ().end () && !existing->confirmed) + auto & inactive_by_hash (inactive_votes_cache.get ()); + auto existing (inactive_by_hash.find (hash_a)); + if (existing != inactive_by_hash.end ()) { - auto is_new (false); - inactive_votes_cache.get<1> ().modify (existing, [representative_a, &is_new](nano::gap_information & info) { - auto it = std::find (info.voters.begin (), info.voters.end (), representative_a); - is_new = (it == info.voters.end ()); - if (is_new) - { - info.arrival = std::chrono::steady_clock::now (); - info.voters.push_back (representative_a); - } - }); - - if (is_new) + if (!existing->confirmed || !existing->bootstrap_started) { - if (node.gap_cache.bootstrap_check (existing->voters, hash_a)) + auto is_new (false); + inactive_by_hash.modify (existing, [representative_a, &is_new](nano::inactive_cache_information & info) { + auto it = std::find (info.voters.begin (), info.voters.end (), representative_a); + is_new = (it == info.voters.end ()); + if (is_new) + { + info.arrival = std::chrono::steady_clock::now (); + info.voters.push_back (representative_a); + } + }); + + if (is_new) { - inactive_votes_cache.get<1> ().modify (existing, [](nano::gap_information & info) { - info.confirmed = true; - }); + bool confirmed (false); + if (inactive_votes_bootstrap_check (existing->voters, hash_a, confirmed) && !existing->bootstrap_started) + { + inactive_by_hash.modify (existing, [](nano::inactive_cache_information & info) { + info.bootstrap_started = true; + }); + } + if (confirmed && !existing->confirmed) + { + inactive_by_hash.modify (existing, [](nano::inactive_cache_information & info) { + info.confirmed = true; + }); + } } } } else { - inactive_votes_cache.insert ({ std::chrono::steady_clock::now (), hash_a, std::vector (1, representative_a) }); - if (inactive_votes_cache.size () > inactive_votes_cache_max) + std::vector representative_vector (1, representative_a); + bool confirmed (false); + bool start_bootstrap (inactive_votes_bootstrap_check (representative_vector, hash_a, confirmed)); + auto & inactive_by_arrival (inactive_votes_cache.get ()); + inactive_by_arrival.emplace (nano::inactive_cache_information{ std::chrono::steady_clock::now (), hash_a, representative_vector, start_bootstrap, confirmed }); + if (inactive_votes_cache.size () > node.flags.inactive_votes_cache_size) { - inactive_votes_cache.get<0> ().erase (inactive_votes_cache.get<0> ().begin ()); + inactive_by_arrival.erase (inactive_by_arrival.begin ()); } } } } -nano::gap_information nano::active_transactions::find_inactive_votes_cache (nano::block_hash const & hash_a) +nano::inactive_cache_information nano::active_transactions::find_inactive_votes_cache (nano::block_hash const & hash_a) { - auto existing (inactive_votes_cache.get<1> ().find (hash_a)); - if (existing != inactive_votes_cache.get<1> ().end ()) + auto & inactive_by_hash (inactive_votes_cache.get ()); + auto existing (inactive_by_hash.find (hash_a)); + if (existing != inactive_by_hash.end ()) { return *existing; } else { - return nano::gap_information{ std::chrono::steady_clock::time_point{}, 0, std::vector{} }; + return nano::inactive_cache_information{}; } } -size_t nano::active_transactions::dropped_elections_cache_size () +void nano::active_transactions::erase_inactive_votes_cache (nano::block_hash const & hash_a) { - nano::lock_guard guard (mutex); - return dropped_elections_cache.size (); + inactive_votes_cache.get ().erase (hash_a); } -void nano::active_transactions::add_dropped_elections_cache (nano::qualified_root const & root_a) +bool nano::active_transactions::inactive_votes_bootstrap_check (std::vector const & voters_a, nano::block_hash const & hash_a, bool & confirmed_a) { - assert (!mutex.try_lock ()); - dropped_elections_cache.insert (nano::election_timepoint{ std::chrono::steady_clock::now (), root_a }); - if (dropped_elections_cache.size () > dropped_elections_cache_max) + uint128_t tally; + for (auto const & voter : voters_a) { - dropped_elections_cache.get<0> ().erase (dropped_elections_cache.get<0> ().begin ()); + tally += node.ledger.weight (voter); } -} - -std::chrono::steady_clock::time_point nano::active_transactions::find_dropped_elections_cache (nano::qualified_root const & root_a) -{ - assert (!mutex.try_lock ()); - auto existing (dropped_elections_cache.get<1> ().find (root_a)); - if (existing != dropped_elections_cache.get<1> ().end ()) + bool start_bootstrap (false); + if (tally >= node.config.online_weight_minimum.number ()) { - return existing->time; + start_bootstrap = true; + confirmed_a = true; } - else + else if (!node.flags.disable_legacy_bootstrap && tally > node.gap_cache.bootstrap_threshold ()) { - return std::chrono::steady_clock::time_point{}; + start_bootstrap = true; } + if (start_bootstrap && !node.ledger.block_exists (hash_a)) + { + auto node_l (node.shared ()); + node.alarm.add (std::chrono::steady_clock::now () + node.network_params.bootstrap.gap_cache_bootstrap_start_interval, [node_l, hash_a]() { + auto transaction (node_l->store.tx_begin_read ()); + if (!node_l->store.block_exists (transaction, hash_a)) + { + if (!node_l->bootstrap_initiator.in_progress ()) + { + node_l->logger.try_log (boost::str (boost::format ("Missing block %1% which has enough votes to warrant lazy bootstrapping it") % hash_a.to_string ())); + } + if (!node_l->flags.disable_lazy_bootstrap) + { + node_l->bootstrap_initiator.bootstrap_lazy (hash_a); + } + else if (!node_l->flags.disable_legacy_bootstrap) + { + node_l->bootstrap_initiator.bootstrap (); + } + } + }); + } + return start_bootstrap; +} + +size_t nano::active_transactions::election_winner_details_size () +{ + nano::lock_guard guard (election_winner_details_mutex); + return election_winner_details.size (); } nano::cementable_account::cementable_account (nano::account const & account_a, size_t blocks_uncemented_a) : @@ -1157,32 +1348,74 @@ account (account_a), blocks_uncemented (blocks_uncemented_a) { } -namespace nano +std::unique_ptr nano::collect_container_info (active_transactions & active_transactions, const std::string & name) { -std::unique_ptr collect_seq_con_info (active_transactions & active_transactions, const std::string & name) -{ - size_t roots_count = 0; - size_t blocks_count = 0; - size_t confirmed_count = 0; - size_t pending_conf_height_count = 0; + size_t roots_count; + size_t blocks_count; + size_t recently_confirmed_count; + size_t recently_cemented_count; { nano::lock_guard guard (active_transactions.mutex); roots_count = active_transactions.roots.size (); blocks_count = active_transactions.blocks.size (); - confirmed_count = active_transactions.confirmed.size (); - pending_conf_height_count = active_transactions.pending_conf_height.size (); + recently_confirmed_count = active_transactions.recently_confirmed.size (); + recently_cemented_count = active_transactions.recently_cemented.size (); } - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "roots", roots_count, sizeof (decltype (active_transactions.roots)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "blocks", blocks_count, sizeof (decltype (active_transactions.blocks)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "pending_conf_height", pending_conf_height_count, sizeof (decltype (active_transactions.pending_conf_height)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "confirmed", confirmed_count, sizeof (decltype (active_transactions.confirmed)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "priority_wallet_cementable_frontiers_count", active_transactions.priority_wallet_cementable_frontiers_size (), sizeof (nano::cementable_account) })); - composite->add_component (std::make_unique (seq_con_info{ "priority_cementable_frontiers_count", active_transactions.priority_cementable_frontiers_size (), sizeof (nano::cementable_account) })); - composite->add_component (std::make_unique (seq_con_info{ "inactive_votes_cache_count", active_transactions.inactive_votes_cache_size (), sizeof (nano::gap_information) })); - composite->add_component (std::make_unique (seq_con_info{ "dropped_elections_count", active_transactions.dropped_elections_cache_size (), sizeof (nano::election_timepoint) })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "roots", roots_count, sizeof (decltype (active_transactions.roots)::value_type) })); + composite->add_component (std::make_unique (container_info{ "blocks", blocks_count, sizeof (decltype (active_transactions.blocks)::value_type) })); + composite->add_component (std::make_unique (container_info{ "election_winner_details", active_transactions.election_winner_details_size (), sizeof (decltype (active_transactions.election_winner_details)::value_type) })); + composite->add_component (std::make_unique (container_info{ "recently_confirmed", recently_confirmed_count, sizeof (decltype (active_transactions.recently_confirmed)::value_type) })); + composite->add_component (std::make_unique (container_info{ "recently_cemented", recently_cemented_count, sizeof (decltype (active_transactions.recently_cemented)::value_type) })); + composite->add_component (std::make_unique (container_info{ "priority_wallet_cementable_frontiers_count", active_transactions.priority_wallet_cementable_frontiers_size (), sizeof (nano::cementable_account) })); + composite->add_component (std::make_unique (container_info{ "priority_cementable_frontiers_count", active_transactions.priority_cementable_frontiers_size (), sizeof (nano::cementable_account) })); + composite->add_component (std::make_unique (container_info{ "inactive_votes_cache_count", active_transactions.inactive_votes_cache_size (), sizeof (nano::gap_information) })); + composite->add_component (collect_container_info (active_transactions.generator, "generator")); return composite; } + +nano::dropped_elections::dropped_elections (nano::stat & stats_a) : +stats (stats_a) +{ +} + +void nano::dropped_elections::add (nano::qualified_root const & root_a) +{ + stats.inc (nano::stat::type::election, nano::stat::detail::election_drop); + nano::lock_guard guard (mutex); + auto & items_by_sequence = items.get (); + items_by_sequence.emplace_back (nano::election_timepoint{ std::chrono::steady_clock::now (), root_a }); + if (items.size () > capacity) + { + items_by_sequence.pop_front (); + } +} + +void nano::dropped_elections::erase (nano::qualified_root const & root_a) +{ + nano::lock_guard guard (mutex); + items.get ().erase (root_a); +} + +std::chrono::steady_clock::time_point nano::dropped_elections::find (nano::qualified_root const & root_a) const +{ + nano::lock_guard guard (mutex); + auto & items_by_root = items.get (); + auto existing (items_by_root.find (root_a)); + if (existing != items_by_root.end ()) + { + return existing->time; + } + else + { + return std::chrono::steady_clock::time_point{}; + } +} + +size_t nano::dropped_elections::size () const +{ + nano::lock_guard guard (mutex); + return items.size (); } diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index f6fde3a31b..31976369a3 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -1,212 +1,287 @@ #pragma once #include -#include -#include -#include -#include -#include +#include #include #include #include #include #include -#include +#include #include -#include +#include #include #include #include #include #include -#include -#include #include #include +namespace mi = boost::multi_index; + namespace nano { class node; class block; class block_sideband; -class vote; class election; +class vote; class transaction; +class confirmation_height_processor; +class stat; -class conflict_info final +class cementable_account final { public: - nano::qualified_root root; - uint64_t difficulty; - uint64_t adjusted_difficulty; - std::shared_ptr election; + cementable_account (nano::account const & account_a, size_t blocks_uncemented_a); + nano::account account; + uint64_t blocks_uncemented{ 0 }; }; -enum class election_status_type : uint8_t +class election_timepoint final { - ongoing = 0, - active_confirmed_quorum = 1, - active_confirmation_height = 2, - inactive_confirmation_height = 3, - stopped = 5 +public: + std::chrono::steady_clock::time_point time; + nano::qualified_root root; }; -class election_status final +class inactive_cache_information final { public: - std::shared_ptr winner; - nano::amount tally; - std::chrono::milliseconds election_end; - std::chrono::milliseconds election_duration; - unsigned confirmation_request_count; - election_status_type type; + std::chrono::steady_clock::time_point arrival; + nano::block_hash hash; + std::vector voters; + bool bootstrap_started{ false }; + bool confirmed{ false }; // Did item reach votes quorum? (minimum config value) }; -class cementable_account final +class dropped_elections final { public: - cementable_account (nano::account const & account_a, size_t blocks_uncemented_a); - nano::account account; - uint64_t blocks_uncemented{ 0 }; + dropped_elections (nano::stat &); + void add (nano::qualified_root const &); + void erase (nano::qualified_root const &); + std::chrono::steady_clock::time_point find (nano::qualified_root const &) const; + size_t size () const; + + static size_t constexpr capacity{ 16 * 1024 }; + + // clang-format off + class tag_sequence {}; + class tag_root {}; + using ordered_dropped = boost::multi_index_container>, + mi::hashed_unique, + mi::member>>>; + // clang-format on + +private: + ordered_dropped items; + mutable std::mutex mutex; + nano::stat & stats; }; -class election_timepoint final +class election_insertion_result final { public: - std::chrono::steady_clock::time_point time; - nano::qualified_root root; + std::shared_ptr election; + bool inserted{ false }; }; // Core class for determining consensus // Holds all active blocks i.e. recently added blocks that need confirmation class active_transactions final { + class conflict_info final + { + public: + nano::qualified_root root; + double multiplier; + double adjusted_multiplier; + std::shared_ptr election; + nano::epoch epoch; + nano::uint128_t previous_balance; + }; + + friend class nano::election; + + // clang-format off + class tag_account {}; + class tag_difficulty {}; + class tag_root {}; + class tag_sequence {}; + class tag_uncemented {}; + class tag_arrival {}; + class tag_hash {}; + // clang-format on + public: - explicit active_transactions (nano::node &); + // clang-format off + using ordered_roots = boost::multi_index_container, + mi::member>, + mi::ordered_non_unique, + mi::member, + std::greater>>>; + // clang-format on + ordered_roots roots; + using roots_iterator = active_transactions::ordered_roots::index_iterator::type; + + explicit active_transactions (nano::node &, nano::confirmation_height_processor &); ~active_transactions (); // Start an election for a block // Call action with confirmed block, may be different than what we started with // clang-format off - bool start (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result insert (std::shared_ptr const &, boost::optional const & = boost::none, std::function)> const & = [](std::shared_ptr) {}); // clang-format on - // If this returns true, the vote is a replay - // If this returns false, the vote may or may not be a replay - bool vote (std::shared_ptr, bool = false); + // Distinguishes replay votes, cannot be determined if the block is not in any election + nano::vote_code vote (std::shared_ptr); // Is the root of this block in the roots container bool active (nano::block const &); bool active (nano::qualified_root const &); - void update_difficulty (std::shared_ptr, boost::optional = boost::none); - void adjust_difficulty (nano::block_hash const &); - void update_active_difficulty (nano::unique_lock &); + std::shared_ptr election (nano::qualified_root const &) const; + std::shared_ptr winner (nano::block_hash const &) const; + // Activates the first unconfirmed block of \p account_a + nano::election_insertion_result activate (nano::account const &); + // Returns false if the election difficulty was updated + bool update_difficulty (nano::block const &); + // Returns false if the election was restarted + bool restart (std::shared_ptr const &, nano::write_transaction const &); + double normalized_multiplier (nano::block const &, boost::optional const & = boost::none) const; + void add_adjust_difficulty (nano::block_hash const &); + void update_adjusted_multiplier (); + void update_active_multiplier (nano::unique_lock &); uint64_t active_difficulty (); - uint64_t limited_active_difficulty (); - std::deque> list_blocks (bool = false); + uint64_t limited_active_difficulty (nano::block const &); + uint64_t limited_active_difficulty (nano::work_version const, uint64_t const); + double active_multiplier (); + std::deque> list_blocks (); void erase (nano::block const &); bool empty (); size_t size (); void stop (); bool publish (std::shared_ptr block_a); boost::optional confirm_block (nano::transaction const &, std::shared_ptr); - void post_confirmation_height_set (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a); - boost::multi_index_container< - nano::conflict_info, - boost::multi_index::indexed_by< - boost::multi_index::hashed_unique< - boost::multi_index::member>, - boost::multi_index::ordered_non_unique< - boost::multi_index::member, - std::greater>>> - roots; + void block_cemented_callback (std::shared_ptr const & block_a); + void block_already_cemented_callback (nano::block_hash const &); + boost::optional last_prioritized_multiplier{ boost::none }; std::unordered_map> blocks; - std::deque list_confirmed (); - std::deque confirmed; - void add_confirmed (nano::election_status const &, nano::qualified_root const &); + std::deque list_recently_cemented (); + std::deque recently_cemented; + dropped_elections recently_dropped; + + void add_recently_cemented (nano::election_status const &); + void add_recently_confirmed (nano::qualified_root const &, nano::block_hash const &); void add_inactive_votes_cache (nano::block_hash const &, nano::account const &); - nano::gap_information find_inactive_votes_cache (nano::block_hash const &); + nano::inactive_cache_information find_inactive_votes_cache (nano::block_hash const &); + void erase_inactive_votes_cache (nano::block_hash const &); + nano::confirmation_height_processor & confirmation_height_processor; nano::node & node; - std::mutex mutex; - std::chrono::seconds const long_election_threshold; - // Delay until requesting confirmation for an election - std::chrono::milliseconds const election_request_delay; - // Maximum time an election can be kept active if it is extending the container - std::chrono::seconds const election_time_to_live; - static size_t constexpr max_block_broadcasts = 30; - static size_t constexpr max_confirm_representatives = 30; - static size_t constexpr max_confirm_req_batches = 20; - static size_t constexpr max_confirm_req = 5; + mutable std::mutex mutex; boost::circular_buffer multipliers_cb; - uint64_t trended_active_difficulty; + double trended_active_multiplier; size_t priority_cementable_frontiers_size (); size_t priority_wallet_cementable_frontiers_size (); boost::circular_buffer difficulty_trend (); size_t inactive_votes_cache_size (); - std::unordered_map> pending_conf_height; - void clear_block (nano::block_hash const & hash_a); - void add_dropped_elections_cache (nano::qualified_root const &); - std::chrono::steady_clock::time_point find_dropped_elections_cache (nano::qualified_root const &); - size_t dropped_elections_cache_size (); + size_t election_winner_details_size (); + void add_election_winner_details (nano::block_hash const &, std::shared_ptr const &); + void remove_election_winner_details (nano::block_hash const &); private: + std::mutex election_winner_details_mutex; + std::unordered_map> election_winner_details; + nano::vote_generator generator; + // Call action with confirmed block, may be different than what we started with // clang-format off - bool add (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result insert_impl (std::shared_ptr const &, boost::optional const & = boost::none, std::function)> const & = [](std::shared_ptr) {}); // clang-format on + // Returns false if the election difficulty was updated + bool update_difficulty_impl (roots_iterator const &, nano::block const &); void request_loop (); - void search_frontiers (nano::transaction const &); - void election_escalate (std::shared_ptr &, nano::transaction const &, size_t const &); - void election_broadcast (std::shared_ptr &, nano::transaction const &, std::deque> &, std::unordered_set &, nano::qualified_root &); - bool election_request_confirm (std::shared_ptr &, std::vector const &, size_t const &, - std::deque, std::shared_ptr>>>> & single_confirm_req_bundle_l, - std::unordered_map, std::deque>> & batched_confirm_req_bundle_l); + void confirm_prioritized_frontiers (nano::transaction const & transaction_a); void request_confirm (nano::unique_lock &); + void frontiers_confirmation (nano::unique_lock &); nano::account next_frontier_account{ 0 }; std::chrono::steady_clock::time_point next_frontier_check{ std::chrono::steady_clock::now () }; + void activate_dependencies (nano::unique_lock &); + std::vector> pending_dependencies; nano::condition_variable condition; bool started{ false }; std::atomic stopped{ false }; - unsigned ongoing_broadcasts{ 0 }; - using ordered_elections_timepoint = boost::multi_index_container< - nano::election_timepoint, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique>>>; - ordered_elections_timepoint confirmed_set; - void prioritize_frontiers_for_confirmation (nano::transaction const &, std::chrono::milliseconds, std::chrono::milliseconds); - using prioritize_num_uncemented = boost::multi_index_container< - nano::cementable_account, - boost::multi_index::indexed_by< - boost::multi_index::hashed_unique< - boost::multi_index::member>, - boost::multi_index::ordered_non_unique< - boost::multi_index::member, - std::greater>>>; + + // Periodically check all elections + std::chrono::milliseconds const check_all_elections_period; + std::chrono::steady_clock::time_point last_check_all_elections{}; + + // Maximum time an election can be kept active if it is extending the container + std::chrono::seconds const election_time_to_live; + + // Elections above this position in the queue are prioritized + size_t const prioritized_cutoff; + + static size_t constexpr recently_confirmed_size{ 65536 }; + using recent_confirmation = std::pair; + // clang-format off + boost::multi_index_container>, + mi::hashed_unique, + mi::member>, + mi::hashed_unique, + mi::member>>> + recently_confirmed; + using prioritize_num_uncemented = boost::multi_index_container, + mi::member>, + mi::ordered_non_unique, + mi::member, + std::greater>>>; + // clang-format on prioritize_num_uncemented priority_wallet_cementable_frontiers; prioritize_num_uncemented priority_cementable_frontiers; + void prioritize_frontiers_for_confirmation (nano::transaction const &, std::chrono::milliseconds, std::chrono::milliseconds); std::unordered_set wallet_ids_already_iterated; std::unordered_map next_wallet_id_accounts; bool skip_wallets{ false }; void prioritize_account_for_confirmation (prioritize_num_uncemented &, size_t &, nano::account const &, nano::account_info const &, uint64_t); static size_t constexpr max_priority_cementable_frontiers{ 100000 }; - static size_t constexpr confirmed_frontiers_max_pending_cut_off{ 1000 }; - boost::multi_index_container< - nano::gap_information, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique>>> - inactive_votes_cache; - static size_t constexpr inactive_votes_cache_max{ 16 * 1024 }; - ordered_elections_timepoint dropped_elections_cache; - static size_t constexpr dropped_elections_cache_max{ 32 * 1024 }; + static size_t constexpr confirmed_frontiers_max_pending_size{ 10000 }; + std::deque adjust_difficulty_list; + // clang-format off + using ordered_cache = boost::multi_index_container, + mi::member>, + mi::hashed_unique, + mi::member>>>; + ordered_cache inactive_votes_cache; + // clang-format on + bool inactive_votes_bootstrap_check (std::vector const &, nano::block_hash const &, bool &); boost::thread thread; + friend class election; + friend std::unique_ptr collect_container_info (active_transactions &, const std::string &); + + friend class active_transactions_activate_dependencies_invalid_Test; + friend class active_transactions_dropped_cleanup_Test; + friend class active_transactions_vote_replays_Test; friend class confirmation_height_prioritize_frontiers_Test; friend class confirmation_height_prioritize_frontiers_overwrite_Test; - friend class confirmation_height_many_accounts_single_confirmation_Test; - friend class confirmation_height_many_accounts_many_confirmations_Test; - friend class confirmation_height_long_chains_Test; + friend class active_transactions_confirmation_consistency_Test; + friend class active_transactions_vote_generator_session_Test; + friend class node_vote_by_hash_bundle_Test; + friend class node_deferred_dependent_elections_Test; + friend class election_bisect_dependencies_Test; + friend class election_dependencies_open_link_Test; }; -std::unique_ptr collect_seq_con_info (active_transactions & active_transactions, const std::string & name); +std::unique_ptr collect_container_info (active_transactions & active_transactions, const std::string & name); } diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index d63991f5cd..32bf0613a8 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -1,20 +1,42 @@ +#include #include #include +#include #include +#include #include -#include +#include std::chrono::milliseconds constexpr nano::block_processor::confirmation_request_delay; +nano::block_post_events::~block_post_events () +{ + for (auto const & i : events) + { + i (); + } +} + nano::block_processor::block_processor (nano::node & node_a, nano::write_database_queue & write_database_queue_a) : -generator (node_a), -stopped (false), -active (false), next_log (std::chrono::steady_clock::now ()), node (node_a), -write_database_queue (write_database_queue_a) +write_database_queue (write_database_queue_a), +state_block_signature_verification (node.checker, node.ledger.network_params.ledger.epochs, node.config, node.logger, node.flags.block_processor_verification_size) { + state_block_signature_verification.blocks_verified_callback = [this](std::deque & items, std::vector const & verifications, std::vector const & hashes, std::vector const & blocks_signatures) { + this->process_verified_state_blocks (items, verifications, hashes, blocks_signatures); + }; + state_block_signature_verification.transition_inactive_callback = [this]() { + if (this->flushing) + { + { + // Prevent a race with condition.wait in block_processor::flush + nano::lock_guard guard (this->mutex); + } + this->condition.notify_all (); + } + }; } nano::block_processor::~block_processor () @@ -24,39 +46,40 @@ nano::block_processor::~block_processor () void nano::block_processor::stop () { - generator.stop (); { nano::lock_guard lock (mutex); stopped = true; } condition.notify_all (); + state_block_signature_verification.stop (); } void nano::block_processor::flush () { node.checker.flush (); + flushing = true; nano::unique_lock lock (mutex); - while (!stopped && (have_blocks () || active)) + while (!stopped && (have_blocks () || active || state_block_signature_verification.is_active ())) { condition.wait (lock); } - blocks_filter.clear (); + flushing = false; } size_t nano::block_processor::size () { nano::unique_lock lock (mutex); - return (blocks.size () + state_blocks.size () + forced.size ()); + return (blocks.size () + state_block_signature_verification.size () + forced.size ()); } bool nano::block_processor::full () { - return size () > node.flags.block_processor_full_size; + return size () >= node.flags.block_processor_full_size; } bool nano::block_processor::half_full () { - return size () > node.flags.block_processor_full_size / 2; + return size () >= node.flags.block_processor_full_size / 2; } void nano::block_processor::add (std::shared_ptr block_a, uint64_t origination) @@ -65,33 +88,32 @@ void nano::block_processor::add (std::shared_ptr block_a, uint64_t add (info); } -void nano::block_processor::add (nano::unchecked_info const & info_a) +void nano::block_processor::add (nano::unchecked_info const & info_a, const bool push_front_preference_a) { - if (!nano::work_validate (info_a.block->root (), info_a.block->block_work ())) + debug_assert (!nano::work_validate_entry (*info_a.block)); + bool quarter_full (size () > node.flags.block_processor_full_size / 4); + if (info_a.verified == nano::signature_verification::unknown && (info_a.block->type () == nano::block_type::state || info_a.block->type () == nano::block_type::open || !info_a.account.is_zero ())) + { + state_block_signature_verification.add (info_a); + } + else if (push_front_preference_a && !quarter_full) { + /* Push blocks from unchecked to front of processing deque to keep more operations with unchecked inside of single write transaction. + It's designed to help with realtime blocks traffic if block processor is not performing large task like bootstrap. + If deque is a quarter full then push back to allow other blocks processing. */ { - auto hash (info_a.block->hash ()); - auto filter_hash (filter_item (hash, info_a.block->block_signature ())); - nano::lock_guard lock (mutex); - if (blocks_filter.find (filter_hash) == blocks_filter.end () && rolled_back.get<1> ().find (hash) == rolled_back.get<1> ().end ()) - { - if (info_a.verified == nano::signature_verification::unknown && (info_a.block->type () == nano::block_type::state || info_a.block->type () == nano::block_type::open || !info_a.account.is_zero ())) - { - state_blocks.push_back (info_a); - } - else - { - blocks.push_back (info_a); - } - blocks_filter.insert (filter_hash); - } + nano::lock_guard guard (mutex); + blocks.push_front (info_a); } condition.notify_all (); } else { - node.logger.try_log ("nano::block_processor::add called for hash ", info_a.block->hash ().to_string (), " with invalid work ", nano::to_string_hex (info_a.block->block_work ())); - assert (false && "nano::block_processor::add called with invalid work"); + { + nano::lock_guard guard (mutex); + blocks.push_back (info_a); + } + condition.notify_all (); } } @@ -115,7 +137,7 @@ void nano::block_processor::process_blocks () nano::unique_lock lock (mutex); while (!stopped) { - if (have_blocks ()) + if (!blocks.empty () || !forced.empty ()) { active = true; lock.unlock (); @@ -125,19 +147,19 @@ void nano::block_processor::process_blocks () } else { - condition.notify_all (); + condition.notify_one (); condition.wait (lock); } } } -bool nano::block_processor::should_log (bool first_time) +bool nano::block_processor::should_log () { auto result (false); auto now (std::chrono::steady_clock::now ()); - if (first_time || next_log < now) + if (next_log < now) { - next_log = now + std::chrono::seconds (15); + next_log = now + (node.config.logging.timing_logging () ? std::chrono::seconds (2) : std::chrono::seconds (15)); result = true; } return result; @@ -145,62 +167,17 @@ bool nano::block_processor::should_log (bool first_time) bool nano::block_processor::have_blocks () { - assert (!mutex.try_lock ()); - return !blocks.empty () || !forced.empty () || !state_blocks.empty (); + debug_assert (!mutex.try_lock ()); + return !blocks.empty () || !forced.empty () || state_block_signature_verification.size () != 0; } -void nano::block_processor::verify_state_blocks (nano::unique_lock & lock_a, size_t max_count) +void nano::block_processor::process_verified_state_blocks (std::deque & items, std::vector const & verifications, std::vector const & hashes, std::vector const & blocks_signatures) { - assert (!mutex.try_lock ()); - nano::timer timer_l (nano::timer_state::started); - std::deque items; - items.swap (state_blocks); - lock_a.unlock (); - if (!items.empty ()) { - auto size (items.size ()); - std::vector hashes; - hashes.reserve (size); - std::vector messages; - messages.reserve (size); - std::vector lengths; - lengths.reserve (size); - std::vector accounts; - accounts.reserve (size); - std::vector pub_keys; - pub_keys.reserve (size); - std::vector blocks_signatures; - blocks_signatures.reserve (size); - std::vector signatures; - signatures.reserve (size); - std::vector verifications; - verifications.resize (size, 0); - for (auto i (0); i < size; ++i) + nano::unique_lock lk (mutex); + for (auto i (0); i < verifications.size (); ++i) { - auto & item (items[i]); - hashes.push_back (item.block->hash ()); - messages.push_back (hashes.back ().bytes.data ()); - lengths.push_back (sizeof (decltype (hashes)::value_type)); - nano::account account (item.block->account ()); - if (!item.block->link ().is_zero () && node.ledger.is_epoch_link (item.block->link ())) - { - account = node.ledger.epoch_signer (item.block->link ()); - } - else if (!item.account.is_zero ()) - { - account = item.account; - } - accounts.push_back (account); - pub_keys.push_back (accounts.back ().bytes.data ()); - blocks_signatures.push_back (item.block->block_signature ()); - signatures.push_back (blocks_signatures.back ().bytes.data ()); - } - nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () }; - node.checker.verify (check); - lock_a.lock (); - for (auto i (0); i < size; ++i) - { - assert (verifications[i] == 1 || verifications[i] == 0); + debug_assert (verifications[i] == 1 || verifications[i] == 0); auto & item (items.front ()); if (!item.block->link ().is_zero () && node.ledger.is_epoch_link (item.block->link ())) { @@ -225,69 +202,29 @@ void nano::block_processor::verify_state_blocks (nano::unique_lock & } else { - blocks_filter.erase (filter_item (hashes[i], blocks_signatures[i])); requeue_invalid (hashes[i], item); } items.pop_front (); } - if (node.config.logging.timing_logging ()) - { - node.logger.try_log (boost::str (boost::format ("Batch verified %1% state blocks in %2% %3%") % size % timer_l.stop ().count () % timer_l.unit ())); - } - } - else - { - lock_a.lock (); } + condition.notify_all (); } void nano::block_processor::process_batch (nano::unique_lock & lock_a) { + auto scoped_write_guard = write_database_queue.wait (nano::writer::process_batch); + block_post_events post_events; + auto transaction (node.store.tx_begin_write ({ tables::accounts, nano::tables::cached_counts, nano::tables::change_blocks, tables::frontiers, tables::open_blocks, tables::pending, tables::receive_blocks, tables::representation, tables::send_blocks, tables::state_blocks, tables::unchecked }, { tables::confirmation_height })); nano::timer timer_l; lock_a.lock (); timer_l.start (); - // Limit state blocks verification time - - { - if (!state_blocks.empty ()) - { - size_t max_verification_batch (node.flags.block_processor_verification_size != 0 ? node.flags.block_processor_verification_size : 2048 * (node.config.signature_checker_threads + 1)); - while (!state_blocks.empty () && timer_l.before_deadline (std::chrono::seconds (2))) - { - verify_state_blocks (lock_a, max_verification_batch); - } - } - } - lock_a.unlock (); - auto scoped_write_guard = write_database_queue.wait (nano::writer::process_batch); - auto transaction (node.store.tx_begin_write ({ nano::tables::accounts, nano::tables::cached_counts, nano::tables::change_blocks, nano::tables::frontiers, nano::tables::open_blocks, nano::tables::pending, nano::tables::receive_blocks, nano::tables::representation, nano::tables::send_blocks, nano::tables::state_blocks, nano::tables::unchecked }, { nano::tables::confirmation_height })); - timer_l.restart (); - lock_a.lock (); // Processing blocks - auto first_time (true); unsigned number_of_blocks_processed (0), number_of_forced_processed (0); while ((!blocks.empty () || !forced.empty ()) && (timer_l.before_deadline (node.config.block_processor_batch_max_time) || (number_of_blocks_processed < node.flags.block_processor_batch_size)) && !awaiting_write) { - auto log_this_record (false); - if (node.config.logging.timing_logging ()) + if ((blocks.size () + state_block_signature_verification.size () + forced.size () > 64) && should_log ()) { - if (should_log (first_time)) - { - log_this_record = true; - } - } - else - { - if (((blocks.size () + state_blocks.size () + forced.size ()) > 64 && should_log (false))) - { - log_this_record = true; - } - } - - if (log_this_record) - { - first_time = false; - node.logger.always_log (boost::str (boost::format ("%1% blocks (+ %2% state blocks) (+ %3% forced) in processing queue") % blocks.size () % state_blocks.size () % forced.size ())); + node.logger.always_log (boost::str (boost::format ("%1% blocks (+ %2% state blocks) (+ %3% forced) in processing queue") % blocks.size () % state_block_signature_verification.size () % forced.size ())); } nano::unchecked_info info; nano::block_hash hash (0); @@ -297,7 +234,6 @@ void nano::block_processor::process_batch (nano::unique_lock & lock_ info = blocks.front (); blocks.pop_front (); hash = info.block->hash (); - blocks_filter.erase (filter_item (hash, info.block->block_signature ())); } else { @@ -324,67 +260,71 @@ void nano::block_processor::process_batch (nano::unique_lock & lock_ { node.logger.always_log (boost::str (boost::format ("%1% blocks rolled back") % rollback_list.size ())); } - lock_a.lock (); - // Prevent rolled back blocks second insertion - auto inserted (rolled_back.insert (nano::rolled_hash{ std::chrono::steady_clock::now (), successor->hash () })); - if (inserted.second) - { - // Possible election winner change - rolled_back.get<1> ().erase (hash); - // Prevent overflow - if (rolled_back.size () > rolled_back_max) - { - rolled_back.erase (rolled_back.begin ()); - } - } - lock_a.unlock (); // Deleting from votes cache & wallet work watcher, stop active transaction for (auto & i : rollback_list) { node.votes_cache.remove (i->hash ()); - node.wallets.watcher->remove (i); - node.active.erase (*i); + node.wallets.watcher->remove (*i); + // Stop all rolled back active transactions except initial + if (i->hash () != successor->hash ()) + { + node.active.erase (*i); + } } } } number_of_blocks_processed++; - process_one (transaction, info); + process_one (transaction, post_events, info); lock_a.lock (); - /* Verify more state blocks if blocks deque is empty - Because verification is long process, avoid large deque verification inside of write transaction */ - if (blocks.empty () && !state_blocks.empty ()) - { - verify_state_blocks (lock_a, 256 * (node.config.signature_checker_threads + 1)); - } } awaiting_write = false; lock_a.unlock (); - if (node.config.logging.timing_logging () && number_of_blocks_processed != 0) + if (node.config.logging.timing_logging () && number_of_blocks_processed != 0 && timer_l.stop () > std::chrono::milliseconds (100)) { - node.logger.always_log (boost::str (boost::format ("Processed %1% blocks (%2% blocks were forced) in %3% %4%") % number_of_blocks_processed % number_of_forced_processed % timer_l.stop ().count () % timer_l.unit ())); + node.logger.always_log (boost::str (boost::format ("Processed %1% blocks (%2% blocks were forced) in %3% %4%") % number_of_blocks_processed % number_of_forced_processed % timer_l.value ().count () % timer_l.unit ())); } } -void nano::block_processor::process_live (nano::block_hash const & hash_a, std::shared_ptr block_a, const bool watch_work_a) +void nano::block_processor::process_live (nano::block_hash const & hash_a, std::shared_ptr block_a, nano::process_return const & process_return_a, const bool watch_work_a, nano::block_origin const origin_a) { - // Start collecting quorum on block - node.active.start (block_a, false); - //add block to watcher if desired after block has been added to active + // Add to work watcher to prevent dropping the election if (watch_work_a) { node.wallets.watcher->add (block_a); } + + // Start collecting quorum on block + if (watch_work_a || node.ledger.can_vote (node.store.tx_begin_read (), *block_a)) + { + auto election = node.active.insert (block_a, process_return_a.previous_balance.number ()); + if (election.inserted) + { + election.election->transition_passive (); + } + else if (election.election) + { + election.election->try_generate_votes (block_a->hash ()); + } + } + // Announce block contents to the network - node.network.flood_block (block_a, false); - if (node.config.enable_voting) + if (origin_a == nano::block_origin::local) { - // Announce our weighted vote to the network - generator.add (hash_a); + node.network.flood_block_initial (block_a); + } + else if (!node.flags.disable_block_processor_republishing) + { + node.network.flood_block (block_a, nano::buffer_drop_policy::no_limiter_drop); + } + + if (node.websocket_server && node.websocket_server->any_subscriber (nano::websocket::topic::new_unconfirmed_block)) + { + node.websocket_server->broadcast (nano::websocket::message_builder ().new_block_arrived (*block_a)); } } -nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, nano::unchecked_info info_a, const bool watch_work_a) +nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, block_post_events & events_a, nano::unchecked_info info_a, const bool watch_work_a, nano::block_origin const origin_a) { nano::process_return result; auto hash (info_a.block->hash ()); @@ -402,7 +342,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } if (info_a.modified > nano::seconds_since_epoch () - 300 && node.block_arrival.recent (hash)) { - process_live (hash, info_a.block, watch_work_a); + events_a.events.emplace_back ([this, hash, block = info_a.block, result, watch_work_a, origin_a]() { process_live (hash, block, result, watch_work_a, origin_a); }); } queue_unchecked (transaction_a, hash); break; @@ -418,8 +358,17 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction { info_a.modified = nano::seconds_since_epoch (); } - node.store.unchecked_put (transaction_a, nano::unchecked_key (info_a.block->previous (), hash), info_a); + + nano::unchecked_key unchecked_key (info_a.block->previous (), hash); + auto exists = node.store.unchecked_exists (transaction_a, unchecked_key); + node.store.unchecked_put (transaction_a, unchecked_key, info_a); + if (!exists) + { + ++node.ledger.cache.unchecked_count; + } + node.gap_cache.add (hash); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_previous); break; } case nano::process_result::gap_source: @@ -433,8 +382,17 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction { info_a.modified = nano::seconds_since_epoch (); } - node.store.unchecked_put (transaction_a, nano::unchecked_key (node.ledger.block_source (transaction_a, *(info_a.block)), hash), info_a); + + nano::unchecked_key unchecked_key (node.ledger.block_source (transaction_a, *(info_a.block)), hash); + auto exists = node.store.unchecked_exists (transaction_a, unchecked_key); + node.store.unchecked_put (transaction_a, unchecked_key, info_a); + if (!exists) + { + ++node.ledger.cache.unchecked_count; + } + node.gap_cache.add (hash); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); break; } case nano::process_result::old: @@ -443,11 +401,8 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction { node.logger.try_log (boost::str (boost::format ("Old for: %1%") % hash.to_string ())); } - if (!node.flags.fast_bootstrap) - { - queue_unchecked (transaction_a, hash); - } - node.active.update_difficulty (info_a.block, transaction_a); + process_old (transaction_a, info_a.block, origin_a); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::old); break; } case nano::process_result::bad_signature: @@ -478,7 +433,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction case nano::process_result::fork: { node.process_fork (transaction_a, info_a.block); - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::fork, nano::stat::dir::in); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::fork); if (node.config.logging.ledger_logging ()) { node.logger.try_log (boost::str (boost::format ("Fork for: %1% root: %2%") % hash.to_string () % info_a.block->root ().to_string ())); @@ -514,50 +469,74 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } break; } + case nano::process_result::insufficient_work: + { + if (node.config.logging.ledger_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Insufficient work for %1% : %2% (difficulty %3%)") % hash.to_string () % nano::to_string_hex (info_a.block->block_work ()) % nano::to_string_hex (info_a.block->difficulty ()))); + } + break; + } } return result; } -nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, std::shared_ptr block_a, const bool watch_work_a) +nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, block_post_events & events_a, std::shared_ptr block_a, const bool watch_work_a) { nano::unchecked_info info (block_a, block_a->account (), 0, nano::signature_verification::unknown); - auto result (process_one (transaction_a, info, watch_work_a)); + auto result (process_one (transaction_a, events_a, info, watch_work_a)); return result; } +void nano::block_processor::process_old (nano::write_transaction const & transaction_a, std::shared_ptr const & block_a, nano::block_origin const origin_a) +{ + // First try to update election difficulty, then attempt to restart an election + if (!node.active.update_difficulty (*block_a) || !node.active.restart (block_a, transaction_a)) + { + // Let others know about the difficulty update + if (origin_a == nano::block_origin::local) + { + node.network.flood_block_initial (block_a); + } + } +} + void nano::block_processor::queue_unchecked (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a) { auto unchecked_blocks (node.store.unchecked_get (transaction_a, hash_a)); for (auto & info : unchecked_blocks) { - if (!node.flags.fast_bootstrap) + if (!node.flags.disable_block_processor_unchecked_deletion) { node.store.unchecked_del (transaction_a, nano::unchecked_key (hash_a, info.block->hash ())); + debug_assert (node.ledger.cache.unchecked_count > 0); + --node.ledger.cache.unchecked_count; } - add (info); + add (info, true); } node.gap_cache.erase (hash_a); } -nano::block_hash nano::block_processor::filter_item (nano::block_hash const & hash_a, nano::signature const & signature_a) +void nano::block_processor::requeue_invalid (nano::block_hash const & hash_a, nano::unchecked_info const & info_a) { - static nano::random_constants constants; - nano::block_hash result; - blake2b_state state; - blake2b_init (&state, sizeof (result.bytes)); - blake2b_update (&state, constants.not_an_account.bytes.data (), constants.not_an_account.bytes.size ()); - blake2b_update (&state, signature_a.bytes.data (), signature_a.bytes.size ()); - blake2b_update (&state, hash_a.bytes.data (), hash_a.bytes.size ()); - blake2b_final (&state, result.bytes.data (), sizeof (result.bytes)); - return result; + debug_assert (hash_a == info_a.block->hash ()); + node.bootstrap_initiator.lazy_requeue (hash_a, info_a.block->previous (), info_a.confirmed); } -void nano::block_processor::requeue_invalid (nano::block_hash const & hash_a, nano::unchecked_info const & info_a) +std::unique_ptr nano::collect_container_info (block_processor & block_processor, const std::string & name) { - assert (hash_a == info_a.block->hash ()); - auto attempt (node.bootstrap_initiator.current_attempt ()); - if (attempt != nullptr && attempt->mode == nano::bootstrap_mode::lazy) + size_t blocks_count; + size_t forced_count; + { - attempt->lazy_requeue (hash_a, info_a.block->previous (), info_a.confirmed); + nano::lock_guard guard (block_processor.mutex); + blocks_count = block_processor.blocks.size (); + forced_count = block_processor.forced.size (); } + + auto composite = std::make_unique (name); + composite->add_component (collect_container_info (block_processor.state_block_signature_verification, "state_block_signature_verification")); + composite->add_component (std::make_unique (container_info{ "blocks", blocks_count, sizeof (decltype (block_processor.blocks)::value_type) })); + composite->add_component (std::make_unique (container_info{ "forced", forced_count, sizeof (decltype (block_processor.forced)::value_type) })); + return composite; } diff --git a/nano/node/blockprocessor.hpp b/nano/node/blockprocessor.hpp index 49fc64dd98..87aa97093a 100644 --- a/nano/node/blockprocessor.hpp +++ b/nano/node/blockprocessor.hpp @@ -1,13 +1,13 @@ #pragma once #include -#include +#include #include #include #include #include -#include +#include #include #include @@ -21,12 +21,19 @@ class transaction; class write_transaction; class write_database_queue; -class rolled_hash +enum class block_origin +{ + local, + remote +}; + +class block_post_events final { public: - std::chrono::steady_clock::time_point time; - nano::block_hash hash; + ~block_post_events (); + std::deque> events; }; + /** * Processing blocks is a potentially long IO operation. * This class isolates block insertion from other operations like servicing network operations @@ -41,46 +48,39 @@ class block_processor final size_t size (); bool full (); bool half_full (); - void add (nano::unchecked_info const &); + void add (nano::unchecked_info const &, const bool = false); void add (std::shared_ptr, uint64_t = 0); void force (std::shared_ptr); void wait_write (); - bool should_log (bool); + bool should_log (); bool have_blocks (); void process_blocks (); - nano::process_return process_one (nano::write_transaction const &, nano::unchecked_info, const bool = false); - nano::process_return process_one (nano::write_transaction const &, std::shared_ptr, const bool = false); - nano::vote_generator generator; + nano::process_return process_one (nano::write_transaction const &, block_post_events &, nano::unchecked_info, const bool = false, nano::block_origin const = nano::block_origin::remote); + nano::process_return process_one (nano::write_transaction const &, block_post_events &, std::shared_ptr, const bool = false); + std::atomic flushing{ false }; // Delay required for average network propagartion before requesting confirmation static std::chrono::milliseconds constexpr confirmation_request_delay{ 1500 }; private: void queue_unchecked (nano::write_transaction const &, nano::block_hash const &); - void verify_state_blocks (nano::unique_lock &, size_t = std::numeric_limits::max ()); void process_batch (nano::unique_lock &); - void process_live (nano::block_hash const &, std::shared_ptr, const bool = false); + void process_live (nano::block_hash const &, std::shared_ptr, nano::process_return const &, const bool = false, nano::block_origin const = nano::block_origin::remote); + void process_old (nano::write_transaction const &, std::shared_ptr const &, nano::block_origin const); void requeue_invalid (nano::block_hash const &, nano::unchecked_info const &); - bool stopped; - bool active; + void process_verified_state_blocks (std::deque &, std::vector const &, std::vector const &, std::vector const &); + bool stopped{ false }; + bool active{ false }; bool awaiting_write{ false }; std::chrono::steady_clock::time_point next_log; - std::deque state_blocks; std::deque blocks; std::deque> forced; - nano::block_hash filter_item (nano::block_hash const &, nano::signature const &); - std::unordered_set blocks_filter; - boost::multi_index_container< - nano::rolled_hash, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique>>> - rolled_back; - static size_t const rolled_back_max = 1024; nano::condition_variable condition; nano::node & node; nano::write_database_queue & write_database_queue; std::mutex mutex; + nano::state_block_signature_verification state_block_signature_verification; - friend std::unique_ptr collect_seq_con_info (block_processor & block_processor, const std::string & name); + friend std::unique_ptr collect_container_info (block_processor & block_processor, const std::string & name); }; +std::unique_ptr collect_container_info (block_processor & block_processor, const std::string & name); } diff --git a/nano/node/bootstrap/bootstrap.cpp b/nano/node/bootstrap/bootstrap.cpp index dc2d1a86cc..ca1d9b4926 100644 --- a/nano/node/bootstrap/bootstrap.cpp +++ b/nano/node/bootstrap/bootstrap.cpp @@ -1,1491 +1,274 @@ -#include +#include #include -#include -#include +#include +#include #include #include -#include -#include -#include +#include #include -constexpr double nano::bootstrap_limits::bootstrap_connection_scale_target_blocks; -constexpr double nano::bootstrap_limits::bootstrap_connection_scale_target_blocks_lazy; -constexpr double nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec; -constexpr double nano::bootstrap_limits::bootstrap_minimum_termination_time_sec; -constexpr unsigned nano::bootstrap_limits::bootstrap_max_new_connections; -constexpr size_t nano::bootstrap_limits::bootstrap_max_confirm_frontiers; -constexpr double nano::bootstrap_limits::required_frontier_confirmation_ratio; -constexpr unsigned nano::bootstrap_limits::frontier_confirmation_blocks_limit; -constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit; -constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit_test; -constexpr std::chrono::seconds nano::bootstrap_limits::lazy_flush_delay_sec; -constexpr unsigned nano::bootstrap_limits::lazy_destinations_request_limit; -constexpr uint64_t nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit; -constexpr double nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio; -constexpr size_t nano::bootstrap_limits::lazy_blocks_restart_limit; -constexpr std::chrono::hours nano::bootstrap_excluded_peers::exclude_time_hours; -constexpr std::chrono::hours nano::bootstrap_excluded_peers::exclude_remove_hours; - -nano::bootstrap_client::bootstrap_client (std::shared_ptr node_a, std::shared_ptr attempt_a, std::shared_ptr channel_a, std::shared_ptr socket_a) : -node (node_a), -attempt (attempt_a), -channel (channel_a), -socket (socket_a), -receive_buffer (std::make_shared> ()), -start_time (std::chrono::steady_clock::now ()), -block_count (0), -pending_stop (false), -hard_stop (false) -{ - ++attempt->connections; - receive_buffer->resize (256); -} - -nano::bootstrap_client::~bootstrap_client () -{ - --attempt->connections; -} - -double nano::bootstrap_client::block_rate () const -{ - auto elapsed = std::max (elapsed_seconds (), nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate); - return static_cast (block_count.load () / elapsed); -} - -double nano::bootstrap_client::elapsed_seconds () const -{ - return std::chrono::duration_cast> (std::chrono::steady_clock::now () - start_time).count (); -} - -void nano::bootstrap_client::stop (bool force) -{ - pending_stop = true; - if (force) - { - hard_stop = true; - } -} - -std::shared_ptr nano::bootstrap_client::shared () -{ - return shared_from_this (); -} - -nano::bootstrap_attempt::bootstrap_attempt (std::shared_ptr node_a, nano::bootstrap_mode mode_a) : -next_log (std::chrono::steady_clock::now ()), -node (node_a), -mode (mode_a) -{ - node->logger.always_log ("Starting bootstrap attempt"); - node->bootstrap_initiator.notify_listeners (true); -} - -nano::bootstrap_attempt::~bootstrap_attempt () -{ - node->logger.always_log ("Exiting bootstrap attempt"); - node->bootstrap_initiator.notify_listeners (false); -} - -bool nano::bootstrap_attempt::should_log () -{ - nano::lock_guard guard (next_log_mutex); - auto result (false); - auto now (std::chrono::steady_clock::now ()); - if (next_log < now) - { - result = true; - next_log = now + std::chrono::seconds (15); - } - return result; -} - -bool nano::bootstrap_attempt::request_frontier (nano::unique_lock & lock_a, bool first_attempt) -{ - auto result (true); - auto connection_l (connection (lock_a, first_attempt)); - connection_frontier_request = connection_l; - if (connection_l) - { - endpoint_frontier_request = connection_l->channel->get_tcp_endpoint (); - std::future future; - { - auto client (std::make_shared (connection_l)); - client->run (); - frontiers = client; - future = client->promise.get_future (); - } - lock_a.unlock (); - result = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. - lock_a.lock (); - if (result) - { - pulls.clear (); - } - if (node->config.logging.network_logging ()) - { - if (!result) - { - node->logger.try_log (boost::str (boost::format ("Completed frontier request, %1% out of sync accounts according to %2%") % pulls.size () % connection_l->channel->to_string ())); - } - else - { - node->stats.inc (nano::stat::type::error, nano::stat::detail::frontier_req, nano::stat::dir::out); - } - } - } - return result; -} - -void nano::bootstrap_attempt::request_pull (nano::unique_lock & lock_a) -{ - auto connection_l (connection (lock_a)); - if (connection_l) - { - auto pull (pulls.front ()); - pulls.pop_front (); - if (mode != nano::bootstrap_mode::legacy) - { - // Check if pull is obsolete (head was processed) - while (!pulls.empty () && !pull.head.is_zero () && lazy_processed_or_exists (pull.head)) - { - pull = pulls.front (); - pulls.pop_front (); - } - } - recent_pulls_head.push_back (pull.head); - if (recent_pulls_head.size () > nano::bootstrap_limits::bootstrap_max_confirm_frontiers) - { - recent_pulls_head.pop_front (); - } - ++pulling; - // The bulk_pull_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference - // Dispatch request in an external thread in case it needs to be destroyed - node->background ([connection_l, pull]() { - auto client (std::make_shared (connection_l, pull)); - client->request (); - }); - } -} - -void nano::bootstrap_attempt::request_push (nano::unique_lock & lock_a) -{ - bool error (false); - if (auto connection_shared = connection_frontier_request.lock ()) - { - std::future future; - { - auto client (std::make_shared (connection_shared)); - client->start (); - push = client; - future = client->promise.get_future (); - } - lock_a.unlock (); - error = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. - lock_a.lock (); - } - if (node->config.logging.network_logging ()) - { - node->logger.try_log ("Exiting bulk push client"); - if (error) - { - node->logger.try_log ("Bulk push client failed"); - } - } -} - -bool nano::bootstrap_attempt::still_pulling () -{ - assert (!mutex.try_lock ()); - auto running (!stopped); - auto more_pulls (!pulls.empty ()); - auto still_pulling (pulling > 0); - return running && (more_pulls || still_pulling); -} - -void nano::bootstrap_attempt::run_start (nano::unique_lock & lock_a) -{ - frontiers_received = false; - frontiers_confirmed = false; - total_blocks = 0; - requeued_pulls = 0; - pulls.clear (); - recent_pulls_head.clear (); - auto frontier_failure (true); - uint64_t frontier_attempts (0); - while (!stopped && frontier_failure) - { - ++frontier_attempts; - frontier_failure = request_frontier (lock_a, frontier_attempts == 1); - } - frontiers_received = true; - // Shuffle pulls. - release_assert (std::numeric_limits::max () > pulls.size ()); - if (!pulls.empty ()) - { - for (auto i = static_cast (pulls.size () - 1); i > 0; --i) - { - auto k = nano::random_pool::generate_word32 (0, i); - std::swap (pulls[i], pulls[k]); - } - } -} - -void nano::bootstrap_attempt::run () -{ - assert (!node->flags.disable_legacy_bootstrap); - start_populate_connections (); - nano::unique_lock lock (mutex); - run_start (lock); - while (still_pulling ()) - { - while (still_pulling ()) - { - if (!pulls.empty ()) - { - request_pull (lock); - } - else - { - condition.wait (lock); - } - attempt_restart_check (lock); - } - // Flushing may resolve forks which can add more pulls - node->logger.try_log ("Flushing unchecked blocks"); - lock.unlock (); - node->block_processor.flush (); - lock.lock (); - node->logger.try_log ("Finished flushing unchecked blocks"); - } - if (!stopped) - { - node->logger.try_log ("Completed pulls"); - if (!node->flags.disable_bootstrap_bulk_push_client) - { - request_push (lock); - } - ++runs_count; - // Start wallet lazy bootstrap if required - if (!wallet_accounts.empty () && !node->flags.disable_wallet_bootstrap) - { - lock.unlock (); - mode = nano::bootstrap_mode::wallet_lazy; - total_blocks = 0; - wallet_run (); - lock.lock (); - } - // Start lazy bootstrap if some lazy keys were inserted - else if (runs_count < 3 && !lazy_finished () && !node->flags.disable_lazy_bootstrap) - { - lock.unlock (); - mode = nano::bootstrap_mode::lazy; - total_blocks = 0; - lazy_run (); - lock.lock (); - } - if (!stopped) - { - node->unchecked_cleanup (); - } - } - stopped = true; - condition.notify_all (); - idle.clear (); -} - -std::shared_ptr nano::bootstrap_attempt::connection (nano::unique_lock & lock_a, bool use_front_connection) -{ - // clang-format off - condition.wait (lock_a, [& stopped = stopped, &idle = idle] { return stopped || !idle.empty (); }); - // clang-format on - std::shared_ptr result; - if (!idle.empty ()) - { - if (!use_front_connection) - { - result = idle.back (); - idle.pop_back (); - } - else - { - result = idle.front (); - idle.pop_front (); - } - } - return result; -} - -bool nano::bootstrap_attempt::consume_future (std::future & future_a) -{ - bool result; - try - { - result = future_a.get (); - } - catch (std::future_error &) - { - result = true; - } - return result; -} - -struct block_rate_cmp -{ - bool operator() (const std::shared_ptr & lhs, const std::shared_ptr & rhs) const - { - return lhs->block_rate () > rhs->block_rate (); - } -}; - -unsigned nano::bootstrap_attempt::target_connections (size_t pulls_remaining) -{ - if (node->config.bootstrap_connections >= node->config.bootstrap_connections_max) - { - return std::max (1U, node->config.bootstrap_connections_max); - } - - // Only scale up to bootstrap_connections_max for large pulls. - double target_blocks = (mode == nano::bootstrap_mode::lazy) ? nano::bootstrap_limits::bootstrap_connection_scale_target_blocks_lazy : nano::bootstrap_limits::bootstrap_connection_scale_target_blocks; - double step_scale = std::min (1.0, std::max (0.0, (double)pulls_remaining / target_blocks)); - double lazy_term = (mode == nano::bootstrap_mode::lazy) ? (double)node->config.bootstrap_connections : 0.0; - double target = (double)node->config.bootstrap_connections + (double)(node->config.bootstrap_connections_max - node->config.bootstrap_connections) * step_scale + lazy_term; - return std::max (1U, (unsigned)(target + 0.5f)); -} - -void nano::bootstrap_attempt::populate_connections () -{ - double rate_sum = 0.0; - size_t num_pulls = 0; - std::priority_queue, std::vector>, block_rate_cmp> sorted_connections; - std::unordered_set endpoints; - { - nano::unique_lock lock (mutex); - num_pulls = pulls.size (); - std::deque> new_clients; - for (auto & c : clients) - { - if (auto client = c.lock ()) - { - if (auto socket_l = client->channel->socket.lock ()) - { - new_clients.push_back (client); - endpoints.insert (socket_l->remote_endpoint ()); - double elapsed_sec = client->elapsed_seconds (); - auto blocks_per_sec = client->block_rate (); - rate_sum += blocks_per_sec; - if (client->elapsed_seconds () > nano::bootstrap_limits::bootstrap_connection_warmup_time_sec && client->block_count > 0) - { - sorted_connections.push (client); - } - // Force-stop the slowest peers, since they can take the whole bootstrap hostage by dribbling out blocks on the last remaining pull. - // This is ~1.5kilobits/sec. - if (elapsed_sec > nano::bootstrap_limits::bootstrap_minimum_termination_time_sec && blocks_per_sec < nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec) - { - if (node->config.logging.bulk_pull_logging ()) - { - node->logger.try_log (boost::str (boost::format ("Stopping slow peer %1% (elapsed sec %2%s > %3%s and %4% blocks per second < %5%)") % client->channel->to_string () % elapsed_sec % nano::bootstrap_limits::bootstrap_minimum_termination_time_sec % blocks_per_sec % nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec)); - } - - client->stop (true); - new_clients.pop_back (); - } - } - } - } - // Cleanup expired clients - clients.swap (new_clients); - } - - auto target = target_connections (num_pulls); - - // We only want to drop slow peers when more than 2/3 are active. 2/3 because 1/2 is too aggressive, and 100% rarely happens. - // Probably needs more tuning. - if (sorted_connections.size () >= (target * 2) / 3 && target >= 4) - { - // 4 -> 1, 8 -> 2, 16 -> 4, arbitrary, but seems to work well. - auto drop = (int)roundf (sqrtf ((float)target - 2.0f)); - - if (node->config.logging.bulk_pull_logging ()) - { - node->logger.try_log (boost::str (boost::format ("Dropping %1% bulk pull peers, target connections %2%") % drop % target)); - } - - for (int i = 0; i < drop; i++) - { - auto client = sorted_connections.top (); - - if (node->config.logging.bulk_pull_logging ()) - { - node->logger.try_log (boost::str (boost::format ("Dropping peer with block rate %1%, block count %2% (%3%) ") % client->block_rate () % client->block_count % client->channel->to_string ())); - } - - client->stop (false); - sorted_connections.pop (); - } - } - - if (node->config.logging.bulk_pull_logging ()) - { - nano::unique_lock lock (mutex); - node->logger.try_log (boost::str (boost::format ("Bulk pull connections: %1%, rate: %2% blocks/sec, remaining account pulls: %3%, total blocks: %4%") % connections.load () % (int)rate_sum % pulls.size () % (int)total_blocks.load ())); - } - - if (connections < target) - { - auto delta = std::min ((target - connections) * 2, nano::bootstrap_limits::bootstrap_max_new_connections); - // TODO - tune this better - // Not many peers respond, need to try to make more connections than we need. - for (auto i = 0u; i < delta; i++) - { - auto endpoint (node->network.bootstrap_peer (mode == nano::bootstrap_mode::lazy)); - if (endpoint != nano::tcp_endpoint (boost::asio::ip::address_v6::any (), 0) && endpoints.find (endpoint) == endpoints.end () && !node->bootstrap_initiator.excluded_peers.check (endpoint)) - { - connect_client (endpoint); - nano::lock_guard lock (mutex); - endpoints.insert (endpoint); - } - else if (connections == 0) - { - node->logger.try_log (boost::str (boost::format ("Bootstrap stopped because there are no peers"))); - stopped = true; - condition.notify_all (); - } - } - } - if (!stopped) - { - std::weak_ptr this_w (shared_from_this ()); - node->alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (1), [this_w]() { - if (auto this_l = this_w.lock ()) - { - this_l->populate_connections (); - } - }); - } -} - -void nano::bootstrap_attempt::start_populate_connections () -{ - if (!populate_connections_started.exchange (true)) - { - populate_connections (); - } -} - -void nano::bootstrap_attempt::add_connection (nano::endpoint const & endpoint_a) -{ - connect_client (nano::tcp_endpoint (endpoint_a.address (), endpoint_a.port ())); -} - -void nano::bootstrap_attempt::connect_client (nano::tcp_endpoint const & endpoint_a) -{ - ++connections; - auto socket (std::make_shared (node)); - auto this_l (shared_from_this ()); - socket->async_connect (endpoint_a, - [this_l, socket, endpoint_a](boost::system::error_code const & ec) { - if (!ec) - { - if (this_l->node->config.logging.bulk_pull_logging ()) - { - this_l->node->logger.try_log (boost::str (boost::format ("Connection established to %1%") % endpoint_a)); - } - auto client (std::make_shared (this_l->node, this_l, std::make_shared (*this_l->node, socket), socket)); - this_l->pool_connection (client); - } - else - { - if (this_l->node->config.logging.network_logging ()) - { - switch (ec.value ()) - { - default: - this_l->node->logger.try_log (boost::str (boost::format ("Error initiating bootstrap connection to %1%: %2%") % endpoint_a % ec.message ())); - break; - case boost::system::errc::connection_refused: - case boost::system::errc::operation_canceled: - case boost::system::errc::timed_out: - case 995: //Windows The I/O operation has been aborted because of either a thread exit or an application request - case 10061: //Windows No connection could be made because the target machine actively refused it - break; - } - } - } - --this_l->connections; - }); -} - -void nano::bootstrap_attempt::pool_connection (std::shared_ptr client_a) -{ - nano::lock_guard lock (mutex); - if (!stopped && !client_a->pending_stop && !node->bootstrap_initiator.excluded_peers.check (client_a->channel->get_tcp_endpoint ())) - { - // Idle bootstrap client socket - if (auto socket_l = client_a->channel->socket.lock ()) - { - socket_l->start_timer (node->network_params.node.idle_timeout); - // Push into idle deque - idle.push_back (client_a); - } - } - condition.notify_all (); -} - -void nano::bootstrap_attempt::stop () -{ - nano::lock_guard lock (mutex); - stopped = true; - condition.notify_all (); - for (auto i : clients) - { - if (auto client = i.lock ()) - { - client->socket->close (); - } - } - if (auto i = frontiers.lock ()) - { - try - { - i->promise.set_value (true); - } - catch (std::future_error &) - { - } - } - if (auto i = push.lock ()) - { - try - { - i->promise.set_value (true); - } - catch (std::future_error &) - { - } - } -} - -void nano::bootstrap_attempt::add_pull (nano::pull_info const & pull_a) -{ - nano::pull_info pull (pull_a); - node->bootstrap_initiator.cache.update_pull (pull); - { - nano::lock_guard lock (mutex); - pulls.push_back (pull); - } - condition.notify_all (); -} - -void nano::bootstrap_attempt::requeue_pull (nano::pull_info const & pull_a, bool network_error) -{ - auto pull (pull_a); - if (!network_error) - { - ++pull.attempts; - } - ++requeued_pulls; - if (mode != nano::bootstrap_mode::lazy && pull.attempts < pull.retry_limit + (pull.processed / 10000)) - { - nano::lock_guard lock (mutex); - pulls.push_front (pull); - condition.notify_all (); - } - else if (mode == nano::bootstrap_mode::lazy && (pull.retry_limit == std::numeric_limits::max () || pull.attempts <= pull.retry_limit + (pull.processed / node->network_params.bootstrap.lazy_max_pull_blocks))) - { - assert (pull.account_or_head == pull.head); - if (!lazy_processed_or_exists (pull.account_or_head)) - { - // Retry for lazy pulls - nano::lock_guard lock (mutex); - pulls.push_back (pull); - condition.notify_all (); - } - } - else - { - if (node->config.logging.bulk_pull_logging ()) - { - node->logger.try_log (boost::str (boost::format ("Failed to pull account %1% down to %2% after %3% attempts and %4% blocks processed") % pull.account_or_head.to_account () % pull.end.to_string () % pull.attempts % pull.processed)); - } - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in); - - node->bootstrap_initiator.cache.add (pull); - if (mode == nano::bootstrap_mode::lazy && pull.processed > 0) - { - assert (pull.account_or_head == pull.head); - nano::lock_guard lazy_lock (lazy_mutex); - lazy_add (pull.account_or_head, pull.retry_limit); - } - } -} - -void nano::bootstrap_attempt::add_bulk_push_target (nano::block_hash const & head, nano::block_hash const & end) -{ - nano::lock_guard lock (mutex); - bulk_push_targets.emplace_back (head, end); -} - -void nano::bootstrap_attempt::attempt_restart_check (nano::unique_lock & lock_a) -{ - /* Conditions to start frontiers confirmation: - - not completed frontiers confirmation - - more than 256 pull retries usually indicating issues with requested pulls - - or 128k processed blocks indicating large bootstrap */ - if (!frontiers_confirmed && (requeued_pulls > (!node->network_params.network.is_test_network () ? nano::bootstrap_limits::requeued_pulls_limit : nano::bootstrap_limits::requeued_pulls_limit_test) || total_blocks > nano::bootstrap_limits::frontier_confirmation_blocks_limit)) - { - auto confirmed (confirm_frontiers (lock_a)); - assert (lock_a.owns_lock ()); - if (!confirmed) - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in); - auto score (node->bootstrap_initiator.excluded_peers.add (endpoint_frontier_request, node->network.size ())); - if (score >= nano::bootstrap_excluded_peers::score_limit) - { - node->logger.always_log (boost::str (boost::format ("Adding peer %1% to excluded peers list with score %2% after %3% seconds bootstrap attempt") % endpoint_frontier_request % score % std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt_start).count ())); - } - lock_a.unlock (); - stop (); - lock_a.lock (); - // Start new bootstrap connection - auto node_l (node->shared ()); - node->background ([node_l]() { - node_l->bootstrap_initiator.bootstrap (true); - }); - } - else - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_successful, nano::stat::dir::in); - } - frontiers_confirmed = confirmed; - } -} - -bool nano::bootstrap_attempt::confirm_frontiers (nano::unique_lock & lock_a) -{ - bool confirmed (false); - assert (!frontiers_confirmed); - // clang-format off - condition.wait (lock_a, [& stopped = stopped] { return !stopped; }); - // clang-format on - std::vector frontiers; - for (auto i (pulls.begin ()), end (pulls.end ()); i != end && frontiers.size () != nano::bootstrap_limits::bootstrap_max_confirm_frontiers; ++i) - { - if (!i->head.is_zero () && std::find (frontiers.begin (), frontiers.end (), i->head) == frontiers.end ()) - { - frontiers.push_back (i->head); - } - } - for (auto i (recent_pulls_head.begin ()), end (recent_pulls_head.end ()); i != end && frontiers.size () != nano::bootstrap_limits::bootstrap_max_confirm_frontiers; ++i) - { - if (!i->is_zero () && std::find (frontiers.begin (), frontiers.end (), *i) == frontiers.end ()) - { - frontiers.push_back (*i); - } - } - lock_a.unlock (); - auto frontiers_count (frontiers.size ()); - if (frontiers_count > 0) - { - const size_t reps_limit = 20; - auto representatives (node->rep_crawler.representatives ()); - auto reps_weight (node->rep_crawler.total_weight ()); - auto representatives_copy (representatives); - nano::uint128_t total_weight (0); - // Select random peers from bottom 50% of principal representatives - if (representatives.size () > 1) - { - std::reverse (representatives.begin (), representatives.end ()); - representatives.resize (representatives.size () / 2); - for (auto i = static_cast (representatives.size () - 1); i > 0; --i) - { - auto k = nano::random_pool::generate_word32 (0, i); - std::swap (representatives[i], representatives[k]); - } - if (representatives.size () > reps_limit) - { - representatives.resize (reps_limit); - } - } - for (auto const & rep : representatives) - { - total_weight += rep.weight.number (); - } - // Select peers with total 25% of reps stake from top 50% of principal representatives - representatives_copy.resize (representatives_copy.size () / 2); - while (total_weight < reps_weight / 4) // 25% - { - auto k = nano::random_pool::generate_word32 (0, static_cast (representatives_copy.size () - 1)); - auto rep (representatives_copy[k]); - if (std::find (representatives.begin (), representatives.end (), rep) == representatives.end ()) - { - representatives.push_back (rep); - total_weight += rep.weight.number (); - } - } - // Start requests - for (auto i (0), max_requests (20); i <= max_requests && !confirmed && !stopped; ++i) - { - std::unordered_map, std::deque>> batched_confirm_req_bundle; - std::deque> request; - // Find confirmed frontiers (tally > 12.5% of reps stake, 60% of requestsed reps responded - for (auto ii (frontiers.begin ()); ii != frontiers.end ();) - { - if (node->ledger.block_exists (*ii)) - { - ii = frontiers.erase (ii); - } - else - { - nano::lock_guard active_lock (node->active.mutex); - auto existing (node->active.find_inactive_votes_cache (*ii)); - nano::uint128_t tally; - for (auto & voter : existing.voters) - { - tally += node->ledger.weight (voter); - } - if (existing.confirmed || (tally > reps_weight / 8 && existing.voters.size () >= representatives.size () * 0.6)) // 12.5% of weight, 60% of reps - { - ii = frontiers.erase (ii); - } - else - { - for (auto const & rep : representatives) - { - if (std::find (existing.voters.begin (), existing.voters.end (), rep.account) == existing.voters.end ()) - { - release_assert (!ii->is_zero ()); - auto rep_request (batched_confirm_req_bundle.find (rep.channel)); - if (rep_request == batched_confirm_req_bundle.end ()) - { - std::deque> insert_root_hash = { std::make_pair (*ii, *ii) }; - batched_confirm_req_bundle.emplace (rep.channel, insert_root_hash); - } - else - { - rep_request->second.emplace_back (*ii, *ii); - } - } - } - ++ii; - } - } - } - auto confirmed_count (frontiers_count - frontiers.size ()); - if (confirmed_count >= frontiers_count * nano::bootstrap_limits::required_frontier_confirmation_ratio) // 80% of frontiers confirmed - { - confirmed = true; - } - else if (i < max_requests) - { - node->network.broadcast_confirm_req_batched_many (batched_confirm_req_bundle); - std::this_thread::sleep_for (std::chrono::milliseconds (!node->network_params.network.is_test_network () ? 500 : 5)); - } - } - if (!confirmed) - { - node->logger.always_log (boost::str (boost::format ("Failed to confirm frontiers for bootstrap attempt. %1% of %2% frontiers were not confirmed") % frontiers.size () % frontiers_count)); - } - } - lock_a.lock (); - return confirmed; -} - -void nano::bootstrap_attempt::lazy_start (nano::hash_or_account const & hash_or_account_a, bool confirmed) -{ - nano::lock_guard lazy_lock (lazy_mutex); - // Add start blocks, limit 1024 (4k with disabled legacy bootstrap) - size_t max_keys (node->flags.disable_legacy_bootstrap ? 4 * 1024 : 1024); - if (lazy_keys.size () < max_keys && lazy_keys.find (hash_or_account_a) == lazy_keys.end () && lazy_blocks.find (hash_or_account_a) == lazy_blocks.end ()) - { - lazy_keys.insert (hash_or_account_a); - lazy_pulls.emplace_back (hash_or_account_a, confirmed ? std::numeric_limits::max () : node->network_params.bootstrap.lazy_retry_limit); - } -} - -void nano::bootstrap_attempt::lazy_add (nano::hash_or_account const & hash_or_account_a, unsigned retry_limit) -{ - // Add only unknown blocks - assert (!lazy_mutex.try_lock ()); - if (lazy_blocks.find (hash_or_account_a) == lazy_blocks.end ()) - { - lazy_pulls.emplace_back (hash_or_account_a, retry_limit); - } -} - -void nano::bootstrap_attempt::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a, bool confirmed_a) -{ - nano::unique_lock lazy_lock (lazy_mutex); - // Add only known blocks - auto existing (lazy_blocks.find (hash_a)); - if (existing != lazy_blocks.end ()) - { - lazy_blocks.erase (existing); - lazy_lock.unlock (); - requeue_pull (nano::pull_info (hash_a, hash_a, previous_a, static_cast (1), confirmed_a ? std::numeric_limits::max () : node->network_params.bootstrap.lazy_destinations_retry_limit)); - } -} - -void nano::bootstrap_attempt::lazy_pull_flush () +nano::bootstrap_initiator::bootstrap_initiator (nano::node & node_a) : +node (node_a) { - assert (!mutex.try_lock ()); - static size_t const max_pulls (nano::bootstrap_limits::bootstrap_connection_scale_target_blocks_lazy * 3); - if (pulls.size () < max_pulls) + connections = std::make_shared (node); + bootstrap_initiator_threads.push_back (boost::thread ([this]() { + nano::thread_role::set (nano::thread_role::name::bootstrap_connections); + connections->run (); + })); + for (size_t i = 0; i < node.config.bootstrap_initiator_threads; ++i) { - last_lazy_flush = std::chrono::steady_clock::now (); - nano::lock_guard lazy_lock (lazy_mutex); - assert (node->network_params.bootstrap.lazy_max_pull_blocks <= std::numeric_limits::max ()); - nano::pull_info::count_t batch_count (node->network_params.bootstrap.lazy_max_pull_blocks); - if (total_blocks > nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit && !lazy_blocks.empty ()) - { - double lazy_blocks_ratio (total_blocks / lazy_blocks.size ()); - if (lazy_blocks_ratio > nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio) - { - // Increasing blocks ratio weight as more important (^3). Small batch count should lower blocks ratio below target - double lazy_blocks_factor (std::pow (lazy_blocks_ratio / nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio, 3.0)); - // Decreasing total block count weight as less important (sqrt) - double total_blocks_factor (std::sqrt (total_blocks / nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit)); - uint32_t batch_count_min (node->network_params.bootstrap.lazy_max_pull_blocks / (lazy_blocks_factor * total_blocks_factor)); - batch_count = std::max (node->network_params.bootstrap.lazy_min_pull_blocks, batch_count_min); - } - } - size_t count (0); - auto transaction (node->store.tx_begin_read ()); - while (!lazy_pulls.empty () && count < max_pulls) - { - auto const & pull_start (lazy_pulls.front ()); - // Recheck if block was already processed - if (lazy_blocks.find (pull_start.first) == lazy_blocks.end () && !node->store.block_exists (transaction, pull_start.first)) - { - pulls.emplace_back (pull_start.first, pull_start.first, nano::block_hash (0), batch_count, pull_start.second); - ++count; - } - lazy_pulls.pop_front (); - } + bootstrap_initiator_threads.push_back (boost::thread ([this]() { + nano::thread_role::set (nano::thread_role::name::bootstrap_initiator); + run_bootstrap (); + })); } } -bool nano::bootstrap_attempt::lazy_finished () +nano::bootstrap_initiator::~bootstrap_initiator () { - if (stopped) - { - return true; - } - bool result (true); - auto transaction (node->store.tx_begin_read ()); - nano::lock_guard lazy_lock (lazy_mutex); - for (auto it (lazy_keys.begin ()), end (lazy_keys.end ()); it != end && !stopped;) - { - if (node->store.block_exists (transaction, *it)) - { - it = lazy_keys.erase (it); - } - else - { - result = false; - break; - // No need to increment `it` as we break above. - } - } - // Finish lazy bootstrap without lazy pulls (in combination with still_pulling ()) - if (!result && lazy_pulls.empty () && lazy_state_backlog.empty ()) - { - result = true; - } - // Don't close lazy bootstrap until all destinations are processed - if (result && !lazy_destinations.empty ()) - { - result = false; - } - return result; + stop (); } -bool nano::bootstrap_attempt::lazy_has_expired () const +void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a) { - bool result (false); - // Max 30 minutes run with enabled legacy bootstrap - static std::chrono::minutes const max_lazy_time (node->flags.disable_legacy_bootstrap ? 7 * 24 * 60 : 30); - if (std::chrono::steady_clock::now () - lazy_start_time >= max_lazy_time) + if (force) { - result = true; + stop_attempts (); } - else if (!node->flags.disable_legacy_bootstrap && lazy_blocks_count > nano::bootstrap_limits::lazy_blocks_restart_limit) + nano::unique_lock lock (mutex); + if (!stopped && find_attempt (nano::bootstrap_mode::legacy) == nullptr) { - result = true; + node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); + auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a)); + attempts_list.push_back (legacy_attempt); + attempts.add (legacy_attempt); + lock.unlock (); + condition.notify_all (); } - return result; } -void nano::bootstrap_attempt::lazy_clear () +void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bool add_to_peers, bool frontiers_confirmed, std::string id_a) { - assert (!lazy_mutex.try_lock ()); - lazy_blocks.clear (); - lazy_blocks_count = 0; - lazy_keys.clear (); - lazy_pulls.clear (); - lazy_state_backlog.clear (); - lazy_balances.clear (); - lazy_destinations.clear (); -} - -void nano::bootstrap_attempt::lazy_run () -{ - assert (!node->flags.disable_lazy_bootstrap); - start_populate_connections (); - lazy_start_time = std::chrono::steady_clock::now (); - nano::unique_lock lock (mutex); - while ((still_pulling () || !lazy_finished ()) && !lazy_has_expired ()) + if (add_to_peers) { - unsigned iterations (0); - while (still_pulling () && !lazy_has_expired ()) + if (!node.flags.disable_udp) { - if (!pulls.empty ()) - { - request_pull (lock); - } - else - { - lazy_pull_flush (); - if (pulls.empty ()) - { - condition.wait_for (lock, std::chrono::seconds (1)); - } - } - ++iterations; - // Flushing lazy pulls - if (iterations % 100 == 0 || last_lazy_flush + nano::bootstrap_limits::lazy_flush_delay_sec < std::chrono::steady_clock::now ()) - { - lazy_pull_flush (); - } - // Start backlog cleanup - if (iterations % 200 == 0) - { - lazy_backlog_cleanup (); - } - // Destinations check - if (pulls.empty () && lazy_destinations_flushed) - { - lazy_destinations_flush (); - } + node.network.udp_channels.insert (nano::transport::map_endpoint_to_v6 (endpoint_a), node.network_params.protocol.protocol_version); } - // Flushing lazy pulls - lazy_pull_flush (); - // Check if some blocks required for backlog were processed. Start destinations check - if (pulls.empty ()) + else if (!node.flags.disable_tcp_realtime) { - lazy_backlog_cleanup (); - lazy_destinations_flush (); + node.network.merge_peer (nano::transport::map_endpoint_to_v6 (endpoint_a)); } } if (!stopped) { - node->logger.try_log ("Completed lazy pulls"); - nano::unique_lock lazy_lock (lazy_mutex); - ++runs_count; - // Start wallet lazy bootstrap if required - if (!wallet_accounts.empty () && !node->flags.disable_wallet_bootstrap) + stop_attempts (); + node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); + nano::lock_guard lock (mutex); + auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a)); + attempts_list.push_back (legacy_attempt); + attempts.add (legacy_attempt); + if (frontiers_confirmed) { - pulls.clear (); - lazy_clear (); - mode = nano::bootstrap_mode::wallet_lazy; - lock.unlock (); - lazy_lock.unlock (); - wallet_run (); - lock.lock (); + node.network.excluded_peers.remove (nano::transport::map_endpoint_to_tcp (endpoint_a)); } - // Fallback to legacy bootstrap - else if (runs_count < 3 && !lazy_keys.empty () && !node->flags.disable_legacy_bootstrap) + if (!node.network.excluded_peers.check (nano::transport::map_endpoint_to_tcp (endpoint_a))) { - pulls.clear (); - lazy_clear (); - mode = nano::bootstrap_mode::legacy; - lock.unlock (); - lazy_lock.unlock (); - run (); - lock.lock (); + connections->add_connection (endpoint_a); } + legacy_attempt->frontiers_confirmed = frontiers_confirmed; } - stopped = true; condition.notify_all (); - idle.clear (); -} - -bool nano::bootstrap_attempt::process_block (std::shared_ptr block_a, nano::account const & known_account_a, uint64_t pull_blocks, nano::bulk_pull::count_t max_blocks, bool block_expected, unsigned retry_limit) -{ - bool stop_pull (false); - if (mode != nano::bootstrap_mode::legacy && block_expected) - { - stop_pull = process_block_lazy (block_a, known_account_a, pull_blocks, max_blocks, retry_limit); - } - else if (mode != nano::bootstrap_mode::legacy) - { - // Drop connection with unexpected block for lazy bootstrap - stop_pull = true; - } - else - { - nano::unchecked_info info (block_a, known_account_a, 0, nano::signature_verification::unknown); - node->block_processor.add (info); - } - return stop_pull; } -bool nano::bootstrap_attempt::process_block_lazy (std::shared_ptr block_a, nano::account const & known_account_a, uint64_t pull_blocks, nano::bulk_pull::count_t max_blocks, unsigned retry_limit) +void nano::bootstrap_initiator::bootstrap_lazy (nano::hash_or_account const & hash_or_account_a, bool force, bool confirmed, std::string id_a) { - bool stop_pull (false); - auto hash (block_a->hash ()); - nano::unique_lock lazy_lock (lazy_mutex); - // Processing new blocks - if (lazy_blocks.find (hash) == lazy_blocks.end ()) + auto lazy_attempt (current_lazy_attempt ()); + if (lazy_attempt == nullptr || force) { - // Search for new dependencies - if (!block_a->source ().is_zero () && !node->ledger.block_exists (block_a->source ()) && block_a->source () != node->network_params.ledger.genesis_account) - { - lazy_add (block_a->source (), retry_limit); - } - else if (block_a->type () == nano::block_type::state) - { - lazy_block_state (block_a, retry_limit); - } - else if (block_a->type () == nano::block_type::send) + if (force) { - std::shared_ptr block_l (std::static_pointer_cast (block_a)); - if (block_l != nullptr && !block_l->hashables.destination.is_zero ()) - { - lazy_destinations_increment (block_l->hashables.destination); - } - } - lazy_blocks.insert (hash); - ++lazy_blocks_count; - // Adding lazy balances for first processed block in pull - if (pull_blocks == 0 && (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send)) - { - lazy_balances.emplace (hash, block_a->balance ().number ()); + stop_attempts (); } - // Clearing lazy balances for previous block - if (!block_a->previous ().is_zero () && lazy_balances.find (block_a->previous ()) != lazy_balances.end ()) + node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_lazy, nano::stat::dir::out); + nano::lock_guard lock (mutex); + if (!stopped && find_attempt (nano::bootstrap_mode::lazy) == nullptr) { - lazy_balances.erase (block_a->previous ()); + lazy_attempt = std::make_shared (node.shared (), attempts.incremental++, id_a.empty () ? hash_or_account_a.to_string () : id_a); + attempts_list.push_back (lazy_attempt); + attempts.add (lazy_attempt); + lazy_attempt->lazy_start (hash_or_account_a, confirmed); } - lazy_block_state_backlog_check (block_a, hash); - lazy_lock.unlock (); - nano::unchecked_info info (block_a, known_account_a, 0, nano::signature_verification::unknown, retry_limit == std::numeric_limits::max ()); - node->block_processor.add (info); } - // Force drop lazy bootstrap connection for long bulk_pull - if (pull_blocks > max_blocks) + else { - stop_pull = true; + lazy_attempt->lazy_start (hash_or_account_a, confirmed); } - return stop_pull; + condition.notify_all (); } -void nano::bootstrap_attempt::lazy_block_state (std::shared_ptr block_a, unsigned retry_limit) +void nano::bootstrap_initiator::bootstrap_wallet (std::deque & accounts_a) { - std::shared_ptr block_l (std::static_pointer_cast (block_a)); - if (block_l != nullptr) + debug_assert (!accounts_a.empty ()); + auto wallet_attempt (current_wallet_attempt ()); + node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_wallet_lazy, nano::stat::dir::out); + if (wallet_attempt == nullptr) { - auto transaction (node->store.tx_begin_read ()); - nano::uint128_t balance (block_l->hashables.balance.number ()); - auto const & link (block_l->hashables.link); - // If link is not epoch link or 0. And if block from link is unknown - if (!link.is_zero () && !node->ledger.is_epoch_link (link) && lazy_blocks.find (link) == lazy_blocks.end () && !node->store.block_exists (transaction, link)) - { - auto const & previous (block_l->hashables.previous); - // If state block previous is 0 then source block required - if (previous.is_zero ()) - { - lazy_add (link, retry_limit); - } - // In other cases previous block balance required to find out subtype of state block - else if (node->store.block_exists (transaction, previous)) - { - if (node->ledger.balance (transaction, previous) <= balance) - { - lazy_add (link, retry_limit); - } - else - { - lazy_destinations_increment (link); - } - } - // Search balance of already processed previous blocks - else if (lazy_blocks.find (previous) != lazy_blocks.end ()) - { - auto previous_balance (lazy_balances.find (previous)); - if (previous_balance != lazy_balances.end ()) - { - if (previous_balance->second <= balance) - { - lazy_add (link, retry_limit); - } - else - { - lazy_destinations_increment (link); - } - lazy_balances.erase (previous_balance); - } - } - // Insert in backlog state blocks if previous wasn't already processed - else - { - lazy_state_backlog.emplace (previous, nano::lazy_state_backlog_item{ link, balance, retry_limit }); - } - } + nano::lock_guard lock (mutex); + std::string id (!accounts_a.empty () ? accounts_a[0].to_account () : ""); + wallet_attempt = std::make_shared (node.shared (), attempts.incremental++, id); + attempts_list.push_back (wallet_attempt); + attempts.add (wallet_attempt); + wallet_attempt->wallet_start (accounts_a); } -} - -void nano::bootstrap_attempt::lazy_block_state_backlog_check (std::shared_ptr block_a, nano::block_hash const & hash_a) -{ - // Search unknown state blocks balances - auto find_state (lazy_state_backlog.find (hash_a)); - if (find_state != lazy_state_backlog.end ()) + else { - auto next_block (find_state->second); - // Retrieve balance for previous state & send blocks - if (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send) - { - if (block_a->balance ().number () <= next_block.balance) // balance - { - lazy_add (next_block.link, next_block.retry_limit); // link - } - else - { - lazy_destinations_increment (next_block.link); - } - } - // Assumption for other legacy block types - else if (lazy_undefined_links.find (next_block.link) == lazy_undefined_links.end ()) - { - lazy_add (next_block.link, node->network_params.bootstrap.lazy_retry_limit); // Head is not confirmed. It can be account or hash or non-existing - lazy_undefined_links.insert (next_block.link); - } - lazy_state_backlog.erase (find_state); + wallet_attempt->wallet_start (accounts_a); } + condition.notify_all (); } -void nano::bootstrap_attempt::lazy_backlog_cleanup () +void nano::bootstrap_initiator::run_bootstrap () { - auto transaction (node->store.tx_begin_read ()); - nano::lock_guard lazy_lock (lazy_mutex); - for (auto it (lazy_state_backlog.begin ()), end (lazy_state_backlog.end ()); it != end && !stopped;) + nano::unique_lock lock (mutex); + while (!stopped) { - if (node->store.block_exists (transaction, it->first)) + if (has_new_attempts ()) { - auto next_block (it->second); - if (node->ledger.balance (transaction, it->first) <= next_block.balance) // balance - { - lazy_add (next_block.link, next_block.retry_limit); // link - } - else + auto attempt (new_attempt ()); + lock.unlock (); + if (attempt != nullptr) { - lazy_destinations_increment (next_block.link); + attempt->run (); + remove_attempt (attempt); } - it = lazy_state_backlog.erase (it); - } - else - { - lazy_add (it->first, it->second.retry_limit); - ++it; - } - } -} - -void nano::bootstrap_attempt::lazy_destinations_increment (nano::account const & destination_a) -{ - // Enabled only if legacy bootstrap is not available. Legacy bootstrap is a more effective way to receive all existing destinations - if (node->flags.disable_legacy_bootstrap) - { - // Update accounts counter for send blocks - auto existing (lazy_destinations.get ().find (destination_a)); - if (existing != lazy_destinations.get ().end ()) - { - lazy_destinations.get ().modify (existing, [](nano::lazy_destinations_item & item_a) { - ++item_a.count; - }); + lock.lock (); } else { - lazy_destinations.insert (nano::lazy_destinations_item{ destination_a, 1 }); - } - } -} - -void nano::bootstrap_attempt::lazy_destinations_flush () -{ - lazy_destinations_flushed = true; - size_t count (0); - nano::lock_guard lazy_lock (lazy_mutex); - for (auto it (lazy_destinations.get ().begin ()), end (lazy_destinations.get ().end ()); it != end && count < nano::bootstrap_limits::lazy_destinations_request_limit && !stopped;) - { - lazy_add (it->account, node->network_params.bootstrap.lazy_destinations_retry_limit); - it = lazy_destinations.get ().erase (it); - ++count; - } -} - -bool nano::bootstrap_attempt::lazy_processed_or_exists (nano::block_hash const & hash_a) -{ - bool result (false); - nano::unique_lock lazy_lock (lazy_mutex); - if (lazy_blocks.find (hash_a) != lazy_blocks.end ()) - { - result = true; - } - else - { - lazy_lock.unlock (); - if (node->ledger.block_exists (hash_a)) - { - result = true; + condition.wait (lock); } } - return result; } -void nano::bootstrap_attempt::request_pending (nano::unique_lock & lock_a) +void nano::bootstrap_initiator::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a, bool confirmed_a) { - auto connection_l (connection (lock_a)); - if (connection_l) + auto lazy_attempt (current_lazy_attempt ()); + if (lazy_attempt != nullptr) { - auto account (wallet_accounts.front ()); - wallet_accounts.pop_front (); - ++pulling; - // The bulk_pull_account_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference - // Dispatch request in an external thread in case it needs to be destroyed - node->background ([connection_l, account]() { - auto client (std::make_shared (connection_l, account)); - client->request (); - }); + lazy_attempt->lazy_requeue (hash_a, previous_a, confirmed_a); } } -void nano::bootstrap_attempt::requeue_pending (nano::account const & account_a) +void nano::bootstrap_initiator::add_observer (std::function const & observer_a) { - auto account (account_a); - { - nano::lock_guard lock (mutex); - wallet_accounts.push_front (account); - condition.notify_all (); - } + nano::lock_guard lock (observers_mutex); + observers.push_back (observer_a); } -void nano::bootstrap_attempt::wallet_start (std::deque & accounts_a) +bool nano::bootstrap_initiator::in_progress () { nano::lock_guard lock (mutex); - wallet_accounts.swap (accounts_a); -} - -bool nano::bootstrap_attempt::wallet_finished () -{ - assert (!mutex.try_lock ()); - auto running (!stopped); - auto more_accounts (!wallet_accounts.empty ()); - auto still_pulling (pulling > 0); - return running && (more_accounts || still_pulling); + return !attempts_list.empty (); } -void nano::bootstrap_attempt::wallet_run () +std::shared_ptr nano::bootstrap_initiator::find_attempt (nano::bootstrap_mode mode_a) { - assert (!node->flags.disable_wallet_bootstrap); - start_populate_connections (); - auto start_time (std::chrono::steady_clock::now ()); - auto max_time (std::chrono::minutes (10)); - nano::unique_lock lock (mutex); - while (wallet_finished () && std::chrono::steady_clock::now () - start_time < max_time) - { - if (!wallet_accounts.empty ()) - { - request_pending (lock); - } - else - { - condition.wait (lock); - } - } - if (!stopped) + for (auto & i : attempts_list) { - node->logger.try_log ("Completed wallet lazy pulls"); - ++runs_count; - // Start lazy bootstrap if some lazy keys were inserted - if (!lazy_finished ()) + if (i->mode == mode_a) { - lock.unlock (); - total_blocks = 0; - lazy_run (); - lock.lock (); + return i; } } - stopped = true; - condition.notify_all (); - idle.clear (); -} - -nano::bootstrap_initiator::bootstrap_initiator (nano::node & node_a) : -node (node_a), -stopped (false), -thread ([this]() { - nano::thread_role::set (nano::thread_role::name::bootstrap_initiator); - run_bootstrap (); -}) -{ -} - -nano::bootstrap_initiator::~bootstrap_initiator () -{ - stop (); -} - -void nano::bootstrap_initiator::bootstrap (bool force) -{ - nano::unique_lock lock (mutex); - if (force && attempt != nullptr) - { - attempt->stop (); - // clang-format off - condition.wait (lock, [&attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - // clang-format on - } - if (!stopped && attempt == nullptr) - { - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); - attempt = std::make_shared (node.shared ()); - condition.notify_all (); - } + return nullptr; } -void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bool add_to_peers, bool frontiers_confirmed) +void nano::bootstrap_initiator::remove_attempt (std::shared_ptr attempt_a) { - if (add_to_peers) - { - node.network.udp_channels.insert (nano::transport::map_endpoint_to_v6 (endpoint_a), node.network_params.protocol.protocol_version); - } nano::unique_lock lock (mutex); - if (!stopped) + auto attempt (std::find (attempts_list.begin (), attempts_list.end (), attempt_a)); + if (attempt != attempts_list.end ()) { - if (attempt != nullptr) - { - attempt->stop (); - // clang-format off - condition.wait (lock, [&attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - // clang-format on - } - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); - attempt = std::make_shared (node.shared ()); - if (frontiers_confirmed) - { - excluded_peers.remove (nano::transport::map_endpoint_to_tcp (endpoint_a)); - } - if (!excluded_peers.check (nano::transport::map_endpoint_to_tcp (endpoint_a))) - { - attempt->add_connection (endpoint_a); - } - attempt->frontiers_confirmed = frontiers_confirmed; - condition.notify_all (); + attempts.remove ((*attempt)->incremental_id); + attempts_list.erase (attempt); + debug_assert (attempts.size () == attempts_list.size ()); } + lock.unlock (); + condition.notify_all (); } -void nano::bootstrap_initiator::bootstrap_lazy (nano::hash_or_account const & hash_or_account_a, bool force, bool confirmed) +std::shared_ptr nano::bootstrap_initiator::new_attempt () { + for (auto & i : attempts_list) { - nano::unique_lock lock (mutex); - if (force && attempt != nullptr) - { - attempt->stop (); - // clang-format off - condition.wait (lock, [&attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - // clang-format on - } - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_lazy, nano::stat::dir::out); - if (attempt == nullptr) + if (!i->started.exchange (true)) { - attempt = std::make_shared (node.shared (), nano::bootstrap_mode::lazy); + return i; } - attempt->lazy_start (hash_or_account_a, confirmed); } - condition.notify_all (); + return nullptr; } -void nano::bootstrap_initiator::bootstrap_wallet (std::deque & accounts_a) +bool nano::bootstrap_initiator::has_new_attempts () { + for (auto & i : attempts_list) { - nano::unique_lock lock (mutex); - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_wallet_lazy, nano::stat::dir::out); - if (attempt == nullptr) + if (!i->started) { - attempt = std::make_shared (node.shared (), nano::bootstrap_mode::wallet_lazy); + return true; } - attempt->wallet_start (accounts_a); } - condition.notify_all (); + return false; } -void nano::bootstrap_initiator::run_bootstrap () +std::shared_ptr nano::bootstrap_initiator::current_attempt () { - nano::unique_lock lock (mutex); - while (!stopped) - { - if (attempt != nullptr) - { - lock.unlock (); - if (attempt->mode == nano::bootstrap_mode::legacy) - { - attempt->run (); - } - else if (attempt->mode == nano::bootstrap_mode::lazy) - { - attempt->lazy_run (); - } - else - { - attempt->wallet_run (); - } - lock.lock (); - attempt = nullptr; - condition.notify_all (); - } - else - { - condition.wait (lock); - } - } + nano::lock_guard lock (mutex); + return find_attempt (nano::bootstrap_mode::legacy); } -void nano::bootstrap_initiator::add_observer (std::function const & observer_a) +std::shared_ptr nano::bootstrap_initiator::current_lazy_attempt () { - nano::lock_guard lock (observers_mutex); - observers.push_back (observer_a); + nano::lock_guard lock (mutex); + return find_attempt (nano::bootstrap_mode::lazy); } -bool nano::bootstrap_initiator::in_progress () +std::shared_ptr nano::bootstrap_initiator::current_wallet_attempt () { - return current_attempt () != nullptr; + nano::lock_guard lock (mutex); + return find_attempt (nano::bootstrap_mode::wallet_lazy); } -std::shared_ptr nano::bootstrap_initiator::current_attempt () +void nano::bootstrap_initiator::stop_attempts () { - nano::lock_guard lock (mutex); - return attempt; + nano::unique_lock lock (mutex); + std::vector> copy_attempts; + copy_attempts.swap (attempts_list); + attempts.clear (); + lock.unlock (); + for (auto & i : copy_attempts) + { + i->stop (); + } } void nano::bootstrap_initiator::stop () { if (!stopped.exchange (true)) { - { - nano::lock_guard guard (mutex); - if (attempt != nullptr) - { - attempt->stop (); - } - } + stop_attempts (); + connections->stop (); condition.notify_all (); - if (thread.joinable ()) + for (auto & thread : bootstrap_initiator_threads) { - thread.join (); + if (thread.joinable ()) + { + thread.join (); + } } } } @@ -1499,13 +282,10 @@ void nano::bootstrap_initiator::notify_listeners (bool in_progress_a) } } -namespace nano +std::unique_ptr nano::collect_container_info (bootstrap_initiator & bootstrap_initiator, const std::string & name) { -std::unique_ptr collect_seq_con_info (bootstrap_initiator & bootstrap_initiator, const std::string & name) -{ - size_t count = 0; - size_t cache_count = 0; - size_t excluded_peers_count = 0; + size_t count; + size_t cache_count; { nano::lock_guard guard (bootstrap_initiator.observers_mutex); count = bootstrap_initiator.observers.size (); @@ -1514,21 +294,14 @@ std::unique_ptr collect_seq_con_info (bootstrap_initiato nano::lock_guard guard (bootstrap_initiator.cache.pulls_cache_mutex); cache_count = bootstrap_initiator.cache.cache.size (); } - { - nano::lock_guard guard (bootstrap_initiator.excluded_peers.excluded_peers_mutex); - excluded_peers_count = bootstrap_initiator.excluded_peers.peers.size (); - } auto sizeof_element = sizeof (decltype (bootstrap_initiator.observers)::value_type); auto sizeof_cache_element = sizeof (decltype (bootstrap_initiator.cache.cache)::value_type); - auto sizeof_excluded_peers_element = sizeof (decltype (bootstrap_initiator.excluded_peers.peers)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "observers", count, sizeof_element })); - composite->add_component (std::make_unique (seq_con_info{ "pulls_cache", cache_count, sizeof_cache_element })); - composite->add_component (std::make_unique (seq_con_info{ "excluded_peers", excluded_peers_count, sizeof_excluded_peers_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "observers", count, sizeof_element })); + composite->add_component (std::make_unique (container_info{ "pulls_cache", cache_count, sizeof_cache_element })); return composite; } -} void nano::pulls_cache::add (nano::pull_info const & pull_a) { @@ -1540,15 +313,15 @@ void nano::pulls_cache::add (nano::pull_info const & pull_a) { cache.erase (cache.begin ()); } - assert (cache.size () <= cache_size_max); + debug_assert (cache.size () <= cache_size_max); nano::uint512_union head_512 (pull_a.account_or_head, pull_a.head_original); auto existing (cache.get ().find (head_512)); if (existing == cache.get ().end ()) { // Insert new pull - auto inserted (cache.insert (nano::cached_pulls{ std::chrono::steady_clock::now (), head_512, pull_a.head })); + auto inserted (cache.emplace (nano::cached_pulls{ std::chrono::steady_clock::now (), head_512, pull_a.head })); (void)inserted; - assert (inserted.second); + debug_assert (inserted.second); } else { @@ -1579,65 +352,40 @@ void nano::pulls_cache::remove (nano::pull_info const & pull_a) cache.get ().erase (head_512); } -uint64_t nano::bootstrap_excluded_peers::add (nano::tcp_endpoint const & endpoint_a, size_t network_peers_count) +void nano::bootstrap_attempts::add (std::shared_ptr attempt_a) { - uint64_t result (0); - nano::lock_guard guard (excluded_peers_mutex); - // Clean old excluded peers - while (peers.size () > 1 && peers.size () > std::min (static_cast (excluded_peers_size_max), network_peers_count * excluded_peers_percentage_limit)) - { - peers.erase (peers.begin ()); - } - assert (peers.size () <= excluded_peers_size_max); - auto existing (peers.get ().find (endpoint_a)); - if (existing == peers.get ().end ()) - { - // Insert new endpoint - auto inserted (peers.insert (nano::excluded_peers_item{ std::chrono::steady_clock::steady_clock::now () + exclude_time_hours, endpoint_a, 1 })); - (void)inserted; - assert (inserted.second); - result = 1; - } - else - { - // Update existing endpoint - peers.get ().modify (existing, [&result](nano::excluded_peers_item & item_a) { - ++item_a.score; - result = item_a.score; - if (item_a.score == nano::bootstrap_excluded_peers::score_limit) - { - item_a.exclude_until = std::chrono::steady_clock::now () + nano::bootstrap_excluded_peers::exclude_time_hours; - } - else if (item_a.score > nano::bootstrap_excluded_peers::score_limit) - { - item_a.exclude_until = std::chrono::steady_clock::now () + nano::bootstrap_excluded_peers::exclude_time_hours * item_a.score * 2; - } - }); - } - return result; + nano::lock_guard lock (bootstrap_attempts_mutex); + attempts.emplace (attempt_a->incremental_id, attempt_a); +} + +void nano::bootstrap_attempts::remove (uint64_t incremental_id_a) +{ + nano::lock_guard lock (bootstrap_attempts_mutex); + attempts.erase (incremental_id_a); +} + +void nano::bootstrap_attempts::clear () +{ + nano::lock_guard lock (bootstrap_attempts_mutex); + attempts.clear (); } -bool nano::bootstrap_excluded_peers::check (nano::tcp_endpoint const & endpoint_a) +std::shared_ptr nano::bootstrap_attempts::find (uint64_t incremental_id_a) { - bool excluded (false); - nano::lock_guard guard (excluded_peers_mutex); - auto existing (peers.get ().find (endpoint_a)); - if (existing != peers.get ().end () && existing->score >= score_limit) + nano::lock_guard lock (bootstrap_attempts_mutex); + auto find_attempt (attempts.find (incremental_id_a)); + if (find_attempt != attempts.end ()) { - if (existing->exclude_until > std::chrono::steady_clock::now ()) - { - excluded = true; - } - else if (existing->exclude_until + exclude_remove_hours * existing->score < std::chrono::steady_clock::now ()) - { - peers.get ().erase (existing); - } + return find_attempt->second; + } + else + { + return nullptr; } - return excluded; } -void nano::bootstrap_excluded_peers::remove (nano::tcp_endpoint const & endpoint_a) +size_t nano::bootstrap_attempts::size () { - nano::lock_guard guard (excluded_peers_mutex); - peers.get ().erase (endpoint_a); + nano::lock_guard lock (bootstrap_attempts_mutex); + return attempts.size (); } diff --git a/nano/node/bootstrap/bootstrap.hpp b/nano/node/bootstrap/bootstrap.hpp index eaf430bf50..5e139e5018 100644 --- a/nano/node/bootstrap/bootstrap.hpp +++ b/nano/node/bootstrap/bootstrap.hpp @@ -1,183 +1,40 @@ #pragma once #include +#include #include -#include -#include -#include -#include #include #include #include -#include #include #include #include -#include #include -#include -#include + +namespace mi = boost::multi_index; namespace nano { -class bootstrap_attempt; -class bootstrap_client; class node; + +class bootstrap_connections; namespace transport { class channel_tcp; } -enum class sync_result -{ - success, - error, - fork -}; enum class bootstrap_mode { legacy, lazy, wallet_lazy }; -class lazy_state_backlog_item final -{ -public: - nano::link link{ 0 }; - nano::uint128_t balance{ 0 }; - unsigned retry_limit{ 0 }; -}; -class lazy_destinations_item final -{ -public: - nano::account account{ 0 }; - uint64_t count{ 0 }; -}; -class frontier_req_client; -class bulk_push_client; -class bootstrap_attempt final : public std::enable_shared_from_this -{ -public: - explicit bootstrap_attempt (std::shared_ptr node_a, nano::bootstrap_mode mode_a = nano::bootstrap_mode::legacy); - ~bootstrap_attempt (); - void run (); - std::shared_ptr connection (nano::unique_lock &, bool = false); - bool consume_future (std::future &); - void populate_connections (); - void start_populate_connections (); - bool request_frontier (nano::unique_lock &, bool = false); - void request_pull (nano::unique_lock &); - void request_push (nano::unique_lock &); - void add_connection (nano::endpoint const &); - void connect_client (nano::tcp_endpoint const &); - void pool_connection (std::shared_ptr); - void stop (); - void requeue_pull (nano::pull_info const &, bool = false); - void add_pull (nano::pull_info const &); - bool still_pulling (); - void run_start (nano::unique_lock &); - unsigned target_connections (size_t pulls_remaining); - bool should_log (); - void add_bulk_push_target (nano::block_hash const &, nano::block_hash const &); - void attempt_restart_check (nano::unique_lock &); - bool confirm_frontiers (nano::unique_lock &); - bool process_block (std::shared_ptr, nano::account const &, uint64_t, nano::bulk_pull::count_t, bool, unsigned); - /** Lazy bootstrap */ - void lazy_run (); - void lazy_start (nano::hash_or_account const &, bool confirmed = true); - void lazy_add (nano::hash_or_account const &, unsigned = std::numeric_limits::max ()); - void lazy_requeue (nano::block_hash const &, nano::block_hash const &, bool); - bool lazy_finished (); - bool lazy_has_expired () const; - void lazy_pull_flush (); - void lazy_clear (); - bool process_block_lazy (std::shared_ptr, nano::account const &, uint64_t, nano::bulk_pull::count_t, unsigned); - void lazy_block_state (std::shared_ptr, unsigned); - void lazy_block_state_backlog_check (std::shared_ptr, nano::block_hash const &); - void lazy_backlog_cleanup (); - void lazy_destinations_increment (nano::account const &); - void lazy_destinations_flush (); - bool lazy_processed_or_exists (nano::block_hash const &); - /** Lazy bootstrap */ - /** Wallet bootstrap */ - void request_pending (nano::unique_lock &); - void requeue_pending (nano::account const &); - void wallet_run (); - void wallet_start (std::deque &); - bool wallet_finished (); - /** Wallet bootstrap */ - std::mutex next_log_mutex; - std::chrono::steady_clock::time_point next_log; - std::deque> clients; - std::weak_ptr connection_frontier_request; - nano::tcp_endpoint endpoint_frontier_request; - std::weak_ptr frontiers; - std::weak_ptr push; - std::deque pulls; - std::deque recent_pulls_head; - std::deque> idle; - std::atomic connections{ 0 }; - std::atomic pulling{ 0 }; - std::shared_ptr node; - std::atomic account_count{ 0 }; - std::atomic total_blocks{ 0 }; - std::atomic runs_count{ 0 }; - std::atomic requeued_pulls{ 0 }; - std::vector> bulk_push_targets; - std::atomic frontiers_received{ false }; - std::atomic frontiers_confirmed{ false }; - std::atomic populate_connections_started{ false }; - std::atomic stopped{ false }; - std::chrono::steady_clock::time_point attempt_start{ std::chrono::steady_clock::now () }; - nano::bootstrap_mode mode; - std::mutex mutex; - nano::condition_variable condition; - // Lazy bootstrap - std::unordered_set lazy_blocks; - std::unordered_map lazy_state_backlog; - std::unordered_set lazy_undefined_links; - std::unordered_map lazy_balances; - std::unordered_set lazy_keys; - std::deque> lazy_pulls; - std::chrono::steady_clock::time_point lazy_start_time; - std::chrono::steady_clock::time_point last_lazy_flush{ std::chrono::steady_clock::now () }; - class account_tag - { - }; - class count_tag - { - }; - boost::multi_index_container< - lazy_destinations_item, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique, boost::multi_index::member, std::greater>, - boost::multi_index::hashed_unique, boost::multi_index::member>>> - lazy_destinations; - std::atomic lazy_blocks_count{ 0 }; - std::atomic lazy_destinations_flushed{ false }; - std::mutex lazy_mutex; - // Wallet lazy bootstrap - std::deque wallet_accounts; -}; -class bootstrap_client final : public std::enable_shared_from_this +enum class sync_result { -public: - bootstrap_client (std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr); - ~bootstrap_client (); - std::shared_ptr shared (); - void stop (bool force); - double block_rate () const; - double elapsed_seconds () const; - std::shared_ptr node; - std::shared_ptr attempt; - std::shared_ptr channel; - std::shared_ptr socket; - std::shared_ptr> receive_buffer; - std::chrono::steady_clock::time_point start_time; - std::atomic block_count; - std::atomic pending_stop; - std::atomic hard_stop; + success, + error, + fork }; class cached_pulls final { @@ -196,42 +53,28 @@ class pulls_cache final class account_head_tag { }; - boost::multi_index_container< - nano::cached_pulls, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique, boost::multi_index::member>>> + // clang-format off + boost::multi_index_container>, + mi::hashed_unique, + mi::member>>> cache; + // clang-format on constexpr static size_t cache_size_max = 10000; }; -class excluded_peers_item final +class bootstrap_attempts final { public: - std::chrono::steady_clock::time_point exclude_until; - nano::tcp_endpoint endpoint; - uint64_t score; -}; -class bootstrap_excluded_peers final -{ -public: - uint64_t add (nano::tcp_endpoint const &, size_t); - bool check (nano::tcp_endpoint const &); - void remove (nano::tcp_endpoint const &); - std::mutex excluded_peers_mutex; - class endpoint_tag - { - }; - boost::multi_index_container< - nano::excluded_peers_item, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique, boost::multi_index::member>>> - peers; - constexpr static size_t excluded_peers_size_max = 5000; - constexpr static double excluded_peers_percentage_limit = 0.5; - constexpr static uint64_t score_limit = 2; - constexpr static std::chrono::hours exclude_time_hours = std::chrono::hours (1); - constexpr static std::chrono::hours exclude_remove_hours = std::chrono::hours (24); + void add (std::shared_ptr); + void remove (uint64_t); + void clear (); + std::shared_ptr find (uint64_t); + size_t size (); + std::atomic incremental{ 0 }; + std::mutex bootstrap_attempts_mutex; + std::map> attempts; }; class bootstrap_initiator final @@ -239,38 +82,46 @@ class bootstrap_initiator final public: explicit bootstrap_initiator (nano::node &); ~bootstrap_initiator (); - void bootstrap (nano::endpoint const &, bool add_to_peers = true, bool frontiers_confirmed = false); - void bootstrap (bool force = false); - void bootstrap_lazy (nano::hash_or_account const &, bool force = false, bool confirmed = true); + void bootstrap (nano::endpoint const &, bool add_to_peers = true, bool frontiers_confirmed = false, std::string id_a = ""); + void bootstrap (bool force = false, std::string id_a = ""); + void bootstrap_lazy (nano::hash_or_account const &, bool force = false, bool confirmed = true, std::string id_a = ""); void bootstrap_wallet (std::deque &); void run_bootstrap (); + void lazy_requeue (nano::block_hash const &, nano::block_hash const &, bool); void notify_listeners (bool); void add_observer (std::function const &); bool in_progress (); + std::shared_ptr connections; + std::shared_ptr new_attempt (); + bool has_new_attempts (); std::shared_ptr current_attempt (); + std::shared_ptr current_lazy_attempt (); + std::shared_ptr current_wallet_attempt (); nano::pulls_cache cache; - nano::bootstrap_excluded_peers excluded_peers; + nano::bootstrap_attempts attempts; void stop (); private: nano::node & node; - std::shared_ptr attempt; - std::atomic stopped; + std::shared_ptr find_attempt (nano::bootstrap_mode); + void remove_attempt (std::shared_ptr); + void stop_attempts (); + std::vector> attempts_list; + std::atomic stopped{ false }; std::mutex mutex; nano::condition_variable condition; std::mutex observers_mutex; std::vector> observers; - boost::thread thread; + std::vector bootstrap_initiator_threads; - friend std::unique_ptr collect_seq_con_info (bootstrap_initiator & bootstrap_initiator, const std::string & name); + friend std::unique_ptr collect_container_info (bootstrap_initiator & bootstrap_initiator, const std::string & name); }; -std::unique_ptr collect_seq_con_info (bootstrap_initiator & bootstrap_initiator, const std::string & name); +std::unique_ptr collect_container_info (bootstrap_initiator & bootstrap_initiator, const std::string & name); class bootstrap_limits final { public: - static constexpr double bootstrap_connection_scale_target_blocks = 50000.0; - static constexpr double bootstrap_connection_scale_target_blocks_lazy = bootstrap_connection_scale_target_blocks / 5; + static constexpr double bootstrap_connection_scale_target_blocks = 10000.0; static constexpr double bootstrap_connection_warmup_time_sec = 5.0; static constexpr double bootstrap_minimum_blocks_per_sec = 10.0; static constexpr double bootstrap_minimum_elapsed_seconds_blockrate = 0.02; @@ -282,6 +133,7 @@ class bootstrap_limits final static constexpr unsigned frontier_confirmation_blocks_limit = 128 * 1024; static constexpr unsigned requeued_pulls_limit = 256; static constexpr unsigned requeued_pulls_limit_test = 2; + static constexpr unsigned requeued_pulls_processed_blocks_factor = 4096; static constexpr unsigned bulk_push_cost_limit = 200; static constexpr std::chrono::seconds lazy_flush_delay_sec = std::chrono::seconds (5); static constexpr unsigned lazy_destinations_request_limit = 256 * 1024; diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp new file mode 100644 index 0000000000..70b257a594 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_attempt.cpp @@ -0,0 +1,627 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +constexpr size_t nano::bootstrap_limits::bootstrap_max_confirm_frontiers; +constexpr double nano::bootstrap_limits::required_frontier_confirmation_ratio; +constexpr unsigned nano::bootstrap_limits::frontier_confirmation_blocks_limit; +constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit; +constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit_test; + +nano::bootstrap_attempt::bootstrap_attempt (std::shared_ptr node_a, nano::bootstrap_mode mode_a, uint64_t incremental_id_a, std::string id_a) : +node (node_a), +incremental_id (incremental_id_a), +id (id_a), +mode (mode_a) +{ + if (id.empty ()) + { + nano::random_constants constants; + id = constants.random_128.to_string (); + } + node->logger.always_log (boost::str (boost::format ("Starting %1% bootstrap attempt with ID %2%") % mode_text () % id)); + node->bootstrap_initiator.notify_listeners (true); + if (node->websocket_server) + { + nano::websocket::message_builder builder; + node->websocket_server->broadcast (builder.bootstrap_started (id, mode_text ())); + } +} + +nano::bootstrap_attempt::~bootstrap_attempt () +{ + node->logger.always_log (boost::str (boost::format ("Exiting %1% bootstrap attempt with ID %2%") % mode_text () % id)); + node->bootstrap_initiator.notify_listeners (false); + if (node->websocket_server) + { + nano::websocket::message_builder builder; + node->websocket_server->broadcast (builder.bootstrap_exited (id, mode_text (), attempt_start, total_blocks)); + } +} + +bool nano::bootstrap_attempt::should_log () +{ + nano::lock_guard guard (next_log_mutex); + auto result (false); + auto now (std::chrono::steady_clock::now ()); + if (next_log < now) + { + result = true; + next_log = now + std::chrono::seconds (15); + } + return result; +} + +bool nano::bootstrap_attempt::still_pulling () +{ + debug_assert (!mutex.try_lock ()); + auto running (!stopped); + auto still_pulling (pulling > 0); + return running && still_pulling; +} + +void nano::bootstrap_attempt::pull_started () +{ + { + nano::lock_guard guard (mutex); + ++pulling; + } + condition.notify_all (); +} + +void nano::bootstrap_attempt::pull_finished () +{ + { + nano::lock_guard guard (mutex); + --pulling; + } + condition.notify_all (); +} + +void nano::bootstrap_attempt::stop () +{ + { + nano::lock_guard lock (mutex); + stopped = true; + } + condition.notify_all (); + node->bootstrap_initiator.connections->clear_pulls (incremental_id); +} + +std::string nano::bootstrap_attempt::mode_text () +{ + std::string mode_text; + if (mode == nano::bootstrap_mode::legacy) + { + mode_text = "legacy"; + } + else if (mode == nano::bootstrap_mode::lazy) + { + mode_text = "lazy"; + } + else if (mode == nano::bootstrap_mode::wallet_lazy) + { + mode_text = "wallet_lazy"; + } + return mode_text; +} + +void nano::bootstrap_attempt::restart_condition () +{ + debug_assert (mode == nano::bootstrap_mode::legacy); +} + +void nano::bootstrap_attempt::add_frontier (nano::pull_info const &) +{ + debug_assert (mode == nano::bootstrap_mode::legacy); +} + +void nano::bootstrap_attempt::add_bulk_push_target (nano::block_hash const &, nano::block_hash const &) +{ + debug_assert (mode == nano::bootstrap_mode::legacy); +} + +bool nano::bootstrap_attempt::request_bulk_push_target (std::pair &) +{ + debug_assert (mode == nano::bootstrap_mode::legacy); + return true; +} + +void nano::bootstrap_attempt::add_recent_pull (nano::block_hash const &) +{ + debug_assert (mode == nano::bootstrap_mode::legacy); +} + +bool nano::bootstrap_attempt::process_block (std::shared_ptr block_a, nano::account const & known_account_a, uint64_t pull_blocks, nano::bulk_pull::count_t max_blocks, bool block_expected, unsigned retry_limit) +{ + nano::unchecked_info info (block_a, known_account_a, 0, nano::signature_verification::unknown); + node->block_processor.add (info); + return false; +} + +void nano::bootstrap_attempt::lazy_start (nano::hash_or_account const &, bool) +{ + debug_assert (mode == nano::bootstrap_mode::lazy); +} + +void nano::bootstrap_attempt::lazy_add (nano::pull_info const &) +{ + debug_assert (mode == nano::bootstrap_mode::lazy); +} + +void nano::bootstrap_attempt::lazy_requeue (nano::block_hash const &, nano::block_hash const &, bool) +{ + debug_assert (mode == nano::bootstrap_mode::lazy); +} + +uint32_t nano::bootstrap_attempt::lazy_batch_size () +{ + debug_assert (mode == nano::bootstrap_mode::lazy); + return node->network_params.bootstrap.lazy_min_pull_blocks; +} + +bool nano::bootstrap_attempt::lazy_processed_or_exists (nano::block_hash const &) +{ + debug_assert (mode == nano::bootstrap_mode::lazy); + return false; +} + +bool nano::bootstrap_attempt::lazy_has_expired () const +{ + debug_assert (mode == nano::bootstrap_mode::lazy); + return true; +} + +void nano::bootstrap_attempt::requeue_pending (nano::account const &) +{ + debug_assert (mode == nano::bootstrap_mode::wallet_lazy); +} + +void nano::bootstrap_attempt::wallet_start (std::deque &) +{ + debug_assert (mode == nano::bootstrap_mode::wallet_lazy); +} + +size_t nano::bootstrap_attempt::wallet_size () +{ + debug_assert (mode == nano::bootstrap_mode::wallet_lazy); + return 0; +} + +nano::bootstrap_attempt_legacy::bootstrap_attempt_legacy (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a) : +nano::bootstrap_attempt (node_a, nano::bootstrap_mode::legacy, incremental_id_a, id_a) +{ + node->bootstrap_initiator.notify_listeners (true); +} + +bool nano::bootstrap_attempt_legacy::consume_future (std::future & future_a) +{ + bool result; + try + { + result = future_a.get (); + } + catch (std::future_error &) + { + result = true; + } + return result; +} + +void nano::bootstrap_attempt_legacy::stop () +{ + nano::unique_lock lock (mutex); + stopped = true; + lock.unlock (); + condition.notify_all (); + lock.lock (); + if (auto i = frontiers.lock ()) + { + try + { + i->promise.set_value (true); + } + catch (std::future_error &) + { + } + } + if (auto i = push.lock ()) + { + try + { + i->promise.set_value (true); + } + catch (std::future_error &) + { + } + } + lock.unlock (); + node->bootstrap_initiator.connections->clear_pulls (incremental_id); +} + +void nano::bootstrap_attempt_legacy::request_push (nano::unique_lock & lock_a) +{ + bool error (false); + lock_a.unlock (); + auto connection_l (node->bootstrap_initiator.connections->find_connection (endpoint_frontier_request)); + lock_a.lock (); + if (connection_l) + { + std::future future; + { + auto this_l (shared_from_this ()); + auto client (std::make_shared (connection_l, this_l)); + client->start (); + push = client; + future = client->promise.get_future (); + } + lock_a.unlock (); + error = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. + lock_a.lock (); + } + if (node->config.logging.network_logging ()) + { + node->logger.try_log ("Exiting bulk push client"); + if (error) + { + node->logger.try_log ("Bulk push client failed"); + } + } +} + +void nano::bootstrap_attempt_legacy::add_frontier (nano::pull_info const & pull_a) +{ + nano::pull_info pull (pull_a); + nano::lock_guard lock (mutex); + frontier_pulls.push_back (pull); +} + +void nano::bootstrap_attempt_legacy::add_bulk_push_target (nano::block_hash const & head, nano::block_hash const & end) +{ + nano::lock_guard lock (mutex); + bulk_push_targets.emplace_back (head, end); +} + +bool nano::bootstrap_attempt_legacy::request_bulk_push_target (std::pair & current_target_a) +{ + nano::lock_guard lock (mutex); + auto empty (bulk_push_targets.empty ()); + if (!empty) + { + current_target_a = bulk_push_targets.back (); + bulk_push_targets.pop_back (); + } + return empty; +} + +void nano::bootstrap_attempt_legacy::add_recent_pull (nano::block_hash const & head_a) +{ + nano::lock_guard lock (mutex); + recent_pulls_head.push_back (head_a); + if (recent_pulls_head.size () > nano::bootstrap_limits::bootstrap_max_confirm_frontiers) + { + recent_pulls_head.pop_front (); + } +} + +void nano::bootstrap_attempt_legacy::restart_condition () +{ + /* Conditions to start frontiers confirmation: + - not completed frontiers confirmation + - more than 256 pull retries usually indicating issues with requested pulls + - or 128k processed blocks indicating large bootstrap */ + if (!frontiers_confirmation_pending && !frontiers_confirmed && (requeued_pulls > (!node->network_params.network.is_test_network () ? nano::bootstrap_limits::requeued_pulls_limit : nano::bootstrap_limits::requeued_pulls_limit_test) || total_blocks > nano::bootstrap_limits::frontier_confirmation_blocks_limit)) + { + frontiers_confirmation_pending = true; + } +} + +void nano::bootstrap_attempt_legacy::attempt_restart_check (nano::unique_lock & lock_a) +{ + if (frontiers_confirmation_pending) + { + auto confirmed (confirm_frontiers (lock_a)); + debug_assert (lock_a.owns_lock ()); + if (!confirmed) + { + node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_failed, nano::stat::dir::in); + auto score (node->network.excluded_peers.add (endpoint_frontier_request, node->network.size ())); + if (score >= nano::peer_exclusion::score_limit) + { + node->logger.always_log (boost::str (boost::format ("Adding peer %1% to excluded peers list with score %2% after %3% seconds bootstrap attempt") % endpoint_frontier_request % score % std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt_start).count ())); + auto channel = node->network.find_channel (nano::transport::map_tcp_to_endpoint (endpoint_frontier_request)); + if (channel != nullptr) + { + node->network.erase (*channel); + } + } + lock_a.unlock (); + stop (); + lock_a.lock (); + // Start new bootstrap connection + auto node_l (node->shared ()); + node->background ([node_l]() { + node_l->bootstrap_initiator.bootstrap (true); + }); + } + else + { + node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::frontier_confirmation_successful, nano::stat::dir::in); + } + frontiers_confirmed = confirmed; + frontiers_confirmation_pending = false; + } +} + +bool nano::bootstrap_attempt_legacy::confirm_frontiers (nano::unique_lock & lock_a) +{ + bool confirmed (false); + debug_assert (!frontiers_confirmed); + condition.wait (lock_a, [& stopped = stopped] { return !stopped; }); + auto this_l (shared_from_this ()); + std::vector frontiers; + lock_a.unlock (); + nano::unique_lock pulls_lock (node->bootstrap_initiator.connections->mutex); + for (auto i (node->bootstrap_initiator.connections->pulls.begin ()), end (node->bootstrap_initiator.connections->pulls.end ()); i != end && frontiers.size () != nano::bootstrap_limits::bootstrap_max_confirm_frontiers; ++i) + { + if (!i->head.is_zero () && i->bootstrap_id == incremental_id && std::find (frontiers.begin (), frontiers.end (), i->head) == frontiers.end ()) + { + frontiers.push_back (i->head); + } + } + pulls_lock.unlock (); + lock_a.lock (); + for (auto i (recent_pulls_head.begin ()), end (recent_pulls_head.end ()); i != end && frontiers.size () != nano::bootstrap_limits::bootstrap_max_confirm_frontiers; ++i) + { + if (!i->is_zero () && std::find (frontiers.begin (), frontiers.end (), *i) == frontiers.end ()) + { + frontiers.push_back (*i); + } + } + lock_a.unlock (); + auto frontiers_count (frontiers.size ()); + if (frontiers_count > 0) + { + const size_t reps_limit = 20; + auto representatives (node->rep_crawler.representatives ()); + auto reps_weight (node->rep_crawler.total_weight ()); + auto representatives_copy (representatives); + nano::uint128_t total_weight (0); + // Select random peers from bottom 50% of principal representatives + if (representatives.size () > 1) + { + std::reverse (representatives.begin (), representatives.end ()); + representatives.resize (representatives.size () / 2); + for (auto i = static_cast (representatives.size () - 1); i > 0; --i) + { + auto k = nano::random_pool::generate_word32 (0, i); + std::swap (representatives[i], representatives[k]); + } + if (representatives.size () > reps_limit) + { + representatives.resize (reps_limit); + } + } + for (auto const & rep : representatives) + { + total_weight += rep.weight.number (); + } + // Select peers with total 25% of reps stake from top 50% of principal representatives + representatives_copy.resize (representatives_copy.size () / 2); + while (total_weight < reps_weight / 4) // 25% + { + auto k = nano::random_pool::generate_word32 (0, static_cast (representatives_copy.size () - 1)); + auto rep (representatives_copy[k]); + if (std::find (representatives.begin (), representatives.end (), rep) == representatives.end ()) + { + representatives.push_back (rep); + total_weight += rep.weight.number (); + } + } + // Start requests + for (auto i (0), max_requests (20); i <= max_requests && !confirmed && !stopped; ++i) + { + std::unordered_map, std::deque>> batched_confirm_req_bundle; + std::deque> request; + // Find confirmed frontiers (tally > 12.5% of reps stake, 60% of requestsed reps responded + for (auto ii (frontiers.begin ()); ii != frontiers.end ();) + { + if (node->ledger.block_exists (*ii)) + { + ii = frontiers.erase (ii); + } + else + { + nano::unique_lock active_lock (node->active.mutex); + auto existing (node->active.find_inactive_votes_cache (*ii)); + active_lock.unlock (); + nano::uint128_t tally; + for (auto & voter : existing.voters) + { + tally += node->ledger.weight (voter); + } + if (existing.confirmed || (tally > reps_weight / 8 && existing.voters.size () >= representatives.size () * 0.6)) // 12.5% of weight, 60% of reps + { + ii = frontiers.erase (ii); + } + else + { + for (auto const & rep : representatives) + { + if (std::find (existing.voters.begin (), existing.voters.end (), rep.account) == existing.voters.end ()) + { + release_assert (!ii->is_zero ()); + auto rep_request (batched_confirm_req_bundle.find (rep.channel)); + if (rep_request == batched_confirm_req_bundle.end ()) + { + std::deque> insert_root_hash = { std::make_pair (*ii, *ii) }; + batched_confirm_req_bundle.emplace (rep.channel, insert_root_hash); + } + else + { + rep_request->second.emplace_back (*ii, *ii); + } + } + } + ++ii; + } + } + } + auto confirmed_count (frontiers_count - frontiers.size ()); + if (confirmed_count >= frontiers_count * nano::bootstrap_limits::required_frontier_confirmation_ratio) // 80% of frontiers confirmed + { + confirmed = true; + } + else if (i < max_requests) + { + node->network.broadcast_confirm_req_batched_many (batched_confirm_req_bundle); + std::this_thread::sleep_for (std::chrono::milliseconds (!node->network_params.network.is_test_network () ? 500 : 5)); + } + } + if (!confirmed) + { + node->logger.always_log (boost::str (boost::format ("Failed to confirm frontiers for bootstrap attempt. %1% of %2% frontiers were not confirmed") % frontiers.size () % frontiers_count)); + } + } + lock_a.lock (); + return confirmed; +} + +bool nano::bootstrap_attempt_legacy::request_frontier (nano::unique_lock & lock_a, bool first_attempt) +{ + auto result (true); + lock_a.unlock (); + auto connection_l (node->bootstrap_initiator.connections->connection (shared_from_this (), first_attempt)); + lock_a.lock (); + if (connection_l && !stopped) + { + endpoint_frontier_request = connection_l->channel->get_tcp_endpoint (); + std::future future; + { + auto this_l (shared_from_this ()); + auto client (std::make_shared (connection_l, this_l)); + client->run (); + frontiers = client; + future = client->promise.get_future (); + } + lock_a.unlock (); + result = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. + lock_a.lock (); + if (result) + { + frontier_pulls.clear (); + } + else + { + account_count = frontier_pulls.size (); + // Shuffle pulls + release_assert (std::numeric_limits::max () > frontier_pulls.size ()); + if (!frontier_pulls.empty ()) + { + for (auto i = static_cast (frontier_pulls.size () - 1); i > 0; --i) + { + auto k = nano::random_pool::generate_word32 (0, i); + std::swap (frontier_pulls[i], frontier_pulls[k]); + } + } + // Add to regular pulls + while (!frontier_pulls.empty ()) + { + auto pull (frontier_pulls.front ()); + lock_a.unlock (); + node->bootstrap_initiator.connections->add_pull (pull); + lock_a.lock (); + ++pulling; + frontier_pulls.pop_front (); + } + } + if (node->config.logging.network_logging ()) + { + if (!result) + { + node->logger.try_log (boost::str (boost::format ("Completed frontier request, %1% out of sync accounts according to %2%") % account_count % connection_l->channel->to_string ())); + } + else + { + node->stats.inc (nano::stat::type::error, nano::stat::detail::frontier_req, nano::stat::dir::out); + } + } + } + return result; +} + +void nano::bootstrap_attempt_legacy::run_start (nano::unique_lock & lock_a) +{ + frontiers_received = false; + frontiers_confirmed = false; + total_blocks = 0; + requeued_pulls = 0; + recent_pulls_head.clear (); + auto frontier_failure (true); + uint64_t frontier_attempts (0); + while (!stopped && frontier_failure) + { + ++frontier_attempts; + frontier_failure = request_frontier (lock_a, frontier_attempts == 1); + } + frontiers_received = true; +} + +void nano::bootstrap_attempt_legacy::run () +{ + debug_assert (started); + debug_assert (!node->flags.disable_legacy_bootstrap); + node->bootstrap_initiator.connections->populate_connections (false); + nano::unique_lock lock (mutex); + run_start (lock); + while (still_pulling ()) + { + while (still_pulling ()) + { + // clang-format off + condition.wait (lock, [&stopped = stopped, &pulling = pulling, &frontiers_confirmation_pending = frontiers_confirmation_pending] { return stopped || pulling == 0 || frontiers_confirmation_pending; }); + // clang-format on + attempt_restart_check (lock); + } + // Flushing may resolve forks which can add more pulls + node->logger.try_log ("Flushing unchecked blocks"); + lock.unlock (); + node->block_processor.flush (); + lock.lock (); + node->logger.try_log ("Finished flushing unchecked blocks"); + } + if (!stopped) + { + node->logger.try_log ("Completed legacy pulls"); + if (!node->flags.disable_bootstrap_bulk_push_client) + { + request_push (lock); + } + if (!stopped) + { + node->unchecked_cleanup (); + } + } + lock.unlock (); + stop (); + condition.notify_all (); +} + +void nano::bootstrap_attempt_legacy::get_information (boost::property_tree::ptree & tree_a) +{ + nano::lock_guard lock (mutex); + tree_a.put ("frontier_pulls", std::to_string (frontier_pulls.size ())); + tree_a.put ("frontiers_received", static_cast (frontiers_received)); + tree_a.put ("frontiers_confirmed", static_cast (frontiers_confirmed)); + tree_a.put ("frontiers_confirmation_pending", static_cast (frontiers_confirmation_pending)); +} diff --git a/nano/node/bootstrap/bootstrap_attempt.hpp b/nano/node/bootstrap/bootstrap_attempt.hpp new file mode 100644 index 0000000000..732c9e1e97 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_attempt.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include +#include + +namespace nano +{ +class node; + +class frontier_req_client; +class bulk_push_client; +class bootstrap_attempt : public std::enable_shared_from_this +{ +public: + explicit bootstrap_attempt (std::shared_ptr node_a, nano::bootstrap_mode mode_a, uint64_t incremental_id_a, std::string id_a); + virtual ~bootstrap_attempt (); + virtual void run () = 0; + virtual void stop (); + bool still_pulling (); + void pull_started (); + void pull_finished (); + bool should_log (); + std::string mode_text (); + virtual void restart_condition (); + virtual void add_frontier (nano::pull_info const &); + virtual void add_bulk_push_target (nano::block_hash const &, nano::block_hash const &); + virtual bool request_bulk_push_target (std::pair &); + virtual void add_recent_pull (nano::block_hash const &); + virtual void lazy_start (nano::hash_or_account const &, bool confirmed = true); + virtual void lazy_add (nano::pull_info const &); + virtual void lazy_requeue (nano::block_hash const &, nano::block_hash const &, bool); + virtual uint32_t lazy_batch_size (); + virtual bool lazy_has_expired () const; + virtual bool lazy_processed_or_exists (nano::block_hash const &); + virtual bool process_block (std::shared_ptr, nano::account const &, uint64_t, nano::bulk_pull::count_t, bool, unsigned); + virtual void requeue_pending (nano::account const &); + virtual void wallet_start (std::deque &); + virtual size_t wallet_size (); + virtual void get_information (boost::property_tree::ptree &) = 0; + std::mutex next_log_mutex; + std::chrono::steady_clock::time_point next_log{ std::chrono::steady_clock::now () }; + std::atomic pulling{ 0 }; + std::shared_ptr node; + std::atomic total_blocks{ 0 }; + std::atomic requeued_pulls{ 0 }; + std::atomic started{ false }; + std::atomic stopped{ false }; + uint64_t incremental_id{ 0 }; + std::string id; + std::chrono::steady_clock::time_point attempt_start{ std::chrono::steady_clock::now () }; + std::atomic frontiers_received{ false }; + std::atomic frontiers_confirmed{ false }; + nano::bootstrap_mode mode; + std::mutex mutex; + nano::condition_variable condition; +}; +class bootstrap_attempt_legacy : public bootstrap_attempt +{ +public: + explicit bootstrap_attempt_legacy (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a = ""); + void run () override; + bool consume_future (std::future &); + void stop () override; + bool request_frontier (nano::unique_lock &, bool = false); + void request_pull (nano::unique_lock &); + void request_push (nano::unique_lock &); + void add_frontier (nano::pull_info const &) override; + void add_bulk_push_target (nano::block_hash const &, nano::block_hash const &) override; + bool request_bulk_push_target (std::pair &) override; + void add_recent_pull (nano::block_hash const &) override; + void run_start (nano::unique_lock &); + void restart_condition () override; + void attempt_restart_check (nano::unique_lock &); + bool confirm_frontiers (nano::unique_lock &); + void get_information (boost::property_tree::ptree &) override; + nano::tcp_endpoint endpoint_frontier_request; + std::weak_ptr frontiers; + std::weak_ptr push; + std::deque frontier_pulls; + std::deque recent_pulls_head; + std::vector> bulk_push_targets; + std::atomic account_count{ 0 }; + std::atomic frontiers_confirmation_pending{ false }; +}; +} diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.cpp b/nano/node/bootstrap/bootstrap_bulk_pull.cpp index bff0c3ca79..d43bd3c301 100644 --- a/nano/node/bootstrap/bootstrap_bulk_pull.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_pull.cpp @@ -1,26 +1,32 @@ #include #include +#include +#include #include #include -nano::pull_info::pull_info (nano::hash_or_account const & account_or_head_a, nano::block_hash const & head_a, nano::block_hash const & end_a, count_t count_a, unsigned retry_limit_a) : +#include + +nano::pull_info::pull_info (nano::hash_or_account const & account_or_head_a, nano::block_hash const & head_a, nano::block_hash const & end_a, uint64_t bootstrap_id_a, count_t count_a, unsigned retry_limit_a) : account_or_head (account_or_head_a), head (head_a), head_original (head_a), end (end_a), count (count_a), -retry_limit (retry_limit_a) +retry_limit (retry_limit_a), +bootstrap_id (bootstrap_id_a) { } -nano::bulk_pull_client::bulk_pull_client (std::shared_ptr connection_a, nano::pull_info const & pull_a) : +nano::bulk_pull_client::bulk_pull_client (std::shared_ptr connection_a, std::shared_ptr attempt_a, nano::pull_info const & pull_a) : connection (connection_a), +attempt (attempt_a), known_account (0), pull (pull_a), pull_blocks (0), unexpected_count (0) { - connection->attempt->condition.notify_all (); + attempt->condition.notify_all (); } nano::bulk_pull_client::~bulk_pull_client () @@ -29,12 +35,12 @@ nano::bulk_pull_client::~bulk_pull_client () if (expected != pull.end) { pull.head = expected; - if (connection->attempt->mode != nano::bootstrap_mode::legacy) + if (attempt->mode != nano::bootstrap_mode::legacy) { pull.account_or_head = expected; } pull.processed += pull_blocks - unexpected_count; - connection->attempt->requeue_pull (pull, network_error); + connection->node->bootstrap_initiator.connections->requeue_pull (pull, network_error); if (connection->node->config.logging.bulk_pull_logging ()) { connection->node->logger.try_log (boost::str (boost::format ("Bulk pull end block is not expected %1% for account %2%") % pull.end.to_string () % pull.account_or_head.to_account ())); @@ -44,16 +50,12 @@ nano::bulk_pull_client::~bulk_pull_client () { connection->node->bootstrap_initiator.cache.remove (pull); } - { - nano::lock_guard mutex (connection->attempt->mutex); - --connection->attempt->pulling; - } - connection->attempt->condition.notify_all (); + attempt->pull_finished (); } void nano::bulk_pull_client::request () { - assert (!pull.head.is_zero () || pull.retry_limit != std::numeric_limits::max ()); + debug_assert (!pull.head.is_zero () || pull.retry_limit != std::numeric_limits::max ()); expected = pull.head; nano::bulk_pull req; if (pull.head == pull.head_original && pull.attempts % 4 < 3) @@ -72,13 +74,11 @@ void nano::bulk_pull_client::request () if (connection->node->config.logging.bulk_pull_logging ()) { - nano::unique_lock lock (connection->attempt->mutex); - connection->node->logger.try_log (boost::str (boost::format ("Requesting account %1% from %2%. %3% accounts in queue") % pull.account_or_head.to_account () % connection->channel->to_string () % connection->attempt->pulls.size ())); + connection->node->logger.try_log (boost::str (boost::format ("Requesting account %1% from %2%. %3% accounts in queue") % pull.account_or_head.to_account () % connection->channel->to_string () % attempt->pulling)); } - else if (connection->node->config.logging.network_logging () && connection->attempt->should_log ()) + else if (connection->node->config.logging.network_logging () && attempt->should_log ()) { - nano::unique_lock lock (connection->attempt->mutex); - connection->node->logger.always_log (boost::str (boost::format ("%1% accounts in pull queue") % connection->attempt->pulls.size ())); + connection->node->logger.always_log (boost::str (boost::format ("%1% accounts in pull queue") % attempt->pulling)); } auto this_l (shared_from_this ()); connection->channel->send ( @@ -96,13 +96,13 @@ void nano::bulk_pull_client::request () this_l->connection->node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_request_failure, nano::stat::dir::in); } }, - false); // is bootstrap traffic is_droppable false + nano::buffer_drop_policy::no_limiter_drop); } void nano::bulk_pull_client::throttled_receive_block () { - assert (!network_error); - if (!connection->node->block_processor.half_full ()) + debug_assert (!network_error); + if (!connection->node->block_processor.half_full () && !connection->node->block_processor.flushing) { receive_block (); } @@ -110,7 +110,7 @@ void nano::bulk_pull_client::throttled_receive_block () { auto this_l (shared_from_this ()); connection->node->alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (1), [this_l]() { - if (!this_l->connection->pending_stop && !this_l->connection->attempt->stopped) + if (!this_l->connection->pending_stop && !this_l->attempt->stopped) { this_l->throttled_receive_block (); } @@ -190,7 +190,7 @@ void nano::bulk_pull_client::received_type () // Avoid re-using slow peers, or peers that sent the wrong blocks. if (!connection->pending_stop && (expected == pull.end || (pull.count != 0 && pull.count == pull_blocks))) { - connection->attempt->pool_connection (connection); + connection->connections->pool_connection (connection); } break; } @@ -212,7 +212,7 @@ void nano::bulk_pull_client::received_block (boost::system::error_code const & e { nano::bufferstream stream (connection->receive_buffer->data (), size_a); std::shared_ptr block (nano::deserialize_block (stream, type_a)); - if (block != nullptr && !nano::work_validate (*block)) + if (block != nullptr && !nano::work_validate_entry (*block)) { auto hash (block->hash ()); if (connection->node->config.logging.bulk_pull_logging ()) @@ -240,24 +240,24 @@ void nano::bulk_pull_client::received_block (boost::system::error_code const & e } if (connection->block_count++ == 0) { - connection->start_time = std::chrono::steady_clock::now (); + connection->set_start_time (std::chrono::steady_clock::now ()); } - connection->attempt->total_blocks++; - bool stop_pull (connection->attempt->process_block (block, known_account, pull_blocks, pull.count, block_expected, pull.retry_limit)); + attempt->total_blocks++; + bool stop_pull (attempt->process_block (block, known_account, pull_blocks, pull.count, block_expected, pull.retry_limit)); pull_blocks++; if (!stop_pull && !connection->hard_stop.load ()) { /* Process block in lazy pull if not stopped Stop usual pull request with unexpected block & more than 16k blocks processed to prevent spam */ - if (connection->attempt->mode != nano::bootstrap_mode::legacy || unexpected_count < 16384) + if (attempt->mode != nano::bootstrap_mode::legacy || unexpected_count < 16384) { throttled_receive_block (); } } else if (stop_pull && block_expected) { - connection->attempt->pool_connection (connection); + connection->connections->pool_connection (connection); } } else @@ -280,21 +280,18 @@ void nano::bulk_pull_client::received_block (boost::system::error_code const & e } } -nano::bulk_pull_account_client::bulk_pull_account_client (std::shared_ptr connection_a, nano::account const & account_a) : +nano::bulk_pull_account_client::bulk_pull_account_client (std::shared_ptr connection_a, std::shared_ptr attempt_a, nano::account const & account_a) : connection (connection_a), +attempt (attempt_a), account (account_a), pull_blocks (0) { - connection->attempt->condition.notify_all (); + attempt->condition.notify_all (); } nano::bulk_pull_account_client::~bulk_pull_account_client () { - { - nano::lock_guard mutex (connection->attempt->mutex); - --connection->attempt->pulling; - } - connection->attempt->condition.notify_all (); + attempt->pull_finished (); } void nano::bulk_pull_account_client::request () @@ -305,13 +302,11 @@ void nano::bulk_pull_account_client::request () req.flags = nano::bulk_pull_account_flags::pending_hash_and_amount; if (connection->node->config.logging.bulk_pull_logging ()) { - nano::unique_lock lock (connection->attempt->mutex); - connection->node->logger.try_log (boost::str (boost::format ("Requesting pending for account %1% from %2%. %3% accounts in queue") % req.account.to_account () % connection->channel->to_string () % connection->attempt->wallet_accounts.size ())); + connection->node->logger.try_log (boost::str (boost::format ("Requesting pending for account %1% from %2%. %3% accounts in queue") % req.account.to_account () % connection->channel->to_string () % attempt->wallet_size ())); } - else if (connection->node->config.logging.network_logging () && connection->attempt->should_log ()) + else if (connection->node->config.logging.network_logging () && attempt->should_log ()) { - nano::unique_lock lock (connection->attempt->mutex); - connection->node->logger.always_log (boost::str (boost::format ("%1% accounts in pull queue") % connection->attempt->wallet_accounts.size ())); + connection->node->logger.always_log (boost::str (boost::format ("%1% accounts in pull queue") % attempt->wallet_size ())); } auto this_l (shared_from_this ()); connection->channel->send ( @@ -322,7 +317,7 @@ void nano::bulk_pull_account_client::request () } else { - this_l->connection->attempt->requeue_pending (this_l->account); + this_l->attempt->requeue_pending (this_l->account); if (this_l->connection->node->config.logging.bulk_pull_logging ()) { this_l->connection->node->logger.try_log (boost::str (boost::format ("Error starting bulk pull request to %1%: to %2%") % ec.message () % this_l->connection->channel->to_string ())); @@ -330,7 +325,7 @@ void nano::bulk_pull_account_client::request () this_l->connection->node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_error_starting_request, nano::stat::dir::in); } }, - false); // is bootstrap traffic is_droppable false + nano::buffer_drop_policy::no_limiter_drop); } void nano::bulk_pull_account_client::receive_pending () @@ -350,12 +345,12 @@ void nano::bulk_pull_account_client::receive_pending () nano::bufferstream frontier_stream (this_l->connection->receive_buffer->data (), sizeof (nano::uint256_union)); auto error1 (nano::try_read (frontier_stream, pending)); (void)error1; - assert (!error1); + debug_assert (!error1); nano::amount balance; nano::bufferstream balance_stream (this_l->connection->receive_buffer->data () + sizeof (nano::uint256_union), sizeof (nano::uint128_union)); auto error2 (nano::try_read (balance_stream, balance)); (void)error2; - assert (!error2); + debug_assert (!error2); if (this_l->pull_blocks == 0 || !pending.is_zero ()) { if (this_l->pull_blocks == 0 || balance.number () >= this_l->connection->node->config.receive_minimum.number ()) @@ -364,10 +359,9 @@ void nano::bulk_pull_account_client::receive_pending () { if (!pending.is_zero ()) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); - if (!this_l->connection->node->store.block_exists (transaction, pending)) + if (!this_l->connection->node->ledger.block_exists (pending)) { - this_l->connection->attempt->lazy_start (pending); + this_l->connection->node->bootstrap_initiator.bootstrap_lazy (pending, false, false); } } } @@ -375,17 +369,17 @@ void nano::bulk_pull_account_client::receive_pending () } else { - this_l->connection->attempt->requeue_pending (this_l->account); + this_l->attempt->requeue_pending (this_l->account); } } else { - this_l->connection->attempt->pool_connection (this_l->connection); + this_l->connection->connections->pool_connection (this_l->connection); } } else { - this_l->connection->attempt->requeue_pending (this_l->account); + this_l->attempt->requeue_pending (this_l->account); if (this_l->connection->node->config.logging.network_logging ()) { this_l->connection->node->logger.try_log (boost::str (boost::format ("Error while receiving bulk pull account frontier %1%") % ec.message ())); @@ -394,7 +388,7 @@ void nano::bulk_pull_account_client::receive_pending () } else { - this_l->connection->attempt->requeue_pending (this_l->account); + this_l->attempt->requeue_pending (this_l->account); if (this_l->connection->node->config.logging.network_message_logging ()) { this_l->connection->node->logger.try_log (boost::str (boost::format ("Invalid size: expected %1%, got %2%") % size_l % size_a)); @@ -422,7 +416,7 @@ void nano::bulk_pull_account_client::receive_pending () void nano::bulk_pull_server::set_current_end () { include_start = false; - assert (request != nullptr); + debug_assert (request != nullptr); auto transaction (connection->node->store.tx_begin_read ()); if (!connection->node->store.block_exists (transaction, request->end)) { @@ -550,8 +544,7 @@ std::shared_ptr nano::bulk_pull_server::get_next () if (send_current) { - auto transaction (connection->node->store.tx_begin_read ()); - result = connection->node->store.block_get (transaction, current); + result = connection->node->block (current); if (result != nullptr && set_current_to_end == false) { auto previous (result->previous ()); @@ -613,7 +606,7 @@ void nano::bulk_pull_server::no_block_sent (boost::system::error_code const & ec { if (!ec) { - assert (size_a == 1); + debug_assert (size_a == 1); connection->finish_request (); } else @@ -637,7 +630,7 @@ request (std::move (request_a)) */ void nano::bulk_pull_account_server::set_params () { - assert (request != nullptr); + debug_assert (request != nullptr); /* * Parse the flags @@ -850,8 +843,8 @@ std::pair, std::unique_ptr (new nano::pending_key (key)); - result.second = std::unique_ptr (new nano::pending_info (info)); + result.first = std::make_unique (key); + result.second = std::make_unique (info); break; } @@ -920,17 +913,17 @@ void nano::bulk_pull_account_server::complete (boost::system::error_code const & { if (pending_address_only) { - assert (size_a == 32); + debug_assert (size_a == 32); } else { if (pending_include_address) { - assert (size_a == 80); + debug_assert (size_a == 80); } else { - assert (size_a == 48); + debug_assert (size_a == 48); } } diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.hpp b/nano/node/bootstrap/bootstrap_bulk_pull.hpp index 68975b76fd..2189395029 100644 --- a/nano/node/bootstrap/bootstrap_bulk_pull.hpp +++ b/nano/node/bootstrap/bootstrap_bulk_pull.hpp @@ -7,12 +7,13 @@ namespace nano { +class bootstrap_attempt; class pull_info { public: using count_t = nano::bulk_pull::count_t; pull_info () = default; - pull_info (nano::hash_or_account const &, nano::block_hash const &, nano::block_hash const &, count_t = 0, unsigned = 16); + pull_info (nano::hash_or_account const &, nano::block_hash const &, nano::block_hash const &, uint64_t, count_t = 0, unsigned = 16); nano::hash_or_account account_or_head{ 0 }; nano::block_hash head{ 0 }; nano::block_hash head_original{ 0 }; @@ -21,12 +22,13 @@ class pull_info unsigned attempts{ 0 }; uint64_t processed{ 0 }; unsigned retry_limit{ 0 }; + uint64_t bootstrap_id{ 0 }; }; class bootstrap_client; class bulk_pull_client final : public std::enable_shared_from_this { public: - bulk_pull_client (std::shared_ptr, nano::pull_info const &); + bulk_pull_client (std::shared_ptr, std::shared_ptr, nano::pull_info const &); ~bulk_pull_client (); void request (); void receive_block (); @@ -35,6 +37,7 @@ class bulk_pull_client final : public std::enable_shared_from_this connection; + std::shared_ptr attempt; nano::block_hash expected; nano::account known_account; nano::pull_info pull; @@ -45,11 +48,12 @@ class bulk_pull_client final : public std::enable_shared_from_this { public: - bulk_pull_account_client (std::shared_ptr, nano::account const &); + bulk_pull_account_client (std::shared_ptr, std::shared_ptr, nano::account const &); ~bulk_pull_account_client (); void request (); void receive_pending (); std::shared_ptr connection; + std::shared_ptr attempt; nano::account account; uint64_t pull_blocks; }; diff --git a/nano/node/bootstrap/bootstrap_bulk_push.cpp b/nano/node/bootstrap/bootstrap_bulk_push.cpp index 435da4a0b1..1ee7b9c2d7 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.cpp @@ -1,10 +1,13 @@ -#include +#include #include #include #include -nano::bulk_push_client::bulk_push_client (std::shared_ptr const & connection_a) : -connection (connection_a) +#include + +nano::bulk_push_client::bulk_push_client (std::shared_ptr const & connection_a, std::shared_ptr const & attempt_a) : +connection (connection_a), +attempt (attempt_a) { } @@ -18,10 +21,9 @@ void nano::bulk_push_client::start () auto this_l (shared_from_this ()); connection->channel->send ( message, [this_l](boost::system::error_code const & ec, size_t size_a) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); if (!ec) { - this_l->push (transaction); + this_l->push (); } else { @@ -31,10 +33,10 @@ void nano::bulk_push_client::start () } } }, - false); // is bootstrap traffic is_droppable false + nano::buffer_drop_policy::no_limiter_drop); } -void nano::bulk_push_client::push (nano::transaction const & transaction_a) +void nano::bulk_push_client::push () { std::shared_ptr block; bool finished (false); @@ -42,20 +44,11 @@ void nano::bulk_push_client::push (nano::transaction const & transaction_a) { if (current_target.first.is_zero () || current_target.first == current_target.second) { - nano::lock_guard guard (connection->attempt->mutex); - if (!connection->attempt->bulk_push_targets.empty ()) - { - current_target = connection->attempt->bulk_push_targets.back (); - connection->attempt->bulk_push_targets.pop_back (); - } - else - { - finished = true; - } + finished = attempt->request_bulk_push_target (current_target); } if (!finished) { - block = connection->node->store.block_get (transaction_a, current_target.first); + block = connection->node->block (current_target.first); if (block == nullptr) { current_target.first = nano::block_hash (0); @@ -106,8 +99,7 @@ void nano::bulk_push_client::push_block (nano::block const & block_a) connection->channel->send_buffer (nano::shared_const_buffer (std::move (buffer)), nano::stat::detail::all, [this_l](boost::system::error_code const & ec, size_t size_a) { if (!ec) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); - this_l->push (transaction); + this_l->push (); } else { @@ -240,7 +232,7 @@ void nano::bulk_push_server::received_block (boost::system::error_code const & e { nano::bufferstream stream (receive_buffer->data (), size_a); auto block (nano::deserialize_block (stream, type_a)); - if (block != nullptr && !nano::work_validate (*block)) + if (block != nullptr && !nano::work_validate_entry (*block)) { connection->node->process_active (std::move (block)); throttled_receive (); diff --git a/nano/node/bootstrap/bootstrap_bulk_push.hpp b/nano/node/bootstrap/bootstrap_bulk_push.hpp index ecf8541e32..9ffececaee 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.hpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.hpp @@ -1,22 +1,24 @@ #pragma once #include -#include + +#include namespace nano { -class transaction; +class bootstrap_attempt; class bootstrap_client; class bulk_push_client final : public std::enable_shared_from_this { public: - explicit bulk_push_client (std::shared_ptr const &); + explicit bulk_push_client (std::shared_ptr const &, std::shared_ptr const &); ~bulk_push_client (); void start (); - void push (nano::transaction const &); + void push (); void push_block (nano::block const &); void send_finished (); std::shared_ptr connection; + std::shared_ptr attempt; std::promise promise; std::pair current_target; }; diff --git a/nano/node/bootstrap/bootstrap_connections.cpp b/nano/node/bootstrap/bootstrap_connections.cpp new file mode 100644 index 0000000000..53724f0287 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_connections.cpp @@ -0,0 +1,504 @@ +#include +#include +#include +#include +#include +#include + +#include + +constexpr double nano::bootstrap_limits::bootstrap_connection_scale_target_blocks; +constexpr double nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec; +constexpr double nano::bootstrap_limits::bootstrap_minimum_termination_time_sec; +constexpr unsigned nano::bootstrap_limits::bootstrap_max_new_connections; +constexpr unsigned nano::bootstrap_limits::requeued_pulls_processed_blocks_factor; + +nano::bootstrap_client::bootstrap_client (std::shared_ptr node_a, std::shared_ptr connections_a, std::shared_ptr channel_a, std::shared_ptr socket_a) : +node (node_a), +connections (connections_a), +channel (channel_a), +socket (socket_a), +receive_buffer (std::make_shared> ()), +start_time_m (std::chrono::steady_clock::now ()) +{ + ++connections->connections_count; + receive_buffer->resize (256); +} + +nano::bootstrap_client::~bootstrap_client () +{ + --connections->connections_count; +} + +double nano::bootstrap_client::block_rate () const +{ + auto elapsed = std::max (elapsed_seconds (), nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate); + return static_cast (block_count.load () / elapsed); +} + +void nano::bootstrap_client::set_start_time (std::chrono::steady_clock::time_point start_time_a) +{ + nano::lock_guard guard (start_time_mutex); + start_time_m = start_time_a; +} + +double nano::bootstrap_client::elapsed_seconds () const +{ + nano::lock_guard guard (start_time_mutex); + return std::chrono::duration_cast> (std::chrono::steady_clock::now () - start_time_m).count (); +} + +void nano::bootstrap_client::stop (bool force) +{ + pending_stop = true; + if (force) + { + hard_stop = true; + } +} + +nano::bootstrap_connections::bootstrap_connections (nano::node & node_a) : +node (node_a) +{ +} + +std::shared_ptr nano::bootstrap_connections::connection (std::shared_ptr attempt_a, bool use_front_connection) +{ + nano::unique_lock lock (mutex); + condition.wait (lock, [& stopped = stopped, &idle = idle, &new_connections_empty = new_connections_empty] { return stopped || !idle.empty () || new_connections_empty; }); + std::shared_ptr result; + if (!stopped && !idle.empty ()) + { + if (!use_front_connection) + { + result = idle.back (); + idle.pop_back (); + } + else + { + result = idle.front (); + idle.pop_front (); + } + } + if (result == nullptr && connections_count == 0 && new_connections_empty && attempt_a != nullptr) + { + node.logger.try_log (boost::str (boost::format ("Bootstrap attempt stopped because there are no peers"))); + lock.unlock (); + attempt_a->stop (); + } + return result; +} + +void nano::bootstrap_connections::pool_connection (std::shared_ptr client_a, bool new_client, bool push_front) +{ + nano::unique_lock lock (mutex); + if (!stopped && !client_a->pending_stop && !node.network.excluded_peers.check (client_a->channel->get_tcp_endpoint ())) + { + // Idle bootstrap client socket + if (auto socket_l = client_a->channel->socket.lock ()) + { + socket_l->start_timer (node.network_params.node.idle_timeout); + // Push into idle deque + if (!push_front) + { + idle.push_back (client_a); + } + else + { + idle.push_front (client_a); + } + if (new_client) + { + clients.push_back (client_a); + } + } + } + else + { + if (auto socket_l = client_a->channel->socket.lock ()) + { + socket_l->close (); + } + } + lock.unlock (); + condition.notify_all (); +} + +void nano::bootstrap_connections::add_connection (nano::endpoint const & endpoint_a) +{ + connect_client (nano::tcp_endpoint (endpoint_a.address (), endpoint_a.port ()), true); +} + +std::shared_ptr nano::bootstrap_connections::find_connection (nano::tcp_endpoint const & endpoint_a) +{ + nano::lock_guard lock (mutex); + std::shared_ptr result; + for (auto i (idle.begin ()), end (idle.end ()); i != end && !stopped; ++i) + { + if ((*i)->channel->get_tcp_endpoint () == endpoint_a) + { + result = *i; + idle.erase (i); + break; + } + } + return result; +} + +void nano::bootstrap_connections::connect_client (nano::tcp_endpoint const & endpoint_a, bool push_front) +{ + ++connections_count; + auto socket (std::make_shared (node.shared ())); + auto this_l (shared_from_this ()); + socket->async_connect (endpoint_a, + [this_l, socket, endpoint_a, push_front](boost::system::error_code const & ec) { + if (!ec) + { + if (this_l->node.config.logging.bulk_pull_logging ()) + { + this_l->node.logger.try_log (boost::str (boost::format ("Connection established to %1%") % endpoint_a)); + } + auto client (std::make_shared (this_l->node.shared (), this_l, std::make_shared (*this_l->node.shared (), socket), socket)); + this_l->pool_connection (client, true, push_front); + } + else + { + if (this_l->node.config.logging.network_logging ()) + { + switch (ec.value ()) + { + default: + this_l->node.logger.try_log (boost::str (boost::format ("Error initiating bootstrap connection to %1%: %2%") % endpoint_a % ec.message ())); + break; + case boost::system::errc::connection_refused: + case boost::system::errc::operation_canceled: + case boost::system::errc::timed_out: + case 995: //Windows The I/O operation has been aborted because of either a thread exit or an application request + case 10061: //Windows No connection could be made because the target machine actively refused it + break; + } + } + } + --this_l->connections_count; + }); +} + +unsigned nano::bootstrap_connections::target_connections (size_t pulls_remaining, size_t attempts_count) +{ + unsigned attempts_factor = node.config.bootstrap_connections * attempts_count; + if (attempts_factor >= node.config.bootstrap_connections_max) + { + return std::max (1U, node.config.bootstrap_connections_max); + } + + // Only scale up to bootstrap_connections_max for large pulls. + double step_scale = std::min (1.0, std::max (0.0, (double)pulls_remaining / nano::bootstrap_limits::bootstrap_connection_scale_target_blocks)); + double target = (double)attempts_factor + (double)(node.config.bootstrap_connections_max - attempts_factor) * step_scale; + return std::max (1U, (unsigned)(target + 0.5f)); +} + +struct block_rate_cmp +{ + bool operator() (const std::shared_ptr & lhs, const std::shared_ptr & rhs) const + { + return lhs->block_rate () > rhs->block_rate (); + } +}; + +void nano::bootstrap_connections::populate_connections (bool repeat) +{ + double rate_sum = 0.0; + size_t num_pulls = 0; + size_t attempts_count = node.bootstrap_initiator.attempts.size (); + std::priority_queue, std::vector>, block_rate_cmp> sorted_connections; + std::unordered_set endpoints; + { + nano::unique_lock lock (mutex); + num_pulls = pulls.size (); + std::deque> new_clients; + for (auto & c : clients) + { + if (auto client = c.lock ()) + { + if (auto socket_l = client->channel->socket.lock ()) + { + new_clients.push_back (client); + endpoints.insert (socket_l->remote_endpoint ()); + double elapsed_sec = client->elapsed_seconds (); + auto blocks_per_sec = client->block_rate (); + rate_sum += blocks_per_sec; + if (client->elapsed_seconds () > nano::bootstrap_limits::bootstrap_connection_warmup_time_sec && client->block_count > 0) + { + sorted_connections.push (client); + } + // Force-stop the slowest peers, since they can take the whole bootstrap hostage by dribbling out blocks on the last remaining pull. + // This is ~1.5kilobits/sec. + if (elapsed_sec > nano::bootstrap_limits::bootstrap_minimum_termination_time_sec && blocks_per_sec < nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec) + { + if (node.config.logging.bulk_pull_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Stopping slow peer %1% (elapsed sec %2%s > %3%s and %4% blocks per second < %5%)") % client->channel->to_string () % elapsed_sec % nano::bootstrap_limits::bootstrap_minimum_termination_time_sec % blocks_per_sec % nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec)); + } + + client->stop (true); + new_clients.pop_back (); + } + } + } + } + // Cleanup expired clients + clients.swap (new_clients); + } + + auto target = target_connections (num_pulls, attempts_count); + + // We only want to drop slow peers when more than 2/3 are active. 2/3 because 1/2 is too aggressive, and 100% rarely happens. + // Probably needs more tuning. + if (sorted_connections.size () >= (target * 2) / 3 && target >= 4) + { + // 4 -> 1, 8 -> 2, 16 -> 4, arbitrary, but seems to work well. + auto drop = (int)roundf (sqrtf ((float)target - 2.0f)); + + if (node.config.logging.bulk_pull_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Dropping %1% bulk pull peers, target connections %2%") % drop % target)); + } + + for (int i = 0; i < drop; i++) + { + auto client = sorted_connections.top (); + + if (node.config.logging.bulk_pull_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Dropping peer with block rate %1%, block count %2% (%3%) ") % client->block_rate () % client->block_count % client->channel->to_string ())); + } + + client->stop (false); + sorted_connections.pop (); + } + } + + if (node.config.logging.bulk_pull_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Bulk pull connections: %1%, rate: %2% blocks/sec, bootstrap attempts %3%, remaining pulls: %4%") % connections_count.load () % (int)rate_sum % attempts_count % num_pulls)); + } + + if (connections_count < target && (attempts_count != 0 || new_connections_empty) && !stopped) + { + auto delta = std::min ((target - connections_count) * 2, nano::bootstrap_limits::bootstrap_max_new_connections); + // TODO - tune this better + // Not many peers respond, need to try to make more connections than we need. + for (auto i = 0u; i < delta; i++) + { + auto endpoint (node.network.bootstrap_peer (true)); + if (endpoint != nano::tcp_endpoint (boost::asio::ip::address_v6::any (), 0) && (node.flags.allow_bootstrap_peers_duplicates || endpoints.find (endpoint) == endpoints.end ()) && !node.network.excluded_peers.check (endpoint)) + { + connect_client (endpoint); + endpoints.insert (endpoint); + nano::lock_guard lock (mutex); + new_connections_empty = false; + } + else if (connections_count == 0) + { + { + nano::lock_guard lock (mutex); + new_connections_empty = true; + } + condition.notify_all (); + } + } + } + if (!stopped && repeat) + { + std::weak_ptr this_w (shared_from_this ()); + node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (1), [this_w]() { + if (auto this_l = this_w.lock ()) + { + this_l->populate_connections (); + } + }); + } +} + +void nano::bootstrap_connections::start_populate_connections () +{ + if (!populate_connections_started.exchange (true)) + { + populate_connections (); + } +} + +void nano::bootstrap_connections::add_pull (nano::pull_info const & pull_a) +{ + nano::pull_info pull (pull_a); + node.bootstrap_initiator.cache.update_pull (pull); + { + nano::lock_guard lock (mutex); + pulls.push_back (pull); + } + condition.notify_all (); +} + +void nano::bootstrap_connections::request_pull (nano::unique_lock & lock_a) +{ + lock_a.unlock (); + auto connection_l (connection ()); + lock_a.lock (); + if (connection_l != nullptr && !pulls.empty ()) + { + std::shared_ptr attempt_l; + nano::pull_info pull; + // Search pulls with existing attempts + while (attempt_l == nullptr && !pulls.empty ()) + { + pull = pulls.front (); + pulls.pop_front (); + attempt_l = node.bootstrap_initiator.attempts.find (pull.bootstrap_id); + // Check if lazy pull is obsolete (head was processed or head is 0 for destinations requests) + if (attempt_l != nullptr && attempt_l->mode == nano::bootstrap_mode::lazy && !pull.head.is_zero () && attempt_l->lazy_processed_or_exists (pull.head)) + { + attempt_l->pull_finished (); + attempt_l = nullptr; + } + } + if (attempt_l != nullptr) + { + if (attempt_l->mode == nano::bootstrap_mode::legacy) + { + attempt_l->add_recent_pull (pull.head); + } + // The bulk_pull_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference + // Dispatch request in an external thread in case it needs to be destroyed + node.background ([connection_l, attempt_l, pull]() { + auto client (std::make_shared (connection_l, attempt_l, pull)); + client->request (); + }); + } + } + else if (connection_l != nullptr) + { + // Reuse connection if pulls deque become empty + lock_a.unlock (); + pool_connection (connection_l); + lock_a.lock (); + } +} + +void nano::bootstrap_connections::requeue_pull (nano::pull_info const & pull_a, bool network_error) +{ + auto pull (pull_a); + if (!network_error) + { + ++pull.attempts; + } + auto attempt_l (node.bootstrap_initiator.attempts.find (pull.bootstrap_id)); + if (attempt_l != nullptr) + { + ++attempt_l->requeued_pulls; + if (attempt_l->mode == nano::bootstrap_mode::legacy) + { + attempt_l->restart_condition (); + } + else if (attempt_l->mode == nano::bootstrap_mode::lazy) + { + pull.count = attempt_l->lazy_batch_size (); + } + if (pull.attempts < pull.retry_limit + (pull.processed / nano::bootstrap_limits::requeued_pulls_processed_blocks_factor)) + { + { + nano::lock_guard lock (mutex); + pulls.push_front (pull); + } + attempt_l->pull_started (); + condition.notify_all (); + } + else if (attempt_l->mode == nano::bootstrap_mode::lazy && (pull.retry_limit == std::numeric_limits::max () || pull.attempts <= pull.retry_limit + (pull.processed / node.network_params.bootstrap.lazy_max_pull_blocks))) + { + debug_assert (pull.account_or_head == pull.head); + if (!attempt_l->lazy_processed_or_exists (pull.account_or_head)) + { + { + nano::lock_guard lock (mutex); + pulls.push_back (pull); + } + attempt_l->pull_started (); + condition.notify_all (); + } + } + else + { + if (node.config.logging.bulk_pull_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Failed to pull account %1% down to %2% after %3% attempts and %4% blocks processed") % pull.account_or_head.to_account () % pull.end.to_string () % pull.attempts % pull.processed)); + } + node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in); + + if (attempt_l->mode == nano::bootstrap_mode::lazy && pull.processed > 0) + { + attempt_l->lazy_add (pull); + } + else if (attempt_l->mode == nano::bootstrap_mode::legacy) + { + node.bootstrap_initiator.cache.add (pull); + } + } + } +} + +void nano::bootstrap_connections::clear_pulls (uint64_t bootstrap_id_a) +{ + { + nano::lock_guard lock (mutex); + auto i (pulls.begin ()); + while (i != pulls.end ()) + { + if (i->bootstrap_id == bootstrap_id_a) + { + i = pulls.erase (i); + } + else + { + ++i; + } + } + } + condition.notify_all (); +} + +void nano::bootstrap_connections::run () +{ + start_populate_connections (); + nano::unique_lock lock (mutex); + while (!stopped) + { + if (!pulls.empty ()) + { + request_pull (lock); + } + else + { + condition.wait (lock); + } + } + stopped = true; + lock.unlock (); + condition.notify_all (); +} + +void nano::bootstrap_connections::stop () +{ + nano::unique_lock lock (mutex); + stopped = true; + lock.unlock (); + condition.notify_all (); + lock.lock (); + for (auto i : clients) + { + if (auto client = i.lock ()) + { + client->socket->close (); + } + } + clients.clear (); + idle.clear (); +} diff --git a/nano/node/bootstrap/bootstrap_connections.hpp b/nano/node/bootstrap/bootstrap_connections.hpp new file mode 100644 index 0000000000..85ef479a1f --- /dev/null +++ b/nano/node/bootstrap/bootstrap_connections.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include + +namespace nano +{ +class node; +namespace transport +{ + class channel_tcp; +} + +class bootstrap_attempt; +class bootstrap_connections; +class frontier_req_client; +class pull_info; +class bootstrap_client final : public std::enable_shared_from_this +{ +public: + bootstrap_client (std::shared_ptr node_a, std::shared_ptr connections_a, std::shared_ptr channel_a, std::shared_ptr socket_a); + ~bootstrap_client (); + std::shared_ptr shared (); + void stop (bool force); + double block_rate () const; + double elapsed_seconds () const; + void set_start_time (std::chrono::steady_clock::time_point start_time_a); + std::shared_ptr node; + std::shared_ptr connections; + std::shared_ptr channel; + std::shared_ptr socket; + std::shared_ptr> receive_buffer; + std::atomic block_count{ 0 }; + std::atomic pending_stop{ false }; + std::atomic hard_stop{ false }; + +private: + mutable std::mutex start_time_mutex; + std::chrono::steady_clock::time_point start_time_m; +}; + +class bootstrap_connections final : public std::enable_shared_from_this +{ +public: + bootstrap_connections (nano::node & node_a); + std::shared_ptr shared (); + std::shared_ptr connection (std::shared_ptr attempt_a = nullptr, bool use_front_connection = false); + void pool_connection (std::shared_ptr client_a, bool new_client = false, bool push_front = false); + void add_connection (nano::endpoint const & endpoint_a); + std::shared_ptr find_connection (nano::tcp_endpoint const & endpoint_a); + void connect_client (nano::tcp_endpoint const & endpoint_a, bool push_front = false); + unsigned target_connections (size_t pulls_remaining, size_t attempts_count); + void populate_connections (bool repeat = true); + void start_populate_connections (); + void add_pull (nano::pull_info const & pull_a); + void request_pull (nano::unique_lock & lock_a); + void requeue_pull (nano::pull_info const & pull_a, bool network_error = false); + void clear_pulls (uint64_t); + void run (); + void stop (); + std::deque> clients; + std::atomic connections_count{ 0 }; + nano::node & node; + std::deque> idle; + std::deque pulls; + std::atomic populate_connections_started{ false }; + std::atomic new_connections_empty{ false }; + std::atomic stopped{ false }; + std::mutex mutex; + nano::condition_variable condition; +}; +} diff --git a/nano/node/bootstrap/bootstrap_frontier.cpp b/nano/node/bootstrap/bootstrap_frontier.cpp index a5ff90ad2c..f5335e1936 100644 --- a/nano/node/bootstrap/bootstrap_frontier.cpp +++ b/nano/node/bootstrap/bootstrap_frontier.cpp @@ -1,8 +1,10 @@ -#include +#include #include #include #include +#include + constexpr double nano::bootstrap_limits::bootstrap_connection_warmup_time_sec; constexpr double nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate; constexpr double nano::bootstrap_limits::bootstrap_minimum_frontier_blocks_per_sec; @@ -31,17 +33,17 @@ void nano::frontier_req_client::run () } } }, - false); // is bootstrap traffic is_droppable false + nano::buffer_drop_policy::no_limiter_drop); } -nano::frontier_req_client::frontier_req_client (std::shared_ptr connection_a) : +nano::frontier_req_client::frontier_req_client (std::shared_ptr connection_a, std::shared_ptr attempt_a) : connection (connection_a), +attempt (attempt_a), current (0), count (0), bulk_push_cost (0) { - auto transaction (connection->node->store.tx_begin_read ()); - next (transaction); + next (); } nano::frontier_req_client::~frontier_req_client () @@ -75,7 +77,7 @@ void nano::frontier_req_client::unsynced (nano::block_hash const & head, nano::b { if (bulk_push_cost < nano::bootstrap_limits::bulk_push_cost_limit) { - connection->attempt->add_bulk_push_target (head, end); + attempt->add_bulk_push_target (head, end); if (end.is_zero ()) { bulk_push_cost += 2; @@ -91,17 +93,17 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con { if (!ec) { - assert (size_a == nano::frontier_req_client::size_frontier); + debug_assert (size_a == nano::frontier_req_client::size_frontier); nano::account account; nano::bufferstream account_stream (connection->receive_buffer->data (), sizeof (account)); auto error1 (nano::try_read (account_stream, account)); (void)error1; - assert (!error1); + debug_assert (!error1); nano::block_hash latest; nano::bufferstream latest_stream (connection->receive_buffer->data () + sizeof (account), sizeof (latest)); auto error2 (nano::try_read (latest_stream, latest)); (void)error2; - assert (!error2); + debug_assert (!error2); if (count == 0) { start_time = std::chrono::steady_clock::now (); @@ -117,18 +119,17 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con promise.set_value (true); return; } - if (connection->attempt->should_log ()) + if (attempt->should_log ()) { connection->node->logger.always_log (boost::str (boost::format ("Received %1% frontiers from %2%") % std::to_string (count) % connection->channel->to_string ())); } - auto transaction (connection->node->store.tx_begin_read ()); if (!account.is_zero ()) { while (!current.is_zero () && current < account) { // We know about an account they don't. unsynced (frontier, 0); - next (transaction); + next (); } if (!current.is_zero ()) { @@ -140,30 +141,30 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con } else { - if (connection->node->store.block_exists (transaction, latest)) + if (connection->node->ledger.block_exists (latest)) { // We know about a block they don't. unsynced (frontier, latest); } else { - connection->attempt->add_pull (nano::pull_info (account, latest, frontier, 0, connection->node->network_params.bootstrap.frontier_retry_limit)); + attempt->add_frontier (nano::pull_info (account, latest, frontier, attempt->incremental_id, 0, connection->node->network_params.bootstrap.frontier_retry_limit)); // Either we're behind or there's a fork we differ on // Either way, bulk pushing will probably not be effective bulk_push_cost += 5; } } - next (transaction); + next (); } else { - assert (account < current); - connection->attempt->add_pull (nano::pull_info (account, latest, nano::block_hash (0), 0, connection->node->network_params.bootstrap.frontier_retry_limit)); + debug_assert (account < current); + attempt->add_frontier (nano::pull_info (account, latest, nano::block_hash (0), attempt->incremental_id, 0, connection->node->network_params.bootstrap.frontier_retry_limit)); } } else { - connection->attempt->add_pull (nano::pull_info (account, latest, nano::block_hash (0), 0, connection->node->network_params.bootstrap.frontier_retry_limit)); + attempt->add_frontier (nano::pull_info (account, latest, nano::block_hash (0), attempt->incremental_id, 0, connection->node->network_params.bootstrap.frontier_retry_limit)); } receive_frontier (); } @@ -173,7 +174,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con { // We know about an account they don't. unsynced (frontier, 0); - next (transaction); + next (); } if (connection->node->config.logging.bulk_pull_logging ()) { @@ -187,7 +188,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con catch (std::future_error &) { } - connection->attempt->pool_connection (connection); + connection->connections->pool_connection (connection); } } } @@ -200,13 +201,14 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con } } -void nano::frontier_req_client::next (nano::transaction const & transaction_a) +void nano::frontier_req_client::next () { // Filling accounts deque to prevent often read transactions if (accounts.empty ()) { size_t max_size (128); - for (auto i (connection->node->store.latest_begin (transaction_a, current.number () + 1)), n (connection->node->store.latest_end ()); i != n && accounts.size () != max_size; ++i) + auto transaction (connection->node->store.tx_begin_read ()); + for (auto i (connection->node->store.latest_begin (transaction, current.number () + 1)), n (connection->node->store.latest_end ()); i != n && accounts.size () != max_size; ++i) { nano::account_info const & info (i->second); nano::account const & account (i->first); diff --git a/nano/node/bootstrap/bootstrap_frontier.hpp b/nano/node/bootstrap/bootstrap_frontier.hpp index 8241022419..90a4419e60 100644 --- a/nano/node/bootstrap/bootstrap_frontier.hpp +++ b/nano/node/bootstrap/bootstrap_frontier.hpp @@ -1,23 +1,26 @@ #pragma once #include -#include + +#include +#include namespace nano { -class transaction; +class bootstrap_attempt; class bootstrap_client; class frontier_req_client final : public std::enable_shared_from_this { public: - explicit frontier_req_client (std::shared_ptr); + explicit frontier_req_client (std::shared_ptr, std::shared_ptr); ~frontier_req_client (); void run (); void receive_frontier (); void received_frontier (boost::system::error_code const &, size_t); void unsynced (nano::block_hash const &, nano::block_hash const &); - void next (nano::transaction const &); + void next (); std::shared_ptr connection; + std::shared_ptr attempt; nano::account current; nano::block_hash frontier; unsigned count; diff --git a/nano/node/bootstrap/bootstrap_lazy.cpp b/nano/node/bootstrap/bootstrap_lazy.cpp new file mode 100644 index 0000000000..ef1dc82e56 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_lazy.cpp @@ -0,0 +1,603 @@ +#include +#include +#include +#include +#include + +#include + +#include + +constexpr std::chrono::seconds nano::bootstrap_limits::lazy_flush_delay_sec; +constexpr unsigned nano::bootstrap_limits::lazy_destinations_request_limit; +constexpr uint64_t nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit; +constexpr double nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio; +constexpr size_t nano::bootstrap_limits::lazy_blocks_restart_limit; + +nano::bootstrap_attempt_lazy::bootstrap_attempt_lazy (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a) : +nano::bootstrap_attempt (node_a, nano::bootstrap_mode::lazy, incremental_id_a, id_a) +{ + node->bootstrap_initiator.notify_listeners (true); +} + +nano::bootstrap_attempt_lazy::~bootstrap_attempt_lazy () +{ + debug_assert (lazy_blocks.size () == lazy_blocks_count); + node->bootstrap_initiator.notify_listeners (false); +} + +void nano::bootstrap_attempt_lazy::lazy_start (nano::hash_or_account const & hash_or_account_a, bool confirmed) +{ + nano::unique_lock lock (mutex); + // Add start blocks, limit 1024 (4k with disabled legacy bootstrap) + size_t max_keys (node->flags.disable_legacy_bootstrap ? 4 * 1024 : 1024); + if (lazy_keys.size () < max_keys && lazy_keys.find (hash_or_account_a) == lazy_keys.end () && !lazy_blocks_processed (hash_or_account_a)) + { + lazy_keys.insert (hash_or_account_a); + lazy_pulls.emplace_back (hash_or_account_a, confirmed ? std::numeric_limits::max () : node->network_params.bootstrap.lazy_retry_limit); + lock.unlock (); + condition.notify_all (); + } +} + +void nano::bootstrap_attempt_lazy::lazy_add (nano::hash_or_account const & hash_or_account_a, unsigned retry_limit) +{ + // Add only unknown blocks + debug_assert (!mutex.try_lock ()); + if (!lazy_blocks_processed (hash_or_account_a)) + { + lazy_pulls.emplace_back (hash_or_account_a, retry_limit); + } +} + +void nano::bootstrap_attempt_lazy::lazy_add (nano::pull_info const & pull_a) +{ + debug_assert (pull_a.account_or_head == pull_a.head); + nano::lock_guard lock (mutex); + lazy_add (pull_a.account_or_head, pull_a.retry_limit); +} + +void nano::bootstrap_attempt_lazy::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a, bool confirmed_a) +{ + nano::unique_lock lock (mutex); + // Add only known blocks + if (lazy_blocks_processed (hash_a)) + { + lazy_blocks_erase (hash_a); + lock.unlock (); + node->bootstrap_initiator.connections->requeue_pull (nano::pull_info (hash_a, hash_a, previous_a, incremental_id, static_cast (1), confirmed_a ? std::numeric_limits::max () : node->network_params.bootstrap.lazy_destinations_retry_limit)); + } +} + +uint32_t nano::bootstrap_attempt_lazy::lazy_batch_size () +{ + auto result (node->network_params.bootstrap.lazy_max_pull_blocks); + if (total_blocks > nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit && lazy_blocks_count != 0) + { + double lazy_blocks_ratio (total_blocks / lazy_blocks_count); + if (lazy_blocks_ratio > nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio) + { + // Increasing blocks ratio weight as more important (^3). Small batch count should lower blocks ratio below target + double lazy_blocks_factor (std::pow (lazy_blocks_ratio / nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio, 3.0)); + // Decreasing total block count weight as less important (sqrt) + double total_blocks_factor (std::sqrt (total_blocks / nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit)); + uint32_t batch_count_min (node->network_params.bootstrap.lazy_max_pull_blocks / (lazy_blocks_factor * total_blocks_factor)); + result = std::max (node->network_params.bootstrap.lazy_min_pull_blocks, batch_count_min); + } + } + return result; +} + +void nano::bootstrap_attempt_lazy::lazy_pull_flush (nano::unique_lock & lock_a) +{ + static size_t const max_pulls (nano::bootstrap_limits::bootstrap_connection_scale_target_blocks * 3); + if (pulling < max_pulls) + { + debug_assert (node->network_params.bootstrap.lazy_max_pull_blocks <= std::numeric_limits::max ()); + nano::pull_info::count_t batch_count (lazy_batch_size ()); + uint64_t read_count (0); + size_t count (0); + auto transaction (node->store.tx_begin_read ()); + while (!lazy_pulls.empty () && count < max_pulls) + { + auto pull_start (lazy_pulls.front ()); + lazy_pulls.pop_front (); + // Recheck if block was already processed + if (!lazy_blocks_processed (pull_start.first) && !node->store.block_exists (transaction, pull_start.first)) + { + lock_a.unlock (); + node->bootstrap_initiator.connections->add_pull (nano::pull_info (pull_start.first, pull_start.first, nano::block_hash (0), incremental_id, batch_count, pull_start.second)); + ++pulling; + ++count; + lock_a.lock (); + } + // We don't want to open read transactions for too long + ++read_count; + if (read_count % batch_read_size == 0) + { + transaction.refresh (); + } + } + } +} + +bool nano::bootstrap_attempt_lazy::lazy_finished () +{ + debug_assert (!mutex.try_lock ()); + if (stopped) + { + return true; + } + bool result (true); + uint64_t read_count (0); + auto transaction (node->store.tx_begin_read ()); + for (auto it (lazy_keys.begin ()), end (lazy_keys.end ()); it != end && !stopped;) + { + if (node->store.block_exists (transaction, *it)) + { + it = lazy_keys.erase (it); + } + else + { + result = false; + break; + // No need to increment `it` as we break above. + } + // We don't want to open read transactions for too long + ++read_count; + if (read_count % batch_read_size == 0) + { + transaction.refresh (); + } + } + // Finish lazy bootstrap without lazy pulls (in combination with still_pulling ()) + if (!result && lazy_pulls.empty () && lazy_state_backlog.empty ()) + { + result = true; + } + // Don't close lazy bootstrap until all destinations are processed + if (result && !lazy_destinations.empty ()) + { + result = false; + } + return result; +} + +bool nano::bootstrap_attempt_lazy::lazy_has_expired () const +{ + bool result (false); + // Max 30 minutes run with enabled legacy bootstrap + static std::chrono::minutes const max_lazy_time (node->flags.disable_legacy_bootstrap ? 7 * 24 * 60 : 30); + if (std::chrono::steady_clock::now () - lazy_start_time >= max_lazy_time) + { + result = true; + } + else if (!node->flags.disable_legacy_bootstrap && lazy_blocks_count > nano::bootstrap_limits::lazy_blocks_restart_limit) + { + result = true; + } + return result; +} + +void nano::bootstrap_attempt_lazy::run () +{ + debug_assert (started); + debug_assert (!node->flags.disable_lazy_bootstrap); + node->bootstrap_initiator.connections->populate_connections (false); + lazy_start_time = std::chrono::steady_clock::now (); + nano::unique_lock lock (mutex); + while ((still_pulling () || !lazy_finished ()) && !lazy_has_expired ()) + { + unsigned iterations (0); + auto this_l (shared_from_this ()); + while (still_pulling () && !lazy_has_expired ()) + { + condition.wait (lock, [& stopped = stopped, &pulling = pulling, &lazy_pulls = lazy_pulls, this_l] { return stopped || pulling == 0 || (pulling < nano::bootstrap_limits::bootstrap_connection_scale_target_blocks && !lazy_pulls.empty ()) || this_l->lazy_has_expired (); }); + ++iterations; + // Flushing lazy pulls + lazy_pull_flush (lock); + // Start backlog cleanup + if (iterations % 100 == 0) + { + lazy_backlog_cleanup (); + } + // Destinations check + if (pulling == 0 && lazy_destinations_flushed) + { + lazy_destinations_flush (); + lazy_pull_flush (lock); + } + } + // Flushing lazy pulls + lazy_pull_flush (lock); + // Check if some blocks required for backlog were processed. Start destinations check + if (pulling == 0) + { + lazy_backlog_cleanup (); + lazy_destinations_flush (); + lazy_pull_flush (lock); + } + } + if (!stopped) + { + node->logger.try_log ("Completed lazy pulls"); + } + lock.unlock (); + stop (); + condition.notify_all (); +} + +bool nano::bootstrap_attempt_lazy::process_block (std::shared_ptr block_a, nano::account const & known_account_a, uint64_t pull_blocks, nano::bulk_pull::count_t max_blocks, bool block_expected, unsigned retry_limit) +{ + bool stop_pull (false); + if (block_expected) + { + stop_pull = process_block_lazy (block_a, known_account_a, pull_blocks, max_blocks, retry_limit); + } + else + { + // Drop connection with unexpected block for lazy bootstrap + stop_pull = true; + } + return stop_pull; +} + +bool nano::bootstrap_attempt_lazy::process_block_lazy (std::shared_ptr block_a, nano::account const & known_account_a, uint64_t pull_blocks, nano::bulk_pull::count_t max_blocks, unsigned retry_limit) +{ + bool stop_pull (false); + auto hash (block_a->hash ()); + nano::unique_lock lock (mutex); + // Processing new blocks + if (!lazy_blocks_processed (hash)) + { + // Search for new dependencies + if (!block_a->source ().is_zero () && !node->ledger.block_exists (block_a->source ()) && block_a->source () != node->network_params.ledger.genesis_account) + { + lazy_add (block_a->source (), retry_limit); + } + else if (block_a->type () == nano::block_type::state) + { + lazy_block_state (block_a, retry_limit); + } + else if (block_a->type () == nano::block_type::send) + { + std::shared_ptr block_l (std::static_pointer_cast (block_a)); + if (block_l != nullptr && !block_l->hashables.destination.is_zero ()) + { + lazy_destinations_increment (block_l->hashables.destination); + } + } + lazy_blocks_insert (hash); + // Adding lazy balances for first processed block in pull + if (pull_blocks == 0 && (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send)) + { + lazy_balances.emplace (hash, block_a->balance ().number ()); + } + // Clearing lazy balances for previous block + if (!block_a->previous ().is_zero () && lazy_balances.find (block_a->previous ()) != lazy_balances.end ()) + { + lazy_balances.erase (block_a->previous ()); + } + lazy_block_state_backlog_check (block_a, hash); + lock.unlock (); + nano::unchecked_info info (block_a, known_account_a, 0, nano::signature_verification::unknown, retry_limit == std::numeric_limits::max ()); + node->block_processor.add (info); + } + // Force drop lazy bootstrap connection for long bulk_pull + if (pull_blocks > max_blocks) + { + stop_pull = true; + } + return stop_pull; +} + +void nano::bootstrap_attempt_lazy::lazy_block_state (std::shared_ptr block_a, unsigned retry_limit) +{ + std::shared_ptr block_l (std::static_pointer_cast (block_a)); + if (block_l != nullptr) + { + auto transaction (node->store.tx_begin_read ()); + nano::uint128_t balance (block_l->hashables.balance.number ()); + auto const & link (block_l->hashables.link); + // If link is not epoch link or 0. And if block from link is unknown + if (!link.is_zero () && !node->ledger.is_epoch_link (link) && !lazy_blocks_processed (link) && !node->store.block_exists (transaction, link)) + { + auto const & previous (block_l->hashables.previous); + // If state block previous is 0 then source block required + if (previous.is_zero ()) + { + lazy_add (link, retry_limit); + } + // In other cases previous block balance required to find out subtype of state block + else if (node->store.block_exists (transaction, previous)) + { + if (node->ledger.balance (transaction, previous) <= balance) + { + lazy_add (link, retry_limit); + } + else + { + lazy_destinations_increment (link); + } + } + // Search balance of already processed previous blocks + else if (lazy_blocks_processed (previous)) + { + auto previous_balance (lazy_balances.find (previous)); + if (previous_balance != lazy_balances.end ()) + { + if (previous_balance->second <= balance) + { + lazy_add (link, retry_limit); + } + else + { + lazy_destinations_increment (link); + } + lazy_balances.erase (previous_balance); + } + } + // Insert in backlog state blocks if previous wasn't already processed + else + { + lazy_state_backlog.emplace (previous, nano::lazy_state_backlog_item{ link, balance, retry_limit }); + } + } + } +} + +void nano::bootstrap_attempt_lazy::lazy_block_state_backlog_check (std::shared_ptr block_a, nano::block_hash const & hash_a) +{ + // Search unknown state blocks balances + auto find_state (lazy_state_backlog.find (hash_a)); + if (find_state != lazy_state_backlog.end ()) + { + auto next_block (find_state->second); + // Retrieve balance for previous state & send blocks + if (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send) + { + if (block_a->balance ().number () <= next_block.balance) // balance + { + lazy_add (next_block.link, next_block.retry_limit); // link + } + else + { + lazy_destinations_increment (next_block.link); + } + } + // Assumption for other legacy block types + else if (lazy_undefined_links.find (next_block.link) == lazy_undefined_links.end ()) + { + lazy_add (next_block.link, node->network_params.bootstrap.lazy_retry_limit); // Head is not confirmed. It can be account or hash or non-existing + lazy_undefined_links.insert (next_block.link); + } + lazy_state_backlog.erase (find_state); + } +} + +void nano::bootstrap_attempt_lazy::lazy_backlog_cleanup () +{ + uint64_t read_count (0); + auto transaction (node->store.tx_begin_read ()); + for (auto it (lazy_state_backlog.begin ()), end (lazy_state_backlog.end ()); it != end && !stopped;) + { + if (node->store.block_exists (transaction, it->first)) + { + auto next_block (it->second); + if (node->ledger.balance (transaction, it->first) <= next_block.balance) // balance + { + lazy_add (next_block.link, next_block.retry_limit); // link + } + else + { + lazy_destinations_increment (next_block.link); + } + it = lazy_state_backlog.erase (it); + } + else + { + lazy_add (it->first, it->second.retry_limit); + ++it; + } + // We don't want to open read transactions for too long + ++read_count; + if (read_count % batch_read_size == 0) + { + transaction.refresh (); + } + } +} + +void nano::bootstrap_attempt_lazy::lazy_destinations_increment (nano::account const & destination_a) +{ + // Enabled only if legacy bootstrap is not available. Legacy bootstrap is a more effective way to receive all existing destinations + if (node->flags.disable_legacy_bootstrap) + { + // Update accounts counter for send blocks + auto existing (lazy_destinations.get ().find (destination_a)); + if (existing != lazy_destinations.get ().end ()) + { + lazy_destinations.get ().modify (existing, [](nano::lazy_destinations_item & item_a) { + ++item_a.count; + }); + } + else + { + lazy_destinations.emplace (nano::lazy_destinations_item{ destination_a, 1 }); + } + } +} + +void nano::bootstrap_attempt_lazy::lazy_destinations_flush () +{ + debug_assert (!mutex.try_lock ()); + lazy_destinations_flushed = true; + size_t count (0); + for (auto it (lazy_destinations.get ().begin ()), end (lazy_destinations.get ().end ()); it != end && count < nano::bootstrap_limits::lazy_destinations_request_limit && !stopped;) + { + lazy_add (it->account, node->network_params.bootstrap.lazy_destinations_retry_limit); + it = lazy_destinations.get ().erase (it); + ++count; + } +} + +void nano::bootstrap_attempt_lazy::lazy_blocks_insert (nano::block_hash const & hash_a) +{ + debug_assert (!mutex.try_lock ()); + auto inserted (lazy_blocks.insert (std::hash<::nano::block_hash> () (hash_a))); + if (inserted.second) + { + ++lazy_blocks_count; + debug_assert (lazy_blocks_count > 0); + } +} + +void nano::bootstrap_attempt_lazy::lazy_blocks_erase (nano::block_hash const & hash_a) +{ + debug_assert (!mutex.try_lock ()); + auto erased (lazy_blocks.erase (std::hash<::nano::block_hash> () (hash_a))); + if (erased) + { + --lazy_blocks_count; + debug_assert (lazy_blocks_count != std::numeric_limits::max ()); + } +} + +bool nano::bootstrap_attempt_lazy::lazy_blocks_processed (nano::block_hash const & hash_a) +{ + return lazy_blocks.find (std::hash<::nano::block_hash> () (hash_a)) != lazy_blocks.end (); +} + +bool nano::bootstrap_attempt_lazy::lazy_processed_or_exists (nano::block_hash const & hash_a) +{ + bool result (false); + nano::unique_lock lock (mutex); + if (lazy_blocks_processed (hash_a)) + { + result = true; + } + else + { + lock.unlock (); + if (node->ledger.block_exists (hash_a)) + { + result = true; + } + } + return result; +} + +void nano::bootstrap_attempt_lazy::get_information (boost::property_tree::ptree & tree_a) +{ + nano::lock_guard lock (mutex); + tree_a.put ("lazy_blocks", std::to_string (lazy_blocks.size ())); + tree_a.put ("lazy_state_backlog", std::to_string (lazy_state_backlog.size ())); + tree_a.put ("lazy_balances", std::to_string (lazy_balances.size ())); + tree_a.put ("lazy_destinations", std::to_string (lazy_destinations.size ())); + tree_a.put ("lazy_undefined_links", std::to_string (lazy_undefined_links.size ())); + tree_a.put ("lazy_pulls", std::to_string (lazy_pulls.size ())); + tree_a.put ("lazy_keys", std::to_string (lazy_keys.size ())); + if (!lazy_keys.empty ()) + { + tree_a.put ("lazy_key_1", (*(lazy_keys.begin ())).to_string ()); + } +} + +nano::bootstrap_attempt_wallet::bootstrap_attempt_wallet (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a) : +nano::bootstrap_attempt (node_a, nano::bootstrap_mode::wallet_lazy, incremental_id_a, id_a) +{ + node->bootstrap_initiator.notify_listeners (true); +} + +nano::bootstrap_attempt_wallet::~bootstrap_attempt_wallet () +{ + node->bootstrap_initiator.notify_listeners (false); +} + +void nano::bootstrap_attempt_wallet::request_pending (nano::unique_lock & lock_a) +{ + lock_a.unlock (); + auto connection_l (node->bootstrap_initiator.connections->connection (shared_from_this ())); + lock_a.lock (); + if (connection_l && !stopped) + { + auto account (wallet_accounts.front ()); + wallet_accounts.pop_front (); + ++pulling; + auto this_l (shared_from_this ()); + // The bulk_pull_account_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference + // Dispatch request in an external thread in case it needs to be destroyed + node->background ([connection_l, this_l, account]() { + auto client (std::make_shared (connection_l, this_l, account)); + client->request (); + }); + } +} + +void nano::bootstrap_attempt_wallet::requeue_pending (nano::account const & account_a) +{ + auto account (account_a); + { + nano::lock_guard lock (mutex); + wallet_accounts.push_front (account); + } + condition.notify_all (); +} + +void nano::bootstrap_attempt_wallet::wallet_start (std::deque & accounts_a) +{ + { + nano::lock_guard lock (mutex); + wallet_accounts.swap (accounts_a); + } + condition.notify_all (); +} + +bool nano::bootstrap_attempt_wallet::wallet_finished () +{ + debug_assert (!mutex.try_lock ()); + auto running (!stopped); + auto more_accounts (!wallet_accounts.empty ()); + auto still_pulling (pulling > 0); + return running && (more_accounts || still_pulling); +} + +void nano::bootstrap_attempt_wallet::run () +{ + debug_assert (started); + debug_assert (!node->flags.disable_wallet_bootstrap); + node->bootstrap_initiator.connections->populate_connections (false); + auto start_time (std::chrono::steady_clock::now ()); + auto max_time (std::chrono::minutes (10)); + nano::unique_lock lock (mutex); + while (wallet_finished () && std::chrono::steady_clock::now () - start_time < max_time) + { + if (!wallet_accounts.empty ()) + { + request_pending (lock); + } + else + { + condition.wait_for (lock, std::chrono::seconds (1)); + } + } + if (!stopped) + { + node->logger.try_log ("Completed wallet lazy pulls"); + } + lock.unlock (); + stop (); + condition.notify_all (); +} + +size_t nano::bootstrap_attempt_wallet::wallet_size () +{ + nano::lock_guard lock (mutex); + return wallet_accounts.size (); +} + +void nano::bootstrap_attempt_wallet::get_information (boost::property_tree::ptree & tree_a) +{ + nano::lock_guard lock (mutex); + tree_a.put ("wallet_accounts", std::to_string (wallet_accounts.size ())); +} diff --git a/nano/node/bootstrap/bootstrap_lazy.hpp b/nano/node/bootstrap/bootstrap_lazy.hpp new file mode 100644 index 0000000000..614b4b56b7 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_lazy.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class node; +class lazy_state_backlog_item final +{ +public: + nano::link link{ 0 }; + nano::uint128_t balance{ 0 }; + unsigned retry_limit{ 0 }; +}; +class lazy_destinations_item final +{ +public: + nano::account account{ 0 }; + uint64_t count{ 0 }; +}; +class bootstrap_attempt_lazy final : public bootstrap_attempt +{ +public: + explicit bootstrap_attempt_lazy (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a = ""); + ~bootstrap_attempt_lazy (); + bool process_block (std::shared_ptr, nano::account const &, uint64_t, nano::bulk_pull::count_t, bool, unsigned) override; + void run () override; + void lazy_start (nano::hash_or_account const &, bool confirmed = true) override; + void lazy_add (nano::hash_or_account const &, unsigned = std::numeric_limits::max ()); + void lazy_add (nano::pull_info const &) override; + void lazy_requeue (nano::block_hash const &, nano::block_hash const &, bool) override; + bool lazy_finished (); + bool lazy_has_expired () const override; + uint32_t lazy_batch_size () override; + void lazy_pull_flush (nano::unique_lock & lock_a); + bool process_block_lazy (std::shared_ptr, nano::account const &, uint64_t, nano::bulk_pull::count_t, unsigned); + void lazy_block_state (std::shared_ptr, unsigned); + void lazy_block_state_backlog_check (std::shared_ptr, nano::block_hash const &); + void lazy_backlog_cleanup (); + void lazy_destinations_increment (nano::account const &); + void lazy_destinations_flush (); + void lazy_blocks_insert (nano::block_hash const &); + void lazy_blocks_erase (nano::block_hash const &); + bool lazy_blocks_processed (nano::block_hash const &); + bool lazy_processed_or_exists (nano::block_hash const &) override; + void get_information (boost::property_tree::ptree &) override; + std::unordered_set lazy_blocks; + std::unordered_map lazy_state_backlog; + std::unordered_set lazy_undefined_links; + std::unordered_map lazy_balances; + std::unordered_set lazy_keys; + std::deque> lazy_pulls; + std::chrono::steady_clock::time_point lazy_start_time; + class account_tag + { + }; + class count_tag + { + }; + // clang-format off + boost::multi_index_container, + mi::member, + std::greater>, + mi::hashed_unique, + mi::member>>> + lazy_destinations; + // clang-format on + std::atomic lazy_blocks_count{ 0 }; + std::atomic lazy_destinations_flushed{ false }; + /** The maximum number of records to be read in while iterating over long lazy containers */ + static uint64_t constexpr batch_read_size = 256; +}; +class bootstrap_attempt_wallet final : public bootstrap_attempt +{ +public: + explicit bootstrap_attempt_wallet (std::shared_ptr node_a, uint64_t incremental_id_a, std::string id_a = ""); + ~bootstrap_attempt_wallet (); + void request_pending (nano::unique_lock &); + void requeue_pending (nano::account const &) override; + void run () override; + void wallet_start (std::deque &) override; + bool wallet_finished (); + size_t wallet_size () override; + void get_information (boost::property_tree::ptree &) override; + std::deque wallet_accounts; +}; +} diff --git a/nano/node/bootstrap/bootstrap_server.cpp b/nano/node/bootstrap/bootstrap_server.cpp index 4935ccf64d..8478d3fe2e 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -1,7 +1,11 @@ +#include +#include #include #include #include +#include + nano::bootstrap_listener::bootstrap_listener (uint16_t port_a, nano::node & node_a) : node (node_a), port (port_a) @@ -10,6 +14,8 @@ port (port_a) void nano::bootstrap_listener::start () { + nano::lock_guard lock (mutex); + on = true; listening_socket = std::make_shared (node.shared (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::any (), port), node.config.tcp_incoming_connections_max); boost::system::error_code ec; listening_socket->start (ec); @@ -18,6 +24,7 @@ void nano::bootstrap_listener::start () node.logger.try_log (boost::str (boost::format ("Error while binding for incoming TCP/bootstrap on port %1%: %2%") % listening_socket->listening_port () % ec.message ())); throw std::runtime_error (ec.message ()); } + debug_assert (node.network.endpoint ().port () == listening_socket->listening_port ()); listening_socket->on_connection ([this](std::shared_ptr new_connection, boost::system::error_code const & ec_a) { bool keep_accepting = true; if (ec_a) @@ -43,6 +50,7 @@ void nano::bootstrap_listener::stop () } if (listening_socket) { + nano::lock_guard lock (mutex); listening_socket->close (); listening_socket = nullptr; } @@ -56,29 +64,43 @@ size_t nano::bootstrap_listener::connection_count () void nano::bootstrap_listener::accept_action (boost::system::error_code const & ec, std::shared_ptr socket_a) { - auto connection (std::make_shared (socket_a, node.shared ())); + if (!node.network.excluded_peers.check (socket_a->remote_endpoint ())) { + auto connection (std::make_shared (socket_a, node.shared ())); nano::lock_guard lock (mutex); connections[connection.get ()] = connection; connection->receive (); } + else + { + node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_excluded); + if (node.config.logging.network_rejected_logging ()) + { + node.logger.try_log ("Rejected connection from excluded peer ", socket_a->remote_endpoint ()); + } + } } boost::asio::ip::tcp::endpoint nano::bootstrap_listener::endpoint () { - return boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::loopback (), listening_socket->listening_port ()); + nano::lock_guard lock (mutex); + if (on && listening_socket) + { + return boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::loopback (), listening_socket->listening_port ()); + } + else + { + return boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::loopback (), 0); + } } -namespace nano -{ -std::unique_ptr collect_seq_con_info (bootstrap_listener & bootstrap_listener, const std::string & name) +std::unique_ptr nano::collect_container_info (bootstrap_listener & bootstrap_listener, const std::string & name) { auto sizeof_element = sizeof (decltype (bootstrap_listener.connections)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "connections", bootstrap_listener.connection_count (), sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "connections", bootstrap_listener.connection_count (), sizeof_element })); return composite; } -} nano::bootstrap_server::bootstrap_server (std::shared_ptr socket_a, std::shared_ptr node_a) : receive_buffer (std::make_shared> ()), @@ -105,7 +127,7 @@ nano::bootstrap_server::~bootstrap_server () auto exisiting_response_channel (node->network.tcp_channels.find_channel (remote_endpoint)); if (exisiting_response_channel != nullptr) { - exisiting_response_channel->server = false; + exisiting_response_channel->temporary = false; node->network.tcp_channels.erase (remote_endpoint); } } @@ -147,7 +169,7 @@ void nano::bootstrap_server::receive_header_action (boost::system::error_code co { if (!ec) { - assert (size_a == 8); + debug_assert (size_a == 8); nano::bufferstream type_stream (receive_buffer->data (), size_a); auto error (false); nano::message_header header (error, type_stream); @@ -185,7 +207,7 @@ void nano::bootstrap_server::receive_header_action (boost::system::error_code co node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_push, nano::stat::dir::in); if (is_bootstrap_connection ()) { - add_request (std::unique_ptr (new nano::bulk_push (header))); + add_request (std::make_unique (header)); } break; } @@ -224,6 +246,33 @@ void nano::bootstrap_server::receive_header_action (boost::system::error_code co }); break; } + case nano::message_type::telemetry_req: + { + if (is_realtime_connection ()) + { + // Only handle telemetry requests if they are outside of the cutoff time + auto is_very_first_message = last_telemetry_req == std::chrono::steady_clock::time_point{}; + auto cache_exceeded = std::chrono::steady_clock::now () >= last_telemetry_req + nano::telemetry_cache_cutoffs::network_to_time (node->network_params.network); + if (is_very_first_message || cache_exceeded) + { + last_telemetry_req = std::chrono::steady_clock::now (); + add_request (std::make_unique (header)); + } + else + { + node->stats.inc (nano::stat::type::telemetry, nano::stat::detail::request_within_protection_cache_zone); + } + } + receive (); + break; + } + case nano::message_type::telemetry_ack: + { + socket->async_read (receive_buffer, header.payload_length_bytes (), [this_l, header](boost::system::error_code const & ec, size_t size_a) { + this_l->receive_telemetry_ack_action (ec, size_a, header); + }); + break; + } default: { if (node->config.logging.network_logging ()) @@ -250,7 +299,7 @@ void nano::bootstrap_server::receive_bulk_pull_action (boost::system::error_code { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::bulk_pull (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (node->config.logging.bulk_pull_logging ()) @@ -271,9 +320,9 @@ void nano::bootstrap_server::receive_bulk_pull_account_action (boost::system::er if (!ec) { auto error (false); - assert (size_a == header_a.payload_length_bytes ()); + debug_assert (size_a == header_a.payload_length_bytes ()); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::bulk_pull_account (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (node->config.logging.bulk_pull_logging ()) @@ -295,7 +344,7 @@ void nano::bootstrap_server::receive_frontier_req_action (boost::system::error_c { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::frontier_req (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (node->config.logging.bulk_pull_logging ()) @@ -324,7 +373,7 @@ void nano::bootstrap_server::receive_keepalive_action (boost::system::error_code { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::keepalive (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (is_realtime_connection ()) @@ -343,13 +392,13 @@ void nano::bootstrap_server::receive_keepalive_action (boost::system::error_code } } -void nano::bootstrap_server::receive_publish_action (boost::system::error_code const & ec, size_t size_a, nano::message_header const & header_a) +void nano::bootstrap_server::receive_telemetry_ack_action (boost::system::error_code const & ec, size_t size_a, nano::message_header const & header_a) { if (!ec) { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::publish (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (is_realtime_connection ()) @@ -360,6 +409,40 @@ void nano::bootstrap_server::receive_publish_action (boost::system::error_code c } } else + { + if (node->config.logging.network_telemetry_logging ()) + { + node->logger.try_log (boost::str (boost::format ("Error receiving telemetry ack: %1%") % ec.message ())); + } + } +} + +void nano::bootstrap_server::receive_publish_action (boost::system::error_code const & ec, size_t size_a, nano::message_header const & header_a) +{ + if (!ec) + { + nano::uint128_t digest; + if (!node->network.publish_filter.apply (receive_buffer->data (), size_a, &digest)) + { + auto error (false); + nano::bufferstream stream (receive_buffer->data (), size_a); + auto request (std::make_unique (error, stream, header_a, digest)); + if (!error) + { + if (is_realtime_connection ()) + { + add_request (std::unique_ptr (request.release ())); + } + receive (); + } + } + else + { + node->stats.inc (nano::stat::type::filter, nano::stat::detail::duplicate_publish); + receive (); + } + } + else { if (node->config.logging.network_message_logging ()) { @@ -374,7 +457,7 @@ void nano::bootstrap_server::receive_confirm_req_action (boost::system::error_co { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::confirm_req (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (is_realtime_connection ()) @@ -396,7 +479,7 @@ void nano::bootstrap_server::receive_confirm_ack_action (boost::system::error_co { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::confirm_ack (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (is_realtime_connection ()) @@ -418,7 +501,7 @@ void nano::bootstrap_server::receive_node_id_handshake_action (boost::system::er { auto error (false); nano::bufferstream stream (receive_buffer->data (), size_a); - std::unique_ptr request (new nano::node_id_handshake (error, stream, header_a)); + auto request (std::make_unique (error, stream, header_a)); if (!error) { if (type == nano::bootstrap_server_type::undefined && !node->flags.disable_tcp_realtime) @@ -436,23 +519,23 @@ void nano::bootstrap_server::receive_node_id_handshake_action (boost::system::er void nano::bootstrap_server::add_request (std::unique_ptr message_a) { - assert (message_a != nullptr); - nano::lock_guard lock (mutex); + debug_assert (message_a != nullptr); + nano::unique_lock lock (mutex); auto start (requests.empty ()); requests.push (std::move (message_a)); if (start) { - run_next (); + run_next (lock); } } void nano::bootstrap_server::finish_request () { - nano::lock_guard lock (mutex); + nano::unique_lock lock (mutex); requests.pop (); if (!requests.empty ()) { - run_next (); + run_next (lock); } else { @@ -510,38 +593,21 @@ class request_response_visitor : public nano::message_visitor connection (connection_a) { } - virtual ~request_response_visitor () = default; void keepalive (nano::keepalive const & message_a) override { - connection->finish_request_async (); - auto connection_l (connection->shared_from_this ()); - connection->node->background ([connection_l, message_a]() { - connection_l->node->network.tcp_channels.process_keepalive (message_a, connection_l->remote_endpoint); - }); + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); } void publish (nano::publish const & message_a) override { - connection->finish_request_async (); - auto connection_l (connection->shared_from_this ()); - connection->node->background ([connection_l, message_a]() { - connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, connection_l->remote_node_id, connection_l->socket, connection_l->type); - }); + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); } void confirm_req (nano::confirm_req const & message_a) override { - connection->finish_request_async (); - auto connection_l (connection->shared_from_this ()); - connection->node->background ([connection_l, message_a]() { - connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, connection_l->remote_node_id, connection_l->socket, connection_l->type); - }); + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); } void confirm_ack (nano::confirm_ack const & message_a) override { - connection->finish_request_async (); - auto connection_l (connection->shared_from_this ()); - connection->node->background ([connection_l, message_a]() { - connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, connection_l->remote_node_id, connection_l->socket, connection_l->type); - }); + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); } void bulk_pull (nano::bulk_pull const &) override { @@ -563,6 +629,14 @@ class request_response_visitor : public nano::message_visitor auto response (std::make_shared (connection, std::unique_ptr (static_cast (connection->requests.front ().release ())))); response->send_next (); } + void telemetry_req (nano::telemetry_req const & message_a) override + { + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); + } void node_id_handshake (nano::node_id_handshake const & message_a) override { if (connection->node->config.logging.network_node_id_handshake_logging ()) @@ -572,12 +646,11 @@ class request_response_visitor : public nano::message_visitor if (message_a.query) { boost::optional> response (std::make_pair (connection->node->node_id.pub, nano::sign_message (connection->node->node_id.prv, connection->node->node_id.pub, *message_a.query))); - assert (!nano::validate_message (response->first, *message_a.query, response->second)); + debug_assert (!nano::validate_message (response->first, *message_a.query, response->second)); auto cookie (connection->node->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (connection->remote_endpoint))); nano::node_id_handshake response_message (cookie, response); - auto shared_const_buffer = response_message.to_shared_const_buffer (); - // clang-format off - connection->socket->async_write (shared_const_buffer, [connection = std::weak_ptr (connection) ](boost::system::error_code const & ec, size_t size_a) { + auto shared_const_buffer = response_message.to_shared_const_buffer (connection->node->ledger.cache.epoch_2_started); + connection->socket->async_write (shared_const_buffer, [connection = std::weak_ptr (connection)](boost::system::error_code const & ec, size_t size_a) { if (auto connection_l = connection.lock ()) { if (ec) @@ -596,7 +669,6 @@ class request_response_visitor : public nano::message_visitor } } }); - // clang-format on } else if (message_a.response) { @@ -620,21 +692,43 @@ class request_response_visitor : public nano::message_visitor } nano::account node_id (connection->remote_node_id); nano::bootstrap_server_type type (connection->type); - assert (node_id.is_zero () || type == nano::bootstrap_server_type::realtime); - auto connection_l (connection->shared_from_this ()); - connection->node->background ([connection_l, message_a, node_id, type]() { - connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, node_id, connection_l->socket, type); - }); + debug_assert (node_id.is_zero () || type == nano::bootstrap_server_type::realtime); + connection->node->network.tcp_message_manager.put_message (nano::tcp_message_item{ std::make_shared (message_a), connection->remote_endpoint, connection->remote_node_id, connection->socket, connection->type }); } std::shared_ptr connection; }; } -void nano::bootstrap_server::run_next () +void nano::bootstrap_server::run_next (nano::unique_lock & lock_a) { - assert (!requests.empty ()); + debug_assert (!requests.empty ()); request_response_visitor visitor (shared_from_this ()); - requests.front ()->visit (visitor); + auto type (requests.front ()->header.type); + if (type == nano::message_type::bulk_pull || type == nano::message_type::bulk_pull_account || type == nano::message_type::bulk_push || type == nano::message_type::frontier_req || type == nano::message_type::node_id_handshake) + { + // Bootstrap & node ID (realtime start) + // Request removed from queue in request_response_visitor. For bootstrap with requests.front ().release (), for node ID with finish_request () + requests.front ()->visit (visitor); + } + else + { + // Realtime + auto request (std::move (requests.front ())); + requests.pop (); + auto timeout_check (requests.empty ()); + lock_a.unlock (); + request->visit (visitor); + if (timeout_check) + { + std::weak_ptr this_w (shared_from_this ()); + node->alarm.add (std::chrono::steady_clock::now () + (node->config.tcp_io_timeout * 2) + std::chrono::seconds (1), [this_w]() { + if (auto this_l = this_w.lock ()) + { + this_l->timeout (); + } + }); + } + } } bool nano::bootstrap_server::is_bootstrap_connection () diff --git a/nano/node/bootstrap/bootstrap_server.hpp b/nano/node/bootstrap/bootstrap_server.hpp index 03a50dd195..699efb887a 100644 --- a/nano/node/bootstrap/bootstrap_server.hpp +++ b/nano/node/bootstrap/bootstrap_server.hpp @@ -23,7 +23,7 @@ class bootstrap_listener final nano::tcp_endpoint endpoint (); nano::node & node; std::shared_ptr listening_socket; - bool on; + bool on{ false }; std::atomic bootstrap_count{ 0 }; std::atomic realtime_count{ 0 }; @@ -31,7 +31,7 @@ class bootstrap_listener final uint16_t port; }; -std::unique_ptr collect_seq_con_info (bootstrap_listener & bootstrap_listener, const std::string & name); +std::unique_ptr collect_container_info (bootstrap_listener & bootstrap_listener, const std::string & name); class message; enum class bootstrap_server_type @@ -57,11 +57,12 @@ class bootstrap_server final : public std::enable_shared_from_this); void finish_request (); void finish_request_async (); void timeout (); - void run_next (); + void run_next (nano::unique_lock & lock_a); bool is_bootstrap_connection (); bool is_realtime_connection (); std::shared_ptr> receive_buffer; @@ -74,5 +75,6 @@ class bootstrap_server final : public std::enable_shared_from_this #include #include #include #include #include +#include + namespace { void reset_confirmation_heights (nano::block_store & store); @@ -29,6 +30,8 @@ std::string nano::error_cli_messages::message (int ev) const return "Config file read error"; case nano::error_cli::disable_all_network: return "Flags --disable_tcp_realtime and --disable_udp cannot be used together"; + case nano::error_cli::ambiguous_udp_options: + return "Flags --disable_udp and --enable_udp cannot be used together"; } return "Invalid error code"; @@ -50,6 +53,7 @@ void nano::add_node_options (boost::program_options::options_description & descr ("peer_clear", "Clear online peers database dump") ("unchecked_clear", "Clear unchecked blocks") ("confirmation_height_clear", "Clear confirmation height") + ("rebuild_database", "Rebuild LMDB database with vacuum for best compaction") ("diagnostics", "Run internal diagnostics") ("generate_config", boost::program_options::value (), "Write configuration to stdout, populated with defaults suitable for this system. Pass the configuration type node or rpc. See also use_defaults.") ("key_create", "Generates a adhoc random keypair and prints it to stdout") @@ -86,14 +90,21 @@ void nano::add_node_flag_options (boost::program_options::options_description & ("disable_wallet_bootstrap", "Disables wallet lazy bootstrap") ("disable_bootstrap_listener", "Disables bootstrap processing for TCP listener (not including realtime network TCP connections)") ("disable_tcp_realtime", "Disables TCP realtime network") - ("disable_udp", "Disables UDP realtime network") + ("disable_udp", "(Deprecated) UDP is disabled by default") + ("enable_udp", "Enables UDP realtime network") ("disable_unchecked_cleanup", "Disables periodic cleanup of old records from unchecked table") ("disable_unchecked_drop", "Disables drop of unchecked table at startup") + ("disable_providing_telemetry_metrics", "Disable using any node information in the telemetry_ack messages.") + ("disable_block_processor_unchecked_deletion", "Disable deletion of unchecked blocks after processing") + ("allow_bootstrap_peers_duplicates", "Allow multiple connections to same peer in bootstrap attempts") ("fast_bootstrap", "Increase bootstrap speed for high end nodes with higher limits") - ("batch_size", boost::program_options::value(), "Increase sideband batch size, default 512") + ("batch_size", boost::program_options::value(), "(Deprecated) Increase sideband batch size, default 512. This change only affects nodes upgrading from v17 (or earlier) of the node.") ("block_processor_batch_size", boost::program_options::value(), "Increase block processor transaction batch write size, default 0 (limited by config block_processor_batch_max_time), 256k for fast_bootstrap") ("block_processor_full_size", boost::program_options::value(), "Increase block processor allowed blocks queue size before dropping live network packets and holding bootstrap download, default 65536, 1 million for fast_bootstrap") - ("block_processor_verification_size", boost::program_options::value(), "Increase batch signature verification size in block processor, default 0 (limited by config signature_checker_threads), unlimited for fast_bootstrap"); + ("block_processor_verification_size", boost::program_options::value(), "Increase batch signature verification size in block processor, default 0 (limited by config signature_checker_threads), unlimited for fast_bootstrap") + ("inactive_votes_cache_size", boost::program_options::value(), "Increase cached votes without active elections size, default 16384") + ("vote_processor_capacity", boost::program_options::value(), "Vote processor queue size before dropping votes, default 144k") + ; // clang-format on } @@ -109,18 +120,29 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o flags_a.disable_lazy_bootstrap = (vm.count ("disable_lazy_bootstrap") > 0); flags_a.disable_legacy_bootstrap = (vm.count ("disable_legacy_bootstrap") > 0); flags_a.disable_wallet_bootstrap = (vm.count ("disable_wallet_bootstrap") > 0); - flags_a.disable_bootstrap_listener = (vm.count ("disable_bootstrap_listener") > 0); - flags_a.disable_tcp_realtime = (vm.count ("disable_tcp_realtime") > 0); - flags_a.disable_udp = (vm.count ("disable_udp") > 0); + if (!flags_a.inactive_node) + { + flags_a.disable_bootstrap_listener = (vm.count ("disable_bootstrap_listener") > 0); + flags_a.disable_tcp_realtime = (vm.count ("disable_tcp_realtime") > 0); + } + flags_a.disable_providing_telemetry_metrics = (vm.count ("disable_providing_telemetry_metrics") > 0); + if ((vm.count ("disable_udp") > 0) && (vm.count ("enable_udp") > 0)) + { + ec = nano::error_cli::ambiguous_udp_options; + } + flags_a.disable_udp = (vm.count ("enable_udp") == 0); if (flags_a.disable_tcp_realtime && flags_a.disable_udp) { ec = nano::error_cli::disable_all_network; } flags_a.disable_unchecked_cleanup = (vm.count ("disable_unchecked_cleanup") > 0); flags_a.disable_unchecked_drop = (vm.count ("disable_unchecked_drop") > 0); + flags_a.disable_block_processor_unchecked_deletion = (vm.count ("disable_block_processor_unchecked_deletion") > 0); + flags_a.allow_bootstrap_peers_duplicates = (vm.count ("allow_bootstrap_peers_duplicates") > 0); flags_a.fast_bootstrap = (vm.count ("fast_bootstrap") > 0); if (flags_a.fast_bootstrap) { + flags_a.disable_block_processor_unchecked_deletion = true; flags_a.block_processor_batch_size = 256 * 1024; flags_a.block_processor_full_size = 1024 * 1024; flags_a.block_processor_verification_size = std::numeric_limits::max (); @@ -140,6 +162,22 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o { flags_a.block_processor_verification_size = block_processor_verification_size_it->second.as (); } + auto inactive_votes_cache_size_it = vm.find ("inactive_votes_cache_size"); + if (inactive_votes_cache_size_it != vm.end ()) + { + flags_a.inactive_votes_cache_size = inactive_votes_cache_size_it->second.as (); + } + auto vote_processor_capacity_it = vm.find ("vote_processor_capacity"); + if (vote_processor_capacity_it != vm.end ()) + { + flags_a.vote_processor_capacity = vote_processor_capacity_it->second.as (); + } + // Config overriding + auto config (vm.find ("config")); + if (config != vm.end ()) + { + flags_a.config_overrides = config->second.as> (); + } return ec; } @@ -151,14 +189,15 @@ void database_write_lock_error (std::error_code & ec) ec = nano::error_cli::database_write_error; } -bool copy_database (boost::filesystem::path const & data_path, boost::program_options::variables_map & vm, boost::filesystem::path const & output_path, std::error_code & ec) +bool copy_database (boost::filesystem::path const & data_path, boost::program_options::variables_map const & vm, boost::filesystem::path const & output_path, std::error_code & ec) { bool success = false; - bool needs_to_write = vm.count ("unchecked_clear") || vm.count ("clear_send_ids") || vm.count ("online_weight_clear") || vm.count ("peer_clear") || vm.count ("confirmation_height_clear"); + bool needs_to_write = vm.count ("unchecked_clear") || vm.count ("clear_send_ids") || vm.count ("online_weight_clear") || vm.count ("peer_clear") || vm.count ("confirmation_height_clear") || vm.count ("rebuild_database"); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = !needs_to_write; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { if (vm.count ("unchecked_clear")) @@ -185,6 +224,11 @@ bool copy_database (boost::filesystem::path const & data_path, boost::program_op { reset_confirmation_heights (node.node->store); } + if (vm.count ("rebuild_database")) + { + auto transaction (node.node->store.tx_begin_write ()); + node.node->store.rebuild_db (transaction); + } success = node.node->copy_with_compaction (output_path); } @@ -196,7 +240,7 @@ bool copy_database (boost::filesystem::path const & data_path, boost::program_op } } -std::error_code nano::handle_node_options (boost::program_options::variables_map & vm) +std::error_code nano::handle_node_options (boost::program_options::variables_map const & vm) { std::error_code ec; boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); @@ -213,8 +257,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { password = vm["password"].as (); } - inactive_node node (data_path); - auto wallet (node.node->wallets.open (wallet_id)); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto wallet (inactive_node->node->wallets.open (wallet_id)); if (wallet != nullptr) { auto transaction (wallet->wallets.tx_begin_write ()); @@ -398,7 +442,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { auto transaction (node.node->store.tx_begin_write ()); @@ -415,7 +460,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { auto transaction (node.node->wallets.tx_begin_write ()); @@ -432,7 +478,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { auto transaction (node.node->store.tx_begin_write ()); @@ -449,7 +496,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { auto transaction (node.node->store.tx_begin_write ()); @@ -466,7 +514,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map boost::filesystem::path data_path = vm.count ("data_path") ? boost::filesystem::path (vm["data_path"].as ()) : nano::working_path (); auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node node (data_path, 24000, node_flags); + nano::update_flags (node_flags, vm); + nano::inactive_node node (data_path, node_flags); if (!node.node->init_error ()) { auto account_it = vm.find ("account"); @@ -476,20 +525,20 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::account account; if (!account.decode_account (account_str)) { - uint64_t confirmation_height; + nano::confirmation_height_info confirmation_height_info; auto transaction (node.node->store.tx_begin_read ()); - if (!node.node->store.confirmation_height_get (transaction, account, confirmation_height)) + if (!node.node->store.confirmation_height_get (transaction, account, confirmation_height_info)) { auto transaction (node.node->store.tx_begin_write ()); auto conf_height_reset_num = 0; if (account == node.node->network_params.ledger.genesis_account) { conf_height_reset_num = 1; - node.node->store.confirmation_height_put (transaction, account, confirmation_height); + node.node->store.confirmation_height_put (transaction, account, { confirmation_height_info.height, node.node->network_params.ledger.genesis_block }); } else { - node.node->store.confirmation_height_clear (transaction, account, confirmation_height); + node.node->store.confirmation_height_clear (transaction, account, confirmation_height_info.height); } std::cout << "Confirmation height of account " << account_str << " is set to " << conf_height_reset_num << std::endl; @@ -531,7 +580,7 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map else if (type == "rpc") { valid_type = true; - nano::rpc_config config (false); + nano::rpc_config config; config.serialize_toml (toml); } else @@ -560,7 +609,7 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map } else if (vm.count ("diagnostics")) { - inactive_node node (data_path); + auto inactive_node = nano::default_inactive_node (data_path, vm); std::cout << "Testing hash function" << std::endl; nano::raw_key key; key.data.clear (); @@ -579,7 +628,7 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map environment.dump (std::cout); std::stringstream stream; environment.dump (stream); - node.node->logger.always_log (stream.str ()); + inactive_node->node->logger.always_log (stream.str ()); } else { @@ -623,8 +672,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { password = vm["password"].as (); } - inactive_node node (data_path); - auto wallet (node.node->wallets.open (wallet_id)); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto wallet (inactive_node->node->wallets.open (wallet_id)); if (wallet != nullptr) { auto transaction (wallet->wallets.tx_begin_write ()); @@ -677,8 +726,8 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { password = vm["password"].as (); } - inactive_node node (data_path); - auto wallet (node.node->wallets.open (wallet_id)); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto wallet (inactive_node->node->wallets.open (wallet_id)); if (wallet != nullptr) { auto transaction (wallet->wallets.tx_begin_write ()); @@ -759,9 +808,9 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map } if (!ec) { - inactive_node node (data_path); + auto inactive_node = nano::default_inactive_node (data_path, vm); auto wallet_key = nano::random_wallet_id (); - auto wallet (node.node->wallets.create (wallet_key)); + auto wallet (inactive_node->node->wallets.create (wallet_key)); if (wallet != nullptr) { if (vm.count ("password") > 0) @@ -801,9 +850,10 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::wallet_id wallet_id; if (!wallet_id.decode_hex (vm["wallet"].as ())) { - inactive_node node (data_path); - auto existing (node.node->wallets.items.find (wallet_id)); - if (existing != node.node->wallets.items.end ()) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto existing (inactive_node->node->wallets.items.find (wallet_id)); + if (existing != inactive_node->node->wallets.items.end ()) { auto transaction (existing->second->wallets.tx_begin_write ()); if (!existing->second->enter_password (transaction, password)) @@ -817,7 +867,7 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::raw_key key; auto error (existing->second->store.fetch (transaction, account, key)); (void)error; - assert (!error); + debug_assert (!error); std::cout << boost::str (boost::format ("Pub: %1% Prv: %2%\n") % account.to_account () % key.data.to_string ()); if (nano::pub_key (key.as_private_key ()) != account) { @@ -856,10 +906,11 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::wallet_id wallet_id; if (!wallet_id.decode_hex (vm["wallet"].as ())) { - inactive_node node (data_path); - if (node.node->wallets.items.find (wallet_id) != node.node->wallets.items.end ()) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + if (node->wallets.items.find (wallet_id) != node->wallets.items.end ()) { - node.node->wallets.destroy (wallet_id); + node->wallets.destroy (wallet_id); } else { @@ -905,13 +956,14 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::wallet_id wallet_id; if (!wallet_id.decode_hex (vm["wallet"].as ())) { - inactive_node node (data_path); - auto existing (node.node->wallets.items.find (wallet_id)); - if (existing != node.node->wallets.items.end ()) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto existing (node->wallets.items.find (wallet_id)); + if (existing != node->wallets.items.end ()) { bool valid (false); { - auto transaction (node.node->wallets.tx_begin_write ()); + auto transaction (node->wallets.tx_begin_write ()); valid = existing->second->store.valid_password (transaction); if (!valid) { @@ -947,9 +999,9 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { bool error (true); { - nano::lock_guard lock (node.node->wallets.mutex); - auto transaction (node.node->wallets.tx_begin_write ()); - nano::wallet wallet (error, transaction, node.node->wallets, wallet_id.to_string (), contents.str ()); + nano::lock_guard lock (node->wallets.mutex); + auto transaction (node->wallets.tx_begin_write ()); + nano::wallet wallet (error, transaction, node->wallets, wallet_id.to_string (), contents.str ()); } if (error) { @@ -958,9 +1010,9 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map } else { - node.node->wallets.reload (); - nano::lock_guard lock (node.node->wallets.mutex); - release_assert (node.node->wallets.items.find (wallet_id) != node.node->wallets.items.end ()); + node->wallets.reload (); + nano::lock_guard lock (node->wallets.mutex); + release_assert (node->wallets.items.find (wallet_id) != node->wallets.items.end ()); std::cout << "Import completed\n"; } } @@ -992,8 +1044,9 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map } else if (vm.count ("wallet_list")) { - inactive_node node (data_path); - for (auto i (node.node->wallets.items.begin ()), n (node.node->wallets.items.end ()); i != n; ++i) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + for (auto i (node->wallets.items.begin ()), n (node->wallets.items.end ()); i != n; ++i) { std::cout << boost::str (boost::format ("Wallet ID: %1%\n") % i->first.to_string ()); auto transaction (i->second->wallets.tx_begin_read ()); @@ -1007,12 +1060,13 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { if (vm.count ("wallet") == 1 && vm.count ("account") == 1) { - inactive_node node (data_path); + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; nano::wallet_id wallet_id; if (!wallet_id.decode_hex (vm["wallet"].as ())) { - auto wallet (node.node->wallets.items.find (wallet_id)); - if (wallet != node.node->wallets.items.end ()) + auto wallet (node->wallets.items.find (wallet_id)); + if (wallet != node->wallets.items.end ()) { nano::account account_id; if (!account_id.decode_account (vm["account"].as ())) @@ -1060,9 +1114,10 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::wallet_id wallet_id; if (!wallet_id.decode_hex (vm["wallet"].as ())) { - inactive_node node (data_path); - auto wallet (node.node->wallets.items.find (wallet_id)); - if (wallet != node.node->wallets.items.end ()) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto wallet (node->wallets.items.find (wallet_id)); + if (wallet != node->wallets.items.end ()) { auto transaction (wallet->second->wallets.tx_begin_read ()); auto representative (wallet->second->store.representative (transaction)); @@ -1098,9 +1153,10 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map nano::account account; if (!account.decode_account (vm["account"].as ())) { - inactive_node node (data_path); - auto wallet (node.node->wallets.items.find (wallet_id)); - if (wallet != node.node->wallets.items.end ()) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto wallet (node->wallets.items.find (wallet_id)); + if (wallet != node->wallets.items.end ()) { auto transaction (wallet->second->wallets.tx_begin_write ()); wallet->second->store.representative_set (transaction, account); @@ -1137,9 +1193,10 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map } else if (vm.count ("vote_dump") == 1) { - inactive_node node (data_path); - auto transaction (node.node->store.tx_begin_read ()); - for (auto i (node.node->store.vote_begin (transaction)), n (node.node->store.vote_end ()); i != n; ++i) + auto inactive_node = nano::default_inactive_node (data_path, vm); + auto node = inactive_node->node; + auto transaction (node->store.tx_begin_read ()); + for (auto i (node->store.vote_begin (transaction)), n (node->store.vote_end ()); i != n; ++i) { auto const & vote (i->second); std::cerr << boost::str (boost::format ("%1%\n") % vote->to_json ()); @@ -1153,6 +1210,13 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map return ec; } +std::unique_ptr nano::default_inactive_node (boost::filesystem::path const & path_a, boost::program_options::variables_map const & vm_a) +{ + auto node_flags = nano::inactive_node_flag_defaults (); + nano::update_flags (node_flags, vm_a); + return std::make_unique (path_a, node_flags); +} + namespace { void reset_confirmation_heights (nano::block_store & store) @@ -1163,7 +1227,7 @@ void reset_confirmation_heights (nano::block_store & store) // Then make sure the confirmation height of the genesis account open block is 1 nano::network_params network_params; - store.confirmation_height_put (transaction, network_params.ledger.genesis_account, 1); + store.confirmation_height_put (transaction, network_params.ledger.genesis_account, { 1, network_params.ledger.genesis_hash }); } bool is_using_rocksdb (boost::filesystem::path const & data_path, std::error_code & ec) diff --git a/nano/node/cli.hpp b/nano/node/cli.hpp index 484ab7129b..a2107e0b19 100644 --- a/nano/node/cli.hpp +++ b/nano/node/cli.hpp @@ -16,13 +16,14 @@ enum class error_cli unknown_command = 4, database_write_error = 5, reading_config = 6, - disable_all_network = 7 + disable_all_network = 7, + ambiguous_udp_options = 8 }; void add_node_options (boost::program_options::options_description &); void add_node_flag_options (boost::program_options::options_description &); std::error_code update_flags (nano::node_flags &, boost::program_options::variables_map const &); -std::error_code handle_node_options (boost::program_options::variables_map &); +std::error_code handle_node_options (boost::program_options::variables_map const &); } REGISTER_ERROR_CODES (nano, error_cli) diff --git a/nano/node/common.cpp b/nano/node/common.cpp index 9d2d5f9827..3a4a7a6bce 100644 --- a/nano/node/common.cpp +++ b/nano/node/common.cpp @@ -4,12 +4,22 @@ #include #include #include +#include #include #include +#include + +#include std::bitset<16> constexpr nano::message_header::block_type_mask; std::bitset<16> constexpr nano::message_header::count_mask; +std::bitset<16> constexpr nano::message_header::telemetry_size_mask; + +std::chrono::seconds constexpr nano::telemetry_cache_cutoffs::test; +std::chrono::seconds constexpr nano::telemetry_cache_cutoffs::beta; +std::chrono::seconds constexpr nano::telemetry_cache_cutoffs::live; + namespace { nano::protocol_constants const & get_protocol_constants () @@ -18,10 +28,29 @@ nano::protocol_constants const & get_protocol_constants () return params.protocol; } } + +uint64_t nano::ip_address_hash_raw (boost::asio::ip::address const & ip_a, uint16_t port) +{ + static nano::random_constants constants; + debug_assert (ip_a.is_v6 ()); + uint64_t result; + nano::uint128_union address; + address.bytes = ip_a.to_v6 ().to_bytes (); + blake2b_state state; + blake2b_init (&state, sizeof (result)); + blake2b_update (&state, constants.random_128.bytes.data (), constants.random_128.bytes.size ()); + if (port != 0) + { + blake2b_update (&state, &port, sizeof (port)); + } + blake2b_update (&state, address.bytes.data (), address.bytes.size ()); + blake2b_final (&state, &result, sizeof (result)); + return result; +} + nano::message_header::message_header (nano::message_type type_a) : version_max (get_protocol_constants ().protocol_version), version_using (get_protocol_constants ().protocol_version), -version_min (get_protocol_constants ().protocol_version_min), type (type_a) { } @@ -34,13 +63,13 @@ nano::message_header::message_header (bool & error_a, nano::stream & stream_a) } } -void nano::message_header::serialize (nano::stream & stream_a) const +void nano::message_header::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { static nano::network_params network_params; nano::write (stream_a, network_params.header_magic_number); nano::write (stream_a, version_max); nano::write (stream_a, version_using); - nano::write (stream_a, version_min); + nano::write (stream_a, get_protocol_constants ().protocol_version_min (use_epoch_2_min_version_a)); nano::write (stream_a, type); nano::write (stream_a, static_cast (extensions.to_ullong ())); } @@ -61,7 +90,7 @@ bool nano::message_header::deserialize (nano::stream & stream_a) nano::read (stream_a, version_max); nano::read (stream_a, version_using); - nano::read (stream_a, version_min); + nano::read (stream_a, version_min_m); nano::read (stream_a, type); nano::read (stream_a, extensions_l); extensions = extensions_l; @@ -74,6 +103,12 @@ bool nano::message_header::deserialize (nano::stream & stream_a) return error; } +uint8_t nano::message_header::version_min () const +{ + debug_assert (version_min_m != std::numeric_limits::max ()); + return version_min_m; +} + nano::message::message (nano::message_type type_a) : header (type_a) { @@ -84,6 +119,19 @@ header (header_a) { } +std::shared_ptr> nano::message::to_bytes (bool use_epoch_2_min_version_a) const +{ + auto bytes = std::make_shared> (); + nano::vectorstream stream (*bytes); + serialize (stream, use_epoch_2_min_version_a); + return bytes; +} + +nano::shared_const_buffer nano::message::to_shared_const_buffer (bool use_epoch_2_min_version_a) const +{ + return shared_const_buffer (to_bytes (use_epoch_2_min_version_a)); +} + nano::block_type nano::message_header::block_type () const { return static_cast (((extensions & block_type_mask) >> 8).to_ullong ()); @@ -102,7 +150,7 @@ uint8_t nano::message_header::count_get () const void nano::message_header::count_set (uint8_t count_a) { - assert (count_a < 16); + debug_assert (count_a < 16); extensions &= ~count_mask; extensions |= std::bitset<16> (static_cast (count_a) << 12); } @@ -110,7 +158,7 @@ void nano::message_header::count_set (uint8_t count_a) void nano::message_header::flag_set (uint8_t flag_a) { // Flags from 8 are block_type & count - assert (flag_a < 8); + debug_assert (flag_a < 8); extensions.set (flag_a, true); } @@ -162,8 +210,9 @@ size_t nano::message_header::payload_length_bytes () const return nano::bulk_pull::size + (bulk_pull_is_count_present () ? nano::bulk_pull::extended_parameters_size : 0); } case nano::message_type::bulk_push: + case nano::message_type::telemetry_req: { - // bulk_push doesn't have a payload + // These don't have a payload return 0; } case nano::message_type::frontier_req: @@ -194,9 +243,13 @@ size_t nano::message_header::payload_length_bytes () const { return nano::node_id_handshake::size (*this); } + case nano::message_type::telemetry_ack: + { + return nano::telemetry_ack::size (*this); + } default: { - assert (false); + debug_assert (false); return 0; } } @@ -245,6 +298,14 @@ std::string nano::message_parser::status_string () { return "invalid_node_id_handshake_message"; } + case nano::message_parser::parse_status::invalid_telemetry_req_message: + { + return "invalid_telemetry_req_message"; + } + case nano::message_parser::parse_status::invalid_telemetry_ack_message: + { + return "invalid_telemetry_ack_message"; + } case nano::message_parser::parse_status::outdated_version: { return "outdated_version"; @@ -257,19 +318,25 @@ std::string nano::message_parser::status_string () { return "invalid_network"; } + case nano::message_parser::parse_status::duplicate_publish_message: + { + return "duplicate_publish_message"; + } } - assert (false); + debug_assert (false); return "[unknown parse_status]"; } -nano::message_parser::message_parser (nano::block_uniquer & block_uniquer_a, nano::vote_uniquer & vote_uniquer_a, nano::message_visitor & visitor_a, nano::work_pool & pool_a) : +nano::message_parser::message_parser (nano::network_filter & publish_filter_a, nano::block_uniquer & block_uniquer_a, nano::vote_uniquer & vote_uniquer_a, nano::message_visitor & visitor_a, nano::work_pool & pool_a, bool use_epoch_2_min_version_a) : +publish_filter (publish_filter_a), block_uniquer (block_uniquer_a), vote_uniquer (vote_uniquer_a), visitor (visitor_a), pool (pool_a), -status (parse_status::success) +status (parse_status::success), +use_epoch_2_min_version (use_epoch_2_min_version_a) { } @@ -285,7 +352,7 @@ void nano::message_parser::deserialize_buffer (uint8_t const * buffer_a, size_t nano::message_header header (error, stream); if (!error) { - if (header.version_using < get_protocol_constants ().protocol_version_min) + if (header.version_using < get_protocol_constants ().protocol_version_min (use_epoch_2_min_version)) { status = parse_status::outdated_version; } @@ -300,7 +367,15 @@ void nano::message_parser::deserialize_buffer (uint8_t const * buffer_a, size_t } case nano::message_type::publish: { - deserialize_publish (stream, header); + nano::uint128_t digest; + if (!publish_filter.apply (buffer_a + header.size, size_a - header.size, &digest)) + { + deserialize_publish (stream, header, digest); + } + else + { + status = parse_status::duplicate_publish_message; + } break; } case nano::message_type::confirm_req: @@ -318,6 +393,16 @@ void nano::message_parser::deserialize_buffer (uint8_t const * buffer_a, size_t deserialize_node_id_handshake (stream, header); break; } + case nano::message_type::telemetry_req: + { + deserialize_telemetry_req (stream, header); + break; + } + case nano::message_type::telemetry_ack: + { + deserialize_telemetry_ack (stream, header); + break; + } default: { status = parse_status::invalid_message_type; @@ -347,13 +432,13 @@ void nano::message_parser::deserialize_keepalive (nano::stream & stream_a, nano: } } -void nano::message_parser::deserialize_publish (nano::stream & stream_a, nano::message_header const & header_a) +void nano::message_parser::deserialize_publish (nano::stream & stream_a, nano::message_header const & header_a, nano::uint128_t const & digest_a) { auto error (false); - nano::publish incoming (error, stream_a, header_a, &block_uniquer); + nano::publish incoming (error, stream_a, header_a, digest_a, &block_uniquer); if (!error && at_end (stream_a)) { - if (!nano::work_validate (*incoming.block)) + if (!nano::work_validate_entry (*incoming.block)) { visitor.publish (incoming); } @@ -374,7 +459,7 @@ void nano::message_parser::deserialize_confirm_req (nano::stream & stream_a, nan nano::confirm_req incoming (error, stream_a, header_a, &block_uniquer); if (!error && at_end (stream_a)) { - if (incoming.block == nullptr || !nano::work_validate (*incoming.block)) + if (incoming.block == nullptr || !nano::work_validate_entry (*incoming.block)) { visitor.confirm_req (incoming); } @@ -400,7 +485,7 @@ void nano::message_parser::deserialize_confirm_ack (nano::stream & stream_a, nan if (!vote_block.which ()) { auto block (boost::get> (vote_block)); - if (nano::work_validate (*block)) + if (nano::work_validate_entry (*block)) { status = parse_status::insufficient_work; break; @@ -432,6 +517,34 @@ void nano::message_parser::deserialize_node_id_handshake (nano::stream & stream_ } } +void nano::message_parser::deserialize_telemetry_req (nano::stream & stream_a, nano::message_header const & header_a) +{ + nano::telemetry_req incoming (header_a); + if (at_end (stream_a)) + { + visitor.telemetry_req (incoming); + } + else + { + status = parse_status::invalid_telemetry_req_message; + } +} + +void nano::message_parser::deserialize_telemetry_ack (nano::stream & stream_a, nano::message_header const & header_a) +{ + bool error_l (false); + nano::telemetry_ack incoming (error_l, stream_a, header_a); + // Intentionally not checking if at the end of stream, because these messages support backwards/forwards compatibility + if (!error_l) + { + visitor.telemetry_ack (incoming); + } + else + { + status = parse_status::invalid_telemetry_ack_message; + } +} + bool nano::message_parser::at_end (nano::stream & stream_a) { uint8_t junk; @@ -463,12 +576,12 @@ void nano::keepalive::visit (nano::message_visitor & visitor_a) const visitor_a.keepalive (*this); } -void nano::keepalive::serialize (nano::stream & stream_a) const +void nano::keepalive::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); for (auto i (peers.begin ()), j (peers.end ()); i != j; ++i) { - assert (i->address ().is_v6 ()); + debug_assert (i->address ().is_v6 ()); auto bytes (i->address ().to_v6 ().to_bytes ()); write (stream_a, bytes); write (stream_a, i->port ()); @@ -477,7 +590,7 @@ void nano::keepalive::serialize (nano::stream & stream_a) const bool nano::keepalive::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::keepalive); + debug_assert (header.type == nano::message_type::keepalive); auto error (false); for (auto i (peers.begin ()), j (peers.end ()); i != j && !error; ++i) { @@ -500,8 +613,9 @@ bool nano::keepalive::operator== (nano::keepalive const & other_a) const return peers == other_a.peers; } -nano::publish::publish (bool & error_a, nano::stream & stream_a, nano::message_header const & header_a, nano::block_uniquer * uniquer_a) : -message (header_a) +nano::publish::publish (bool & error_a, nano::stream & stream_a, nano::message_header const & header_a, nano::uint128_t const & digest_a, nano::block_uniquer * uniquer_a) : +message (header_a), +digest (digest_a) { if (!error_a) { @@ -516,16 +630,16 @@ block (block_a) header.block_type_set (block->type ()); } -void nano::publish::serialize (nano::stream & stream_a) const +void nano::publish::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - assert (block != nullptr); - header.serialize (stream_a); + debug_assert (block != nullptr); + header.serialize (stream_a, use_epoch_2_min_version_a); block->serialize (stream_a); } bool nano::publish::deserialize (nano::stream & stream_a, nano::block_uniquer * uniquer_a) { - assert (header.type == nano::message_type::publish); + debug_assert (header.type == nano::message_type::publish); block = nano::deserialize_block (stream_a, header.block_type (), uniquer_a); auto result (block == nullptr); return result; @@ -563,7 +677,7 @@ roots_hashes (roots_hashes_a) { // not_a_block (1) block type for hashes + roots request header.block_type_set (nano::block_type::not_a_block); - assert (roots_hashes.size () < 16); + debug_assert (roots_hashes.size () < 16); header.count_set (static_cast (roots_hashes.size ())); } @@ -571,10 +685,10 @@ nano::confirm_req::confirm_req (nano::block_hash const & hash_a, nano::root cons message (nano::message_type::confirm_req), roots_hashes (std::vector> (1, std::make_pair (hash_a, root_a))) { - assert (!roots_hashes.empty ()); + debug_assert (!roots_hashes.empty ()); // not_a_block (1) block type for hashes + roots request header.block_type_set (nano::block_type::not_a_block); - assert (roots_hashes.size () < 16); + debug_assert (roots_hashes.size () < 16); header.count_set (static_cast (roots_hashes.size ())); } @@ -583,12 +697,12 @@ void nano::confirm_req::visit (nano::message_visitor & visitor_a) const visitor_a.confirm_req (*this); } -void nano::confirm_req::serialize (nano::stream & stream_a) const +void nano::confirm_req::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); if (header.block_type () == nano::block_type::not_a_block) { - assert (!roots_hashes.empty ()); + debug_assert (!roots_hashes.empty ()); // Write hashes & roots for (auto & root_hash : roots_hashes) { @@ -598,7 +712,7 @@ void nano::confirm_req::serialize (nano::stream & stream_a) const } else { - assert (block != nullptr); + debug_assert (block != nullptr); block->serialize (stream_a); } } @@ -606,7 +720,7 @@ void nano::confirm_req::serialize (nano::stream & stream_a) const bool nano::confirm_req::deserialize (nano::stream & stream_a, nano::block_uniquer * uniquer_a) { bool result (false); - assert (header.type == nano::message_type::confirm_req); + debug_assert (header.type == nano::message_type::confirm_req); try { if (header.block_type () == nano::block_type::not_a_block) @@ -695,12 +809,12 @@ nano::confirm_ack::confirm_ack (std::shared_ptr vote_a) : message (nano::message_type::confirm_ack), vote (vote_a) { - assert (!vote_a->blocks.empty ()); + debug_assert (!vote_a->blocks.empty ()); auto & first_vote_block (vote_a->blocks[0]); if (first_vote_block.which ()) { header.block_type_set (nano::block_type::not_a_block); - assert (vote_a->blocks.size () < 16); + debug_assert (vote_a->blocks.size () < 16); header.count_set (static_cast (vote_a->blocks.size ())); } else @@ -709,10 +823,10 @@ vote (vote_a) } } -void nano::confirm_ack::serialize (nano::stream & stream_a) const +void nano::confirm_ack::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - assert (header.block_type () == nano::block_type::not_a_block || header.block_type () == nano::block_type::send || header.block_type () == nano::block_type::receive || header.block_type () == nano::block_type::open || header.block_type () == nano::block_type::change || header.block_type () == nano::block_type::state); - header.serialize (stream_a); + debug_assert (header.block_type () == nano::block_type::not_a_block || header.block_type () == nano::block_type::send || header.block_type () == nano::block_type::receive || header.block_type () == nano::block_type::open || header.block_type () == nano::block_type::change || header.block_type () == nano::block_type::state); + header.serialize (stream_a, use_epoch_2_min_version_a); vote->serialize (stream_a, header.block_type ()); } @@ -755,9 +869,9 @@ message (header_a) } } -void nano::frontier_req::serialize (nano::stream & stream_a) const +void nano::frontier_req::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); write (stream_a, start.bytes); write (stream_a, age); write (stream_a, count); @@ -765,7 +879,7 @@ void nano::frontier_req::serialize (nano::stream & stream_a) const bool nano::frontier_req::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::frontier_req); + debug_assert (header.type == nano::message_type::frontier_req); auto error (false); try { @@ -810,7 +924,7 @@ void nano::bulk_pull::visit (nano::message_visitor & visitor_a) const visitor_a.bulk_pull (*this); } -void nano::bulk_pull::serialize (nano::stream & stream_a) const +void nano::bulk_pull::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { /* * Ensure the "count_present" flag is set if there @@ -820,9 +934,9 @@ void nano::bulk_pull::serialize (nano::stream & stream_a) const * and that is the behavior of not having the flag set * so it is wasteful to do this. */ - assert ((count == 0 && !is_count_present ()) || (count != 0 && is_count_present ())); + debug_assert ((count == 0 && !is_count_present ()) || (count != 0 && is_count_present ())); - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); write (stream_a, start); write (stream_a, end); @@ -841,7 +955,7 @@ void nano::bulk_pull::serialize (nano::stream & stream_a) const bool nano::bulk_pull::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::bulk_pull); + debug_assert (header.type == nano::message_type::bulk_pull); auto error (false); try { @@ -906,9 +1020,9 @@ void nano::bulk_pull_account::visit (nano::message_visitor & visitor_a) const visitor_a.bulk_pull_account (*this); } -void nano::bulk_pull_account::serialize (nano::stream & stream_a) const +void nano::bulk_pull_account::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); write (stream_a, account); write (stream_a, minimum_amount); write (stream_a, flags); @@ -916,7 +1030,7 @@ void nano::bulk_pull_account::serialize (nano::stream & stream_a) const bool nano::bulk_pull_account::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::bulk_pull_account); + debug_assert (header.type == nano::message_type::bulk_pull_account); auto error (false); try { @@ -944,13 +1058,13 @@ message (header_a) bool nano::bulk_push::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::bulk_push); + debug_assert (header.type == nano::message_type::bulk_push); return false; } -void nano::bulk_push::serialize (nano::stream & stream_a) const +void nano::bulk_push::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); } void nano::bulk_push::visit (nano::message_visitor & visitor_a) const @@ -958,6 +1072,280 @@ void nano::bulk_push::visit (nano::message_visitor & visitor_a) const visitor_a.bulk_push (*this); } +nano::telemetry_req::telemetry_req () : +message (nano::message_type::telemetry_req) +{ +} + +nano::telemetry_req::telemetry_req (nano::message_header const & header_a) : +message (header_a) +{ +} + +bool nano::telemetry_req::deserialize (nano::stream & stream_a) +{ + debug_assert (header.type == nano::message_type::telemetry_req); + return false; +} + +void nano::telemetry_req::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const +{ + header.serialize (stream_a, use_epoch_2_min_version_a); +} + +void nano::telemetry_req::visit (nano::message_visitor & visitor_a) const +{ + visitor_a.telemetry_req (*this); +} + +nano::telemetry_ack::telemetry_ack () : +message (nano::message_type::telemetry_ack) +{ +} + +nano::telemetry_ack::telemetry_ack (bool & error_a, nano::stream & stream_a, nano::message_header const & message_header) : +message (message_header) +{ + if (!error_a) + { + error_a = deserialize (stream_a); + } +} + +nano::telemetry_ack::telemetry_ack (nano::telemetry_data const & telemetry_data_a) : +message (nano::message_type::telemetry_ack), +data (telemetry_data_a) +{ + debug_assert (telemetry_data::size < 2048); // Maximum size the mask allows + header.extensions &= ~message_header::telemetry_size_mask; + header.extensions |= std::bitset<16> (static_cast (telemetry_data::size)); +} + +void nano::telemetry_ack::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const +{ + header.serialize (stream_a, use_epoch_2_min_version_a); + if (!is_empty_payload ()) + { + data.serialize (stream_a); + } +} + +bool nano::telemetry_ack::deserialize (nano::stream & stream_a) +{ + auto error (false); + debug_assert (header.type == nano::message_type::telemetry_ack); + try + { + if (!is_empty_payload ()) + { + data.deserialize (stream_a, header.extensions.to_ulong ()); + } + } + catch (std::runtime_error const &) + { + error = true; + } + + return error; +} + +void nano::telemetry_ack::visit (nano::message_visitor & visitor_a) const +{ + visitor_a.telemetry_ack (*this); +} + +uint16_t nano::telemetry_ack::size () const +{ + return size (header); +} + +uint16_t nano::telemetry_ack::size (nano::message_header const & message_header_a) +{ + return static_cast ((message_header_a.extensions & message_header::telemetry_size_mask).to_ullong ()); +} + +bool nano::telemetry_ack::is_empty_payload () const +{ + return size () == 0; +} + +void nano::telemetry_data::deserialize (nano::stream & stream_a, uint16_t payload_length_a) +{ + read (stream_a, signature); + read (stream_a, node_id); + read (stream_a, block_count); + boost::endian::big_to_native_inplace (block_count); + read (stream_a, cemented_count); + boost::endian::big_to_native_inplace (cemented_count); + read (stream_a, unchecked_count); + boost::endian::big_to_native_inplace (unchecked_count); + read (stream_a, account_count); + boost::endian::big_to_native_inplace (account_count); + read (stream_a, bandwidth_cap); + boost::endian::big_to_native_inplace (bandwidth_cap); + read (stream_a, peer_count); + boost::endian::big_to_native_inplace (peer_count); + read (stream_a, protocol_version); + read (stream_a, uptime); + boost::endian::big_to_native_inplace (uptime); + read (stream_a, genesis_block.bytes); + read (stream_a, major_version); + read (stream_a, minor_version); + read (stream_a, patch_version); + read (stream_a, pre_release_version); + read (stream_a, maker); + + uint64_t timestamp_l; + read (stream_a, timestamp_l); + boost::endian::big_to_native_inplace (timestamp_l); + timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (timestamp_l)); + read (stream_a, active_difficulty); + boost::endian::big_to_native_inplace (active_difficulty); +} + +void nano::telemetry_data::serialize_without_signature (nano::stream & stream_a, uint16_t /* size_a */) const +{ + // All values should be serialized in big endian + write (stream_a, node_id); + write (stream_a, boost::endian::native_to_big (block_count)); + write (stream_a, boost::endian::native_to_big (cemented_count)); + write (stream_a, boost::endian::native_to_big (unchecked_count)); + write (stream_a, boost::endian::native_to_big (account_count)); + write (stream_a, boost::endian::native_to_big (bandwidth_cap)); + write (stream_a, boost::endian::native_to_big (peer_count)); + write (stream_a, protocol_version); + write (stream_a, boost::endian::native_to_big (uptime)); + write (stream_a, genesis_block.bytes); + write (stream_a, major_version); + write (stream_a, minor_version); + write (stream_a, patch_version); + write (stream_a, pre_release_version); + write (stream_a, maker); + write (stream_a, boost::endian::native_to_big (std::chrono::duration_cast (timestamp.time_since_epoch ()).count ())); + write (stream_a, boost::endian::native_to_big (active_difficulty)); +} + +void nano::telemetry_data::serialize (nano::stream & stream_a) const +{ + write (stream_a, signature); + serialize_without_signature (stream_a, size); +} + +nano::error nano::telemetry_data::serialize_json (nano::jsonconfig & json, bool ignore_identification_metrics_a) const +{ + json.put ("block_count", block_count); + json.put ("cemented_count", cemented_count); + json.put ("unchecked_count", unchecked_count); + json.put ("account_count", account_count); + json.put ("bandwidth_cap", bandwidth_cap); + json.put ("peer_count", peer_count); + json.put ("protocol_version", protocol_version); + json.put ("uptime", uptime); + json.put ("genesis_block", genesis_block.to_string ()); + json.put ("major_version", major_version); + json.put ("minor_version", minor_version); + json.put ("patch_version", patch_version); + json.put ("pre_release_version", pre_release_version); + json.put ("maker", maker); + json.put ("timestamp", std::chrono::duration_cast (timestamp.time_since_epoch ()).count ()); + json.put ("active_difficulty", nano::to_string_hex (active_difficulty)); + // Keep these last for UI purposes + if (!ignore_identification_metrics_a) + { + json.put ("node_id", node_id.to_string ()); + json.put ("signature", signature.to_string ()); + } + return json.get_error (); +} + +nano::error nano::telemetry_data::deserialize_json (nano::jsonconfig & json, bool ignore_identification_metrics_a) +{ + if (!ignore_identification_metrics_a) + { + std::string signature_l; + json.get ("signature", signature_l); + if (!json.get_error ()) + { + if (signature.decode_hex (signature_l)) + { + json.get_error ().set ("Could not deserialize signature"); + } + } + + std::string node_id_l; + json.get ("node_id", node_id_l); + if (!json.get_error ()) + { + if (node_id.decode_hex (node_id_l)) + { + json.get_error ().set ("Could not deserialize node id"); + } + } + } + + json.get ("block_count", block_count); + json.get ("cemented_count", cemented_count); + json.get ("unchecked_count", unchecked_count); + json.get ("account_count", account_count); + json.get ("bandwidth_cap", bandwidth_cap); + json.get ("peer_count", peer_count); + json.get ("protocol_version", protocol_version); + json.get ("uptime", uptime); + std::string genesis_block_l; + json.get ("genesis_block", genesis_block_l); + if (!json.get_error ()) + { + if (genesis_block.decode_hex (genesis_block_l)) + { + json.get_error ().set ("Could not deserialize genesis block"); + } + } + json.get ("major_version", major_version); + json.get ("minor_version", minor_version); + json.get ("patch_version", patch_version); + json.get ("pre_release_version", pre_release_version); + json.get ("maker", maker); + auto timestamp_l = json.get ("timestamp"); + timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (timestamp_l)); + auto current_active_difficulty_text = json.get ("active_difficulty"); + auto ec = nano::from_string_hex (current_active_difficulty_text, active_difficulty); + debug_assert (!ec); + return json.get_error (); +} + +bool nano::telemetry_data::operator== (nano::telemetry_data const & data_a) const +{ + return (signature == data_a.signature && node_id == data_a.node_id && block_count == data_a.block_count && cemented_count == data_a.cemented_count && unchecked_count == data_a.unchecked_count && account_count == data_a.account_count && bandwidth_cap == data_a.bandwidth_cap && uptime == data_a.uptime && peer_count == data_a.peer_count && protocol_version == data_a.protocol_version && genesis_block == data_a.genesis_block && major_version == data_a.major_version && minor_version == data_a.minor_version && patch_version == data_a.patch_version && pre_release_version == data_a.pre_release_version && maker == data_a.maker && timestamp == data_a.timestamp && active_difficulty == data_a.active_difficulty); +} + +bool nano::telemetry_data::operator!= (nano::telemetry_data const & data_a) const +{ + return !(*this == data_a); +} + +void nano::telemetry_data::sign (nano::keypair const & node_id_a) +{ + debug_assert (node_id == node_id_a.pub); + std::vector bytes; + { + nano::vectorstream stream (bytes); + serialize_without_signature (stream, size); + } + + signature = nano::sign_message (node_id_a.prv, node_id_a.pub, bytes.data (), bytes.size ()); +} + +bool nano::telemetry_data::validate_signature (uint16_t size_a) const +{ + std::vector bytes; + { + nano::vectorstream stream (bytes); + serialize_without_signature (stream, size_a); + } + + return nano::validate_message (node_id, bytes.data (), bytes.size (), signature); +} + nano::node_id_handshake::node_id_handshake (bool & error_a, nano::stream & stream_a, nano::message_header const & header_a) : message (header_a), query (boost::none), @@ -981,9 +1369,9 @@ response (response) } } -void nano::node_id_handshake::serialize (nano::stream & stream_a) const +void nano::node_id_handshake::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const { - header.serialize (stream_a); + header.serialize (stream_a, use_epoch_2_min_version_a); if (query) { write (stream_a, *query); @@ -997,7 +1385,7 @@ void nano::node_id_handshake::serialize (nano::stream & stream_a) const bool nano::node_id_handshake::deserialize (nano::stream & stream_a) { - assert (header.type == nano::message_type::node_id_handshake); + debug_assert (header.type == nano::message_type::node_id_handshake); auto error (false); try { @@ -1073,6 +1461,21 @@ bool nano::parse_port (std::string const & string_a, uint16_t & port_a) return result; } +// Can handle both ipv4 & ipv6 addresses (with and without square brackets) +bool nano::parse_address (std::string const & address_text_a, boost::asio::ip::address & address_a) +{ + auto address_text = address_text_a; + if (!address_text.empty () && address_text.front () == '[' && address_text.back () == ']') + { + // Chop the square brackets off as make_address doesn't always like them + address_text = address_text.substr (1, address_text.size () - 2); + } + + boost::system::error_code address_ec; + address_a = boost::asio::ip::make_address (address_text, address_ec); + return !!address_ec; +} + bool nano::parse_address_port (std::string const & string, boost::asio::ip::address & address_a, uint16_t & port_a) { auto result (false); @@ -1087,7 +1490,7 @@ bool nano::parse_address_port (std::string const & string, boost::asio::ip::addr if (!result) { boost::system::error_code ec; - auto address (boost::asio::ip::address_v6::from_string (string.substr (0, port_position), ec)); + auto address (boost::asio::ip::make_address_v6 (string.substr (0, port_position), ec)); if (!ec) { address_a = address; @@ -1139,6 +1542,11 @@ bool nano::parse_tcp_endpoint (std::string const & string, nano::tcp_endpoint & return result; } +std::chrono::seconds nano::telemetry_cache_cutoffs::network_to_time (network_constants const & network_constants) +{ + return std::chrono::seconds{ network_constants.is_live_network () ? live : network_constants.is_beta_network () ? beta : test }; +} + nano::node_singleton_memory_pool_purge_guard::node_singleton_memory_pool_purge_guard () : cleanup_guard ({ nano::block_memory_pool_purge, nano::purge_singleton_pool_memory, nano::purge_singleton_pool_memory }) { diff --git a/nano/node/common.hpp b/nano/node/common.hpp index ce368e5c1d..4672b7783a 100644 --- a/nano/node/common.hpp +++ b/nano/node/common.hpp @@ -1,11 +1,13 @@ #pragma once -#include +#include +#include #include #include -#include +#include #include #include +#include #include @@ -13,40 +15,24 @@ namespace nano { using endpoint = boost::asio::ip::udp::endpoint; bool parse_port (std::string const &, uint16_t &); +bool parse_address (std::string const &, boost::asio::ip::address &); bool parse_address_port (std::string const &, boost::asio::ip::address &, uint16_t &); using tcp_endpoint = boost::asio::ip::tcp::endpoint; bool parse_endpoint (std::string const &, nano::endpoint &); bool parse_tcp_endpoint (std::string const &, nano::tcp_endpoint &); +uint64_t ip_address_hash_raw (boost::asio::ip::address const & ip_a, uint16_t port = 0); } namespace { -uint64_t ip_address_hash_raw (boost::asio::ip::address const & ip_a, uint16_t port = 0) -{ - static nano::random_constants constants; - assert (ip_a.is_v6 ()); - uint64_t result; - nano::uint128_union address; - address.bytes = ip_a.to_v6 ().to_bytes (); - blake2b_state state; - blake2b_init (&state, sizeof (result)); - blake2b_update (&state, constants.random_128.bytes.data (), constants.random_128.bytes.size ()); - if (port != 0) - { - blake2b_update (&state, &port, sizeof (port)); - } - blake2b_update (&state, address.bytes.data (), address.bytes.size ()); - blake2b_final (&state, &result, sizeof (result)); - return result; -} uint64_t endpoint_hash_raw (nano::endpoint const & endpoint_a) { - uint64_t result (ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); + uint64_t result (nano::ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); return result; } uint64_t endpoint_hash_raw (nano::tcp_endpoint const & endpoint_a) { - uint64_t result (ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); + uint64_t result (nano::ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); return result; } @@ -91,7 +77,7 @@ struct ip_address_hash<8> { size_t operator() (boost::asio::ip::address const & ip_address_a) const { - return ip_address_hash_raw (ip_address_a); + return nano::ip_address_hash_raw (ip_address_a); } }; template <> @@ -99,7 +85,7 @@ struct ip_address_hash<4> { size_t operator() (boost::asio::ip::address const & ip_address_a) const { - uint64_t big (ip_address_hash_raw (ip_address_a)); + uint64_t big (nano::ip_address_hash_raw (ip_address_a)); uint32_t result (static_cast (big) ^ static_cast (big >> 32)); return result; } @@ -186,8 +172,11 @@ enum class message_type : uint8_t frontier_req = 0x8, /* deleted 0x9 */ node_id_handshake = 0x0a, - bulk_pull_account = 0x0b + bulk_pull_account = 0x0b, + telemetry_req = 0x0c, + telemetry_ack = 0x0d }; + enum class bulk_pull_account_flags : uint8_t { pending_hash_and_amount = 0x0, @@ -200,7 +189,7 @@ class message_header final public: explicit message_header (nano::message_type); message_header (bool &, nano::stream &); - void serialize (nano::stream &) const; + void serialize (nano::stream &, bool) const; bool deserialize (nano::stream &); nano::block_type block_type () const; void block_type_set (nano::block_type); @@ -208,9 +197,14 @@ class message_header final void count_set (uint8_t); uint8_t version_max; uint8_t version_using; - uint8_t version_min; + +private: + uint8_t version_min_m{ std::numeric_limits::max () }; + +public: nano::message_type type; std::bitset<16> extensions; + static size_t constexpr size = sizeof (network_params::header_magic_number) + sizeof (version_max) + sizeof (version_using) + sizeof (version_min_m) + sizeof (type) + sizeof (/* extensions */ uint16_t); void flag_set (uint8_t); static uint8_t constexpr bulk_pull_count_present_flag = 0; @@ -219,12 +213,14 @@ class message_header final static uint8_t constexpr node_id_handshake_response_flag = 1; bool node_id_handshake_is_query () const; bool node_id_handshake_is_response () const; + uint8_t version_min () const; /** Size of the payload in bytes. For some messages, the payload size is based on header flags. */ size_t payload_length_bytes () const; - static std::bitset<16> constexpr block_type_mask = std::bitset<16> (0x0f00); - static std::bitset<16> constexpr count_mask = std::bitset<16> (0xf000); + static std::bitset<16> constexpr block_type_mask{ 0x0f00 }; + static std::bitset<16> constexpr count_mask{ 0xf000 }; + static std::bitset<16> constexpr telemetry_size_mask{ 0x07ff }; }; class message { @@ -232,19 +228,11 @@ class message explicit message (nano::message_type); explicit message (nano::message_header const &); virtual ~message () = default; - virtual void serialize (nano::stream &) const = 0; + virtual void serialize (nano::stream &, bool) const = 0; virtual void visit (nano::message_visitor &) const = 0; - std::shared_ptr> to_bytes () const - { - auto bytes = std::make_shared> (); - nano::vectorstream stream (*bytes); - serialize (stream); - return bytes; - } - nano::shared_const_buffer to_shared_const_buffer () const - { - return shared_const_buffer (to_bytes ()); - } + std::shared_ptr> to_bytes (bool) const; + nano::shared_const_buffer to_shared_const_buffer (bool) const; + nano::message_header header; }; class work_pool; @@ -262,23 +250,30 @@ class message_parser final invalid_confirm_req_message, invalid_confirm_ack_message, invalid_node_id_handshake_message, + invalid_telemetry_req_message, + invalid_telemetry_ack_message, outdated_version, invalid_magic, - invalid_network + invalid_network, + duplicate_publish_message }; - message_parser (nano::block_uniquer &, nano::vote_uniquer &, nano::message_visitor &, nano::work_pool &); + message_parser (nano::network_filter &, nano::block_uniquer &, nano::vote_uniquer &, nano::message_visitor &, nano::work_pool &, bool); void deserialize_buffer (uint8_t const *, size_t); void deserialize_keepalive (nano::stream &, nano::message_header const &); - void deserialize_publish (nano::stream &, nano::message_header const &); + void deserialize_publish (nano::stream &, nano::message_header const &, nano::uint128_t const & = 0); void deserialize_confirm_req (nano::stream &, nano::message_header const &); void deserialize_confirm_ack (nano::stream &, nano::message_header const &); void deserialize_node_id_handshake (nano::stream &, nano::message_header const &); + void deserialize_telemetry_req (nano::stream &, nano::message_header const &); + void deserialize_telemetry_ack (nano::stream &, nano::message_header const &); bool at_end (nano::stream &); + nano::network_filter & publish_filter; nano::block_uniquer & block_uniquer; nano::vote_uniquer & vote_uniquer; nano::message_visitor & visitor; nano::work_pool & pool; parse_status status; + bool use_epoch_2_min_version; std::string status_string (); static const size_t max_safe_udp_message_size; }; @@ -288,7 +283,7 @@ class keepalive final : public message keepalive (); keepalive (bool &, nano::stream &, nano::message_header const &); void visit (nano::message_visitor &) const override; - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); bool operator== (nano::keepalive const &) const; std::array peers; @@ -297,13 +292,14 @@ class keepalive final : public message class publish final : public message { public: - publish (bool &, nano::stream &, nano::message_header const &, nano::block_uniquer * = nullptr); + publish (bool &, nano::stream &, nano::message_header const &, nano::uint128_t const & = 0, nano::block_uniquer * = nullptr); explicit publish (std::shared_ptr); void visit (nano::message_visitor &) const override; - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &, nano::block_uniquer * = nullptr); bool operator== (nano::publish const &) const; std::shared_ptr block; + nano::uint128_t digest{ 0 }; }; class confirm_req final : public message { @@ -312,7 +308,7 @@ class confirm_req final : public message explicit confirm_req (std::shared_ptr); confirm_req (std::vector> const &); confirm_req (nano::block_hash const &, nano::root const &); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &, nano::block_uniquer * = nullptr); void visit (nano::message_visitor &) const override; bool operator== (nano::confirm_req const &) const; @@ -326,7 +322,7 @@ class confirm_ack final : public message public: confirm_ack (bool &, nano::stream &, nano::message_header const &, nano::vote_uniquer * = nullptr); explicit confirm_ack (std::shared_ptr); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; void visit (nano::message_visitor &) const override; bool operator== (nano::confirm_ack const &) const; std::shared_ptr vote; @@ -337,7 +333,7 @@ class frontier_req final : public message public: frontier_req (); frontier_req (bool &, nano::stream &, nano::message_header const &); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); void visit (nano::message_visitor &) const override; bool operator== (nano::frontier_req const &) const; @@ -346,13 +342,74 @@ class frontier_req final : public message uint32_t count; static size_t constexpr size = sizeof (start) + sizeof (age) + sizeof (count); }; + +class telemetry_data +{ +public: + nano::signature signature{ 0 }; + nano::account node_id{ 0 }; + uint64_t block_count{ 0 }; + uint64_t cemented_count{ 0 }; + uint64_t unchecked_count{ 0 }; + uint64_t account_count{ 0 }; + uint64_t bandwidth_cap{ 0 }; + uint64_t uptime{ 0 }; + uint32_t peer_count{ 0 }; + uint8_t protocol_version{ 0 }; + nano::block_hash genesis_block{ 0 }; + uint8_t major_version{ 0 }; + uint8_t minor_version{ 0 }; + uint8_t patch_version{ 0 }; + uint8_t pre_release_version{ 0 }; + uint8_t maker{ 0 }; // 0 for NF node + std::chrono::system_clock::time_point timestamp; + uint64_t active_difficulty{ 0 }; + + void serialize (nano::stream &) const; + void deserialize (nano::stream &, uint16_t); + nano::error serialize_json (nano::jsonconfig &, bool) const; + nano::error deserialize_json (nano::jsonconfig &, bool); + void sign (nano::keypair const &); + bool validate_signature (uint16_t) const; + bool operator== (nano::telemetry_data const &) const; + bool operator!= (nano::telemetry_data const &) const; + + static auto constexpr size = sizeof (signature) + sizeof (node_id) + sizeof (block_count) + sizeof (cemented_count) + sizeof (unchecked_count) + sizeof (account_count) + sizeof (bandwidth_cap) + sizeof (peer_count) + sizeof (protocol_version) + sizeof (uptime) + sizeof (genesis_block) + sizeof (major_version) + sizeof (minor_version) + sizeof (patch_version) + sizeof (pre_release_version) + sizeof (maker) + sizeof (uint64_t) + sizeof (active_difficulty); + +private: + void serialize_without_signature (nano::stream &, uint16_t) const; +}; +class telemetry_req final : public message +{ +public: + telemetry_req (); + explicit telemetry_req (nano::message_header const &); + void serialize (nano::stream &, bool) const override; + bool deserialize (nano::stream &); + void visit (nano::message_visitor &) const override; +}; +class telemetry_ack final : public message +{ +public: + telemetry_ack (); + telemetry_ack (bool &, nano::stream &, nano::message_header const &); + explicit telemetry_ack (telemetry_data const &); + void serialize (nano::stream &, bool) const override; + void visit (nano::message_visitor &) const override; + bool deserialize (nano::stream &); + uint16_t size () const; + bool is_empty_payload () const; + static uint16_t size (nano::message_header const &); + nano::telemetry_data data; +}; + class bulk_pull final : public message { public: using count_t = uint32_t; bulk_pull (); bulk_pull (bool &, nano::stream &, nano::message_header const &); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); void visit (nano::message_visitor &) const override; nano::hash_or_account start{ 0 }; @@ -369,7 +426,7 @@ class bulk_pull_account final : public message public: bulk_pull_account (); bulk_pull_account (bool &, nano::stream &, nano::message_header const &); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); void visit (nano::message_visitor &) const override; nano::account account; @@ -382,7 +439,7 @@ class bulk_push final : public message public: bulk_push (); explicit bulk_push (nano::message_header const &); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); void visit (nano::message_visitor &) const override; }; @@ -391,7 +448,7 @@ class node_id_handshake final : public message public: node_id_handshake (bool &, nano::stream &, nano::message_header const &); node_id_handshake (boost::optional, boost::optional>); - void serialize (nano::stream &) const override; + void serialize (nano::stream &, bool) const override; bool deserialize (nano::stream &); void visit (nano::message_visitor &) const override; bool operator== (nano::node_id_handshake const &) const; @@ -412,9 +469,21 @@ class message_visitor virtual void bulk_push (nano::bulk_push const &) = 0; virtual void frontier_req (nano::frontier_req const &) = 0; virtual void node_id_handshake (nano::node_id_handshake const &) = 0; + virtual void telemetry_req (nano::telemetry_req const &) = 0; + virtual void telemetry_ack (nano::telemetry_ack const &) = 0; virtual ~message_visitor (); }; +class telemetry_cache_cutoffs +{ +public: + static std::chrono::seconds constexpr test{ 3 }; + static std::chrono::seconds constexpr beta{ 15 }; + static std::chrono::seconds constexpr live{ 60 }; + + static std::chrono::seconds network_to_time (network_constants const & network_constants); +}; + /** Helper guard which contains all the necessary purge (remove all memory even if used) functions */ class node_singleton_memory_pool_purge_guard { diff --git a/nano/node/confirmation_height_bounded.cpp b/nano/node/confirmation_height_bounded.cpp new file mode 100644 index 0000000000..05928da0ad --- /dev/null +++ b/nano/node/confirmation_height_bounded.cpp @@ -0,0 +1,592 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +nano::confirmation_height_bounded::confirmation_height_bounded (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger_mt & logger_a, std::atomic & stopped_a, nano::block_hash const & original_hash_a, uint64_t & batch_write_size_a, std::function> const &)> const & notify_observers_callback_a, std::function const & notify_block_already_cemented_observers_callback_a, std::function const & awaiting_processing_size_callback_a) : +ledger (ledger_a), +write_database_queue (write_database_queue_a), +batch_separate_pending_min_time (batch_separate_pending_min_time_a), +logger (logger_a), +stopped (stopped_a), +original_hash (original_hash_a), +batch_write_size (batch_write_size_a), +notify_observers_callback (notify_observers_callback_a), +notify_block_already_cemented_observers_callback (notify_block_already_cemented_observers_callback_a), +awaiting_processing_size_callback (awaiting_processing_size_callback_a) +{ +} + +// The next block hash to iterate over, the priority is as follows: +// 1 - The next block in the account chain for the last processed receive (if there is any) +// 2 - The next receive block which is closest to genesis +// 3 - The last checkpoint hit. +// 4 - The hash that was passed in originally. Either all checkpoints were exhausted (this can happen when there are many accounts to genesis) +// or all other blocks have been processed. +nano::confirmation_height_bounded::top_and_next_hash nano::confirmation_height_bounded::get_next_block (boost::optional const & next_in_receive_chain_a, boost::circular_buffer_space_optimized const & checkpoints_a, boost::circular_buffer_space_optimized const & receive_source_pairs, boost::optional & receive_details_a) +{ + top_and_next_hash next; + if (next_in_receive_chain_a.is_initialized ()) + { + next = *next_in_receive_chain_a; + } + else if (!receive_source_pairs.empty ()) + { + auto next_receive_source_pair = receive_source_pairs.back (); + receive_details_a = next_receive_source_pair.receive_details; + next = { next_receive_source_pair.source_hash, receive_details_a->next, receive_details_a->height + 1 }; + } + else if (!checkpoints_a.empty ()) + { + next = { checkpoints_a.back (), boost::none, 0 }; + } + else + { + next = { original_hash, boost::none, 0 }; + } + + return next; +} + +void nano::confirmation_height_bounded::process () +{ + if (pending_empty ()) + { + clear_process_vars (); + timer.restart (); + } + + boost::optional next_in_receive_chain; + boost::circular_buffer_space_optimized checkpoints{ max_items }; + boost::circular_buffer_space_optimized receive_source_pairs{ max_items }; + nano::block_hash current; + bool first_iter = true; + auto transaction (ledger.store.tx_begin_read ()); + do + { + boost::optional receive_details; + auto hash_to_process = get_next_block (next_in_receive_chain, checkpoints, receive_source_pairs, receive_details); + current = hash_to_process.top; + + auto top_level_hash = current; + auto block = ledger.store.block_get (transaction, current); + if (!block) + { + auto error_str = (boost::format ("Ledger mismatch trying to set confirmation height for block %1% (bounded processor)") % current.to_string ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + } + release_assert (block); + nano::account account (block->account ()); + if (account.is_zero ()) + { + account = block->sideband ().account; + } + + // Checks if we have encountered this account before but not commited changes yet, if so then update the cached confirmation height + nano::confirmation_height_info confirmation_height_info; + auto account_it = accounts_confirmed_info.find (account); + if (account_it != accounts_confirmed_info.cend ()) + { + confirmation_height_info.height = account_it->second.confirmed_height; + confirmation_height_info.frontier = account_it->second.iterated_frontier; + } + else + { + auto error = ledger.store.confirmation_height_get (transaction, account, confirmation_height_info); + (void)error; + debug_assert (!error); + // This block was added to the confirmation height processor but is already confirmed + if (first_iter && confirmation_height_info.height >= block->sideband ().height && current == original_hash) + { + notify_block_already_cemented_observers_callback (original_hash); + } + } + + auto block_height = block->sideband ().height; + bool already_cemented = confirmation_height_info.height >= block_height; + + // If we are not already at the bottom of the account chain (1 above cemented frontier) then find it + if (!already_cemented && block_height - confirmation_height_info.height > 1) + { + if (block_height - confirmation_height_info.height == 2) + { + // If there is 1 uncemented block in-between this block and the cemented frontier, + // we can just use the previous block to get the least unconfirmed hash. + current = block->previous (); + --block_height; + } + else if (!next_in_receive_chain.is_initialized ()) + { + current = get_least_unconfirmed_hash_from_top_level (transaction, current, account, confirmation_height_info, block_height); + } + else + { + // Use the cached successor of the last receive which saves having to do more IO in get_least_unconfirmed_hash_from_top_level + // as we already know what the next block we should process should be. + current = *hash_to_process.next; + block_height = hash_to_process.next_height; + } + } + + auto top_most_non_receive_block_hash = current; + + bool hit_receive = false; + if (!already_cemented) + { + hit_receive = iterate (transaction, block_height, current, checkpoints, top_most_non_receive_block_hash, top_level_hash, receive_source_pairs, account); + } + + // Exit early when the processor has been stopped, otherwise this function may take a + // while (and hence keep the process running) if updating a long chain. + if (stopped) + { + break; + } + + // next_in_receive_chain can be modified when writing, so need to cache it here before resetting + auto is_set = next_in_receive_chain.is_initialized (); + next_in_receive_chain = boost::none; + + // Need to also handle the case where we are hitting receives where the sends below should be confirmed + if (!hit_receive || (receive_source_pairs.size () == 1 && top_most_non_receive_block_hash != current)) + { + preparation_data preparation_data{ transaction, top_most_non_receive_block_hash, already_cemented, checkpoints, account_it, confirmation_height_info, account, block_height, current, receive_details, next_in_receive_chain }; + prepare_iterated_blocks_for_cementing (preparation_data); + + // If used the top level, don't pop off the receive source pair because it wasn't used + if (!is_set && !receive_source_pairs.empty ()) + { + receive_source_pairs.pop_back (); + } + + auto total_pending_write_block_count = std::accumulate (pending_writes.cbegin (), pending_writes.cend (), uint64_t (0), [](uint64_t total, auto const & write_details_a) { + return total += write_details_a.top_height - write_details_a.bottom_height + 1; + }); + + auto max_batch_write_size_reached = (total_pending_write_block_count >= batch_write_size); + // When there are a lot of pending confirmation height blocks, it is more efficient to + // bulk some of them up to enable better write performance which becomes the bottleneck. + auto min_time_exceeded = (timer.since_start () >= batch_separate_pending_min_time); + auto finished_iterating = current == original_hash; + auto non_awaiting_processing = awaiting_processing_size_callback () == 0; + auto should_output = finished_iterating && (non_awaiting_processing || min_time_exceeded); + auto force_write = pending_writes.size () >= pending_writes_max_size || accounts_confirmed_info.size () >= pending_writes_max_size; + + if ((max_batch_write_size_reached || should_output || force_write) && !pending_writes.empty ()) + { + // If nothing is currently using the database write lock then write the cemented pending blocks otherwise continue iterating + if (write_database_queue.process (nano::writer::confirmation_height)) + { + auto scoped_write_guard = write_database_queue.pop (); + cement_blocks (scoped_write_guard); + } + else if (force_write) + { + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + cement_blocks (scoped_write_guard); + } + } + } + + first_iter = false; + transaction.refresh (); + } while ((!receive_source_pairs.empty () || current != original_hash) && !stopped); + + debug_assert (checkpoints.empty ()); +} + +nano::block_hash nano::confirmation_height_bounded::get_least_unconfirmed_hash_from_top_level (nano::transaction const & transaction_a, nano::block_hash const & hash_a, nano::account const & account_a, nano::confirmation_height_info const & confirmation_height_info_a, uint64_t & block_height_a) +{ + nano::block_hash least_unconfirmed_hash = hash_a; + if (confirmation_height_info_a.height != 0) + { + if (block_height_a > confirmation_height_info_a.height) + { + auto block (ledger.store.block_get (transaction_a, confirmation_height_info_a.frontier)); + release_assert (block != nullptr); + least_unconfirmed_hash = block->sideband ().successor; + block_height_a = block->sideband ().height + 1; + } + } + else + { + // No blocks have been confirmed, so the first block will be the open block + nano::account_info account_info; + release_assert (!ledger.store.account_get (transaction_a, account_a, account_info)); + least_unconfirmed_hash = account_info.open_block; + block_height_a = 1; + } + return least_unconfirmed_hash; +} + +bool nano::confirmation_height_bounded::iterate (nano::read_transaction const & transaction_a, uint64_t bottom_height_a, nano::block_hash const & bottom_hash_a, boost::circular_buffer_space_optimized & checkpoints_a, nano::block_hash & top_most_non_receive_block_hash_a, nano::block_hash const & top_level_hash_a, boost::circular_buffer_space_optimized & receive_source_pairs_a, nano::account const & account_a) +{ + bool reached_target = false; + bool hit_receive = false; + auto hash = bottom_hash_a; + uint64_t num_blocks = 0; + while (!hash.is_zero () && !reached_target && !stopped) + { + // Keep iterating upwards until we either reach the desired block or the second receive. + // Once a receive is cemented, we can cement all blocks above it until the next receive, so store those details for later. + ++num_blocks; + auto block = ledger.store.block_get (transaction_a, hash); + auto source (block->source ()); + if (source.is_zero ()) + { + source = block->link (); + } + + if (!source.is_zero () && !ledger.is_epoch_link (source) && ledger.store.source_exists (transaction_a, source)) + { + hit_receive = true; + reached_target = true; + auto const & sideband (block->sideband ()); + auto next = !sideband.successor.is_zero () && sideband.successor != top_level_hash_a ? boost::optional (sideband.successor) : boost::none; + receive_source_pairs_a.push_back ({ receive_chain_details{ account_a, sideband.height, hash, top_level_hash_a, next, bottom_height_a, bottom_hash_a }, source }); + // Store a checkpoint every max_items so that we can always traverse a long number of accounts to genesis + if (receive_source_pairs_a.size () % max_items == 0) + { + checkpoints_a.push_back (top_level_hash_a); + } + } + else + { + // Found a send/change/epoch block which isn't the desired top level + top_most_non_receive_block_hash_a = hash; + if (hash == top_level_hash_a) + { + reached_target = true; + } + else + { + hash = block->sideband ().successor; + } + } + + // We could be traversing a very large account so we don't want to open read transactions for too long. + if ((num_blocks > 0) && num_blocks % batch_read_size == 0) + { + transaction_a.refresh (); + } + } + + return hit_receive; +} + +// Once the path to genesis has been iterated to, we can begin to cement the lowest blocks in the accounts. This sets up +// the non-receive blocks which have been iterated for an account, and the associated receive block. +void nano::confirmation_height_bounded::prepare_iterated_blocks_for_cementing (preparation_data & preparation_data_a) +{ + if (!preparation_data_a.already_cemented) + { + // Add the non-receive blocks iterated for this account + auto block_height = (ledger.store.block_account_height (preparation_data_a.transaction, preparation_data_a.top_most_non_receive_block_hash)); + if (block_height > preparation_data_a.confirmation_height_info.height) + { + confirmed_info confirmed_info_l{ block_height, preparation_data_a.top_most_non_receive_block_hash }; + if (preparation_data_a.account_it != accounts_confirmed_info.cend ()) + { + preparation_data_a.account_it->second = confirmed_info_l; + } + else + { + accounts_confirmed_info.emplace (preparation_data_a.account, confirmed_info_l); + ++accounts_confirmed_info_size; + } + + preparation_data_a.checkpoints.erase (std::remove (preparation_data_a.checkpoints.begin (), preparation_data_a.checkpoints.end (), preparation_data_a.top_most_non_receive_block_hash), preparation_data_a.checkpoints.end ()); + pending_writes.emplace_back (preparation_data_a.account, preparation_data_a.bottom_height, preparation_data_a.bottom_most, block_height, preparation_data_a.top_most_non_receive_block_hash); + ++pending_writes_size; + } + } + + // Add the receive block and all non-receive blocks above that one + auto & receive_details = preparation_data_a.receive_details; + if (receive_details) + { + auto receive_confirmed_info_it = accounts_confirmed_info.find (receive_details->account); + if (receive_confirmed_info_it != accounts_confirmed_info.cend ()) + { + auto & receive_confirmed_info = receive_confirmed_info_it->second; + receive_confirmed_info.confirmed_height = receive_details->height; + receive_confirmed_info.iterated_frontier = receive_details->hash; + } + else + { + accounts_confirmed_info.emplace (std::piecewise_construct, std::forward_as_tuple (receive_details->account), std::forward_as_tuple (receive_details->height, receive_details->hash)); + ++accounts_confirmed_info_size; + } + + if (receive_details->next.is_initialized ()) + { + preparation_data_a.next_in_receive_chain = top_and_next_hash{ receive_details->top_level, receive_details->next, receive_details->height + 1 }; + } + else + { + preparation_data_a.checkpoints.erase (std::remove (preparation_data_a.checkpoints.begin (), preparation_data_a.checkpoints.end (), receive_details->hash), preparation_data_a.checkpoints.end ()); + } + + pending_writes.emplace_back (receive_details->account, receive_details->bottom_height, receive_details->bottom_most, receive_details->height, receive_details->hash); + ++pending_writes_size; + } +} + +void nano::confirmation_height_bounded::cement_blocks (nano::write_guard & scoped_write_guard_a) +{ + // Will contain all blocks that have been cemented (bounded by batch_write_size) + // and will get run through the cemented observer callback + std::vector> cemented_blocks; + auto const maximum_batch_write_time = 250; // milliseconds + auto const maximum_batch_write_time_increase_cutoff = maximum_batch_write_time - (maximum_batch_write_time / 5); + auto const amount_to_change = batch_write_size / 10; // 10% + auto const minimum_batch_write_size = 16384u; + nano::timer<> cemented_batch_timer; + auto error = false; + { + // This only writes to the confirmation_height table and is the only place to do so in a single process + auto transaction (ledger.store.tx_begin_write ({}, { nano::tables::confirmation_height })); + cemented_batch_timer.start (); + // Cement all pending entries, each entry is specific to an account and contains the least amount + // of blocks to retain consistent cementing across all account chains to genesis. + while (!error && !pending_writes.empty ()) + { + const auto & pending = pending_writes.front (); + const auto & account = pending.account; + + auto write_confirmation_height = [&account, &ledger = ledger, &transaction](uint64_t num_blocks_cemented, uint64_t confirmation_height, nano::block_hash const & confirmed_frontier) { +#ifndef NDEBUG + // Extra debug checks + nano::confirmation_height_info confirmation_height_info; + debug_assert (!ledger.store.confirmation_height_get (transaction, account, confirmation_height_info)); + auto block (ledger.store.block_get (transaction, confirmed_frontier)); + debug_assert (block != nullptr); + debug_assert (block->sideband ().height == confirmation_height_info.height + num_blocks_cemented); +#endif + ledger.store.confirmation_height_put (transaction, account, nano::confirmation_height_info{ confirmation_height, confirmed_frontier }); + ledger.cache.cemented_count += num_blocks_cemented; + ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in, num_blocks_cemented); + ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in, num_blocks_cemented); + }; + + nano::confirmation_height_info confirmation_height_info; + error = ledger.store.confirmation_height_get (transaction, pending.account, confirmation_height_info); + if (error) + { + auto error_str = (boost::format ("Failed to read confirmation height for account %1% (bounded processor)") % pending.account.to_account ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + } + + // Some blocks need to be cemented at least + if (!error && pending.top_height > confirmation_height_info.height) + { + // The highest hash which will be cemented + nano::block_hash new_cemented_frontier; + uint64_t num_blocks_confirmed = 0; + uint64_t start_height = 0; + if (pending.bottom_height > confirmation_height_info.height) + { + new_cemented_frontier = pending.bottom_hash; + // If we are higher than the cemented frontier, we should be exactly 1 block above + debug_assert (pending.bottom_height == confirmation_height_info.height + 1); + num_blocks_confirmed = pending.top_height - pending.bottom_height + 1; + start_height = pending.bottom_height; + } + else + { + auto block = ledger.store.block_get (transaction, confirmation_height_info.frontier); + new_cemented_frontier = block->sideband ().successor; + num_blocks_confirmed = pending.top_height - confirmation_height_info.height; + start_height = confirmation_height_info.height + 1; + } + + auto total_blocks_cemented = 0; + auto block = ledger.store.block_get (transaction, new_cemented_frontier); + + // Cementing starts from the bottom of the chain and works upwards. This is because chains can have effectively + // an infinite number of send/change blocks in a row. We don't want to hold the write transaction open for too long. + for (auto num_blocks_iterated = 0; num_blocks_confirmed - num_blocks_iterated != 0; ++num_blocks_iterated) + { + if (!block) + { + auto error_str = (boost::format ("Failed to write confirmation height for block %1% (bounded processor)") % new_cemented_frontier.to_string ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + // Undo any blocks about to be cemented from this account for this pending write. + cemented_blocks.erase (cemented_blocks.end () - num_blocks_iterated, cemented_blocks.end ()); + error = true; + break; + } + + auto last_iteration = (num_blocks_confirmed - num_blocks_iterated) == 1; + + cemented_blocks.emplace_back (block); + + // Flush these callbacks and continue as we write in batches (ideally maximum 250ms) to not hold write db transaction for too long. + // Include a tolerance to save having to potentially wait on the block processor if the number of blocks to cement is only a bit higher than the max. + if (cemented_blocks.size () > batch_write_size + (batch_write_size / 10)) + { + auto time_spent_cementing = cemented_batch_timer.since_start ().count (); + auto num_blocks_cemented = num_blocks_iterated - total_blocks_cemented + 1; + total_blocks_cemented += num_blocks_cemented; + write_confirmation_height (num_blocks_cemented, start_height + total_blocks_cemented - 1, new_cemented_frontier); + transaction.commit (); + logger.always_log (boost::str (boost::format ("Cemented %1% blocks in %2% %3% (bounded processor)") % cemented_blocks.size () % time_spent_cementing % cemented_batch_timer.unit ())); + + // Update the maximum amount of blocks to write next time based on the time it took to cement this batch. + if (!network_params.network.is_test_network ()) + { + if (time_spent_cementing > maximum_batch_write_time) + { + // Reduce (unless we have hit a floor) + batch_write_size = std::max (minimum_batch_write_size, batch_write_size - amount_to_change); + } + else if (time_spent_cementing < maximum_batch_write_time_increase_cutoff) + { + // Increase amount of blocks written for next batch if the time for writing this one is sufficiently lower than the max time to warrant changing + batch_write_size += amount_to_change; + } + } + + scoped_write_guard_a.release (); + notify_observers_callback (cemented_blocks); + cemented_blocks.clear (); + + // Only aquire transaction if there are blocks left + if (!(last_iteration && pending_writes.size () == 1)) + { + scoped_write_guard_a = write_database_queue.wait (nano::writer::confirmation_height); + transaction.renew (); + } + cemented_batch_timer.restart (); + } + + // Get the next block in the chain until we have reached the final desired one + if (!last_iteration) + { + new_cemented_frontier = block->sideband ().successor; + block = ledger.store.block_get (transaction, new_cemented_frontier); + } + else + { + // Confirm it is indeed the last one + debug_assert (new_cemented_frontier == pending.top_hash); + } + } + + if (error) + { + // There was an error writing a block, do not process any more + break; + } + + auto num_blocks_cemented = num_blocks_confirmed - total_blocks_cemented; + if (num_blocks_cemented > 0) + { + write_confirmation_height (num_blocks_cemented, pending.top_height, new_cemented_frontier); + } + } + + auto it = accounts_confirmed_info.find (pending.account); + if (it != accounts_confirmed_info.cend () && it->second.confirmed_height == pending.top_height) + { + accounts_confirmed_info.erase (pending.account); + --accounts_confirmed_info_size; + } + pending_writes.pop_front (); + --pending_writes_size; + } + } + auto time_spent_cementing = cemented_batch_timer.since_start ().count (); + if (time_spent_cementing > 50) + { + logger.always_log (boost::str (boost::format ("Cemented %1% blocks in %2% %3% (bounded processor)") % cemented_blocks.size () % time_spent_cementing % cemented_batch_timer.unit ())); + } + + // Scope guard could have been released earlier (0 cemented_blocks would indicate that) + if (scoped_write_guard_a.is_owned () && !cemented_blocks.empty ()) + { + scoped_write_guard_a.release (); + notify_observers_callback (cemented_blocks); + } + release_assert (!error); + // Tests should check this already at the end, but not all blocks may have elections (e.g from manual calls to confirmation_height_processor::add), this should catch any inconsistencies on live/beta though + if (!network_params.network.is_test_network ()) + { + // Bail if there was an error. This indicates that there was a fatal issue with the ledger + // (the blocks probably got rolled back when they shouldn't have). + auto blocks_confirmed_stats = ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed); + auto observer_stats = ledger.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out); + debug_assert (blocks_confirmed_stats == observer_stats); + + // Lower batch_write_size if it took too long to write that amount. + if (time_spent_cementing > maximum_batch_write_time) + { + // Reduce (unless we have hit a floor) + batch_write_size = std::max (minimum_batch_write_size, batch_write_size - amount_to_change); + } + } + + debug_assert (pending_writes.empty ()); + debug_assert (pending_writes_size == 0); + timer.restart (); +} + +bool nano::confirmation_height_bounded::pending_empty () const +{ + return pending_writes.empty (); +} + +void nano::confirmation_height_bounded::clear_process_vars () +{ + accounts_confirmed_info.clear (); + accounts_confirmed_info_size = 0; +} + +nano::confirmation_height_bounded::receive_chain_details::receive_chain_details (nano::account const & account_a, uint64_t height_a, nano::block_hash const & hash_a, nano::block_hash const & top_level_a, boost::optional next_a, uint64_t bottom_height_a, nano::block_hash const & bottom_most_a) : +account (account_a), +height (height_a), +hash (hash_a), +top_level (top_level_a), +next (next_a), +bottom_height (bottom_height_a), +bottom_most (bottom_most_a) +{ +} + +nano::confirmation_height_bounded::write_details::write_details (nano::account const & account_a, uint64_t bottom_height_a, nano::block_hash const & bottom_hash_a, uint64_t top_height_a, nano::block_hash const & top_hash_a) : +account (account_a), +bottom_height (bottom_height_a), +bottom_hash (bottom_hash_a), +top_height (top_height_a), +top_hash (top_hash_a) +{ +} + +nano::confirmation_height_bounded::receive_source_pair::receive_source_pair (confirmation_height_bounded::receive_chain_details const & receive_details_a, const block_hash & source_a) : +receive_details (receive_details_a), +source_hash (source_a) +{ +} + +nano::confirmation_height_bounded::confirmed_info::confirmed_info (uint64_t confirmed_height_a, nano::block_hash const & iterated_frontier_a) : +confirmed_height (confirmed_height_a), +iterated_frontier (iterated_frontier_a) +{ +} + +std::unique_ptr nano::collect_container_info (confirmation_height_bounded & confirmation_height_bounded, const std::string & name_a) +{ + auto composite = std::make_unique (name_a); + composite->add_component (std::make_unique (container_info{ "pending_writes", confirmation_height_bounded.pending_writes_size, sizeof (decltype (confirmation_height_bounded.pending_writes)::value_type) })); + composite->add_component (std::make_unique (container_info{ "accounts_confirmed_info", confirmation_height_bounded.accounts_confirmed_info_size, sizeof (decltype (confirmation_height_bounded.accounts_confirmed_info)::value_type) })); + return composite; +} diff --git a/nano/node/confirmation_height_bounded.hpp b/nano/node/confirmation_height_bounded.hpp new file mode 100644 index 0000000000..586ea464b5 --- /dev/null +++ b/nano/node/confirmation_height_bounded.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include + +#include + +namespace nano +{ +class ledger; +class read_transaction; +class logger_mt; +class write_database_queue; +class write_guard; + +class confirmation_height_bounded final +{ +public: + confirmation_height_bounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &, std::atomic &, nano::block_hash const &, uint64_t &, std::function> const &)> const &, std::function const &, std::function const &); + bool pending_empty () const; + void clear_process_vars (); + void process (); + void cement_blocks (nano::write_guard & scoped_write_guard_a); + +private: + class top_and_next_hash final + { + public: + nano::block_hash top; + boost::optional next; + uint64_t next_height; + }; + + class confirmed_info + { + public: + confirmed_info (uint64_t confirmed_height_a, nano::block_hash const & iterated_frontier); + uint64_t confirmed_height; + nano::block_hash iterated_frontier; + }; + + class write_details final + { + public: + write_details (nano::account const &, uint64_t, nano::block_hash const &, uint64_t, nano::block_hash const &); + nano::account account; + // This is the first block hash (bottom most) which is not cemented + uint64_t bottom_height; + nano::block_hash bottom_hash; + // Desired cemented frontier + uint64_t top_height; + nano::block_hash top_hash; + }; + + /** The maximum number of blocks to be read in while iterating over a long account chain */ + uint64_t const batch_read_size = 65536; + + /** The maximum number of various containers to keep the memory bounded */ + uint32_t const max_items{ 131072 }; + + // All of the atomic variables here just track the size for use in collect_container_info. + // This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed + // for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted + // upon in any way (does not synchronize with any other data). + // This allows the load and stores to use relaxed atomic memory ordering. + std::deque pending_writes; + nano::relaxed_atomic_integral pending_writes_size{ 0 }; + uint32_t const pending_writes_max_size{ max_items }; + /* Holds confirmation height/cemented frontier in memory for accounts while iterating */ + std::unordered_map accounts_confirmed_info; + nano::relaxed_atomic_integral accounts_confirmed_info_size{ 0 }; + + class receive_chain_details final + { + public: + receive_chain_details (nano::account const &, uint64_t, nano::block_hash const &, nano::block_hash const &, boost::optional, uint64_t, nano::block_hash const &); + nano::account account; + uint64_t height; + nano::block_hash hash; + nano::block_hash top_level; + boost::optional next; + uint64_t bottom_height; + nano::block_hash bottom_most; + }; + + class preparation_data final + { + public: + nano::transaction const & transaction; + nano::block_hash const & top_most_non_receive_block_hash; + bool already_cemented; + boost::circular_buffer_space_optimized & checkpoints; + decltype (accounts_confirmed_info.begin ()) account_it; + nano::confirmation_height_info const & confirmation_height_info; + nano::account const & account; + uint64_t bottom_height; + nano::block_hash const & bottom_most; + boost::optional & receive_details; + boost::optional & next_in_receive_chain; + }; + + class receive_source_pair final + { + public: + receive_source_pair (receive_chain_details const &, const nano::block_hash &); + + receive_chain_details receive_details; + nano::block_hash source_hash; + }; + + nano::timer timer; + + top_and_next_hash get_next_block (boost::optional const &, boost::circular_buffer_space_optimized const &, boost::circular_buffer_space_optimized const & receive_source_pairs, boost::optional &); + nano::block_hash get_least_unconfirmed_hash_from_top_level (nano::transaction const &, nano::block_hash const &, nano::account const &, nano::confirmation_height_info const &, uint64_t &); + void prepare_iterated_blocks_for_cementing (preparation_data &); + bool iterate (nano::read_transaction const &, uint64_t, nano::block_hash const &, boost::circular_buffer_space_optimized &, nano::block_hash &, nano::block_hash const &, boost::circular_buffer_space_optimized &, nano::account const &); + + nano::ledger & ledger; + nano::write_database_queue & write_database_queue; + std::chrono::milliseconds batch_separate_pending_min_time; + nano::logger_mt & logger; + std::atomic & stopped; + nano::block_hash const & original_hash; + uint64_t & batch_write_size; + std::function> const &)> notify_observers_callback; + std::function notify_block_already_cemented_observers_callback; + std::function awaiting_processing_size_callback; + nano::network_params network_params; + + friend std::unique_ptr collect_container_info (confirmation_height_bounded &, const std::string & name_a); +}; + +std::unique_ptr collect_container_info (confirmation_height_bounded &, const std::string & name_a); +} \ No newline at end of file diff --git a/nano/node/confirmation_height_processor.cpp b/nano/node/confirmation_height_processor.cpp index 3a19fde2f0..d05ffeabc4 100644 --- a/nano/node/confirmation_height_processor.cpp +++ b/nano/node/confirmation_height_processor.cpp @@ -1,30 +1,28 @@ #include #include -#include +#include #include -#include #include -#include #include -#include #include #include -#include +#include -#include #include -nano::confirmation_height_processor::confirmation_height_processor (nano::pending_confirmation_height & pending_confirmation_height_a, nano::ledger & ledger_a, nano::active_transactions & active_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger_mt & logger_a) : -pending_confirmations (pending_confirmation_height_a), +nano::confirmation_height_processor::confirmation_height_processor (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger_mt & logger_a, boost::latch & latch, confirmation_height_mode mode_a) : ledger (ledger_a), -active (active_a), -logger (logger_a), write_database_queue (write_database_queue_a), -batch_separate_pending_min_time (batch_separate_pending_min_time_a), -thread ([this]() { +// clang-format off +unbounded_processor (ledger_a, write_database_queue_a, batch_separate_pending_min_time_a, logger_a, stopped, original_hash, batch_write_size, [this](auto & cemented_blocks) { this->notify_observers (cemented_blocks); }, [this](auto const & block_hash_a) { this->notify_observers (block_hash_a); }, [this]() { return this->awaiting_processing_size (); }), +bounded_processor (ledger_a, write_database_queue_a, batch_separate_pending_min_time_a, logger_a, stopped, original_hash, batch_write_size, [this](auto & cemented_blocks) { this->notify_observers (cemented_blocks); }, [this](auto const & block_hash_a) { this->notify_observers (block_hash_a); }, [this]() { return this->awaiting_processing_size (); }), +// clang-format on +thread ([this, &latch, mode_a]() { nano::thread_role::set (nano::thread_role::name::confirmation_height_processing); - this->run (); + // Do not start running the processing thread until other threads have finished their operations + latch.wait (); + this->run (mode_a); }) { } @@ -36,7 +34,10 @@ nano::confirmation_height_processor::~confirmation_height_processor () void nano::confirmation_height_processor::stop () { - stopped = true; + { + nano::lock_guard guard (mutex); + stopped = true; + } condition.notify_one (); if (thread.joinable ()) { @@ -44,463 +45,184 @@ void nano::confirmation_height_processor::stop () } } -void nano::confirmation_height_processor::run () +void nano::confirmation_height_processor::run (confirmation_height_mode mode_a) { - nano::unique_lock lk (pending_confirmations.mutex); + nano::unique_lock lk (mutex); while (!stopped) { - if (!paused && !pending_confirmations.pending.empty ()) + if (!paused && !awaiting_processing.empty ()) { - pending_confirmations.current_hash = *pending_confirmations.pending.begin (); - pending_confirmations.pending.erase (pending_confirmations.current_hash); - // Copy the hash so can be used outside owning the lock - auto current_pending_block = pending_confirmations.current_hash; lk.unlock (); - if (pending_writes.empty ()) + if (bounded_processor.pending_empty () && unbounded_processor.pending_empty ()) { - // Separate blocks which are pending confirmation height can be batched by a minimum processing time (to improve disk write performance), so make sure the slate is clean when a new batch is starting. - confirmed_iterated_pairs.clear (); - timer.restart (); - } - add_confirmation_height (current_pending_block); - lk.lock (); - pending_confirmations.current_hash = 0; - } - else - { - // If there are no blocks pending confirmation, then make sure we flush out the remaining writes - if (!pending_writes.empty ()) - { - lk.unlock (); - auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); - write_pending (pending_writes); lk.lock (); + original_hashes_pending.clear (); + lk.unlock (); } - else - { - condition.wait (lk); - } - } - } -} - -void nano::confirmation_height_processor::pause () -{ - paused = true; -} - -void nano::confirmation_height_processor::unpause () -{ - paused = false; - condition.notify_one (); -} - -void nano::confirmation_height_processor::add (nano::block_hash const & hash_a) -{ - { - nano::lock_guard lk (pending_confirmations.mutex); - pending_confirmations.pending.insert (hash_a); - } - condition.notify_one (); -} - -/** - * For all the blocks below this height which have been implicitly confirmed check if they - * are open/receive blocks, and if so follow the source blocks and iteratively repeat to genesis. - * To limit write locking and to keep the confirmation height ledger correctly synced, confirmations are - * written from the ground upwards in batches. - */ -void nano::confirmation_height_processor::add_confirmation_height (nano::block_hash const & hash_a) -{ - boost::optional receive_details; - auto current = hash_a; - assert (receive_source_pairs_size == 0); - release_assert (receive_source_pairs.empty ()); - - auto read_transaction (ledger.store.tx_begin_read ()); - auto last_iteration = false; - // Traverse account chain and all sources for receive blocks iteratively - do - { - if (!receive_source_pairs.empty ()) - { - receive_details = receive_source_pairs.back ().receive_details; - current = receive_source_pairs.back ().source_hash; - } - else - { - // If receive_details is set then this is the final iteration and we are back to the original chain. - // We need to confirm any blocks below the original hash (incl self) and the first receive block - // (if the original block is not already a receive) - if (receive_details) - { - current = hash_a; - receive_details = boost::none; - last_iteration = true; - } - } - auto block_height (ledger.store.block_account_height (read_transaction, current)); - nano::account account (ledger.store.block_account (read_transaction, current)); - uint64_t confirmation_height; - release_assert (!ledger.store.confirmation_height_get (read_transaction, account, confirmation_height)); - auto iterated_height = confirmation_height; - auto account_it = confirmed_iterated_pairs.find (account); - if (account_it != confirmed_iterated_pairs.cend ()) - { - if (account_it->second.confirmed_height > confirmation_height) - { - confirmation_height = account_it->second.confirmed_height; - iterated_height = confirmation_height; - } - if (account_it->second.iterated_height > iterated_height) - { - iterated_height = account_it->second.iterated_height; - } - } + set_next_hash (); - if (!last_iteration && current == hash_a && confirmation_height >= block_height) - { - auto it = std::find_if (pending_writes.begin (), pending_writes.end (), [&hash_a](auto & conf_height_details) { - auto it = std::find_if (conf_height_details.block_callbacks_required.begin (), conf_height_details.block_callbacks_required.end (), [&hash_a](auto & callback_data) { - return callback_data.block->hash () == hash_a; - }); - return (it != conf_height_details.block_callbacks_required.end ()); - }); + const auto num_blocks_to_use_unbounded = confirmation_height::unbounded_cutoff; + auto blocks_within_automatic_unbounded_selection = (ledger.cache.block_count < num_blocks_to_use_unbounded || ledger.cache.block_count - num_blocks_to_use_unbounded < ledger.cache.cemented_count); - if (it == pending_writes.end ()) + // Don't want to mix up pending writes across different processors + auto valid_unbounded = (mode_a == confirmation_height_mode::automatic && blocks_within_automatic_unbounded_selection && bounded_processor.pending_empty ()); + auto force_unbounded = (!unbounded_processor.pending_empty () || mode_a == confirmation_height_mode::unbounded); + if (force_unbounded || valid_unbounded) { - // This is a block which has been added to the processor but already has its confirmation height set (or about to be set) - // Just need to perform active cleanup, no callbacks are needed. - active.clear_block (hash_a); + debug_assert (bounded_processor.pending_empty ()); + unbounded_processor.process (); } - } - - auto count_before_receive = receive_source_pairs.size (); - std::vector block_callbacks_required; - if (block_height > iterated_height) - { - if ((block_height - iterated_height) > 20000) + else { - logger.always_log ("Iterating over a large account chain for setting confirmation height. The top block: ", current.to_string ()); + debug_assert (mode_a == confirmation_height_mode::bounded || mode_a == confirmation_height_mode::automatic); + debug_assert (unbounded_processor.pending_empty ()); + bounded_processor.process (); } - collect_unconfirmed_receive_and_sources_for_account (block_height, iterated_height, current, account, read_transaction, block_callbacks_required); + lk.lock (); } - - // Exit early when the processor has been stopped, otherwise this function may take a - // while (and hence keep the process running) if updating a long chain. - if (stopped) + else { - break; - } - - // No longer need the read transaction - read_transaction.reset (); + auto lock_and_cleanup = [&lk, this]() { + lk.lock (); + original_hash.clear (); + original_hashes_pending.clear (); + bounded_processor.clear_process_vars (); + unbounded_processor.clear_process_vars (); + }; - // If this adds no more open or receive blocks, then we can now confirm this account as well as the linked open/receive block - // Collect as pending any writes to the database and do them in bulk after a certain time. - auto confirmed_receives_pending = (count_before_receive != receive_source_pairs.size ()); - if (!confirmed_receives_pending) - { - if (block_height > confirmation_height) + if (!paused) { - // Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them - if (account_it != confirmed_iterated_pairs.cend ()) + lk.unlock (); + + // If there are blocks pending cementing, then make sure we flush out the remaining writes + if (!bounded_processor.pending_empty ()) { - account_it->second.confirmed_height = block_height; - if (block_height > iterated_height) + debug_assert (unbounded_processor.pending_empty ()); { - account_it->second.iterated_height = block_height; + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + bounded_processor.cement_blocks (scoped_write_guard); } + lock_and_cleanup (); } - else - { - confirmed_iterated_pairs.emplace (account, confirmed_iterated_pair{ block_height, block_height }); - } - - pending_writes.emplace_back (account, current, block_height, block_height - confirmation_height, block_callbacks_required); - } - - if (receive_details) - { - // Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them - auto const & receive_account = receive_details->account; - auto receive_account_it = confirmed_iterated_pairs.find (receive_account); - if (receive_account_it != confirmed_iterated_pairs.cend ()) + else if (!unbounded_processor.pending_empty ()) { - // Get current height - auto current_height = receive_account_it->second.confirmed_height; - receive_account_it->second.confirmed_height = receive_details->height; - receive_details->num_blocks_confirmed = receive_details->height - current_height; + debug_assert (bounded_processor.pending_empty ()); + { + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + unbounded_processor.cement_blocks (scoped_write_guard); + } + lock_and_cleanup (); } else { - confirmed_iterated_pairs.emplace (receive_account, confirmed_iterated_pair{ receive_details->height, receive_details->height }); + lock_and_cleanup (); + condition.wait (lk); } - - pending_writes.push_back (*receive_details); - } - - if (!receive_source_pairs.empty ()) - { - // Pop from the end - receive_source_pairs.erase (receive_source_pairs.end () - 1); - --receive_source_pairs_size; - } - } - else if (block_height > iterated_height) - { - if (account_it != confirmed_iterated_pairs.cend ()) - { - account_it->second.iterated_height = block_height; } else { - confirmed_iterated_pairs.emplace (account, confirmed_iterated_pair{ confirmation_height, block_height }); - } - } - - auto max_write_size_reached = (pending_writes.size () >= batch_write_size); - // When there are a lot of pending confirmation height blocks, it is more efficient to - // bulk some of them up to enable better write performance which becomes the bottleneck. - auto min_time_exceeded = (timer.since_start () >= batch_separate_pending_min_time); - auto finished_iterating = receive_source_pairs.empty (); - auto no_pending = pending_confirmations.size () == 0; - auto should_output = finished_iterating && (no_pending || min_time_exceeded); - - if ((max_write_size_reached || should_output) && !pending_writes.empty ()) - { - if (write_database_queue.process (nano::writer::confirmation_height)) - { - auto scoped_write_guard = write_database_queue.pop (); - auto error = write_pending (pending_writes); - // Don't set any more blocks as confirmed from the original hash if an inconsistency is found - if (error) - { - break; - } + original_hash.clear (); + condition.wait (lk); } } - - read_transaction.renew (); - } while (!receive_source_pairs.empty () || current != hash_a); + } } -/* - * Returns true if there was an error in finding one of the blocks to write a confirmation height for, false otherwise - */ -bool nano::confirmation_height_processor::write_pending (std::deque & all_pending_a) +// Pausing only affects processing new blocks, not the current one being processed. Currently only used in tests +void nano::confirmation_height_processor::pause () { - auto total_pending_write_block_count = std::accumulate (all_pending_a.cbegin (), all_pending_a.cend (), uint64_t (0), [](uint64_t total, conf_height_details const & conf_height_details_a) { - return total += conf_height_details_a.num_blocks_confirmed; - }); - - // Write in batches - while (total_pending_write_block_count > 0) - { - uint64_t num_accounts_processed = 0; - auto transaction (ledger.store.tx_begin_write ({}, { nano::tables::confirmation_height })); - while (!all_pending_a.empty ()) - { - const auto & pending = all_pending_a.front (); - uint64_t confirmation_height; - auto error = ledger.store.confirmation_height_get (transaction, pending.account, confirmation_height); - release_assert (!error); - if (pending.height > confirmation_height) - { -#ifndef NDEBUG - // Do more thorough checking in Debug mode, indicates programming error. - nano::block_sideband sideband; - auto block = ledger.store.block_get (transaction, pending.hash, &sideband); - static nano::network_constants network_constants; - assert (network_constants.is_test_network () || block != nullptr); - assert (network_constants.is_test_network () || sideband.height == pending.height); -#else - auto block = ledger.store.block_get (transaction, pending.hash); -#endif - // Check that the block still exists as there may have been changes outside this processor. - if (!block) - { - logger.always_log ("Failed to write confirmation height for: ", pending.hash.to_string ()); - ledger.stats.inc (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block); - receive_source_pairs.clear (); - receive_source_pairs_size = 0; - all_pending_a.clear (); - return true; - } - - for (auto & callback_data : pending.block_callbacks_required) - { - active.post_confirmation_height_set (transaction, callback_data.block, callback_data.sideband, callback_data.election_status_type); - } - - ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in, pending.height - confirmation_height); - assert (pending.num_blocks_confirmed == pending.height - confirmation_height); - confirmation_height = pending.height; - ledger.cemented_count += pending.num_blocks_confirmed; - ledger.store.confirmation_height_put (transaction, pending.account, confirmation_height); - } - total_pending_write_block_count -= pending.num_blocks_confirmed; - ++num_accounts_processed; - all_pending_a.erase (all_pending_a.begin ()); - - if (num_accounts_processed >= batch_write_size) - { - // Commit changes periodically to reduce time holding write locks for long chains - break; - } - } - } - assert (all_pending_a.empty ()); - return false; + nano::lock_guard lk (mutex); + paused = true; } -void nano::confirmation_height_processor::collect_unconfirmed_receive_and_sources_for_account (uint64_t block_height_a, uint64_t confirmation_height_a, nano::block_hash const & hash_a, nano::account const & account_a, nano::read_transaction const & transaction_a, std::vector & block_callbacks_required) +void nano::confirmation_height_processor::unpause () { - auto hash (hash_a); - auto num_to_confirm = block_height_a - confirmation_height_a; - - // Store heights of blocks - constexpr auto height_not_set = std::numeric_limits::max (); - auto next_height = height_not_set; - while ((num_to_confirm > 0) && !hash.is_zero () && !stopped) { - nano::block_sideband sideband; - auto block (ledger.store.block_get (transaction_a, hash, &sideband)); - if (block) - { - if (!pending_confirmations.is_processing_block (hash)) - { - auto election_status_type = active.confirm_block (transaction_a, block); - if (election_status_type.is_initialized ()) - { - block_callbacks_required.emplace_back (block, sideband, *election_status_type); - } - } - else - { - // This block is the original which is having its confirmation height set on - block_callbacks_required.emplace_back (block, sideband, nano::election_status_type::active_confirmed_quorum); - } - - auto source (block->source ()); - if (source.is_zero ()) - { - source = block->link (); - } - - if (!source.is_zero () && !ledger.is_epoch_link (source) && ledger.store.source_exists (transaction_a, source)) - { - auto block_height = confirmation_height_a + num_to_confirm; - // Set the height for the receive block above (if there is one) - if (next_height != height_not_set) - { - receive_source_pairs.back ().receive_details.num_blocks_confirmed = next_height - block_height; - - auto & receive_callbacks_required = receive_source_pairs.back ().receive_details.block_callbacks_required; - - // Don't include the last one as that belongs to the next recieve - std::copy (block_callbacks_required.begin (), block_callbacks_required.end () - 1, std::back_inserter (receive_callbacks_required)); - block_callbacks_required = { block_callbacks_required.back () }; - } - - receive_source_pairs.emplace_back (conf_height_details{ account_a, hash, block_height, height_not_set, {} }, source); - ++receive_source_pairs_size; - next_height = block_height; - } - - hash = block->previous (); - } - - // We could be traversing a very large account so we don't want to open read transactions for too long. - if (num_to_confirm % batch_read_size == 0) - { - transaction_a.refresh (); - } - - --num_to_confirm; + nano::lock_guard lk (mutex); + paused = false; } + condition.notify_one (); +} - // Update the number of blocks confirmed by the last receive block - if (!receive_source_pairs.empty ()) +void nano::confirmation_height_processor::add (nano::block_hash const & hash_a) +{ { - auto & last_receive_details = receive_source_pairs.back ().receive_details; - last_receive_details.num_blocks_confirmed = last_receive_details.height - confirmation_height_a; - last_receive_details.block_callbacks_required = block_callbacks_required; + nano::lock_guard lk (mutex); + awaiting_processing.get ().emplace_back (hash_a); } + condition.notify_one (); } -namespace nano -{ -confirmation_height_processor::conf_height_details::conf_height_details (nano::account const & account_a, nano::block_hash const & hash_a, uint64_t height_a, uint64_t num_blocks_confirmed_a, std::vector const & block_callbacks_required_a) : -account (account_a), -hash (hash_a), -height (height_a), -num_blocks_confirmed (num_blocks_confirmed_a), -block_callbacks_required (block_callbacks_required_a) +void nano::confirmation_height_processor::set_next_hash () { + nano::lock_guard guard (mutex); + debug_assert (!awaiting_processing.empty ()); + original_hash = awaiting_processing.get ().front (); + original_hashes_pending.insert (original_hash); + awaiting_processing.get ().pop_front (); } -confirmation_height_processor::receive_source_pair::receive_source_pair (confirmation_height_processor::conf_height_details const & receive_details_a, const block_hash & source_a) : -receive_details (receive_details_a), -source_hash (source_a) +// Not thread-safe, only call before this processor has begun cementing +void nano::confirmation_height_processor::add_cemented_observer (std::function)> const & callback_a) { + cemented_observers.push_back (callback_a); } -confirmation_height_processor::confirmed_iterated_pair::confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a) : -confirmed_height (confirmed_height_a), iterated_height (iterated_height_a) +// Not thread-safe, only call before this processor has begun cementing +void nano::confirmation_height_processor::add_block_already_cemented_observer (std::function const & callback_a) { + block_already_cemented_observers.push_back (callback_a); } -confirmation_height_processor::callback_data::callback_data (std::shared_ptr const & block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a) : -block (block_a), -sideband (sideband_a), -election_status_type (election_status_type_a) +void nano::confirmation_height_processor::notify_observers (std::vector> const & cemented_blocks) { + for (auto const & block_callback_data : cemented_blocks) + { + for (auto const & observer : cemented_observers) + { + observer (block_callback_data); + } + } } -std::unique_ptr collect_seq_con_info (confirmation_height_processor & confirmation_height_processor_a, const std::string & name_a) +void nano::confirmation_height_processor::notify_observers (nano::block_hash const & hash_already_cemented_a) { - size_t receive_source_pairs_count = confirmation_height_processor_a.receive_source_pairs_size; - auto composite = std::make_unique (name_a); - composite->add_component (std::make_unique (seq_con_info{ "receive_source_pairs", receive_source_pairs_count, sizeof (decltype (confirmation_height_processor_a.receive_source_pairs)::value_type) })); - return composite; -} + for (auto const & observer : block_already_cemented_observers) + { + observer (hash_already_cemented_a); + } } -size_t nano::pending_confirmation_height::size () +std::unique_ptr nano::collect_container_info (confirmation_height_processor & confirmation_height_processor_a, const std::string & name_a) { - nano::lock_guard lk (mutex); - return pending.size (); + auto composite = std::make_unique (name_a); + + size_t cemented_observers_count = confirmation_height_processor_a.cemented_observers.size (); + size_t block_already_cemented_observers_count = confirmation_height_processor_a.block_already_cemented_observers.size (); + composite->add_component (std::make_unique (container_info{ "cemented_observers", cemented_observers_count, sizeof (decltype (confirmation_height_processor_a.cemented_observers)::value_type) })); + composite->add_component (std::make_unique (container_info{ "block_already_cemented_observers", block_already_cemented_observers_count, sizeof (decltype (confirmation_height_processor_a.block_already_cemented_observers)::value_type) })); + composite->add_component (std::make_unique (container_info{ "awaiting_processing", confirmation_height_processor_a.awaiting_processing_size (), sizeof (decltype (confirmation_height_processor_a.awaiting_processing)::value_type) })); + composite->add_component (collect_container_info (confirmation_height_processor_a.bounded_processor, "bounded_processor")); + composite->add_component (collect_container_info (confirmation_height_processor_a.unbounded_processor, "unbounded_processor")); + return composite; } -bool nano::pending_confirmation_height::is_processing_block (nano::block_hash const & hash_a) +size_t nano::confirmation_height_processor::awaiting_processing_size () { - // First check the hash currently being processed - nano::lock_guard lk (mutex); - if (!current_hash.is_zero () && current_hash == hash_a) - { - return true; - } - - // Check remaining pending confirmations - return pending.find (hash_a) != pending.cend (); + nano::lock_guard guard (mutex); + return awaiting_processing.size (); } -nano::block_hash nano::pending_confirmation_height::current () +bool nano::confirmation_height_processor::is_processing_block (nano::block_hash const & hash_a) { - nano::lock_guard lk (mutex); - return current_hash; + nano::lock_guard guard (mutex); + return original_hashes_pending.count (hash_a) > 0 || awaiting_processing.get ().count (hash_a) > 0; } -namespace nano -{ -std::unique_ptr collect_seq_con_info (pending_confirmation_height & pending_confirmation_height_a, const std::string & name_a) +nano::block_hash nano::confirmation_height_processor::current () { - size_t pending_count = pending_confirmation_height_a.size (); - auto composite = std::make_unique (name_a); - composite->add_component (std::make_unique (seq_con_info{ "pending", pending_count, sizeof (nano::block_hash) })); - return composite; -} + nano::lock_guard lk (mutex); + return original_hash; } diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index e1dfd28aad..60270a102a 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -1,125 +1,97 @@ #pragma once #include -#include +#include +#include #include #include +#include +#include +#include +#include + #include #include #include #include +namespace mi = boost::multi_index; +namespace boost +{ +class latch; +} namespace nano { class ledger; -class active_transactions; -class read_transaction; class logger_mt; class write_database_queue; -class pending_confirmation_height -{ -public: - size_t size (); - bool is_processing_block (nano::block_hash const &); - nano::block_hash current (); - -private: - std::mutex mutex; - std::unordered_set pending; - /** This is the last block popped off the confirmation height pending collection */ - nano::block_hash current_hash{ 0 }; - friend class confirmation_height_processor; - friend class confirmation_height_pending_observer_callbacks_Test; - friend class confirmation_height_dependent_election_Test; - friend class confirmation_height_dependent_election_after_already_cemented_Test; -}; - -std::unique_ptr collect_seq_con_info (pending_confirmation_height &, const std::string &); - class confirmation_height_processor final { public: - confirmation_height_processor (pending_confirmation_height &, nano::ledger &, nano::active_transactions &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &); + confirmation_height_processor (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &, boost::latch & initialized_latch, confirmation_height_mode = confirmation_height_mode::automatic); ~confirmation_height_processor (); - void add (nano::block_hash const &); - void stop (); void pause (); void unpause (); + void stop (); + void add (nano::block_hash const & hash_a); + void run (confirmation_height_mode); + size_t awaiting_processing_size (); + bool is_processing_block (nano::block_hash const &); + nano::block_hash current (); - /** The maximum amount of accounts to iterate over while writing */ - static uint64_t constexpr batch_write_size = 2048; - - /** The maximum number of blocks to be read in while iterating over a long account chain */ - static uint64_t constexpr batch_read_size = 4096; + void add_cemented_observer (std::function)> const &); + void add_block_already_cemented_observer (std::function const &); private: - class callback_data final - { - public: - callback_data (std::shared_ptr const &, nano::block_sideband const &, nano::election_status_type); - std::shared_ptr block; - nano::block_sideband sideband; - nano::election_status_type election_status_type; - }; - - class conf_height_details final - { - public: - conf_height_details (nano::account const &, nano::block_hash const &, uint64_t, uint64_t, std::vector const &); - - nano::account account; - nano::block_hash hash; - uint64_t height; - uint64_t num_blocks_confirmed; - std::vector block_callbacks_required; - }; - - class receive_source_pair final - { - public: - receive_source_pair (conf_height_details const &, const nano::block_hash &); - - conf_height_details receive_details; - nano::block_hash source_hash; - }; + std::mutex mutex; + // Hashes which have been added to the confirmation height processor, but not yet processed + // clang-format off + class tag_sequence {}; + class tag_hash {}; + boost::multi_index_container>, + mi::hashed_unique, + mi::identity>>> awaiting_processing; + // clang-format on + + // Hashes which have been added and processed, but have not been cemented + std::unordered_set original_hashes_pending; + bool paused{ false }; - class confirmed_iterated_pair - { - public: - confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a); - uint64_t confirmed_height; - uint64_t iterated_height; - }; + /** This is the last block popped off the confirmation height pending collection */ + nano::block_hash original_hash{ 0 }; nano::condition_variable condition; - nano::pending_confirmation_height & pending_confirmations; std::atomic stopped{ false }; - std::atomic paused{ false }; - nano::ledger & ledger; - nano::active_transactions & active; - nano::logger_mt & logger; - std::atomic receive_source_pairs_size{ 0 }; - std::vector receive_source_pairs; + // No mutex needed for the observers as these should be set up during initialization of the node + std::vector)>> cemented_observers; + std::vector> block_already_cemented_observers; - std::deque pending_writes; - // Store the highest confirmation heights for accounts in pending_writes to reduce unnecessary iterating, - // and iterated height to prevent iterating over the same blocks more than once from self-sends or "circular" sends between the same accounts. - std::unordered_map confirmed_iterated_pairs; - nano::timer timer; + nano::ledger & ledger; nano::write_database_queue & write_database_queue; - std::chrono::milliseconds batch_separate_pending_min_time; + /** The maximum amount of blocks to write at once. This is dynamically modified by the bounded processor based on previous write performance **/ + uint64_t batch_write_size{ 16384 }; + + confirmation_height_unbounded unbounded_processor; + confirmation_height_bounded bounded_processor; std::thread thread; - void run (); - void add_confirmation_height (nano::block_hash const &); - void collect_unconfirmed_receive_and_sources_for_account (uint64_t, uint64_t, nano::block_hash const &, nano::account const &, nano::read_transaction const &, std::vector &); - bool write_pending (std::deque &); + void set_next_hash (); + void notify_observers (std::vector> const &); + void notify_observers (nano::block_hash const &); - friend std::unique_ptr collect_seq_con_info (confirmation_height_processor &, const std::string &); + friend std::unique_ptr collect_container_info (confirmation_height_processor &, const std::string &); friend class confirmation_height_pending_observer_callbacks_Test; + friend class confirmation_height_dependent_election_Test; + friend class confirmation_height_dependent_election_after_already_cemented_Test; + friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test; + friend class confirmation_height_many_accounts_many_confirmations_Test; + friend class confirmation_height_long_chains_Test; + friend class confirmation_height_many_accounts_single_confirmation_Test; }; -std::unique_ptr collect_seq_con_info (confirmation_height_processor &, const std::string &); +std::unique_ptr collect_container_info (confirmation_height_processor &, const std::string &); } diff --git a/nano/node/confirmation_height_unbounded.cpp b/nano/node/confirmation_height_unbounded.cpp new file mode 100644 index 0000000000..9cd29f02ea --- /dev/null +++ b/nano/node/confirmation_height_unbounded.cpp @@ -0,0 +1,476 @@ +#include +#include +#include +#include + +#include + +#include + +nano::confirmation_height_unbounded::confirmation_height_unbounded (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger_mt & logger_a, std::atomic & stopped_a, nano::block_hash const & original_hash_a, uint64_t & batch_write_size_a, std::function> const &)> const & notify_observers_callback_a, std::function const & notify_block_already_cemented_observers_callback_a, std::function const & awaiting_processing_size_callback_a) : +ledger (ledger_a), +write_database_queue (write_database_queue_a), +batch_separate_pending_min_time (batch_separate_pending_min_time_a), +logger (logger_a), +stopped (stopped_a), +original_hash (original_hash_a), +batch_write_size (batch_write_size_a), +notify_observers_callback (notify_observers_callback_a), +notify_block_already_cemented_observers_callback (notify_block_already_cemented_observers_callback_a), +awaiting_processing_size_callback (awaiting_processing_size_callback_a) +{ +} + +void nano::confirmation_height_unbounded::process () +{ + if (pending_empty ()) + { + clear_process_vars (); + timer.restart (); + } + std::shared_ptr receive_details; + auto current = original_hash; + std::vector orig_block_callback_data; + + std::vector receive_source_pairs; + release_assert (receive_source_pairs.empty ()); + + bool first_iter = true; + auto read_transaction (ledger.store.tx_begin_read ()); + + do + { + if (!receive_source_pairs.empty ()) + { + receive_details = receive_source_pairs.back ().receive_details; + current = receive_source_pairs.back ().source_hash; + } + else + { + // If receive_details is set then this is the final iteration and we are back to the original chain. + // We need to confirm any blocks below the original hash (incl self) and the first receive block + // (if the original block is not already a receive) + if (receive_details) + { + current = original_hash; + receive_details = nullptr; + } + } + + auto block (get_block_and_sideband (current, read_transaction)); + if (!block) + { + auto error_str = (boost::format ("Ledger mismatch trying to set confirmation height for block %1% (unbounded processor)") % current.to_string ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + } + release_assert (block); + + nano::account account (block->account ()); + if (account.is_zero ()) + { + account = block->sideband ().account; + } + + auto block_height = block->sideband ().height; + uint64_t confirmation_height = 0; + auto account_it = confirmed_iterated_pairs.find (account); + if (account_it != confirmed_iterated_pairs.cend ()) + { + confirmation_height = account_it->second.confirmed_height; + } + else + { + nano::confirmation_height_info confirmation_height_info; + release_assert (!ledger.store.confirmation_height_get (read_transaction, account, confirmation_height_info)); + confirmation_height = confirmation_height_info.height; + + // This block was added to the confirmation height processor but is already confirmed + if (first_iter && confirmation_height >= block_height && current == original_hash) + { + notify_block_already_cemented_observers_callback (original_hash); + } + } + auto iterated_height = confirmation_height; + if (account_it != confirmed_iterated_pairs.cend () && account_it->second.iterated_height > iterated_height) + { + iterated_height = account_it->second.iterated_height; + } + + auto count_before_receive = receive_source_pairs.size (); + std::vector block_callback_datas_required; + auto already_traversed = iterated_height >= block_height; + if (!already_traversed) + { + collect_unconfirmed_receive_and_sources_for_account (block_height, iterated_height, current, account, read_transaction, receive_source_pairs, block_callback_datas_required, orig_block_callback_data); + } + + // Exit early when the processor has been stopped, otherwise this function may take a + // while (and hence keep the process running) if updating a long chain. + if (stopped) + { + break; + } + + // No longer need the read transaction + read_transaction.reset (); + + // If this adds no more open or receive blocks, then we can now confirm this account as well as the linked open/receive block + // Collect as pending any writes to the database and do them in bulk after a certain time. + auto confirmed_receives_pending = (count_before_receive != receive_source_pairs.size ()); + if (!confirmed_receives_pending) + { + preparation_data preparation_data{ block_height, confirmation_height, iterated_height, account_it, account, receive_details, already_traversed, current, block_callback_datas_required, orig_block_callback_data }; + prepare_iterated_blocks_for_cementing (preparation_data); + + if (!receive_source_pairs.empty ()) + { + // Pop from the end + receive_source_pairs.erase (receive_source_pairs.end () - 1); + } + } + else if (block_height > iterated_height) + { + if (account_it != confirmed_iterated_pairs.cend ()) + { + account_it->second.iterated_height = block_height; + } + else + { + confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (account), std::forward_as_tuple (confirmation_height, block_height)); + ++confirmed_iterated_pairs_size; + } + } + + auto max_write_size_reached = (pending_writes.size () >= confirmation_height::unbounded_cutoff); + // When there are a lot of pending confirmation height blocks, it is more efficient to + // bulk some of them up to enable better write performance which becomes the bottleneck. + auto min_time_exceeded = (timer.since_start () >= batch_separate_pending_min_time); + auto finished_iterating = receive_source_pairs.empty (); + auto no_pending = awaiting_processing_size_callback () == 0; + auto should_output = finished_iterating && (no_pending || min_time_exceeded); + + auto total_pending_write_block_count = std::accumulate (pending_writes.cbegin (), pending_writes.cend (), uint64_t (0), [](uint64_t total, conf_height_details const & receive_details_a) { + return total += receive_details_a.num_blocks_confirmed; + }); + auto force_write = total_pending_write_block_count > batch_write_size; + + if ((max_write_size_reached || should_output || force_write) && !pending_writes.empty ()) + { + if (write_database_queue.process (nano::writer::confirmation_height)) + { + auto scoped_write_guard = write_database_queue.pop (); + cement_blocks (scoped_write_guard); + } + else if (force_write) + { + // Unbounded processor has grown too large, force a write + auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height); + cement_blocks (scoped_write_guard); + } + } + + first_iter = false; + read_transaction.renew (); + } while ((!receive_source_pairs.empty () || current != original_hash) && !stopped); +} + +void nano::confirmation_height_unbounded::collect_unconfirmed_receive_and_sources_for_account (uint64_t block_height_a, uint64_t confirmation_height_a, nano::block_hash const & hash_a, nano::account const & account_a, nano::read_transaction const & transaction_a, std::vector & receive_source_pairs_a, std::vector & block_callback_data_a, std::vector & orig_block_callback_data_a) +{ + auto hash (hash_a); + auto num_to_confirm = block_height_a - confirmation_height_a; + + // Handle any sends above a receive + auto is_original_block = (hash == original_hash); + bool hit_receive = false; + while ((num_to_confirm > 0) && !hash.is_zero () && !stopped) + { + auto block (get_block_and_sideband (hash, transaction_a)); + if (block) + { + auto source (block->source ()); + if (source.is_zero ()) + { + source = block->link (); + } + + if (!source.is_zero () && !ledger.is_epoch_link (source) && ledger.store.source_exists (transaction_a, source)) + { + if (!hit_receive && !block_callback_data_a.empty ()) + { + // Add the callbacks to the associated receive to retrieve later + debug_assert (!receive_source_pairs_a.empty ()); + auto & last_receive_details = receive_source_pairs_a.back ().receive_details; + last_receive_details->source_block_callback_data.assign (block_callback_data_a.begin (), block_callback_data_a.end ()); + block_callback_data_a.clear (); + } + + is_original_block = false; + hit_receive = true; + + auto block_height = confirmation_height_a + num_to_confirm; + receive_source_pairs_a.emplace_back (std::make_shared (account_a, hash, block_height, 1, std::vector{ hash }), source); + } + else if (is_original_block) + { + orig_block_callback_data_a.push_back (hash); + } + else + { + if (!hit_receive) + { + // This block is cemented via a recieve, as opposed to below a receive being cemented + block_callback_data_a.push_back (hash); + } + else + { + // We have hit a receive before, add the block to it + auto & last_receive_details = receive_source_pairs_a.back ().receive_details; + ++last_receive_details->num_blocks_confirmed; + last_receive_details->block_callback_data.push_back (hash); + + implicit_receive_cemented_mapping[hash] = std::weak_ptr (last_receive_details); + implicit_receive_cemented_mapping_size = implicit_receive_cemented_mapping.size (); + } + } + + hash = block->previous (); + } + + --num_to_confirm; + } +} + +void nano::confirmation_height_unbounded::prepare_iterated_blocks_for_cementing (preparation_data & preparation_data_a) +{ + auto receive_details = preparation_data_a.receive_details; + auto block_height = preparation_data_a.block_height; + if (block_height > preparation_data_a.confirmation_height) + { + // Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them + if (preparation_data_a.account_it != confirmed_iterated_pairs.cend ()) + { + preparation_data_a.account_it->second.confirmed_height = block_height; + if (block_height > preparation_data_a.iterated_height) + { + preparation_data_a.account_it->second.iterated_height = block_height; + } + } + else + { + confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (preparation_data_a.account), std::forward_as_tuple (block_height, block_height)); + ++confirmed_iterated_pairs_size; + } + + auto num_blocks_confirmed = block_height - preparation_data_a.confirmation_height; + auto block_callback_data = preparation_data_a.block_callback_data; + if (block_callback_data.empty ()) + { + if (!receive_details) + { + block_callback_data = preparation_data_a.orig_block_callback_data; + } + else + { + debug_assert (receive_details); + + if (preparation_data_a.already_traversed && receive_details->source_block_callback_data.empty ()) + { + // We are confirming a block which has already been traversed and found no associated receive details for it. + auto & above_receive_details_w = implicit_receive_cemented_mapping[preparation_data_a.current]; + debug_assert (!above_receive_details_w.expired ()); + auto above_receive_details = above_receive_details_w.lock (); + + auto num_blocks_already_confirmed = above_receive_details->num_blocks_confirmed - (above_receive_details->height - preparation_data_a.confirmation_height); + + auto end_it = above_receive_details->block_callback_data.begin () + above_receive_details->block_callback_data.size () - (num_blocks_already_confirmed); + auto start_it = end_it - num_blocks_confirmed; + + block_callback_data.assign (start_it, end_it); + } + else + { + block_callback_data = receive_details->source_block_callback_data; + } + + auto num_to_remove = block_callback_data.size () - num_blocks_confirmed; + block_callback_data.erase (std::next (block_callback_data.rbegin (), num_to_remove).base (), block_callback_data.end ()); + receive_details->source_block_callback_data.clear (); + } + } + + pending_writes.emplace_back (preparation_data_a.account, preparation_data_a.current, block_height, num_blocks_confirmed, block_callback_data); + ++pending_writes_size; + } + + if (receive_details) + { + // Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them + auto const & receive_account = receive_details->account; + auto receive_account_it = confirmed_iterated_pairs.find (receive_account); + if (receive_account_it != confirmed_iterated_pairs.cend ()) + { + // Get current height + auto current_height = receive_account_it->second.confirmed_height; + receive_account_it->second.confirmed_height = receive_details->height; + auto const orig_num_blocks_confirmed = receive_details->num_blocks_confirmed; + receive_details->num_blocks_confirmed = receive_details->height - current_height; + + // Get the difference and remove the callbacks + auto block_callbacks_to_remove = orig_num_blocks_confirmed - receive_details->num_blocks_confirmed; + receive_details->block_callback_data.erase (std::next (receive_details->block_callback_data.rbegin (), block_callbacks_to_remove).base (), receive_details->block_callback_data.end ()); + debug_assert (receive_details->block_callback_data.size () == receive_details->num_blocks_confirmed); + } + else + { + confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (receive_account), std::forward_as_tuple (receive_details->height, receive_details->height)); + ++confirmed_iterated_pairs_size; + } + + pending_writes.push_back (*receive_details); + ++pending_writes_size; + } +} + +/* + * Returns true if there was an error in finding one of the blocks to write a confirmation height for, false otherwise + */ +void nano::confirmation_height_unbounded::cement_blocks (nano::write_guard & scoped_write_guard_a) +{ + nano::timer cemented_batch_timer; + std::vector> cemented_blocks; + auto error = false; + { + auto transaction (ledger.store.tx_begin_write ({}, { nano::tables::confirmation_height })); + cemented_batch_timer.start (); + while (!pending_writes.empty ()) + { + auto & pending = pending_writes.front (); + nano::confirmation_height_info confirmation_height_info; + error = ledger.store.confirmation_height_get (transaction, pending.account, confirmation_height_info); + if (error) + { + auto error_str = (boost::format ("Failed to read confirmation height for account %1% when writing block %2% (unbounded processor)") % pending.account.to_account () % pending.hash.to_string ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + } + auto confirmation_height = confirmation_height_info.height; + if (!error && pending.height > confirmation_height) + { + auto block = ledger.store.block_get (transaction, pending.hash); + debug_assert (network_params.network.is_test_network () || block != nullptr); + debug_assert (network_params.network.is_test_network () || block->sideband ().height == pending.height); + + if (!block) + { + auto error_str = (boost::format ("Failed to write confirmation height for block %1% (unbounded processor)") % pending.hash.to_string ()).str (); + logger.always_log (error_str); + std::cerr << error_str << std::endl; + error = true; + break; + } + ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in, pending.height - confirmation_height); + ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in, pending.height - confirmation_height); + debug_assert (pending.num_blocks_confirmed == pending.height - confirmation_height); + confirmation_height = pending.height; + ledger.cache.cemented_count += pending.num_blocks_confirmed; + ledger.store.confirmation_height_put (transaction, pending.account, { confirmation_height, pending.hash }); + + // Reverse it so that the callbacks start from the lowest newly cemented block and move upwards + std::reverse (pending.block_callback_data.begin (), pending.block_callback_data.end ()); + + std::transform (pending.block_callback_data.begin (), pending.block_callback_data.end (), std::back_inserter (cemented_blocks), [& block_cache = block_cache](auto const & hash_a) { + debug_assert (block_cache.find (hash_a) != block_cache.end ()); + return block_cache.at (hash_a); + }); + } + pending_writes.erase (pending_writes.begin ()); + --pending_writes_size; + } + } + + auto time_spent_cementing = cemented_batch_timer.since_start ().count (); + if (time_spent_cementing > 50) + { + logger.always_log (boost::str (boost::format ("Cemented %1% blocks in %2% %3% (unbounded processor)") % cemented_blocks.size () % time_spent_cementing % cemented_batch_timer.unit ())); + } + + scoped_write_guard_a.release (); + notify_observers_callback (cemented_blocks); + release_assert (!error); + + // Tests should check this already at the end, but not all blocks may have elections (e.g from manual calls to confirmation_height_processor::add), this should catch any inconsistencies on live/beta though + if (!network_params.network.is_test_network ()) + { + auto blocks_confirmed_stats = ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed); + auto observer_stats = ledger.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out); + debug_assert (blocks_confirmed_stats == observer_stats); + } + debug_assert (pending_writes.empty ()); + debug_assert (pending_writes_size == 0); + timer.restart (); +} + +std::shared_ptr nano::confirmation_height_unbounded::get_block_and_sideband (nano::block_hash const & hash_a, nano::transaction const & transaction_a) +{ + auto block_cache_it = block_cache.find (hash_a); + if (block_cache_it != block_cache.cend ()) + { + return block_cache_it->second; + } + else + { + auto block (ledger.store.block_get (transaction_a, hash_a)); + block_cache.emplace (hash_a, block); + ++block_cache_size; + return block; + } +} + +bool nano::confirmation_height_unbounded::pending_empty () const +{ + return pending_writes.empty (); +} + +void nano::confirmation_height_unbounded::clear_process_vars () +{ + // Separate blocks which are pending confirmation height can be batched by a minimum processing time (to improve lmdb disk write performance), + // so make sure the slate is clean when a new batch is starting. + confirmed_iterated_pairs.clear (); + confirmed_iterated_pairs_size = 0; + implicit_receive_cemented_mapping.clear (); + implicit_receive_cemented_mapping_size = 0; + block_cache.clear (); + block_cache_size = 0; +} + +nano::confirmation_height_unbounded::conf_height_details::conf_height_details (nano::account const & account_a, nano::block_hash const & hash_a, uint64_t height_a, uint64_t num_blocks_confirmed_a, std::vector const & block_callback_data_a) : +account (account_a), +hash (hash_a), +height (height_a), +num_blocks_confirmed (num_blocks_confirmed_a), +block_callback_data (block_callback_data_a) +{ +} + +nano::confirmation_height_unbounded::receive_source_pair::receive_source_pair (std::shared_ptr const & receive_details_a, const block_hash & source_a) : +receive_details (receive_details_a), +source_hash (source_a) +{ +} + +nano::confirmation_height_unbounded::confirmed_iterated_pair::confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a) : +confirmed_height (confirmed_height_a), +iterated_height (iterated_height_a) +{ +} + +std::unique_ptr nano::collect_container_info (confirmation_height_unbounded & confirmation_height_unbounded, const std::string & name_a) +{ + auto composite = std::make_unique (name_a); + composite->add_component (std::make_unique (container_info{ "confirmed_iterated_pairs", confirmation_height_unbounded.confirmed_iterated_pairs_size, sizeof (decltype (confirmation_height_unbounded.confirmed_iterated_pairs)::value_type) })); + composite->add_component (std::make_unique (container_info{ "pending_writes", confirmation_height_unbounded.pending_writes_size, sizeof (decltype (confirmation_height_unbounded.pending_writes)::value_type) })); + composite->add_component (std::make_unique (container_info{ "implicit_receive_cemented_mapping", confirmation_height_unbounded.implicit_receive_cemented_mapping_size, sizeof (decltype (confirmation_height_unbounded.implicit_receive_cemented_mapping)::value_type) })); + composite->add_component (std::make_unique (container_info{ "block_cache", confirmation_height_unbounded.block_cache_size, sizeof (decltype (confirmation_height_unbounded.block_cache)::value_type) })); + return composite; +} diff --git a/nano/node/confirmation_height_unbounded.hpp b/nano/node/confirmation_height_unbounded.hpp new file mode 100644 index 0000000000..8a8d1aa197 --- /dev/null +++ b/nano/node/confirmation_height_unbounded.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace nano +{ +class ledger; +class read_transaction; +class logger_mt; +class write_database_queue; +class write_guard; + +class confirmation_height_unbounded final +{ +public: + confirmation_height_unbounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &, std::atomic &, nano::block_hash const &, uint64_t &, std::function> const &)> const &, std::function const &, std::function const &); + bool pending_empty () const; + void clear_process_vars (); + void process (); + void cement_blocks (nano::write_guard &); + +private: + class confirmed_iterated_pair + { + public: + confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a); + uint64_t confirmed_height; + uint64_t iterated_height; + }; + + class conf_height_details final + { + public: + conf_height_details (nano::account const &, nano::block_hash const &, uint64_t, uint64_t, std::vector const &); + + nano::account account; + nano::block_hash hash; + uint64_t height; + uint64_t num_blocks_confirmed; + std::vector block_callback_data; + std::vector source_block_callback_data; + }; + + class receive_source_pair final + { + public: + receive_source_pair (std::shared_ptr const &, const nano::block_hash &); + + std::shared_ptr receive_details; + nano::block_hash source_hash; + }; + + // All of the atomic variables here just track the size for use in collect_container_info. + // This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed + // for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted + // upon in any way (does not synchronize with any other data). + // This allows the load and stores to use relaxed atomic memory ordering. + std::unordered_map confirmed_iterated_pairs; + nano::relaxed_atomic_integral confirmed_iterated_pairs_size{ 0 }; + std::unordered_map> block_cache; + nano::relaxed_atomic_integral block_cache_size{ 0 }; + std::shared_ptr get_block_and_sideband (nano::block_hash const &, nano::transaction const &); + std::deque pending_writes; + nano::relaxed_atomic_integral pending_writes_size{ 0 }; + std::unordered_map> implicit_receive_cemented_mapping; + nano::relaxed_atomic_integral implicit_receive_cemented_mapping_size{ 0 }; + + nano::timer timer; + + class preparation_data final + { + public: + uint64_t block_height; + uint64_t confirmation_height; + uint64_t iterated_height; + decltype (confirmed_iterated_pairs.begin ()) account_it; + nano::account const & account; + std::shared_ptr receive_details; + bool already_traversed; + nano::block_hash const & current; + std::vector const & block_callback_data; + std::vector const & orig_block_callback_data; + }; + + void collect_unconfirmed_receive_and_sources_for_account (uint64_t, uint64_t, nano::block_hash const &, nano::account const &, nano::read_transaction const &, std::vector &, std::vector &, std::vector &); + void prepare_iterated_blocks_for_cementing (preparation_data &); + + nano::network_params network_params; + nano::ledger & ledger; + nano::write_database_queue & write_database_queue; + std::chrono::milliseconds batch_separate_pending_min_time; + nano::logger_mt & logger; + std::atomic & stopped; + nano::block_hash const & original_hash; + uint64_t & batch_write_size; + + std::function> const &)> notify_observers_callback; + std::function notify_block_already_cemented_observers_callback; + std::function awaiting_processing_size_callback; + + friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test; + friend std::unique_ptr collect_container_info (confirmation_height_unbounded &, const std::string & name_a); +}; + +std::unique_ptr collect_container_info (confirmation_height_unbounded &, const std::string & name_a); +} diff --git a/nano/node/confirmation_solicitor.cpp b/nano/node/confirmation_solicitor.cpp new file mode 100644 index 0000000000..eba41a2129 --- /dev/null +++ b/nano/node/confirmation_solicitor.cpp @@ -0,0 +1,105 @@ +#include +#include + +using namespace std::chrono_literals; + +nano::confirmation_solicitor::confirmation_solicitor (nano::network & network_a, nano::network_constants const & params_a) : +max_confirm_req_batches (params_a.is_test_network () ? 1 : 20), +max_block_broadcasts (params_a.is_test_network () ? 4 : 30), +max_election_requests (30), +max_election_broadcasts (std::max (network_a.fanout () / 2, 1)), +network (network_a) +{ +} + +void nano::confirmation_solicitor::prepare (std::vector const & representatives_a) +{ + debug_assert (!prepared); + requests.clear (); + rebroadcasted = 0; + /** Two copies are required as representatives can be erased from \p representatives_requests */ + representatives_requests = representatives_a; + representatives_broadcasts = representatives_a; + prepared = true; +} + +bool nano::confirmation_solicitor::broadcast (nano::election const & election_a) +{ + debug_assert (prepared); + bool error (true); + if (rebroadcasted++ < max_block_broadcasts) + { + auto const & hash (election_a.status.winner->hash ()); + nano::publish winner (election_a.status.winner); + unsigned count = 0; + // Directed broadcasting to principal representatives + for (auto i (representatives_broadcasts.begin ()), n (representatives_broadcasts.end ()); i != n && count < max_election_broadcasts; ++i) + { + auto existing (election_a.last_votes.find (i->account)); + if (existing == election_a.last_votes.end () || existing->second.hash != hash) + { + i->channel->send (winner); + ++count; + } + } + // Random flood for block propagation + network.flood_message (winner, nano::buffer_drop_policy::limiter, 0.5f); + error = false; + } + return error; +} + +bool nano::confirmation_solicitor::add (nano::election const & election_a) +{ + debug_assert (prepared); + auto const max_channel_requests (max_confirm_req_batches * nano::network::confirm_req_hashes_max); + unsigned count = 0; + auto const & hash (election_a.status.winner->hash ()); + for (auto i (representatives_requests.begin ()); i != representatives_requests.end () && count < max_election_requests;) + { + bool full_queue (false); + auto rep (*i); + auto existing (election_a.last_votes.find (rep.account)); + if (existing == election_a.last_votes.end () || existing->second.hash != hash) + { + auto & request_queue (requests[rep.channel]); + if (request_queue.size () < max_channel_requests) + { + request_queue.emplace_back (election_a.status.winner->hash (), election_a.status.winner->root ()); + ++count; + } + else + { + full_queue = true; + } + } + i = !full_queue ? i + 1 : representatives_requests.erase (i); + } + return count == 0; +} + +void nano::confirmation_solicitor::flush () +{ + debug_assert (prepared); + for (auto const & request_queue : requests) + { + auto const & channel (request_queue.first); + std::vector> roots_hashes_l; + for (auto const & root_hash : request_queue.second) + { + roots_hashes_l.push_back (root_hash); + if (roots_hashes_l.size () == nano::network::confirm_req_hashes_max) + { + nano::confirm_req req (roots_hashes_l); + channel->send (req); + roots_hashes_l.clear (); + } + } + if (!roots_hashes_l.empty ()) + { + nano::confirm_req req (roots_hashes_l); + channel->send (req); + } + } + prepared = false; +} diff --git a/nano/node/confirmation_solicitor.hpp b/nano/node/confirmation_solicitor.hpp new file mode 100644 index 0000000000..bce99b3233 --- /dev/null +++ b/nano/node/confirmation_solicitor.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +namespace nano +{ +class election; +class node; +/** This class accepts elections that need further votes before they can be confirmed and bundles them in to single confirm_req packets */ +class confirmation_solicitor final +{ +public: + confirmation_solicitor (nano::network &, nano::network_constants const &); + /** Prepare object for batching election confirmation requests*/ + void prepare (std::vector const &); + /** Broadcast the winner of an election if the broadcast limit has not been reached. Returns false if the broadcast was performed */ + bool broadcast (nano::election const &); + /** Add an election that needs to be confirmed. Returns false if successfully added */ + bool add (nano::election const &); + /** Dispatch bundled requests to each channel*/ + void flush (); + /** Maximum amount of confirmation requests (batches) to be sent to each channel */ + size_t const max_confirm_req_batches; + /** Global maximum amount of block broadcasts */ + size_t const max_block_broadcasts; + /** Maximum amount of requests to be sent per election */ + size_t const max_election_requests; + /** Maximum amount of directed broadcasts to be sent per election */ + size_t const max_election_broadcasts; + +private: + nano::network & network; + + unsigned rebroadcasted{ 0 }; + std::vector representatives_requests; + std::vector representatives_broadcasts; + using vector_root_hashes = std::vector>; + std::unordered_map, vector_root_hashes> requests; + bool prepared{ false }; +}; +} diff --git a/nano/node/daemonconfig.cpp b/nano/node/daemonconfig.cpp index 2c18f76466..7b58ca4c85 100644 --- a/nano/node/daemonconfig.cpp +++ b/nano/node/daemonconfig.cpp @@ -135,9 +135,7 @@ nano::error nano::daemon_config::deserialize_json (bool & upgraded_a, nano::json return json.get_error (); } -namespace nano -{ -nano::error read_node_config_toml (boost::filesystem::path const & data_path_a, nano::daemon_config & config_a, std::vector const & config_overrides) +nano::error nano::read_node_config_toml (boost::filesystem::path const & data_path_a, nano::daemon_config & config_a, std::vector const & config_overrides) { nano::error error; auto json_config_path = nano::get_config_path (data_path_a); @@ -225,7 +223,7 @@ nano::error read_node_config_toml (boost::filesystem::path const & data_path_a, } else { - toml.read (config_overrides_stream); + error = toml.read (config_overrides_stream); } } @@ -237,7 +235,7 @@ nano::error read_node_config_toml (boost::filesystem::path const & data_path_a, return error; } -nano::error read_and_update_daemon_config (boost::filesystem::path const & data_path, nano::daemon_config & config_a, nano::jsonconfig & json_a) +nano::error nano::read_and_update_daemon_config (boost::filesystem::path const & data_path, nano::daemon_config & config_a, nano::jsonconfig & json_a) { boost::system::error_code error_chmod; auto config_path = nano::get_config_path (data_path); @@ -245,4 +243,3 @@ nano::error read_and_update_daemon_config (boost::filesystem::path const & data_ nano::set_secure_perm_file (config_path, error_chmod); return error; } -} diff --git a/nano/node/distributed_work.cpp b/nano/node/distributed_work.cpp index 1425581c65..dd315c5d6c 100644 --- a/nano/node/distributed_work.cpp +++ b/nano/node/distributed_work.cpp @@ -1,12 +1,18 @@ +#include +#include #include #include #include -std::shared_ptr nano::work_peer_request::get_prepared_json_request (std::string const & request_string_a) const +#include + +std::shared_ptr nano::distributed_work::peer_request::get_prepared_json_request (std::string const & request_string_a) const { - auto request (std::make_shared> ()); + auto request (std::make_shared ()); request->method (boost::beast::http::verb::post); request->set (boost::beast::http::field::content_type, "application/json"); + auto address_string = boost::algorithm::erase_first_copy (endpoint.address ().to_string (), "::ffff:"); + request->set (boost::beast::http::field::host, address_string); request->target ("/"); request->version (11); request->body () = request_string_a; @@ -14,250 +20,250 @@ std::shared_ptr nano::work_peer_request::get_prepared_json_request return request; } -nano::distributed_work::distributed_work (nano::node & node_a, nano::root const & root_a, std::vector> const & peers_a, unsigned int backoff_a, std::function)> const & callback_a, uint64_t difficulty_a, boost::optional const & account_a) : -callback (callback_a), -backoff (backoff_a), +nano::distributed_work::distributed_work (nano::node & node_a, nano::work_request const & request_a, std::chrono::seconds const & backoff_a) : node (node_a), -root (root_a), -account (account_a), -peers (peers_a), -need_resolve (peers_a), -difficulty (difficulty_a), +node_w (node_a.shared ()), +request (request_a), +backoff (backoff_a), +strand (node_a.io_ctx.get_executor ()), +need_resolve (request_a.peers), elapsed (nano::timer_state::started, "distributed work generation timer") { - assert (!completed); + debug_assert (!finished); + debug_assert (status == work_generation_status::ongoing); } nano::distributed_work::~distributed_work () { - if (!node.stopped && node.websocket_server && node.websocket_server->any_subscriber (nano::websocket::topic::work)) + debug_assert (status != work_generation_status::ongoing); + if (auto node_l = node_w.lock ()) { - nano::websocket::message_builder builder; - if (completed) - { - node.websocket_server->broadcast (builder.work_generation (root, work_result, difficulty, node.network_params.network.publish_threshold, elapsed.value (), winner, bad_peers)); - } - else if (cancelled) + if (!node_l->stopped && node_l->websocket_server && node_l->websocket_server->any_subscriber (nano::websocket::topic::work)) { - node.websocket_server->broadcast (builder.work_cancelled (root, difficulty, node.network_params.network.publish_threshold, elapsed.value (), bad_peers)); - } - else - { - node.websocket_server->broadcast (builder.work_failed (root, difficulty, node.network_params.network.publish_threshold, elapsed.value (), bad_peers)); + nano::websocket::message_builder builder; + if (status == work_generation_status::success) + { + node_l->websocket_server->broadcast (builder.work_generation (request.version, request.root, work_result, request.difficulty, node_l->default_difficulty (request.version), elapsed.value (), winner, bad_peers)); + } + else if (status == work_generation_status::cancelled) + { + node_l->websocket_server->broadcast (builder.work_cancelled (request.version, request.root, request.difficulty, node_l->default_difficulty (request.version), elapsed.value (), bad_peers)); + } + else if (status == work_generation_status::failure_local || status == work_generation_status::failure_peers) + { + node_l->websocket_server->broadcast (builder.work_failed (request.version, request.root, request.difficulty, node_l->default_difficulty (request.version), elapsed.value (), bad_peers)); + } } + stop_once (true); } - stop_once (true); } void nano::distributed_work::start () { - if (need_resolve.empty ()) + // Start work generation if peers are not acting correctly, or if there are no peers configured + if ((need_resolve.empty () || node.unresponsive_work_peers) && node.local_work_generation_enabled ()) + { + start_local (); + } + // Fallback when local generation is required but it is not enabled is to simply call the callback with an error + else if (need_resolve.empty () && request.callback) { - start_work (); + status = work_generation_status::failure_local; + request.callback (boost::none); } - else + for (auto const & peer : need_resolve) { - auto current (need_resolve.back ()); - need_resolve.pop_back (); - auto this_l (shared_from_this ()); boost::system::error_code ec; - auto parsed_address (boost::asio::ip::address_v6::from_string (current.first, ec)); + auto parsed_address (boost::asio::ip::make_address_v6 (peer.first, ec)); if (!ec) { - outstanding[parsed_address] = current.second; - start (); + do_request (nano::tcp_endpoint (parsed_address, peer.second)); } else { - node.network.resolver.async_resolve (boost::asio::ip::udp::resolver::query (current.first, std::to_string (current.second)), [current, this_l](boost::system::error_code const & ec, boost::asio::ip::udp::resolver::iterator i_a) { + auto this_l (shared_from_this ()); + node.network.resolver.async_resolve (boost::asio::ip::udp::resolver::query (peer.first, std::to_string (peer.second)), [peer, this_l, &extra = resolved_extra](boost::system::error_code const & ec, boost::asio::ip::udp::resolver::iterator i_a) { if (!ec) { - for (auto i (i_a), n (boost::asio::ip::udp::resolver::iterator{}); i != n; ++i) + this_l->do_request (nano::tcp_endpoint (i_a->endpoint ().address (), i_a->endpoint ().port ())); + ++i_a; + for (auto & i : boost::make_iterator_range (i_a, {})) { - auto endpoint (i->endpoint ()); - this_l->outstanding[endpoint.address ()] = endpoint.port (); + ++extra; + this_l->do_request (nano::tcp_endpoint (i.endpoint ().address (), i.endpoint ().port ())); } } else { - this_l->node.logger.try_log (boost::str (boost::format ("Error resolving work peer: %1%:%2%: %3%") % current.first % current.second % ec.message ())); + this_l->node.logger.try_log (boost::str (boost::format ("Error resolving work peer: %1%:%2%: %3%") % peer.first % peer.second % ec.message ())); + this_l->failure (); } - this_l->start (); }); } } } -void nano::distributed_work::start_work () +void nano::distributed_work::start_local () { auto this_l (shared_from_this ()); - - // Start work generation if peers are not acting correctly, or if there are no peers configured - if ((outstanding.empty () || node.unresponsive_work_peers) && node.local_work_generation_enabled ()) - { - local_generation_started = true; - node.work.generate ( - root, [this_l](boost::optional const & work_a) { - if (work_a.is_initialized ()) - { - this_l->set_once (*work_a); - } - else if (!this_l->cancelled && !this_l->completed) + local_generation_started = true; + node.work.generate (request.version, request.root, request.difficulty, [this_l](boost::optional const & work_a) { + if (work_a.is_initialized ()) + { + this_l->set_once (*work_a); + } + else if (!this_l->finished.exchange (true)) + { + this_l->status = work_generation_status::failure_local; + if (this_l->request.callback) { - this_l->callback (boost::none); + this_l->request.callback (boost::none); } - this_l->stop_once (false); - }, - difficulty); - } + } + this_l->stop_once (false); + }); +} - if (!outstanding.empty ()) +void nano::distributed_work::do_request (nano::tcp_endpoint const & endpoint_a) +{ + auto this_l (shared_from_this ()); + auto connection (std::make_shared (node.io_ctx, endpoint_a)); { - nano::lock_guard guard (mutex); - for (auto const & i : outstanding) + nano::lock_guard lock (mutex); + connections.emplace_back (connection); + } + connection->socket.async_connect (connection->endpoint, + boost::asio::bind_executor (strand, + [this_l, connection](boost::system::error_code const & ec) { + if (!ec && !this_l->stopped) { - auto host (i.first); - auto service (i.second); - auto connection (std::make_shared (this_l->node.io_ctx, host, service)); - connections.emplace_back (connection); - connection->socket.async_connect (nano::tcp_endpoint (host, service), [this_l, connection](boost::system::error_code const & ec) { - if (!ec) + std::string request_string; + { + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", this_l->request.root.to_string ()); + request.put ("difficulty", nano::to_string_hex (this_l->request.difficulty)); + if (this_l->request.account.is_initialized ()) { - std::string request_string; - { - boost::property_tree::ptree request; - request.put ("action", "work_generate"); - request.put ("hash", this_l->root.to_string ()); - request.put ("difficulty", nano::to_string_hex (this_l->difficulty)); - if (this_l->account.is_initialized ()) - { - request.put ("account", this_l->account.get ().to_account ()); - } - std::stringstream ostream; - boost::property_tree::write_json (ostream, request); - request_string = ostream.str (); - } - auto request (connection->get_prepared_json_request (request_string)); - boost::beast::http::async_write (connection->socket, *request, [this_l, connection, request](boost::system::error_code const & ec, size_t bytes_transferred) { - if (!ec) + request.put ("account", this_l->request.account.get ().to_account ()); + } + std::stringstream ostream; + boost::property_tree::write_json (ostream, request); + request_string = ostream.str (); + } + auto peer_request (connection->get_prepared_json_request (request_string)); + boost::beast::http::async_write (connection->socket, *peer_request, + boost::asio::bind_executor (this_l->strand, + [this_l, connection, peer_request](boost::system::error_code const & ec, size_t size_a) { + if (!ec && !this_l->stopped) + { + boost::beast::http::async_read (connection->socket, connection->buffer, connection->response, + boost::asio::bind_executor (this_l->strand, [this_l, connection](boost::system::error_code const & ec, size_t size_a) { + if (!ec && !this_l->stopped) { - boost::beast::http::async_read (connection->socket, connection->buffer, connection->response, [this_l, connection](boost::system::error_code const & ec, size_t bytes_transferred) { - if (!ec) - { - if (connection->response.result () == boost::beast::http::status::ok) - { - this_l->success (connection->response.body (), connection->address, connection->port); - } - else - { - this_l->node.logger.try_log (boost::str (boost::format ("Work peer responded with an error %1% %2%: %3%") % connection->address % connection->port % connection->response.result ())); - this_l->add_bad_peer (connection->address, connection->port); - this_l->failure (connection->address); - } - } - else if (ec == boost::system::errc::operation_canceled) - { - // The only case where we send a cancel is if we preempt stopped waiting for the response - this_l->cancel_connection (connection); - this_l->failure (connection->address); - } - else - { - this_l->node.logger.try_log (boost::str (boost::format ("Unable to read from work_peer %1% %2%: %3% (%4%)") % connection->address % connection->port % ec.message () % ec.value ())); - this_l->add_bad_peer (connection->address, connection->port); - this_l->failure (connection->address); - } - }); + if (connection->response.result () == boost::beast::http::status::ok) + { + this_l->success (connection->response.body (), connection->endpoint); + } + else if (ec) + { + this_l->node.logger.try_log (boost::str (boost::format ("Work peer responded with an error %1% %2%: %3%") % connection->endpoint.address () % connection->endpoint.port () % connection->response.result ())); + this_l->add_bad_peer (connection->endpoint); + this_l->failure (); + } } - else + else if (ec) { - this_l->node.logger.try_log (boost::str (boost::format ("Unable to write to work_peer %1% %2%: %3% (%4%)") % connection->address % connection->port % ec.message () % ec.value ())); - this_l->add_bad_peer (connection->address, connection->port); - this_l->failure (connection->address); + this_l->do_cancel (connection->endpoint); + this_l->failure (); } - }); + })); } - else + else if (ec && ec != boost::system::errc::operation_canceled) { - this_l->node.logger.try_log (boost::str (boost::format ("Unable to connect to work_peer %1% %2%: %3% (%4%)") % connection->address % connection->port % ec.message () % ec.value ())); - this_l->add_bad_peer (connection->address, connection->port); - this_l->failure (connection->address); + this_l->node.logger.try_log (boost::str (boost::format ("Unable to write to work_peer %1% %2%: %3% (%4%)") % connection->endpoint.address () % connection->endpoint.port () % ec.message () % ec.value ())); + this_l->add_bad_peer (connection->endpoint); + this_l->failure (); } - }); + })); } - } - - if (!local_generation_started && outstanding.empty ()) - { - callback (boost::none); - } + else if (ec && ec != boost::system::errc::operation_canceled) + { + this_l->node.logger.try_log (boost::str (boost::format ("Unable to connect to work_peer %1% %2%: %3% (%4%)") % connection->endpoint.address () % connection->endpoint.port () % ec.message () % ec.value ())); + this_l->add_bad_peer (connection->endpoint); + this_l->failure (); + } + })); } -void nano::distributed_work::cancel_connection (std::shared_ptr connection_a) +void nano::distributed_work::do_cancel (nano::tcp_endpoint const & endpoint_a) { auto this_l (shared_from_this ()); - auto cancelling_l (std::make_shared (node.io_ctx, connection_a->address, connection_a->port)); - cancelling_l->socket.async_connect (nano::tcp_endpoint (cancelling_l->address, cancelling_l->port), [this_l, cancelling_l](boost::system::error_code const & ec) { + auto cancelling_l (std::make_shared (node.io_ctx, endpoint_a)); + cancelling_l->socket.async_connect (cancelling_l->endpoint, + boost::asio::bind_executor (strand, + [this_l, cancelling_l](boost::system::error_code const & ec) { if (!ec) { std::string request_string; { boost::property_tree::ptree request; request.put ("action", "work_cancel"); - request.put ("hash", this_l->root.to_string ()); + request.put ("hash", this_l->request.root.to_string ()); std::stringstream ostream; boost::property_tree::write_json (ostream, request); request_string = ostream.str (); } - auto request (cancelling_l->get_prepared_json_request (request_string)); - boost::beast::http::async_write (cancelling_l->socket, *request, [this_l, request, cancelling_l](boost::system::error_code const & ec, size_t bytes_transferred) { - if (ec) + auto peer_cancel (cancelling_l->get_prepared_json_request (request_string)); + boost::beast::http::async_write (cancelling_l->socket, *peer_cancel, + boost::asio::bind_executor (this_l->strand, + [this_l, peer_cancel, cancelling_l](boost::system::error_code const & ec, size_t bytes_transferred) { + if (ec && ec != boost::system::errc::operation_canceled) { - this_l->node.logger.try_log (boost::str (boost::format ("Unable to send work_cancel to work_peer %1% %2%: %3% (%4%)") % cancelling_l->address % cancelling_l->port % ec.message () % ec.value ())); + this_l->node.logger.try_log (boost::str (boost::format ("Unable to send work_cancel to work_peer %1% %2%: %3% (%4%)") % cancelling_l->endpoint.address () % cancelling_l->endpoint.port () % ec.message () % ec.value ())); } - }); + })); } - }); + })); } -void nano::distributed_work::success (std::string const & body_a, boost::asio::ip::address const & address_a, uint16_t port_a) +void nano::distributed_work::success (std::string const & body_a, nano::tcp_endpoint const & endpoint_a) { - auto last (remove (address_a)); - std::stringstream istream (body_a); + bool error = true; try { + std::stringstream istream (body_a); boost::property_tree::ptree result; boost::property_tree::read_json (istream, result); auto work_text (result.get ("work")); uint64_t work; if (!nano::from_string_hex (work_text, work)) { - uint64_t result_difficulty (0); - if (!nano::work_validate (root, work, &result_difficulty) && result_difficulty >= difficulty) + if (nano::work_difficulty (request.version, request.root, work) >= request.difficulty) { + error = false; node.unresponsive_work_peers = false; - set_once (work, boost::str (boost::format ("%1%:%2%") % address_a % port_a)); + set_once (work, boost::str (boost::format ("%1%:%2%") % endpoint_a.address () % endpoint_a.port ())); stop_once (true); } else { - node.logger.try_log (boost::str (boost::format ("Incorrect work response from %1%:%2% for root %3% with diffuculty %4%: %5%") % address_a % port_a % root.to_string () % nano::to_string_hex (difficulty) % work_text)); - add_bad_peer (address_a, port_a); - handle_failure (last); + node.logger.try_log (boost::str (boost::format ("Incorrect work response from %1%:%2% for root %3% with diffuculty %4%: %5%") % endpoint_a.address () % endpoint_a.port () % request.root.to_string () % nano::to_string_hex (request.difficulty) % work_text)); } } else { - node.logger.try_log (boost::str (boost::format ("Work response from %1%:%2% wasn't a number: %3%") % address_a % port_a % work_text)); - add_bad_peer (address_a, port_a); - handle_failure (last); + node.logger.try_log (boost::str (boost::format ("Work response from %1%:%2% wasn't a number: %3%") % endpoint_a.address () % endpoint_a.port () % work_text)); } } catch (...) { - node.logger.try_log (boost::str (boost::format ("Work response from %1%:%2% wasn't parsable: %3%") % address_a % port_a % body_a)); - add_bad_peer (address_a, port_a); - handle_failure (last); + node.logger.try_log (boost::str (boost::format ("Work response from %1%:%2% wasn't parsable: %3%") % endpoint_a.address () % endpoint_a.port () % body_a)); + } + if (error) + { + add_bad_peer (endpoint_a); + failure (); } } @@ -268,97 +274,111 @@ void nano::distributed_work::stop_once (bool const local_stop_a) nano::lock_guard guard (mutex); if (local_stop_a && node.local_work_generation_enabled ()) { - node.work.cancel (root); + node.work.cancel (request.root); } for (auto & connection_w : connections) { if (auto connection_l = connection_w.lock ()) { - boost::system::error_code ec; - connection_l->socket.cancel (ec); - if (ec) - { - node.logger.try_log (boost::str (boost::format ("Error cancelling operation with work_peer %1% %2%: %3%") % connection_l->address % connection_l->port % ec.message () % ec.value ())); - } - try - { - connection_l->socket.close (); - } - catch (const boost::system::system_error & ec) - { - node.logger.try_log (boost::str (boost::format ("Error closing socket with work_peer %1% %2%: %3%") % connection_l->address % connection_l->port % ec.what () % ec.code ())); - } + auto this_l (shared_from_this ()); + boost::asio::post (strand, boost::asio::bind_executor (strand, [this_l, connection_l] { + boost::system::error_code ec; + if (connection_l->socket.is_open ()) + { + connection_l->socket.cancel (ec); + if (!ec) + { + connection_l->socket.close (ec); + if (ec) + { + this_l->node.logger.try_log (boost::str (boost::format ("Error closing socket with work_peer %1% %2%: %3%") % connection_l->endpoint.address () % connection_l->endpoint.port () % ec.message () % ec.value ())); + } + } + else + { + this_l->node.logger.try_log (boost::str (boost::format ("Error cancelling operation with work_peer %1% %2%: %3%") % connection_l->endpoint.address () % connection_l->endpoint.port () % ec.message () % ec.value ())); + } + } + })); } } connections.clear (); - outstanding.clear (); } } -void nano::distributed_work::set_once (uint64_t work_a, std::string const & source_a) +void nano::distributed_work::set_once (uint64_t const work_a, std::string const & source_a) { - if (!cancelled && !completed.exchange (true)) + if (!finished.exchange (true)) { elapsed.stop (); - callback (work_a); + status = work_generation_status::success; + if (request.callback) + { + request.callback (work_a); + } winner = source_a; work_result = work_a; if (node.config.logging.work_generation_time ()) { boost::format unformatted_l ("Work generation for %1%, with a threshold difficulty of %2% (multiplier %3%x) complete: %4% ms"); - auto multiplier_text_l (nano::to_string (nano::difficulty::to_multiplier (difficulty, node.network_params.network.publish_threshold), 2)); - node.logger.try_log (boost::str (unformatted_l % root.to_string () % nano::to_string_hex (difficulty) % multiplier_text_l % elapsed.value ().count ())); + auto multiplier_text_l (nano::to_string (nano::difficulty::to_multiplier (request.difficulty, node.default_difficulty (request.version)), 2)); + node.logger.try_log (boost::str (unformatted_l % request.root.to_string () % nano::to_string_hex (request.difficulty) % multiplier_text_l % elapsed.value ().count ())); } } } -void nano::distributed_work::cancel_once () +void nano::distributed_work::cancel () { - if (!completed && !cancelled.exchange (true)) + if (!finished.exchange (true)) { elapsed.stop (); - callback (boost::none); + status = work_generation_status::cancelled; + if (request.callback) + { + request.callback (boost::none); + } stop_once (true); if (node.config.logging.work_generation_time ()) { - node.logger.try_log (boost::str (boost::format ("Work generation for %1% was cancelled after %2% ms") % root.to_string () % elapsed.value ().count ())); + node.logger.try_log (boost::str (boost::format ("Work generation for %1% was cancelled after %2% ms") % request.root.to_string () % elapsed.value ().count ())); } } } -void nano::distributed_work::failure (boost::asio::ip::address const & address_a) +void nano::distributed_work::failure () { - auto last (remove (address_a)); - handle_failure (last); + if (++failures == need_resolve.size () + resolved_extra.load ()) + { + handle_failure (); + } } -void nano::distributed_work::handle_failure (bool const last_a) +void nano::distributed_work::handle_failure () { - if (last_a && !completed && !cancelled) + if (!finished) { node.unresponsive_work_peers = true; - if (!local_generation_started) + if (!local_generation_started && !finished.exchange (true)) { - if (backoff == 1 && node.config.logging.work_generation_time ()) + status = work_generation_status::failure_peers; + if (backoff == std::chrono::seconds (1) && node.config.logging.work_generation_time ()) { - node.logger.always_log ("Work peer(s) failed to generate work for root ", root.to_string (), ", retrying..."); + node.logger.always_log ("Work peer(s) failed to generate work for root ", request.root.to_string (), ", retrying..."); } auto now (std::chrono::steady_clock::now ()); std::weak_ptr node_w (node.shared ()); - auto next_backoff (std::min (backoff * 2, (unsigned int)60 * 5)); - // clang-format off - node.alarm.add (now + std::chrono::seconds (backoff), [ node_w, root_l = root, peers_l = peers, callback_l = callback, next_backoff, difficulty = difficulty, account_l = account ] { - bool error_l {true}; + auto next_backoff (std::min (backoff * 2, std::chrono::seconds (5 * 60))); + node.alarm.add (now + std::chrono::seconds (backoff), [node_w, request_l = request, next_backoff] { + bool error_l{ true }; if (auto node_l = node_w.lock ()) { - error_l = node_l->distributed_work.make (next_backoff, root_l, peers_l, callback_l, difficulty, account_l); + error_l = node_l->distributed_work.make (next_backoff, request_l); } - if (error_l && callback_l) + if (error_l && request_l.callback) { - callback_l (boost::none); + request_l.callback (boost::none); } }); - // clang-format on } else { @@ -367,127 +387,8 @@ void nano::distributed_work::handle_failure (bool const last_a) } } -bool nano::distributed_work::remove (boost::asio::ip::address const & address_a) +void nano::distributed_work::add_bad_peer (nano::tcp_endpoint const & endpoint_a) { nano::lock_guard guard (mutex); - outstanding.erase (address_a); - return outstanding.empty (); -} - -void nano::distributed_work::add_bad_peer (boost::asio::ip::address const & address_a, uint16_t port_a) -{ - nano::lock_guard guard (mutex); - bad_peers.emplace_back (boost::str (boost::format ("%1%:%2%") % address_a % port_a)); -} - -nano::distributed_work_factory::distributed_work_factory (nano::node & node_a) : -node (node_a) -{ -} - -nano::distributed_work_factory::~distributed_work_factory () -{ - stop (); -} - -bool nano::distributed_work_factory::make (nano::root const & root_a, std::vector> const & peers_a, std::function)> const & callback_a, uint64_t difficulty_a, boost::optional const & account_a) -{ - return make (1, root_a, peers_a, callback_a, difficulty_a, account_a); -} - -bool nano::distributed_work_factory::make (unsigned int backoff_a, nano::root const & root_a, std::vector> const & peers_a, std::function)> const & callback_a, uint64_t difficulty_a, boost::optional const & account_a) -{ - bool error_l{ true }; - if (!stopped) - { - cleanup_finished (); - if (node.work_generation_enabled ()) - { - auto distributed (std::make_shared (node, root_a, peers_a, backoff_a, callback_a, difficulty_a, account_a)); - { - nano::lock_guard guard (mutex); - items[root_a].emplace_back (distributed); - } - distributed->start (); - error_l = false; - } - } - return error_l; -} - -void nano::distributed_work_factory::cancel (nano::root const & root_a, bool const local_stop) -{ - nano::lock_guard guard_l (mutex); - auto existing_l (items.find (root_a)); - if (existing_l != items.end ()) - { - for (auto & distributed_w : existing_l->second) - { - if (auto distributed_l = distributed_w.lock ()) - { - // Send work_cancel to work peers and stop local work generation - distributed_l->cancel_once (); - } - } - items.erase (existing_l); - } -} - -void nano::distributed_work_factory::cleanup_finished () -{ - nano::lock_guard guard (mutex); - for (auto it (items.begin ()), end (items.end ()); it != end;) - { - it->second.erase (std::remove_if (it->second.begin (), it->second.end (), [](auto distributed_a) { - return distributed_a.expired (); - }), - it->second.end ()); - - if (it->second.empty ()) - { - it = items.erase (it); - } - else - { - ++it; - } - } -} - -void nano::distributed_work_factory::stop () -{ - if (!stopped.exchange (true)) - { - // Cancel any ongoing work - std::unordered_set roots_l; - nano::unique_lock lock_l (mutex); - for (auto const & item_l : items) - { - roots_l.insert (item_l.first); - } - lock_l.unlock (); - for (auto const & root_l : roots_l) - { - cancel (root_l, true); - } - lock_l.lock (); - items.clear (); - } -} - -namespace nano -{ -std::unique_ptr collect_seq_con_info (distributed_work_factory & distributed_work, const std::string & name) -{ - size_t item_count = 0; - { - nano::lock_guard guard (distributed_work.mutex); - item_count = distributed_work.items.size (); - } - - auto composite = std::make_unique (name); - auto sizeof_item_element = sizeof (decltype (distributed_work.items)::value_type); - composite->add_component (std::make_unique (seq_con_info{ "items", item_count, sizeof_item_element })); - return composite; + bad_peers.emplace_back (boost::str (boost::format ("%1%:%2%") % endpoint_a.address () % endpoint_a.port ())); } -} \ No newline at end of file diff --git a/nano/node/distributed_work.hpp b/nano/node/distributed_work.hpp index d7feb993f1..29a26ba8c5 100644 --- a/nano/node/distributed_work.hpp +++ b/nano/node/distributed_work.hpp @@ -1,35 +1,40 @@ #pragma once -#include -#include +#include +#include +#include +#include #include #include +#include +#include #include -#include +#include using request_type = boost::beast::http::request; +namespace boost +{ +namespace asio +{ + class io_context; +} +} + namespace nano { class node; -class work_peer_request final +struct work_request final { -public: - work_peer_request (boost::asio::io_context & io_ctx_a, boost::asio::ip::address address_a, uint16_t port_a) : - address (address_a), - port (port_a), - socket (io_ctx_a) - { - } - std::shared_ptr get_prepared_json_request (std::string const &) const; - boost::asio::ip::address address; - uint16_t port; - boost::beast::flat_buffer buffer; - boost::beast::http::response response; - boost::asio::ip::tcp::socket socket; + nano::work_version version; + nano::root root; + uint64_t difficulty; + boost::optional const account; + std::function)> callback; + std::vector> const peers; }; /** @@ -37,59 +42,73 @@ class work_peer_request final */ class distributed_work final : public std::enable_shared_from_this { + enum class work_generation_status + { + ongoing, + success, + cancelled, + failure_local, + failure_peers + }; + + class peer_request final + { + public: + peer_request (boost::asio::io_context & io_ctx_a, nano::tcp_endpoint const & endpoint_a) : + endpoint (endpoint_a), + socket (io_ctx_a) + { + } + std::shared_ptr get_prepared_json_request (std::string const &) const; + nano::tcp_endpoint const endpoint; + boost::beast::flat_buffer buffer; + boost::beast::http::response response; + boost::asio::ip::tcp::socket socket; + }; + public: - distributed_work (nano::node &, nano::root const &, std::vector> const & peers_a, unsigned int, std::function)> const &, uint64_t, boost::optional const & = boost::none); + distributed_work (nano::node &, nano::work_request const &, std::chrono::seconds const &); ~distributed_work (); void start (); - void start_work (); - void cancel_connection (std::shared_ptr); - void success (std::string const &, boost::asio::ip::address const &, uint16_t const); + void cancel (); + +private: + void start_local (); + /** Send a work_generate message to \p endpoint_a and handle a response */ + void do_request (nano::tcp_endpoint const & endpoint_a); + /** Send a work_cancel message using a new connection to \p endpoint_a */ + void do_cancel (nano::tcp_endpoint const & endpoint_a); + /** Called on a successful peer response, validates the reply */ + void success (std::string const &, nano::tcp_endpoint const &); + /** Send a work_cancel message to all remaining connections */ void stop_once (bool const); - void set_once (uint64_t, std::string const & source_a = "local"); - void cancel_once (); - void failure (boost::asio::ip::address const &); - void handle_failure (bool const); - bool remove (boost::asio::ip::address const &); - void add_bad_peer (boost::asio::ip::address const &, uint16_t const); + void set_once (uint64_t const, std::string const & source_a = "local"); + void failure (); + void handle_failure (); + void add_bad_peer (nano::tcp_endpoint const &); - std::function)> callback; - unsigned int backoff; // in seconds nano::node & node; - nano::root root; - boost::optional const account; - std::mutex mutex; - std::map outstanding; - std::vector> connections; - std::vector> const peers; - std::vector> need_resolve; - uint64_t difficulty; + // Only used in destructor, as the node reference can become invalid before distributed_work objects go out of scope + std::weak_ptr node_w; + nano::work_request request; + + std::chrono::seconds backoff; + boost::asio::strand strand; + std::vector> const need_resolve; + std::vector> connections; // protected by the mutex + + work_generation_status status{ work_generation_status::ongoing }; uint64_t work_result{ 0 }; - std::atomic completed{ false }; - std::atomic cancelled{ false }; - std::atomic stopped{ false }; - std::atomic local_generation_started{ false }; + nano::timer elapsed; // logging only std::vector bad_peers; // websocket std::string winner; // websocket -}; - -class distributed_work_factory final -{ -public: - distributed_work_factory (nano::node &); - ~distributed_work_factory (); - bool make (nano::root const &, std::vector> const &, std::function)> const &, uint64_t, boost::optional const & = boost::none); - bool make (unsigned int, nano::root const &, std::vector> const &, std::function)> const &, uint64_t, boost::optional const & = boost::none); - void cancel (nano::root const &, bool const local_stop = false); - void cleanup_finished (); - void stop (); - nano::node & node; - std::unordered_map>> items; std::mutex mutex; + std::atomic resolved_extra{ 0 }; + std::atomic failures{ 0 }; + std::atomic finished{ false }; std::atomic stopped{ false }; + std::atomic local_generation_started{ false }; }; - -class seq_con_info_component; -std::unique_ptr collect_seq_con_info (distributed_work_factory & distributed_work, const std::string & name); } \ No newline at end of file diff --git a/nano/node/distributed_work_factory.cpp b/nano/node/distributed_work_factory.cpp new file mode 100644 index 0000000000..69703a5411 --- /dev/null +++ b/nano/node/distributed_work_factory.cpp @@ -0,0 +1,111 @@ +#include +#include + +nano::distributed_work_factory::distributed_work_factory (nano::node & node_a) : +node (node_a) +{ +} + +nano::distributed_work_factory::~distributed_work_factory () +{ + stop (); +} + +bool nano::distributed_work_factory::make (nano::work_version const version_a, nano::root const & root_a, std::vector> const & peers_a, uint64_t difficulty_a, std::function)> const & callback_a, boost::optional const & account_a) +{ + return make (std::chrono::seconds (1), nano::work_request{ version_a, root_a, difficulty_a, account_a, callback_a, peers_a }); +} + +bool nano::distributed_work_factory::make (std::chrono::seconds const & backoff_a, nano::work_request const & request_a) +{ + bool error_l{ true }; + if (!stopped) + { + cleanup_finished (); + if (node.work_generation_enabled (request_a.peers)) + { + auto distributed (std::make_shared (node, request_a, backoff_a)); + { + nano::lock_guard guard (mutex); + items[request_a.root].emplace_back (distributed); + } + distributed->start (); + error_l = false; + } + } + return error_l; +} + +void nano::distributed_work_factory::cancel (nano::root const & root_a, bool const local_stop) +{ + nano::lock_guard guard_l (mutex); + auto existing_l (items.find (root_a)); + if (existing_l != items.end ()) + { + for (auto & distributed_w : existing_l->second) + { + if (auto distributed_l = distributed_w.lock ()) + { + // Send work_cancel to work peers and stop local work generation + distributed_l->cancel (); + } + } + items.erase (existing_l); + } +} + +void nano::distributed_work_factory::cleanup_finished () +{ + nano::lock_guard guard (mutex); + for (auto it (items.begin ()), end (items.end ()); it != end;) + { + it->second.erase (std::remove_if (it->second.begin (), it->second.end (), [](auto distributed_a) { + return distributed_a.expired (); + }), + it->second.end ()); + + if (it->second.empty ()) + { + it = items.erase (it); + } + else + { + ++it; + } + } +} + +void nano::distributed_work_factory::stop () +{ + if (!stopped.exchange (true)) + { + // Cancel any ongoing work + std::unordered_set roots_l; + nano::unique_lock lock_l (mutex); + for (auto const & item_l : items) + { + roots_l.insert (item_l.first); + } + lock_l.unlock (); + for (auto const & root_l : roots_l) + { + cancel (root_l, true); + } + lock_l.lock (); + items.clear (); + } +} + +std::unique_ptr nano::collect_container_info (distributed_work_factory & distributed_work, const std::string & name) +{ + size_t item_count; + { + nano::lock_guard guard (distributed_work.mutex); + item_count = distributed_work.items.size (); + } + + auto sizeof_item_element = sizeof (decltype (distributed_work.items)::value_type); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "items", item_count, sizeof_item_element })); + return composite; +} diff --git a/nano/node/distributed_work_factory.hpp b/nano/node/distributed_work_factory.hpp new file mode 100644 index 0000000000..350b1735f8 --- /dev/null +++ b/nano/node/distributed_work_factory.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace nano +{ +class node; +class distributed_work; +class root; + +class distributed_work_factory final +{ +public: + distributed_work_factory (nano::node &); + ~distributed_work_factory (); + bool make (nano::work_version const, nano::root const &, std::vector> const &, uint64_t, std::function)> const &, boost::optional const & = boost::none); + bool make (std::chrono::seconds const &, nano::work_request const &); + void cancel (nano::root const &, bool const local_stop = false); + void cleanup_finished (); + void stop (); + + nano::node & node; + std::unordered_map>> items; + std::mutex mutex; + std::atomic stopped{ false }; +}; + +class container_info_component; +std::unique_ptr collect_container_info (distributed_work_factory & distributed_work, const std::string & name); +} diff --git a/nano/node/election.cpp b/nano/node/election.cpp index c4c519b9db..6fe1854556 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -1,70 +1,288 @@ +#include #include +#include #include +#include + +using namespace std::chrono; + +int constexpr nano::election::passive_duration_factor; +int constexpr nano::election::active_request_count_min; +int constexpr nano::election::active_broadcasting_duration_factor; +int constexpr nano::election::confirmed_duration_factor; + +std::chrono::milliseconds nano::election::base_latency () const +{ + return node.network_params.network.is_test_network () ? 25ms : 1000ms; +} + nano::election_vote_result::election_vote_result (bool replay_a, bool processed_a) { replay = replay_a; processed = processed_a; } -nano::election::election (nano::node & node_a, std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) : +nano::election::election (nano::node & node_a, std::shared_ptr block_a, std::function)> const & confirmation_action_a, bool prioritized_a) : confirmation_action (confirmation_action_a), +prioritized_m (prioritized_a), node (node_a), -election_start (std::chrono::steady_clock::now ()), -status ({ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, nano::election_status_type::ongoing }), -skip_delay (skip_delay_a), -confirmed (false), -stopped (false) -{ - last_votes.insert (std::make_pair (node.network_params.random.not_an_account, nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () })); - blocks.insert (std::make_pair (block_a->hash (), block_a)); - update_dependent (); -} - -void nano::election::compute_rep_votes (nano::transaction const & transaction_a) +status ({ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::ongoing }), +height (block_a->sideband ().height) { - if (node.config.enable_voting) + last_votes.emplace (node.network_params.random.not_an_account, nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () }); + blocks.emplace (block_a->hash (), block_a); + update_dependent (); + if (prioritized_a) { - node.wallets.foreach_representative ([this, &transaction_a](nano::public_key const & pub_a, nano::raw_key const & prv_a) { - auto vote (this->node.store.vote_generate (transaction_a, pub_a, prv_a, status.winner)); - this->node.vote_processor.vote (vote, std::make_shared (this->node.network.udp_channels, this->node.network.endpoint (), this->node.network_params.protocol.protocol_version)); - }); + generate_votes (block_a->hash ()); } } void nano::election::confirm_once (nano::election_status_type type_a) { - if (!confirmed.exchange (true)) + debug_assert (!node.active.mutex.try_lock ()); + // This must be kept above the setting of election state, as dependent confirmed elections require up to date changes to election_winner_details + nano::unique_lock election_winners_lk (node.active.election_winner_details_mutex); + if (state_m.exchange (nano::election::state_t::confirmed) != nano::election::state_t::confirmed && (node.active.election_winner_details.count (status.winner->hash ()) == 0)) { status.election_end = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()); status.election_duration = std::chrono::duration_cast (std::chrono::steady_clock::now () - election_start); status.confirmation_request_count = confirmation_request_count; + status.block_count = nano::narrow_cast (blocks.size ()); + status.voter_count = nano::narrow_cast (last_votes.size ()); status.type = type_a; auto status_l (status); auto node_l (node.shared ()); auto confirmation_action_l (confirmation_action); + node.active.election_winner_details.emplace (status.winner->hash (), shared_from_this ()); + node.active.add_recently_confirmed (status_l.winner->qualified_root (), status_l.winner->hash ()); + node_l->process_confirmed (status_l); node.background ([node_l, status_l, confirmation_action_l]() { - node_l->process_confirmed (status_l); confirmation_action_l (status_l.winner); }); - auto root (status.winner->qualified_root ()); - node.active.pending_conf_height.emplace (status.winner->hash (), shared_from_this ()); - clear_blocks (); - clear_dependent (); - node.active.roots.erase (root); + adjust_dependent_difficulty (); } } -void nano::election::stop () +bool nano::election::valid_change (nano::election::state_t expected_a, nano::election::state_t desired_a) const { - if (!stopped && !confirmed) + bool result = false; + switch (expected_a) { - stopped = true; - status.election_end = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()); - status.election_duration = std::chrono::duration_cast (std::chrono::steady_clock::now () - election_start); - status.confirmation_request_count = confirmation_request_count; + case nano::election::state_t::idle: + switch (desired_a) + { + case nano::election::state_t::passive: + case nano::election::state_t::active: + result = true; + break; + default: + break; + } + break; + case nano::election::state_t::passive: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::active: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + break; + case nano::election::state_t::active: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::broadcasting: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::broadcasting: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::backtracking: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::backtracking: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::confirmed: + switch (desired_a) + { + case nano::election::state_t::expired_confirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::expired_unconfirmed: + break; + case nano::election::state_t::expired_confirmed: + break; + } + return result; +} + +bool nano::election::state_change (nano::election::state_t expected_a, nano::election::state_t desired_a) +{ + debug_assert (!timepoints_mutex.try_lock ()); + bool result = true; + if (valid_change (expected_a, desired_a)) + { + if (state_m.compare_exchange_strong (expected_a, desired_a)) + { + state_start = std::chrono::steady_clock::now (); + result = false; + } + } + else + { + debug_assert (false); + } + return result; +} + +void nano::election::send_confirm_req (nano::confirmation_solicitor & solicitor_a) +{ + if (base_latency () * 5 < std::chrono::steady_clock::now () - last_req) + { + if (!solicitor_a.add (*this)) + { + last_req = std::chrono::steady_clock::now (); + ++confirmation_request_count; + } + } +} + +void nano::election::transition_passive () +{ + nano::lock_guard guard (timepoints_mutex); + transition_passive_impl (); +} + +void nano::election::transition_passive_impl () +{ + state_change (nano::election::state_t::idle, nano::election::state_t::passive); +} + +void nano::election::transition_active () +{ + nano::lock_guard guard (timepoints_mutex); + transition_active_impl (); +} + +void nano::election::transition_active_impl () +{ + state_change (nano::election::state_t::idle, nano::election::state_t::active); +} + +bool nano::election::idle () const +{ + return state_m == nano::election::state_t::idle; +} + +bool nano::election::confirmed () const +{ + return state_m == nano::election::state_t::confirmed || state_m == nano::election::state_t::expired_confirmed; +} + +void nano::election::activate_dependencies () +{ + debug_assert (!node.active.mutex.try_lock ()); + node.active.pending_dependencies.emplace_back (status.winner->hash (), height); +} + +void nano::election::broadcast_block (nano::confirmation_solicitor & solicitor_a) +{ + if (base_latency () * 15 < std::chrono::steady_clock::now () - last_block) + { + if (!solicitor_a.broadcast (*this)) + { + last_block = std::chrono::steady_clock::now (); + } + } +} + +bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a) +{ + debug_assert (!node.active.mutex.try_lock ()); + nano::lock_guard guard (timepoints_mutex); + bool result = false; + switch (state_m) + { + case nano::election::state_t::idle: + break; + case nano::election::state_t::passive: + { + if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now () - state_start) + { + state_change (nano::election::state_t::passive, nano::election::state_t::active); + } + break; + } + case nano::election::state_t::active: + send_confirm_req (solicitor_a); + if (confirmation_request_count > active_request_count_min) + { + state_change (nano::election::state_t::active, nano::election::state_t::broadcasting); + } + break; + case nano::election::state_t::broadcasting: + broadcast_block (solicitor_a); + send_confirm_req (solicitor_a); + if (base_latency () * active_broadcasting_duration_factor < std::chrono::steady_clock::now () - state_start) + { + state_change (nano::election::state_t::broadcasting, nano::election::state_t::backtracking); + activate_dependencies (); + } + break; + case nano::election::state_t::backtracking: + broadcast_block (solicitor_a); + send_confirm_req (solicitor_a); + break; + case nano::election::state_t::confirmed: + if (base_latency () * confirmed_duration_factor < std::chrono::steady_clock::now () - state_start) + { + result = true; + state_change (nano::election::state_t::confirmed, nano::election::state_t::expired_confirmed); + } + break; + case nano::election::state_t::expired_unconfirmed: + case nano::election::state_t::expired_confirmed: + debug_assert (false); + break; + } + if (!confirmed () && std::chrono::minutes (5) < std::chrono::steady_clock::now () - election_start) + { + result = true; + state_change (state_m.load (), nano::election::state_t::expired_unconfirmed); status.type = nano::election_status_type::stopped; + log_votes (tally ()); } + return result; } bool nano::election::have_quorum (nano::tally_t const & tally_a, nano::uint128_t tally_sum) const @@ -95,7 +313,7 @@ nano::tally_t nano::election::tally () auto block (blocks.find (item.first)); if (block != blocks.end ()) { - result.insert (std::make_pair (item.second, block->second)); + result.emplace (item.second, block->second); } } return result; @@ -104,22 +322,24 @@ nano::tally_t nano::election::tally () void nano::election::confirm_if_quorum () { auto tally_l (tally ()); - assert (!tally_l.empty ()); + debug_assert (!tally_l.empty ()); auto winner (tally_l.begin ()); auto block_l (winner->second); + auto winner_hash_l (block_l->hash ()); status.tally = winner->first; + auto status_winner_hash_l (status.winner->hash ()); nano::uint128_t sum (0); for (auto & i : tally_l) { sum += i.first; } - if (sum >= node.config.online_weight_minimum.number () && block_l->hash () != status.winner->hash ()) + if (sum >= node.config.online_weight_minimum.number () && winner_hash_l != status_winner_hash_l) { - auto node_l (node.shared ()); - node_l->block_processor.force (block_l); status.winner = block_l; + remove_votes (status_winner_hash_l); + node.block_processor.force (block_l); update_dependent (); - node_l->active.adjust_difficulty (block_l->hash ()); + node.active.add_adjust_difficulty (winner_hash_l); } if (have_quorum (tally_l, sum)) { @@ -142,7 +362,10 @@ void nano::election::log_votes (nano::tally_t const & tally_a) const } for (auto i (last_votes.begin ()), n (last_votes.end ()); i != n; ++i) { - tally << boost::str (boost::format ("%1%%2% %3%") % line_end % i->first.to_account () % i->second.hash.to_string ()); + if (i->first != node.network_params.random.not_an_account) + { + tally << boost::str (boost::format ("%1%%2% %3% %4%") % line_end % i->first.to_account () % std::to_string (i->second.sequence) % i->second.hash.to_string ()); + } } node.logger.try_log (tally.str ()); } @@ -176,10 +399,10 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq } else { - auto last_vote (last_vote_it->second); - if (last_vote.sequence < sequence || (last_vote.sequence == sequence && last_vote.hash < block_hash)) + auto last_vote_l (last_vote_it->second); + if (last_vote_l.sequence < sequence || (last_vote_l.sequence == sequence && last_vote_l.hash < block_hash)) { - if (last_vote.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown)) + if (last_vote_l.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown)) { should_process = true; } @@ -193,7 +416,7 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq { node.stats.inc (nano::stat::type::election, nano::stat::detail::vote_new); last_votes[rep] = { std::chrono::steady_clock::now (), sequence, block_hash }; - if (!confirmed) + if (!confirmed ()) { confirm_if_quorum (); } @@ -204,8 +427,9 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq bool nano::election::publish (std::shared_ptr block_a) { - auto result (false); - if (blocks.size () >= 10) + // Do not insert new blocks if already confirmed + auto result (confirmed ()); + if (!result && blocks.size () >= 10) { if (last_tally[block_a->hash ()] < node.online_reps.online_stake () / 10) { @@ -214,19 +438,24 @@ bool nano::election::publish (std::shared_ptr block_a) } if (!result) { - auto transaction (node.store.tx_begin_read ()); - result = node.validate_block_by_previous (transaction, block_a); - if (!result) + auto existing = blocks.find (block_a->hash ()); + if (existing == blocks.end ()) { - if (blocks.find (block_a->hash ()) == blocks.end ()) + blocks.emplace (std::make_pair (block_a->hash (), block_a)); + if (!insert_inactive_votes_cache (block_a->hash ())) { - blocks.insert (std::make_pair (block_a->hash (), block_a)); + // Even if no votes were in cache, they could be in the election confirm_if_quorum (); - node.network.flood_block (block_a, false); } - else + node.network.flood_block (block_a, nano::buffer_drop_policy::no_limiter_drop); + } + else + { + result = true; + existing->second = block_a; + if (status.winner->hash () == block_a->hash ()) { - result = true; + status.winner = block_a; } } } @@ -241,7 +470,7 @@ size_t nano::election::last_votes_size () void nano::election::update_dependent () { - assert (!node.active.mutex.try_lock ()); + debug_assert (!node.active.mutex.try_lock ()); std::vector blocks_search; auto hash (status.winner->hash ()); auto previous (status.winner->previous ()); @@ -262,7 +491,7 @@ void nano::election::update_dependent () for (auto & block_search : blocks_search) { auto existing (node.active.blocks.find (block_search)); - if (existing != node.active.blocks.end () && !existing->second->confirmed && !existing->second->stopped) + if (existing != node.active.blocks.end () && !existing->second->confirmed ()) { if (existing->second->dependent_blocks.find (hash) == existing->second->dependent_blocks.end ()) { @@ -272,45 +501,58 @@ void nano::election::update_dependent () } } -void nano::election::clear_dependent () +void nano::election::adjust_dependent_difficulty () { for (auto & dependent_block : dependent_blocks) { - node.active.adjust_difficulty (dependent_block); + node.active.add_adjust_difficulty (dependent_block); } } -void nano::election::clear_blocks () +void nano::election::cleanup () { + bool unconfirmed (!confirmed ()); + auto winner_root (status.winner->qualified_root ()); auto winner_hash (status.winner->hash ()); - for (auto & block : blocks) + for (auto const & block : blocks) { auto & hash (block.first); auto erased (node.active.blocks.erase (hash)); (void)erased; - // clear_blocks () can be called in active_transactions::publish () before blocks insertion if election was confirmed - assert (erased == 1 || confirmed); + debug_assert (erased == 1); + node.active.erase_inactive_votes_cache (hash); // Notify observers about dropped elections & blocks lost confirmed elections - if (stopped || hash != winner_hash) + if (unconfirmed || hash != winner_hash) { node.observers.active_stopped.notify (hash); } } + if (unconfirmed) + { + node.active.recently_dropped.add (winner_root); + + // Clear network filter in another thread + node.worker.push_task ([node_l = node.shared (), blocks_l = std::move (blocks)]() { + for (auto const & block : blocks_l) + { + node_l->network.publish_filter.clear (block.second); + } + }); + } } -void nano::election::insert_inactive_votes_cache () +size_t nano::election::insert_inactive_votes_cache (nano::block_hash const & hash_a) { - auto winner_hash (status.winner->hash ()); - auto cache (node.active.find_inactive_votes_cache (winner_hash)); - for (auto & rep : cache.voters) + auto cache (node.active.find_inactive_votes_cache (hash_a)); + for (auto const & rep : cache.voters) { - auto inserted (last_votes.emplace (rep, nano::vote_info{ std::chrono::steady_clock::time_point::min (), 0, winner_hash })); + auto inserted (last_votes.emplace (rep, nano::vote_info{ std::chrono::steady_clock::time_point::min (), 0, hash_a })); if (inserted.second) { node.stats.inc (nano::stat::type::election, nano::stat::detail::vote_cached); } } - if (!confirmed && !cache.voters.empty ()) + if (!confirmed () && !cache.voters.empty ()) { auto delay (std::chrono::duration_cast (std::chrono::steady_clock::now () - cache.arrival)); if (delay > late_blocks_delay) @@ -320,4 +562,51 @@ void nano::election::insert_inactive_votes_cache () } confirm_if_quorum (); } + return cache.voters.size (); +} + +bool nano::election::prioritized () const +{ + return prioritized_m; +} + +void nano::election::prioritize_election (nano::vote_generator_session & generator_session_a) +{ + debug_assert (!node.active.mutex.try_lock ()); + debug_assert (!prioritized_m); + prioritized_m = true; + generator_session_a.add (status.winner->hash ()); +} + +void nano::election::try_generate_votes (nano::block_hash const & hash_a) +{ + nano::unique_lock lock (node.active.mutex); + if (status.winner->hash () == hash_a) + { + lock.unlock (); + generate_votes (hash_a); + } +} + +void nano::election::generate_votes (nano::block_hash const & hash_a) +{ + if (node.config.enable_voting && node.wallets.reps ().voting > 0) + { + node.active.generator.add (hash_a); + } +} + +void nano::election::remove_votes (nano::block_hash const & hash_a) +{ + if (node.config.enable_voting && node.wallets.reps ().voting > 0) + { + // Remove votes from election + auto list_generated_votes (node.votes_cache.find (hash_a)); + for (auto const & vote : list_generated_votes) + { + last_votes.erase (vote->account); + } + // Clear votes cache + node.votes_cache.remove (hash_a); + } } diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 85581c016a..7b048cb075 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -13,7 +12,9 @@ namespace nano { class channel; +class confirmation_solicitor; class node; +class vote_generator_session; class vote_info final { public: @@ -31,16 +32,50 @@ class election_vote_result final }; class election final : public std::enable_shared_from_this { + // Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations + std::chrono::milliseconds base_latency () const; std::function)> confirmation_action; +private: // State management + enum class state_t + { + idle, + passive, // only listening for incoming votes + active, // actively request confirmations + broadcasting, // request confirmations and broadcast the winner + backtracking, // start an election for unconfirmed dependent blocks + confirmed, // confirmed but still listening for votes + expired_confirmed, + expired_unconfirmed + }; + static int constexpr passive_duration_factor = 5; + static int constexpr active_request_count_min = 2; + static int constexpr active_broadcasting_duration_factor = 30; + static int constexpr confirmed_duration_factor = 5; + std::atomic state_m = { state_t::idle }; + + // These time points must be protected by this mutex + std::mutex timepoints_mutex; + std::chrono::steady_clock::time_point state_start = { std::chrono::steady_clock::now () }; + std::chrono::steady_clock::time_point last_block = { std::chrono::steady_clock::now () }; + std::chrono::steady_clock::time_point last_req = { std::chrono::steady_clock::time_point () }; + + bool valid_change (nano::election::state_t, nano::election::state_t) const; + bool state_change (nano::election::state_t, nano::election::state_t); + void broadcast_block (nano::confirmation_solicitor &); + void send_confirm_req (nano::confirmation_solicitor &); + void activate_dependencies (); + // Calculate votes for local representatives + void generate_votes (nano::block_hash const &); + void remove_votes (nano::block_hash const &); + std::atomic prioritized_m = { false }; + public: - election (nano::node &, std::shared_ptr, bool const, std::function)> const &); + election (nano::node &, std::shared_ptr, std::function)> const &, bool); nano::election_vote_result vote (nano::account, uint64_t, nano::block_hash); nano::tally_t tally (); // Check if we have vote quorum bool have_quorum (nano::tally_t const &, nano::uint128_t) const; - // Change our winner to agree with the network - void compute_rep_votes (nano::transaction const &); void confirm_once (nano::election_status_type = nano::election_status_type::active_confirmed_quorum); // Confirm this block if quorum is met void confirm_if_quorum (); @@ -48,21 +83,41 @@ class election final : public std::enable_shared_from_this bool publish (std::shared_ptr block_a); size_t last_votes_size (); void update_dependent (); - void clear_dependent (); - void clear_blocks (); - void insert_inactive_votes_cache (); - void stop (); + void adjust_dependent_difficulty (); + size_t insert_inactive_votes_cache (nano::block_hash const &); + bool prioritized () const; + void prioritize_election (nano::vote_generator_session &); + // Calculate votes if the current winner matches \p hash_a + void try_generate_votes (nano::block_hash const & hash_a); + // Erase all blocks from active and, if not confirmed, clear digests from network filters + void cleanup (); + +public: // State transitions + bool transition_time (nano::confirmation_solicitor &); + void transition_passive (); + void transition_active (); + +private: + void transition_passive_impl (); + void transition_active_impl (); + +public: + bool idle () const; + bool confirmed () const; nano::node & node; std::unordered_map last_votes; std::unordered_map> blocks; - std::chrono::steady_clock::time_point election_start; + std::chrono::steady_clock::time_point election_start = { std::chrono::steady_clock::now () }; nano::election_status status; - bool skip_delay; - std::atomic confirmed; - bool stopped; - std::unordered_map last_tally; unsigned confirmation_request_count{ 0 }; + std::unordered_map last_tally; std::unordered_set dependent_blocks; std::chrono::seconds late_blocks_delay{ 5 }; + uint64_t const height; + + friend class active_transactions; + + friend class election_bisect_dependencies_Test; + friend class election_dependencies_open_link_Test; }; } diff --git a/nano/node/gap_cache.cpp b/nano/node/gap_cache.cpp index ef06d318a9..e57f2f9c13 100644 --- a/nano/node/gap_cache.cpp +++ b/nano/node/gap_cache.cpp @@ -2,6 +2,8 @@ #include #include +#include + nano::gap_cache::gap_cache (nano::node & node_a) : node (node_a) { @@ -10,19 +12,19 @@ node (node_a) void nano::gap_cache::add (nano::block_hash const & hash_a, std::chrono::steady_clock::time_point time_point_a) { nano::lock_guard lock (mutex); - auto existing (blocks.get<1> ().find (hash_a)); - if (existing != blocks.get<1> ().end ()) + auto existing (blocks.get ().find (hash_a)); + if (existing != blocks.get ().end ()) { - blocks.get<1> ().modify (existing, [time_point_a](nano::gap_information & info) { + blocks.get ().modify (existing, [time_point_a](nano::gap_information & info) { info.arrival = time_point_a; }); } else { - blocks.insert ({ time_point_a, hash_a, std::vector () }); - if (blocks.size () > max) + blocks.get ().emplace (nano::gap_information{ time_point_a, hash_a, std::vector () }); + if (blocks.get ().size () > max) { - blocks.get<0> ().erase (blocks.get<0> ().begin ()); + blocks.get ().erase (blocks.get ().begin ()); } } } @@ -30,7 +32,7 @@ void nano::gap_cache::add (nano::block_hash const & hash_a, std::chrono::steady_ void nano::gap_cache::erase (nano::block_hash const & hash_a) { nano::lock_guard lock (mutex); - blocks.get<1> ().erase (hash_a); + blocks.get ().erase (hash_a); } void nano::gap_cache::vote (std::shared_ptr vote_a) @@ -38,11 +40,12 @@ void nano::gap_cache::vote (std::shared_ptr vote_a) nano::lock_guard lock (mutex); for (auto hash : *vote_a) { - auto existing (blocks.get<1> ().find (hash)); - if (existing != blocks.get<1> ().end () && !existing->confirmed) + auto & gap_blocks_by_hash (blocks.get ()); + auto existing (gap_blocks_by_hash.find (hash)); + if (existing != gap_blocks_by_hash.end () && !existing->bootstrap_started) { auto is_new (false); - blocks.get<1> ().modify (existing, [&is_new, &vote_a](nano::gap_information & info) { + gap_blocks_by_hash.modify (existing, [&is_new, &vote_a](nano::gap_information & info) { auto it = std::find (info.voters.begin (), info.voters.end (), vote_a->account); is_new = (it == info.voters.end ()); if (is_new) @@ -55,8 +58,8 @@ void nano::gap_cache::vote (std::shared_ptr vote_a) { if (bootstrap_check (existing->voters, hash)) { - blocks.get<1> ().modify (existing, [](nano::gap_information & info) { - info.confirmed = true; + gap_blocks_by_hash.modify (existing, [](nano::gap_information & info) { + info.bootstrap_started = true; }); } } @@ -67,7 +70,7 @@ void nano::gap_cache::vote (std::shared_ptr vote_a) bool nano::gap_cache::bootstrap_check (std::vector const & voters_a, nano::block_hash const & hash_a) { uint128_t tally; - for (auto & voter : voters_a) + for (auto const & voter : voters_a) { tally += node.ledger.weight (voter); } @@ -83,11 +86,10 @@ bool nano::gap_cache::bootstrap_check (std::vector const & voters { start_bootstrap = true; } - if (start_bootstrap) + if (start_bootstrap && !node.ledger.block_exists (hash_a)) { auto node_l (node.shared ()); - auto now (std::chrono::steady_clock::now ()); - node.alarm.add (node_l->network_params.network.is_test_network () ? now + std::chrono::milliseconds (5) : now + std::chrono::seconds (5), [node_l, hash_a]() { + node.alarm.add (std::chrono::steady_clock::now () + node.network_params.bootstrap.gap_cache_bootstrap_start_interval, [node_l, hash_a]() { auto transaction (node_l->store.tx_begin_read ()); if (!node_l->store.block_exists (transaction, hash_a)) { @@ -121,14 +123,11 @@ size_t nano::gap_cache::size () return blocks.size (); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (gap_cache & gap_cache, const std::string & name) +std::unique_ptr nano::collect_container_info (gap_cache & gap_cache, const std::string & name) { auto count = gap_cache.size (); auto sizeof_element = sizeof (decltype (gap_cache.blocks)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "blocks", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "blocks", count, sizeof_element })); return composite; } -} diff --git a/nano/node/gap_cache.hpp b/nano/node/gap_cache.hpp index 5a123e2dae..f6cf41c72a 100644 --- a/nano/node/gap_cache.hpp +++ b/nano/node/gap_cache.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -27,7 +27,7 @@ class gap_information final std::chrono::steady_clock::time_point arrival; nano::block_hash hash; std::vector voters; - bool confirmed{ false }; + bool bootstrap_started{ false }; }; /** Maintains voting and arrival information for gaps (missing source or previous blocks in account chains) */ @@ -41,16 +41,21 @@ class gap_cache final bool bootstrap_check (std::vector const &, nano::block_hash const &); nano::uint128_t bootstrap_threshold (); size_t size (); - boost::multi_index_container< - nano::gap_information, + // clang-format off + class tag_arrival {}; + class tag_hash {}; + using ordered_gaps = boost::multi_index_container>, - boost::multi_index::hashed_unique>>> - blocks; + boost::multi_index::ordered_non_unique, + boost::multi_index::member>, + boost::multi_index::hashed_unique, + boost::multi_index::member>>>; + ordered_gaps blocks; + // clang-format on size_t const max = 256; std::mutex mutex; nano::node & node; }; -std::unique_ptr collect_seq_con_info (gap_cache & gap_cache, const std::string & name); +std::unique_ptr collect_container_info (gap_cache & gap_cache, const std::string & name); } diff --git a/nano/node/ipc.cpp b/nano/node/ipc.cpp deleted file mode 100644 index b98b3dd794..0000000000 --- a/nano/node/ipc.cpp +++ /dev/null @@ -1,334 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace boost::log; - -namespace -{ -/** - * A session represents an inbound connection over which multiple requests/reponses are transmitted. - */ -template -class session : public nano::ipc::socket_base, public std::enable_shared_from_this> -{ -public: - session (nano::ipc::ipc_server & server_a, boost::asio::io_context & io_ctx_a, nano::ipc::ipc_config_transport & config_transport_a) : - socket_base (io_ctx_a), - server (server_a), node (server_a.node), session_id (server_a.id_dispenser.fetch_add (1)), io_ctx (io_ctx_a), socket (io_ctx_a), config_transport (config_transport_a) - { - if (node.config.logging.log_ipc ()) - { - node.logger.always_log ("IPC: created session with id: ", session_id); - } - } - - SOCKET_TYPE & get_socket () - { - return socket; - } - - /** - * Async read of exactly \p size_a bytes. The callback is invoked only when all the data is available and - * no error has occurred. On error, the error is logged, the read cycle stops and the session ends. Clients - * are expected to implement reconnect logic. - */ - void async_read_exactly (void * buff_a, size_t size_a, std::function const & callback_a) - { - async_read_exactly (buff_a, size_a, std::chrono::seconds (config_transport.io_timeout), callback_a); - } - - /** - * Async read of exactly \p size_a bytes and a specific \p timeout_a. - * @see async_read_exactly (void *, size_t, std::function) - */ - void async_read_exactly (void * buff_a, size_t size_a, std::chrono::seconds timeout_a, std::function const & callback_a) - { - timer_start (timeout_a); - auto this_l (this->shared_from_this ()); - boost::asio::async_read (socket, - boost::asio::buffer (buff_a, size_a), - boost::asio::transfer_exactly (size_a), - [this_l, callback_a](boost::system::error_code const & ec, size_t bytes_transferred_a) { - this_l->timer_cancel (); - if (ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset) - { - if (this_l->node.config.logging.log_ipc ()) - { - this_l->node.logger.always_log (boost::str (boost::format ("IPC: error reading %1% ") % ec.message ())); - } - } - else if (bytes_transferred_a > 0) - { - callback_a (); - } - }); - } - - /** Handler for payload_encoding::json_legacy */ - void handle_json_query (bool allow_unsafe) - { - session_timer.restart (); - auto request_id_l (std::to_string (server.id_dispenser.fetch_add (1))); - - // This is called when nano::rpc_handler#process_request is done. We convert to - // json and write the response to the ipc socket with a length prefix. - auto this_l (this->shared_from_this ()); - auto response_handler_l ([this_l, request_id_l](std::string const & body) { - auto big = boost::endian::native_to_big (static_cast (body.size ())); - std::vector buffer; - buffer.insert (buffer.end (), reinterpret_cast (&big), reinterpret_cast (&big) + sizeof (std::uint32_t)); - buffer.insert (buffer.end (), body.begin (), body.end ()); - if (this_l->node.config.logging.log_ipc ()) - { - this_l->node.logger.always_log (boost::str (boost::format ("IPC/RPC request %1% completed in: %2% %3%") % request_id_l % this_l->session_timer.stop ().count () % this_l->session_timer.unit ())); - } - - this_l->timer_start (std::chrono::seconds (this_l->config_transport.io_timeout)); - nano::async_write (this_l->socket, nano::shared_const_buffer (buffer), [this_l](boost::system::error_code const & error_a, size_t size_a) { - this_l->timer_cancel (); - if (!error_a) - { - this_l->read_next_request (); - } - else if (this_l->node.config.logging.log_ipc ()) - { - this_l->node.logger.always_log ("IPC: Write failed: ", error_a.message ()); - } - }); - - // Do not call any member variables here (like session_timer) as it's possible that the next request may already be underway. - }); - - node.stats.inc (nano::stat::type::ipc, nano::stat::detail::invocations); - auto body (std::string (reinterpret_cast (buffer.data ()), buffer.size ())); - - // Note that if the rpc action is async, the shared_ptr lifetime will be extended by the action handler - auto handler (std::make_shared (node, server.node_rpc_config, body, response_handler_l, [& server = server]() { - server.stop (); - server.node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (3), [& io_ctx = server.node.alarm.io_ctx]() { - io_ctx.stop (); - }); - })); - // For unsafe actions to be allowed, the unsafe encoding must be used AND the transport config must allow it - handler->process_request (allow_unsafe && config_transport.allow_unsafe); - } - - /** Async request reader */ - void read_next_request () - { - auto this_l = this->shared_from_this (); - - // Await next request indefinitely - buffer.resize (sizeof (buffer_size)); - async_read_exactly (buffer.data (), buffer.size (), std::chrono::seconds::max (), [this_l]() { - if (this_l->buffer[nano::ipc::preamble_offset::lead] != 'N' || this_l->buffer[nano::ipc::preamble_offset::reserved_1] != 0 || this_l->buffer[nano::ipc::preamble_offset::reserved_2] != 0) - { - if (this_l->node.config.logging.log_ipc ()) - { - this_l->node.logger.always_log ("IPC: Invalid preamble"); - } - } - else if (this_l->buffer[nano::ipc::preamble_offset::encoding] == static_cast (nano::ipc::payload_encoding::json_legacy) || this_l->buffer[nano::ipc::preamble_offset::encoding] == static_cast (nano::ipc::payload_encoding::json_unsafe)) - { - auto allow_unsafe (this_l->buffer[nano::ipc::preamble_offset::encoding] == static_cast (nano::ipc::payload_encoding::json_unsafe)); - // Length of payload - this_l->async_read_exactly (&this_l->buffer_size, sizeof (this_l->buffer_size), [this_l, allow_unsafe]() { - boost::endian::big_to_native_inplace (this_l->buffer_size); - this_l->buffer.resize (this_l->buffer_size); - // Payload (ptree compliant JSON string) - this_l->async_read_exactly (this_l->buffer.data (), this_l->buffer_size, [this_l, allow_unsafe]() { - this_l->handle_json_query (allow_unsafe); - }); - }); - } - else if (this_l->node.config.logging.log_ipc ()) - { - this_l->node.logger.always_log ("IPC: Unsupported payload encoding"); - } - }); - } - - /** Shut down and close socket */ - void close () - { - socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both); - socket.close (); - } - -private: - nano::ipc::ipc_server & server; - nano::node & node; - - /** Unique session id used for logging */ - uint64_t session_id; - - /** Timer for measuring the duration of ipc calls */ - nano::timer session_timer; - - /** - * IO context from node, or per-transport, depending on configuration. - * Certain transports may scale better if they use a separate context. - */ - boost::asio::io_context & io_ctx; - - /** A socket of the given asio type */ - SOCKET_TYPE socket; - - /** Buffer sizes are read into this */ - uint32_t buffer_size{ 0 }; - - /** Buffer used to store data received from the client */ - std::vector buffer; - - /** Transport configuration */ - nano::ipc::ipc_config_transport & config_transport; -}; - -/** Domain and TCP socket transport */ -template -class socket_transport : public nano::ipc::transport -{ -public: - socket_transport (nano::ipc::ipc_server & server_a, ENDPOINT_TYPE endpoint_a, nano::ipc::ipc_config_transport & config_transport_a, int concurrency_a) : - server (server_a), config_transport (config_transport_a) - { - // Using a per-transport event dispatcher? - if (concurrency_a > 0) - { - io_ctx = std::make_unique (); - } - - boost::asio::socket_base::reuse_address option (true); - boost::asio::socket_base::keep_alive option_keepalive (true); - acceptor = std::make_unique (context (), endpoint_a); - acceptor->set_option (option); - acceptor->set_option (option_keepalive); - accept (); - - // Start serving IO requests. If concurrency_a is < 1, the node's thread pool/io_context is used instead. - // A separate io_context for domain sockets may facilitate better performance on some systems. - if (concurrency_a > 0) - { - runner = std::make_unique (*io_ctx, static_cast (concurrency_a)); - } - } - - boost::asio::io_context & context () const - { - return io_ctx ? *io_ctx : server.node.io_ctx; - } - - void accept () - { - // Prepare the next session - auto new_session (std::make_shared> (server, context (), config_transport)); - - acceptor->async_accept (new_session->get_socket (), [this, new_session](boost::system::error_code const & ec) { - if (!ec) - { - new_session->read_next_request (); - } - else - { - server.node.logger.always_log ("IPC: acceptor error: ", ec.message ()); - } - - if (ec != boost::asio::error::operation_aborted && acceptor->is_open ()) - { - this->accept (); - } - else - { - server.node.logger.always_log ("IPC: shutting down"); - } - }); - } - - void stop () - { - acceptor->close (); - if (io_ctx) - { - io_ctx->stop (); - } - - if (runner) - { - runner->join (); - } - } - -private: - nano::ipc::ipc_server & server; - nano::ipc::ipc_config_transport & config_transport; - std::unique_ptr runner; - std::unique_ptr io_ctx; - std::unique_ptr acceptor; -}; -} - -nano::ipc::ipc_server::ipc_server (nano::node & node_a, nano::node_rpc_config const & node_rpc_config_a) : -node (node_a), -node_rpc_config (node_rpc_config_a) -{ - try - { - if (node_a.config.ipc_config.transport_domain.enabled) - { -#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) - auto threads = node_a.config.ipc_config.transport_domain.io_threads; - file_remover = std::make_unique (node_a.config.ipc_config.transport_domain.path); - boost::asio::local::stream_protocol::endpoint ep{ node_a.config.ipc_config.transport_domain.path }; - transports.push_back (std::make_shared> (*this, ep, node_a.config.ipc_config.transport_domain, threads)); -#else - node.logger.always_log ("IPC: Domain sockets are not supported on this platform"); -#endif - } - - if (node_a.config.ipc_config.transport_tcp.enabled) - { - auto threads = node_a.config.ipc_config.transport_tcp.io_threads; - transports.push_back (std::make_shared> (*this, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v6 (), node_a.config.ipc_config.transport_tcp.port), node_a.config.ipc_config.transport_tcp, threads)); - } - - node.logger.always_log ("IPC: server started"); - } - catch (std::runtime_error const & ex) - { - node.logger.always_log ("IPC: ", ex.what ()); - } -} - -nano::ipc::ipc_server::~ipc_server () -{ - node.logger.always_log ("IPC: server stopped"); -} - -void nano::ipc::ipc_server::stop () -{ - for (auto & transport : transports) - { - transport->stop (); - } -} diff --git a/nano/node/ipc.hpp b/nano/node/ipc.hpp deleted file mode 100644 index abbeff2727..0000000000 --- a/nano/node/ipc.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace nano -{ -class node; - -namespace ipc -{ - /** The IPC server accepts connections on one or more configured transports */ - class ipc_server - { - public: - ipc_server (nano::node & node_a, nano::node_rpc_config const & node_rpc_config); - - virtual ~ipc_server (); - void stop (); - - nano::node & node; - nano::node_rpc_config const & node_rpc_config; - - /** Unique counter/id shared across sessions */ - std::atomic id_dispenser{ 0 }; - - private: - std::unique_ptr file_remover; - std::vector> transports; - }; -} -} diff --git a/nano/node/ipc/action_handler.cpp b/nano/node/ipc/action_handler.cpp new file mode 100644 index 0000000000..bdc0665672 --- /dev/null +++ b/nano/node/ipc/action_handler.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +nano::account parse_account (std::string const & account, bool & out_is_deprecated_format) +{ + nano::account result (0); + if (account.empty ()) + { + throw nano::error (nano::error_common::bad_account_number); + } + if (result.decode_account (account)) + { + throw nano::error (nano::error_common::bad_account_number); + } + else if (account[3] == '-' || account[4] == '-') + { + out_is_deprecated_format = true; + } + + return result; +} +/** Returns the message as a Flatbuffers ObjectAPI type, managed by a unique_ptr */ +template +auto get_message (nanoapi::Envelope const & envelope) +{ + auto raw (envelope.message_as ()->UnPack ()); + return std::unique_ptr (raw); +} +} + +/** + * Mapping from message type to handler function. + * @note This must be updated whenever a new message type is added to the Flatbuffers IDL. + */ +auto nano::ipc::action_handler::handler_map () -> std::unordered_map, nano::ipc::enum_hash> +{ + static std::unordered_map, nano::ipc::enum_hash> handlers; + if (handlers.empty ()) + { + handlers.emplace (nanoapi::Message::Message_IsAlive, &nano::ipc::action_handler::on_is_alive); + handlers.emplace (nanoapi::Message::Message_TopicConfirmation, &nano::ipc::action_handler::on_topic_confirmation); + handlers.emplace (nanoapi::Message::Message_AccountWeight, &nano::ipc::action_handler::on_account_weight); + handlers.emplace (nanoapi::Message::Message_ServiceRegister, &nano::ipc::action_handler::on_service_register); + handlers.emplace (nanoapi::Message::Message_ServiceStop, &nano::ipc::action_handler::on_service_stop); + handlers.emplace (nanoapi::Message::Message_TopicServiceStop, &nano::ipc::action_handler::on_topic_service_stop); + } + return handlers; +} + +nano::ipc::action_handler::action_handler (nano::node & node_a, nano::ipc::ipc_server & server_a, std::weak_ptr const & subscriber_a, std::shared_ptr const & builder_a) : +flatbuffer_producer (builder_a), +node (node_a), +ipc_server (server_a), +subscriber (subscriber_a) +{ +} + +void nano::ipc::action_handler::on_topic_confirmation (nanoapi::Envelope const & envelope_a) +{ + auto confirmationTopic (get_message (envelope_a)); + ipc_server.get_broker ().subscribe (subscriber, std::move (confirmationTopic)); + nanoapi::EventAckT ack; + create_response (ack); +} + +void nano::ipc::action_handler::on_service_register (nanoapi::Envelope const & envelope_a) +{ + require_oneof (envelope_a, { nano::ipc::access_permission::api_service_register, nano::ipc::access_permission::service }); + auto query (get_message (envelope_a)); + ipc_server.get_broker ().service_register (query->service_name, this->subscriber); + nanoapi::SuccessT success; + create_response (success); +} + +void nano::ipc::action_handler::on_service_stop (nanoapi::Envelope const & envelope_a) +{ + require_oneof (envelope_a, { nano::ipc::access_permission::api_service_stop, nano::ipc::access_permission::service }); + auto query (get_message (envelope_a)); + if (query->service_name == "node") + { + ipc_server.node.stop (); + } + else + { + ipc_server.get_broker ().service_stop (query->service_name); + } + nanoapi::SuccessT success; + create_response (success); +} + +void nano::ipc::action_handler::on_topic_service_stop (nanoapi::Envelope const & envelope_a) +{ + auto topic (get_message (envelope_a)); + ipc_server.get_broker ().subscribe (subscriber, std::move (topic)); + nanoapi::EventAckT ack; + create_response (ack); +} + +void nano::ipc::action_handler::on_account_weight (nanoapi::Envelope const & envelope_a) +{ + require_oneof (envelope_a, { nano::ipc::access_permission::api_account_weight, nano::ipc::access_permission::account_query }); + bool is_deprecated_format{ false }; + auto query (get_message (envelope_a)); + auto balance (node.weight (parse_account (query->account, is_deprecated_format))); + + nanoapi::AccountWeightResponseT response; + response.voting_weight = balance.str (); + create_response (response); +} + +void nano::ipc::action_handler::on_is_alive (nanoapi::Envelope const & envelope) +{ + nanoapi::IsAliveT alive; + create_response (alive); +} + +bool nano::ipc::action_handler::has_access (nanoapi::Envelope const & envelope_a, nano::ipc::access_permission permission_a) const noexcept +{ + // If credentials are missing in the envelope, the default user is used + std::string credentials; + if (envelope_a.credentials () != nullptr) + { + credentials = envelope_a.credentials ()->str (); + } + + return ipc_server.get_access ().has_access (credentials, permission_a); +} + +bool nano::ipc::action_handler::has_access_to_all (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const noexcept +{ + // If credentials are missing in the envelope, the default user is used + std::string credentials; + if (envelope_a.credentials () != nullptr) + { + credentials = envelope_a.credentials ()->str (); + } + + return ipc_server.get_access ().has_access_to_all (credentials, permissions_a); +} + +bool nano::ipc::action_handler::has_access_to_oneof (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const noexcept +{ + // If credentials are missing in the envelope, the default user is used + std::string credentials; + if (envelope_a.credentials () != nullptr) + { + credentials = envelope_a.credentials ()->str (); + } + + return ipc_server.get_access ().has_access_to_oneof (credentials, permissions_a); +} + +void nano::ipc::action_handler::require (nanoapi::Envelope const & envelope_a, nano::ipc::access_permission permission_a) const +{ + if (!has_access (envelope_a, permission_a)) + { + throw nano::error (nano::error_common::access_denied); + } +} + +void nano::ipc::action_handler::require_all (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const +{ + if (!has_access_to_all (envelope_a, permissions_a)) + { + throw nano::error (nano::error_common::access_denied); + } +} + +void nano::ipc::action_handler::require_oneof (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const +{ + if (!has_access_to_oneof (envelope_a, permissions_a)) + { + throw nano::error (nano::error_common::access_denied); + } +} diff --git a/nano/node/ipc/action_handler.hpp b/nano/node/ipc/action_handler.hpp new file mode 100644 index 0000000000..63c51c57a9 --- /dev/null +++ b/nano/node/ipc/action_handler.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nano +{ +class error; +class node; +namespace ipc +{ + class ipc_server; + class subscriber; + + /** + * Implements handlers for the various public IPC messages. When an action handler is completed, + * the flatbuffer contains the serialized response object. + * @note This is a light-weight class, and an instance can be created for every request. + */ + class action_handler final : public flatbuffer_producer, public std::enable_shared_from_this + { + public: + action_handler (nano::node & node, nano::ipc::ipc_server & server, std::weak_ptr const & subscriber, std::shared_ptr const & builder); + + void on_account_weight (nanoapi::Envelope const & envelope); + void on_is_alive (nanoapi::Envelope const & envelope); + void on_topic_confirmation (nanoapi::Envelope const & envelope); + + /** Request to register a service. The service name is associated with the current session. */ + void on_service_register (nanoapi::Envelope const & envelope); + + /** Request to stop a service by name */ + void on_service_stop (nanoapi::Envelope const & envelope); + + /** Subscribe to the ServiceStop event. The service must first have registered itself on the same session. */ + void on_topic_service_stop (nanoapi::Envelope const & envelope); + + /** Returns a mapping from api message types to handler functions */ + static auto handler_map () -> std::unordered_map, nano::ipc::enum_hash>; + + private: + bool has_access (nanoapi::Envelope const & envelope_a, nano::ipc::access_permission permission_a) const noexcept; + bool has_access_to_all (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const noexcept; + bool has_access_to_oneof (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const noexcept; + void require (nanoapi::Envelope const & envelope_a, nano::ipc::access_permission permission_a) const; + void require_all (nanoapi::Envelope const & envelope_a, std::initializer_list permissions_a) const; + void require_oneof (nanoapi::Envelope const & envelope_a, std::initializer_list alternative_permissions_a) const; + + nano::node & node; + nano::ipc::ipc_server & ipc_server; + std::weak_ptr subscriber; + }; +} +} diff --git a/nano/node/ipc/flatbuffers_handler.cpp b/nano/node/ipc/flatbuffers_handler.cpp new file mode 100644 index 0000000000..f3fd2b3505 --- /dev/null +++ b/nano/node/ipc/flatbuffers_handler.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ +auto handler_map = nano::ipc::action_handler::handler_map (); + +/** + * A helper for when it's necessary to create a JSON error response manually + */ +std::string make_error_response (std::string const & error_message) +{ + std::ostringstream json; + json << R"json({"message_type": "Error", "message": {"code": 1, "message": ")json" + << error_message + << R"json("}})json"; + return json.str (); +} + +/** + * Returns the 'api/flatbuffers' directory, boost::none if not found. + * This searches the binary path as well as the parent (which is mostly useful for development) + */ +boost::optional get_api_path () +{ + auto parent_path = boost::dll::program_location ().parent_path (); + if (!boost::filesystem::exists (parent_path / "api" / "flatbuffers")) + { + // See if the parent directory has the api subdirectories + if (parent_path.has_parent_path ()) + { + parent_path = boost::dll::program_location ().parent_path ().parent_path (); + } + + if (!boost::filesystem::exists (parent_path / "api" / "flatbuffers")) + { + return boost::none; + } + } + return parent_path / "api" / "flatbuffers"; +} +} + +nano::ipc::flatbuffers_handler::flatbuffers_handler (nano::node & node_a, nano::ipc::ipc_server & ipc_server_a, std::shared_ptr const & subscriber_a, nano::ipc::ipc_config const & ipc_config_a) : +node (node_a), +ipc_server (ipc_server_a), +subscriber (subscriber_a), +ipc_config (ipc_config_a) +{ +} + +std::shared_ptr nano::ipc::flatbuffers_handler::make_flatbuffers_parser (nano::ipc::ipc_config const & ipc_config_a) +{ + auto parser (std::make_shared ()); + parser->opts.strict_json = true; + parser->opts.skip_unexpected_fields_in_json = ipc_config_a.flatbuffers.skip_unexpected_fields_in_json; + + auto api_path = get_api_path (); + if (!api_path) + { + throw nano::error ("Internal IPC error: unable to find api path"); + } + + const char * include_directories[] = { api_path->string ().c_str (), nullptr }; + std::string schemafile; + if (!flatbuffers::LoadFile ((*api_path / "nanoapi.fbs").string ().c_str (), false, &schemafile)) + { + throw nano::error ("Internal IPC error: unable to load schema file"); + } + + auto parse_success = parser->Parse (schemafile.c_str (), include_directories); + if (!parse_success) + { + std::string parser_error = "Internal IPC error: unable to parse schema file: "; + parser_error += parser->error_.c_str (); + throw nano::error (parser_error); + } + return parser; +} + +void nano::ipc::flatbuffers_handler::process_json (const uint8_t * message_buffer_a, size_t buffer_size_a, +std::function)> response_handler) +{ + try + { + if (!parser) + { + parser = make_flatbuffers_parser (ipc_config); + } + + // Convert request from JSON + auto body (std::string (reinterpret_cast (const_cast (message_buffer_a)), buffer_size_a)); + body += '\0'; + if (parser->Parse (reinterpret_cast (body.data ()))) + { + process (parser->builder_.GetBufferPointer (), parser->builder_.GetSize (), [parser = parser, response_handler](std::shared_ptr fbb) { + // Convert response to JSON + auto json (std::make_shared ()); + if (!flatbuffers::GenerateText (*parser, fbb->GetBufferPointer (), json.get ())) + { + throw nano::error ("Couldn't serialize response to JSON"); + } + + response_handler (json); + }); + } + else + { + std::string parser_error = "Invalid message format: "; + parser_error += parser->error_.c_str (); + throw nano::error (parser_error); + } + } + catch (nano::error const & err) + { + // Forces the parser construction to be retried as certain errors are + // recoverable (such path errors getting fixed by the user without a node restart) + parser = nullptr; + + // Convert error response to JSON. We must construct this manually since the exception + // may be parser related (such as not being able to load the schema) + response_handler (std::make_shared (make_error_response (err.get_message ()))); + } + catch (...) + { + std::cerr << "Unknown exception in " << __FUNCTION__ << std::endl; + response_handler (std::make_shared (make_error_response ("Unknown exception"))); + } +} + +void nano::ipc::flatbuffers_handler::process (const uint8_t * message_buffer_a, size_t buffer_size_a, +std::function)> response_handler) +{ + auto buffer_l (std::make_shared ()); + auto actionhandler (std::make_shared (node, ipc_server, subscriber, buffer_l)); + std::string correlationId = ""; + + // Find and call the action handler + try + { + // By default we verify the buffers, to make sure offsets reside inside the buffer. + // This brings the buffer into cache, making the overall verify+parse overhead low. + if (ipc_config.flatbuffers.verify_buffers) + { + auto verifier (flatbuffers::Verifier (message_buffer_a, buffer_size_a)); + if (!nanoapi::VerifyEnvelopeBuffer (verifier)) + { + throw nano::error ("Envelope buffer did not pass verifier"); + } + } + + auto incoming = nanoapi::GetEnvelope (message_buffer_a); + if (incoming == nullptr) + { + nano::error err ("Invalid message"); + actionhandler->make_error (err.error_code_as_int (), err.get_message ()); + response_handler (buffer_l); + return; + } + + auto handler_method = handler_map.find (incoming->message_type ()); + if (handler_method != handler_map.end ()) + { + if (incoming->correlation_id ()) + { + actionhandler->set_correlation_id (incoming->correlation_id ()->str ()); + } + handler_method->second (actionhandler.get (), *incoming); + } + else + { + nano::error err ("Unknown message type"); + actionhandler->make_error (err.error_code_as_int (), err.get_message ()); + } + } + catch (nano::error const & err) + { + actionhandler->make_error (err.error_code_as_int (), err.get_message ()); + } + + response_handler (buffer_l); +} diff --git a/nano/node/ipc/flatbuffers_handler.hpp b/nano/node/ipc/flatbuffers_handler.hpp new file mode 100644 index 0000000000..5965766177 --- /dev/null +++ b/nano/node/ipc/flatbuffers_handler.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + +namespace flatbuffers +{ +class FlatBufferBuilder; +class Parser; +} +namespace nano +{ +class node; +namespace ipc +{ + class subscriber; + class ipc_config; + class ipc_server; + /** + * This handler sits between the IPC server and the action handler. Its job is to deserialize + * Flatbuffers in binary and json formats into high level message objects. These messages are + * then used to dispatch the correct action handler. + * @throws Methods of this class throw nano::error on failure. + * @note This class is not thread safe; use one instance per session/thread. + */ + class flatbuffers_handler final : public std::enable_shared_from_this + { + public: + /** + * Constructs the handler. + * @param node_a Node + * @param subscriber Subscriber instance + * @param ipc_server_a Optional IPC server (may be nullptr, i.e when calling through the RPC gateway) + */ + flatbuffers_handler (nano::node & node_a, nano::ipc::ipc_server & ipc_server_a, std::shared_ptr const & subscriber_a, nano::ipc::ipc_config const & ipc_config_a); + + /** + * Deserialize flatbuffer message, look up and call the action handler, then call the response handler with a + * FlatBufferBuilder to allow for zero-copy transfers of data. + * @param response_handler Receives a shared pointer to the flatbuffer builder, from which the buffer and size can be queried + * @throw Throws std:runtime_error on deserialization or processing errors + */ + void process (const uint8_t * message_buffer_a, size_t buffer_size_a, std::function)> response_handler); + + /** + * Parses a JSON encoded requests into Flatbuffer format, calls process(), yields the result as a JSON string + */ + void process_json (const uint8_t * message_buffer_a, size_t buffer_size_a, std::function)> response_handler); + + /** + * Creates a Flatbuffers parser with the schema preparsed. This can then be used to parse and produce JSON. + */ + static std::shared_ptr make_flatbuffers_parser (nano::ipc::ipc_config const & ipc_config_a); + + private: + std::shared_ptr parser; + nano::node & node; + nano::ipc::ipc_server & ipc_server; + std::weak_ptr subscriber; + nano::ipc::ipc_config const & ipc_config; + }; +} +} diff --git a/nano/node/ipc/flatbuffers_util.cpp b/nano/node/ipc/flatbuffers_util.cpp new file mode 100644 index 0000000000..20813943d3 --- /dev/null +++ b/nano/node/ipc/flatbuffers_util.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +std::unique_ptr nano::ipc::flatbuffers_builder::from (nano::state_block const & block_a, nano::amount const & amount_a, bool is_state_send_a) +{ + static nano::network_params params; + auto block (std::make_unique ()); + block->account = block_a.account ().to_account (); + block->hash = block_a.hash ().to_string (); + block->previous = block_a.previous ().to_string (); + block->representative = block_a.representative ().to_account (); + block->balance = block_a.balance ().to_string_dec (); + block->link = block_a.link ().to_string (); + block->link_as_account = block_a.link ().to_account (); + block_a.signature.encode_hex (block->signature); + block->work = nano::to_string_hex (block_a.work); + + if (is_state_send_a) + { + block->subtype = nanoapi::BlockSubType::BlockSubType_send; + } + else if (block_a.link ().is_zero ()) + { + block->subtype = nanoapi::BlockSubType::BlockSubType_change; + } + else if (amount_a == 0 && params.ledger.epochs.is_epoch_link (block_a.link ())) + { + block->subtype = nanoapi::BlockSubType::BlockSubType_epoch; + } + else + { + block->subtype = nanoapi::BlockSubType::BlockSubType_receive; + } + return block; +} + +std::unique_ptr nano::ipc::flatbuffers_builder::from (nano::send_block const & block_a) +{ + auto block (std::make_unique ()); + block->hash = block_a.hash ().to_string (); + block->balance = block_a.balance ().to_string_dec (); + block->destination = block_a.hashables.destination.to_account (); + block->previous = block_a.previous ().to_string (); + block_a.signature.encode_hex (block->signature); + block->work = nano::to_string_hex (block_a.work); + return block; +} + +std::unique_ptr nano::ipc::flatbuffers_builder::from (nano::receive_block const & block_a) +{ + auto block (std::make_unique ()); + block->hash = block_a.hash ().to_string (); + block->source = block_a.source ().to_string (); + block->previous = block_a.previous ().to_string (); + block_a.signature.encode_hex (block->signature); + block->work = nano::to_string_hex (block_a.work); + return block; +} + +std::unique_ptr nano::ipc::flatbuffers_builder::from (nano::open_block const & block_a) +{ + auto block (std::make_unique ()); + block->hash = block_a.hash ().to_string (); + block->source = block_a.source ().to_string (); + block->account = block_a.account ().to_account (); + block->representative = block_a.representative ().to_account (); + block_a.signature.encode_hex (block->signature); + block->work = nano::to_string_hex (block_a.work); + return block; +} + +std::unique_ptr nano::ipc::flatbuffers_builder::from (nano::change_block const & block_a) +{ + auto block (std::make_unique ()); + block->hash = block_a.hash ().to_string (); + block->previous = block_a.previous ().to_string (); + block->representative = block_a.representative ().to_account (); + block_a.signature.encode_hex (block->signature); + block->work = nano::to_string_hex (block_a.work); + return block; +} + +nanoapi::BlockUnion nano::ipc::flatbuffers_builder::block_to_union (nano::block const & block_a, nano::amount const & amount_a, bool is_state_send_a) +{ + nanoapi::BlockUnion u; + switch (block_a.type ()) + { + case nano::block_type::state: + { + u.Set (*from (dynamic_cast (block_a), amount_a, is_state_send_a)); + break; + } + case nano::block_type::send: + { + u.Set (*from (dynamic_cast (block_a))); + break; + } + case nano::block_type::receive: + { + u.Set (*from (dynamic_cast (block_a))); + break; + } + case nano::block_type::open: + { + u.Set (*from (dynamic_cast (block_a))); + break; + } + case nano::block_type::change: + { + u.Set (*from (dynamic_cast (block_a))); + break; + } + + default: + debug_assert (false); + } + return u; +} diff --git a/nano/node/ipc/flatbuffers_util.hpp b/nano/node/ipc/flatbuffers_util.hpp new file mode 100644 index 0000000000..c1236a9413 --- /dev/null +++ b/nano/node/ipc/flatbuffers_util.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +namespace nano +{ +class amount; +class block; +class send_block; +class receive_block; +class change_block; +class open_block; +class state_block; +namespace ipc +{ + /** + * Utilities to convert between blocks and Flatbuffers equivalents + */ + class flatbuffers_builder + { + public: + static nanoapi::BlockUnion block_to_union (nano::block const & block_a, nano::amount const & amount_a, bool is_state_send_a = false); + static std::unique_ptr from (nano::state_block const & block_a, nano::amount const & amount_a, bool is_state_send_a); + static std::unique_ptr from (nano::send_block const & block_a); + static std::unique_ptr from (nano::receive_block const & block_a); + static std::unique_ptr from (nano::open_block const & block_a); + static std::unique_ptr from (nano::change_block const & block_a); + }; +} +} diff --git a/nano/node/ipc/ipc_access_config.cpp b/nano/node/ipc/ipc_access_config.cpp new file mode 100644 index 0000000000..8ff6ef3df2 --- /dev/null +++ b/nano/node/ipc/ipc_access_config.cpp @@ -0,0 +1,307 @@ +#include +#include + +#include + +namespace +{ +/** Convert string to permission */ +nano::ipc::access_permission from_string (std::string permission) +{ + if (permission == "unrestricted") + return nano::ipc::access_permission::unrestricted; + if (permission == "api_account_weight") + return nano::ipc::access_permission::api_account_weight; + if (permission == "api_service_register") + return nano::ipc::access_permission::api_service_register; + if (permission == "api_service_stop") + return nano::ipc::access_permission::api_service_stop; + if (permission == "api_topic_service_stop") + return nano::ipc::access_permission::api_topic_service_stop; + if (permission == "api_topic_confirmation") + return nano::ipc::access_permission::api_topic_confirmation; + if (permission == "account_query") + return nano::ipc::access_permission::account_query; + if (permission == "epoch_upgrade") + return nano::ipc::access_permission::epoch_upgrade; + if (permission == "service") + return nano::ipc::access_permission::service; + if (permission == "wallet") + return nano::ipc::access_permission::wallet; + if (permission == "wallet_read") + return nano::ipc::access_permission::wallet_read; + if (permission == "wallet_write") + return nano::ipc::access_permission::wallet_write; + if (permission == "wallet_seed_change") + return nano::ipc::access_permission::wallet_seed_change; + + return nano::ipc::access_permission::invalid; +} +} + +void nano::ipc::access::set_effective_permissions (nano::ipc::access_subject & subject_a, std::shared_ptr const & config_subject_a) +{ + std::string allow_l (config_subject_a->get_as ("allow").value_or ("")); + std::vector allow_strings_l; + boost::split (allow_strings_l, allow_l, boost::is_any_of (",")); + for (auto const & permission : allow_strings_l) + { + if (!permission.empty ()) + { + auto permission_enum = from_string (boost::trim_copy (permission)); + if (permission_enum != nano::ipc::access_permission::invalid) + { + subject_a.permissions.insert (permission_enum); + } + } + } + + std::string deny_l (config_subject_a->get_as ("deny").value_or ("")); + std::vector deny_strings_l; + boost::split (deny_strings_l, deny_l, boost::is_any_of (",")); + for (auto const & permission : deny_strings_l) + { + if (!permission.empty ()) + { + auto permission_enum = from_string (boost::trim_copy (permission)); + if (permission_enum != nano::ipc::access_permission::invalid) + { + subject_a.permissions.erase (permission_enum); + } + } + } +} + +void nano::ipc::access::clear () +{ + users.clear (); + roles.clear (); + + // Create default user. The node operator can add additional roles + // and permissions to the default user by adding a toml [[user]] entry + // without an id (or set it to the empty string). + // The default permissions can be overriden by marking the default user + // as bare, and then set specific permissions. + default_user.clear (); + default_user.id = ""; + + // The default set of permissions. A new insert should be made as new safe + // api's or resource permissions are made. + default_user.permissions.insert (nano::ipc::access_permission::api_account_weight); +} + +nano::error nano::ipc::access::deserialize_toml (nano::tomlconfig & toml) +{ + nano::unique_lock lock (mutex); + clear (); + + nano::error error; + if (toml.has_key ("role")) + { + auto get_role = [this](std::shared_ptr const & role_a) { + nano::ipc::access_role role; + std::string id_l (role_a->get_as ("id").value_or ("")); + role.id = id_l; + set_effective_permissions (role, role_a); + return role; + }; + + auto role_l = toml.get_tree ()->get ("role"); + if (role_l->is_table ()) + { + auto role = get_role (role_l->as_table ()); + if (role_l->as_table ()->contains ("deny")) + { + error.set ("Only users can have deny entries"); + } + else + { + roles.emplace (role.id, role); + } + } + else if (role_l->is_table_array ()) + { + for (auto & table : *role_l->as_table_array ()) + { + if (table->contains ("deny")) + { + error.set ("Only users can have deny entries"); + } + + auto role = get_role (table); + roles.emplace (role.id, role); + } + } + } + + if (!error && toml.has_key ("user")) + { + auto get_user = [this, &error](std::shared_ptr const & user_a) { + nano::ipc::access_user user; + user.id = user_a->get_as ("id").value_or (""); + // Check bare flag. The tomlconfig parser stringifies values, so we must retrieve as string. + bool is_bare = user_a->get_as ("bare").value_or ("false") == "true"; + + // Adopt all permissions from the roles. This must be done before setting user permissions, since + // the user config may add deny-entries. + std::string roles_l (user_a->get_as ("roles").value_or ("")); + std::vector role_strings_l; + boost::split (role_strings_l, roles_l, boost::is_any_of (",")); + for (auto const & role : role_strings_l) + { + auto role_id (boost::trim_copy (role)); + if (!role_id.empty ()) + { + auto match = roles.find (role_id); + if (match != roles.end ()) + { + user.permissions.insert (match->second.permissions.begin (), match->second.permissions.end ()); + } + else + { + error.set ("Unknown role: " + role_id); + } + } + } + + // A user with the bare flag does not inherit default permissions + if (!is_bare) + { + user.permissions.insert (default_user.permissions.begin (), default_user.permissions.end ()); + } + + set_effective_permissions (user, user_a); + + return user; + }; + + auto user_l = toml.get_tree ()->get ("user"); + if (user_l->is_table ()) + { + auto user = get_user (user_l->as_table ()); + users.emplace (user.id, user); + } + else if (user_l->is_table_array ()) + { + for (auto & table : *user_l->as_table_array ()) + { + auto user = get_user (table); + if (user.id.empty () && users.size () > 0) + { + // This is a requirement because other users inherit permissions from the default user + error.set ("Changes to the default user must appear before other users in the access config file"); + break; + } + users.emplace (user.id, user); + } + } + } + + // Add default user if it wasn't present in the config file + if (users.find ("") == users.end ()) + { + users.emplace (default_user.id, default_user); + } + + return error; +} + +bool nano::ipc::access::has_access (std::string const & credentials_a, nano::ipc::access_permission permssion_a) const +{ + nano::unique_lock lock (mutex); + bool permitted = false; + auto user = users.find (credentials_a); + if (user != users.end ()) + { + permitted = user->second.permissions.find (permssion_a) != user->second.permissions.end (); + if (!permitted) + { + permitted = user->second.permissions.find (nano::ipc::access_permission::unrestricted) != user->second.permissions.end (); + } + } + return permitted; +} + +bool nano::ipc::access::has_access_to_all (std::string const & credentials_a, std::initializer_list permissions_a) const +{ + nano::unique_lock lock (mutex); + bool permitted = false; + auto user = users.find (credentials_a); + if (user != users.end ()) + { + for (auto permission : permissions_a) + { + permitted = user->second.permissions.find (permission) != user->second.permissions.end (); + if (!permitted) + { + break; + } + } + } + return permitted; +} + +bool nano::ipc::access::has_access_to_oneof (std::string const & credentials_a, std::initializer_list permissions_a) const +{ + nano::unique_lock lock (mutex); + bool permitted = false; + auto user = users.find (credentials_a); + if (user != users.end ()) + { + for (auto permission : permissions_a) + { + permitted = user->second.permissions.find (permission) != user->second.permissions.end (); + if (permitted) + { + break; + } + } + if (!permitted) + { + permitted = user->second.permissions.find (nano::ipc::access_permission::unrestricted) != user->second.permissions.end (); + } + } + return permitted; +} + +void nano::ipc::access_subject::clear () +{ + permissions.clear (); +} + +void nano::ipc::access_user::clear () +{ + access_subject::clear (); + roles.clear (); +} + +namespace nano +{ +namespace ipc +{ + nano::error read_access_config_toml (boost::filesystem::path const & data_path_a, nano::ipc::access & config_a) + { + nano::error error; + auto toml_config_path = nano::get_access_toml_config_path (data_path_a); + + nano::tomlconfig toml; + if (boost::filesystem::exists (toml_config_path)) + { + error = toml.read (toml_config_path); + } + else + { + std::stringstream config_overrides_stream; + config_overrides_stream << std::endl; + toml.read (config_overrides_stream); + } + + if (!error) + { + error = config_a.deserialize_toml (toml); + } + + return error; + } +} +} diff --git a/nano/node/ipc/ipc_access_config.hpp b/nano/node/ipc/ipc_access_config.hpp new file mode 100644 index 0000000000..076c4d1dc0 --- /dev/null +++ b/nano/node/ipc/ipc_access_config.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace boost +{ +namespace filesystem +{ + class path; +} +} +namespace cpptoml +{ +class table; +} + +namespace nano +{ +class tomlconfig; +namespace ipc +{ + struct enum_hash + { + template + constexpr typename std::enable_if::value, std::size_t>::type + operator() (T s) const noexcept + { + return static_cast (s); + } + }; + + /** + * Permissions come in roughly two forms: api permissions (one for every api we expose) and + * higher level resource permissions. We define a permission per api because a common use case is to + * allow a specific set of RPCs. The higher level resource permissions makes it easier to + * grant access to groups of operations or resources. An API implementation will typically check + * against the corresponding api permission (such as api_account_weight), but may also allow + * resource permissions (such as account_query). + */ + enum class access_permission + { + invalid, + /** Unrestricted access to the node, suitable for debugging and development */ + unrestricted, + api_account_weight, + api_service_register, + api_service_stop, + api_topic_service_stop, + api_topic_confirmation, + /** Query account information */ + account_query, + /** Epoch upgrade */ + epoch_upgrade, + /** All service operations */ + service, + /** All wallet operations */ + wallet, + /** Non-mutable wallet operations */ + wallet_read, + /** Mutable wallet operations */ + wallet_write, + /** Seed change */ + wallet_seed_change + }; + + /** A subject is a user or role with a set of permissions */ + class access_subject + { + public: + std::unordered_set permissions; + virtual ~access_subject () = default; + virtual void clear (); + }; + + /** Permissions can be organized into roles */ + class access_role final : public access_subject + { + public: + std::string id; + }; + + /** A user with credentials and a set of permissions (either directly or through roles) */ + class access_user final : public access_subject + { + public: + /* User credentials, serving as the id */ + std::string id; + std::vector roles; + void clear () override; + }; + + /** + * Constructs a user/role/permission domain model from config-access.toml, and + * allows permissions for a user to be checked. + * @note This class is thread safe + */ + class access final + { + public: + bool has_access (std::string const & credentials_a, nano::ipc::access_permission permission_a) const; + bool has_access_to_all (std::string const & credentials_a, std::initializer_list permissions_a) const; + bool has_access_to_oneof (std::string const & credentials_a, std::initializer_list permissions_a) const; + nano::error deserialize_toml (nano::tomlconfig &); + + private: + /** Process allow and deny entries for the given subject */ + void set_effective_permissions (nano::ipc::access_subject & subject_a, std::shared_ptr const & config_subject_a); + + /** Clear current users, roles and default permissions */ + void clear (); + + std::unordered_map users; + std::unordered_map roles; + + /** + * Default user with a basic set of permissions. Additional users will derive the permissions + * from the default user (unless "bare" is true in the access config file) + */ + access_user default_user; + /** The config can be externally reloaded and concurrently accessed */ + mutable std::mutex mutex; + }; + + nano::error read_access_config_toml (boost::filesystem::path const & data_path_a, nano::ipc::access & config_a); +} +} diff --git a/nano/node/ipc/ipc_broker.cpp b/nano/node/ipc/ipc_broker.cpp new file mode 100644 index 0000000000..65922c7419 --- /dev/null +++ b/nano/node/ipc/ipc_broker.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include + +nano::ipc::broker::broker (nano::node & node_a) : +node (node_a) +{ +} + +std::shared_ptr nano::ipc::subscriber::get_parser (nano::ipc::ipc_config const & ipc_config_a) +{ + if (!parser) + { + parser = nano::ipc::flatbuffers_handler::make_flatbuffers_parser (ipc_config_a); + } + return parser; +} + +void nano::ipc::broker::start () +{ + node.observers.blocks.add ([this](nano::election_status const & status_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { + debug_assert (status_a.type != nano::election_status_type::ongoing); + + try + { + // The subscriber(s) may be gone after the count check, but the only consequence + // is that broadcast is called only to not find any live sessions. + if (confirmation_subscriber_count () > 0) + { + auto confirmation (std::make_shared ()); + + confirmation->account = account_a.to_account (); + confirmation->amount = amount_a.to_string_dec (); + switch (status_a.type) + { + case nano::election_status_type::active_confirmed_quorum: + confirmation->confirmation_type = nanoapi::TopicConfirmationType::TopicConfirmationType_active_quorum; + break; + case nano::election_status_type::active_confirmation_height: + confirmation->confirmation_type = nanoapi::TopicConfirmationType::TopicConfirmationType_active_confirmation_height; + break; + case nano::election_status_type::inactive_confirmation_height: + confirmation->confirmation_type = nanoapi::TopicConfirmationType::TopicConfirmationType_inactive; + break; + default: + debug_assert (false); + break; + }; + confirmation->confirmation_type = nanoapi::TopicConfirmationType::TopicConfirmationType_active_quorum; + confirmation->block = nano::ipc::flatbuffers_builder::block_to_union (*status_a.winner, amount_a, is_state_send_a); + confirmation->election_info = std::make_unique (); + confirmation->election_info->duration = status_a.election_duration.count (); + confirmation->election_info->time = status_a.election_end.count (); + confirmation->election_info->tally = status_a.tally.to_string_dec (); + confirmation->election_info->block_count = status_a.block_count; + confirmation->election_info->voter_count = status_a.voter_count; + confirmation->election_info->request_count = status_a.confirmation_request_count; + + broadcast (confirmation); + } + } + catch (nano::error const & err) + { + this->node.logger.always_log ("IPC: could not broadcast message: ", err.get_message ()); + } + }); +} + +template +void subscribe_or_unsubscribe (nano::logger_mt & logger, COLL & subscriber_collection, std::weak_ptr const & subscriber_a, TOPIC_TYPE topic_a) +{ + // Evict subscribers from dead sessions. Also remove current subscriber if unsubscribing. + subscriber_collection.erase (std::remove_if (subscriber_collection.begin (), subscriber_collection.end (), + [& logger = logger, topic_a, subscriber_a](auto & sub) { + bool remove = false; + auto subscriber_l = sub.subscriber.lock (); + if (subscriber_l) + { + if (auto calling_subscriber_l = subscriber_a.lock ()) + { + remove = topic_a->unsubscribe && subscriber_l->get_id () == calling_subscriber_l->get_id (); + if (remove) + { + logger.always_log ("IPC: unsubscription from subscriber #", calling_subscriber_l->get_id ()); + } + } + } + else + { + remove = true; + } + return remove; + }), + subscriber_collection.end ()); + + if (!topic_a->unsubscribe) + { + subscriber_collection.emplace_back (subscriber_a, topic_a); + } +} + +void nano::ipc::broker::subscribe (std::weak_ptr const & subscriber_a, std::shared_ptr const & confirmation_a) +{ + auto subscribers = confirmation_subscribers.lock (); + subscribe_or_unsubscribe (node.logger, subscribers.get (), subscriber_a, confirmation_a); +} + +void nano::ipc::broker::broadcast (std::shared_ptr const & confirmation_a) +{ + using Filter = nanoapi::TopicConfirmationTypeFilter; + decltype (confirmation_a->election_info) election_info; + nanoapi::BlockUnion block; + auto itr (confirmation_subscribers->begin ()); + while (itr != confirmation_subscribers->end ()) + { + if (auto subscriber_l = itr->subscriber.lock ()) + { + auto should_filter = [this, &itr, confirmation_a]() { + debug_assert (itr->topic->options != nullptr); + auto conf_filter (itr->topic->options->confirmation_type_filter); + + bool should_filter_conf_type_l (true); + bool all_filter = conf_filter == Filter::TopicConfirmationTypeFilter_all; + bool inactive_filter = conf_filter == Filter::TopicConfirmationTypeFilter_inactive; + bool active_filter = conf_filter == Filter::TopicConfirmationTypeFilter_active || conf_filter == Filter::TopicConfirmationTypeFilter_active_quorum || conf_filter == Filter::TopicConfirmationTypeFilter_active_confirmation_height; + + if ((confirmation_a->confirmation_type == nanoapi::TopicConfirmationType::TopicConfirmationType_active_quorum || confirmation_a->confirmation_type == nanoapi::TopicConfirmationType::TopicConfirmationType_active_confirmation_height) && (all_filter || active_filter)) + { + should_filter_conf_type_l = false; + } + else if (confirmation_a->confirmation_type == nanoapi::TopicConfirmationType::TopicConfirmationType_inactive && (all_filter || inactive_filter)) + { + should_filter_conf_type_l = false; + } + + bool should_filter_account_l (itr->topic->options->all_local_accounts || !itr->topic->options->accounts.empty ()); + auto state (confirmation_a->block.AsBlockState ()); + if (state && !should_filter_conf_type_l) + { + if (itr->topic->options->all_local_accounts) + { + auto transaction_l (this->node.wallets.tx_begin_read ()); + nano::account source_l (0), destination_l (0); + auto decode_source_ok_l (!source_l.decode_account (state->account)); + auto decode_destination_ok_l (!destination_l.decode_account (state->link_as_account)); + (void)decode_source_ok_l; + (void)decode_destination_ok_l; + debug_assert (decode_source_ok_l && decode_destination_ok_l); + if (this->node.wallets.exists (transaction_l, source_l) || this->node.wallets.exists (transaction_l, destination_l)) + { + should_filter_account_l = false; + } + } + + if (std::find (itr->topic->options->accounts.begin (), itr->topic->options->accounts.end (), state->account) != itr->topic->options->accounts.end () || std::find (itr->topic->options->accounts.begin (), itr->topic->options->accounts.end (), state->link_as_account) != itr->topic->options->accounts.end ()) + { + should_filter_account_l = false; + } + } + + return should_filter_conf_type_l || should_filter_account_l; + }; + // Apply any filters + auto & options (itr->topic->options); + if (options) + { + if (!options->include_election_info) + { + election_info = std::move (confirmation_a->election_info); + confirmation_a->election_info = nullptr; + } + if (!options->include_block) + { + block = confirmation_a->block; + confirmation_a->block.Reset (); + } + } + if (!options || !should_filter ()) + { + auto fb (nano::ipc::flatbuffer_producer::make_buffer (*confirmation_a)); + + if (subscriber_l->get_active_encoding () == nano::ipc::payload_encoding::flatbuffers_json) + { + auto parser (subscriber_l->get_parser (node.config.ipc_config)); + + // Convert response to JSON + auto json (std::make_shared ()); + if (!flatbuffers::GenerateText (*parser, fb->GetBufferPointer (), json.get ())) + { + throw nano::error ("Couldn't serialize response to JSON"); + } + + subscriber_l->async_send_message (reinterpret_cast (json->data ()), json->size (), [json](const nano::error & err) {}); + } + else + { + subscriber_l->async_send_message (fb->GetBufferPointer (), fb->GetSize (), [fb](const nano::error & err) {}); + } + } + + // Restore full object, the next subscriber may request it + if (election_info) + { + confirmation_a->election_info = std::move (election_info); + } + if (block.type != nanoapi::Block::Block_NONE) + { + confirmation_a->block = block; + } + + ++itr; + } + else + { + itr = confirmation_subscribers->erase (itr); + } + } +} + +size_t nano::ipc::broker::confirmation_subscriber_count () const +{ + return confirmation_subscribers->size (); +} + +void nano::ipc::broker::service_register (std::string const & service_name_a, std::weak_ptr const & subscriber_a) +{ + if (auto subscriber_l = subscriber_a.lock ()) + { + subscriber_l->set_service_name (service_name_a); + } +} + +void nano::ipc::broker::service_stop (std::string const & service_name_a) +{ + auto subscribers = service_stop_subscribers.lock (); + for (auto & subcription : subscribers.get ()) + { + if (auto subscriber_l = subcription.subscriber.lock ()) + { + if (subscriber_l->get_service_name () == service_name_a) + { + nanoapi::EventServiceStopT event_stop; + auto fb (nano::ipc::flatbuffer_producer::make_buffer (event_stop)); + subscriber_l->async_send_message (fb->GetBufferPointer (), fb->GetSize (), [fb](const nano::error & err) {}); + + break; + } + } + } +} + +void nano::ipc::broker::subscribe (std::weak_ptr const & subscriber_a, std::shared_ptr const & service_stop_a) +{ + auto subscribers = service_stop_subscribers.lock (); + subscribe_or_unsubscribe (node.logger, subscribers.get (), subscriber_a, service_stop_a); +} diff --git a/nano/node/ipc/ipc_broker.hpp b/nano/node/ipc/ipc_broker.hpp new file mode 100644 index 0000000000..84b1a3bdd9 --- /dev/null +++ b/nano/node/ipc/ipc_broker.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace flatbuffers +{ +class Parser; +} +namespace nano +{ +class node; +class error; +namespace ipc +{ + class ipc_config; + /** + * A subscriber represents a live session, and is weakly referenced nano::ipc::subscription whenever a subscription is made. + * This construction helps making the session implementation opaque to clients. + */ + class subscriber + { + public: + virtual ~subscriber () = default; + + /** + * Send message payload to the client. The implementation will prepend the bigendian length. + * @param data_a The caller must ensure the lifetime is extended until the completion handler is called, such as through a lambda capture. + * @param length_a Length of payload message in bytes + * @param broadcast_completion_handler_a Called once sending is completed + */ + virtual void async_send_message (uint8_t const * data_a, size_t length_a, std::function broadcast_completion_handler_a) = 0; + /** Returns the unique id of the associated session */ + virtual uint64_t get_id () const = 0; + /** Returns the service name associated with the session */ + virtual std::string get_service_name () const = 0; + /** Sets the service name associated with the session */ + virtual void set_service_name (std::string const & service_name_a) = 0; + /** Returns the session's active payload encoding */ + virtual nano::ipc::payload_encoding get_active_encoding () const = 0; + + /** Get flatbuffer parser instance for this subscriber; create it if necessary */ + std::shared_ptr get_parser (nano::ipc::ipc_config const & ipc_config_a); + + private: + std::shared_ptr parser; + }; + + /** + * Subscriptions are added to the broker whenever a topic message is sent from a client. + * The subscription is removed when the client unsubscribes, or lazily removed after the + * session is closed. + */ + template + class subscription final + { + public: + subscription (std::weak_ptr const & subscriber_a, std::shared_ptr const & topic_a) : + subscriber (subscriber_a), topic (topic_a) + { + } + + std::weak_ptr subscriber; + std::shared_ptr topic; + }; + + /** + * The broker manages subscribers and performs message broadcasting + * @note Add subscribe overloads for new topics + */ + class broker final + { + public: + broker (nano::node & node_a); + /** Starts the broker by setting up observers */ + void start (); + /** Subscribe to block confirmations */ + void subscribe (std::weak_ptr const & subscriber_a, std::shared_ptr const & confirmation_a); + /** Subscribe to EventServiceStop notifications for \p subscriber_a. The subscriber must first have called ServiceRegister. */ + void subscribe (std::weak_ptr const & subscriber_a, std::shared_ptr const & service_stop_a); + + /** Returns the number of confirmation subscribers */ + size_t confirmation_subscriber_count () const; + /** Associate the service name with the subscriber */ + void service_register (std::string const & service_name_a, std::weak_ptr const & subscriber_a); + /** Sends a notification to the session associated with the given service (if the session has subscribed to TopicServiceStop) */ + void service_stop (std::string const & service_name_a); + + private: + /** Broadcast block confirmations */ + void broadcast (std::shared_ptr const & confirmation_a); + + nano::node & node; + mutable nano::locked>> confirmation_subscribers; + mutable nano::locked>> service_stop_subscribers; + }; +} +} diff --git a/nano/node/ipcconfig.cpp b/nano/node/ipc/ipc_config.cpp similarity index 85% rename from nano/node/ipcconfig.cpp rename to nano/node/ipc/ipc_config.cpp index 4180e40beb..9bfff2c2c6 100644 --- a/nano/node/ipcconfig.cpp +++ b/nano/node/ipc/ipc_config.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include nano::error nano::ipc::ipc_config::serialize_toml (nano::tomlconfig & toml) const { @@ -25,6 +25,12 @@ nano::error nano::ipc::ipc_config::serialize_toml (nano::tomlconfig & toml) cons domain_l.put ("path", transport_domain.path, "Path to the local domain socket.\ntype:string"); domain_l.put ("io_timeout", transport_domain.io_timeout, "Timeout for requests.\ntype:seconds"); toml.put_child ("local", domain_l); + + nano::tomlconfig flatbuffers_l; + flatbuffers_l.put ("skip_unexpected_fields_in_json", flatbuffers.skip_unexpected_fields_in_json, "Allow client to send unknown fields in json messages. These will be ignored.\ntype:bool"); + flatbuffers_l.put ("verify_buffers", flatbuffers.verify_buffers, "Verify that the buffer is valid before parsing. This is recommended when receiving data from untrusted sources.\ntype:bool"); + toml.put_child ("flatbuffers", flatbuffers_l); + return toml.get_error (); } @@ -50,6 +56,13 @@ nano::error nano::ipc::ipc_config::deserialize_toml (nano::tomlconfig & toml) domain_l->get ("io_timeout", transport_domain.io_timeout); } + auto flatbuffers_l (toml.get_optional_child ("flatbuffers")); + if (flatbuffers_l) + { + flatbuffers_l->get ("skip_unexpected_fields_in_json", flatbuffers.skip_unexpected_fields_in_json); + flatbuffers_l->get ("verify_buffers", flatbuffers.verify_buffers); + } + return toml.get_error (); } diff --git a/nano/node/ipcconfig.hpp b/nano/node/ipc/ipc_config.hpp similarity index 84% rename from nano/node/ipcconfig.hpp rename to nano/node/ipc/ipc_config.hpp index 7abf8f3afe..a09b3b051d 100644 --- a/nano/node/ipcconfig.hpp +++ b/nano/node/ipc/ipc_config.hpp @@ -22,6 +22,16 @@ namespace ipc long io_threads{ -1 }; }; + /** + * Flatbuffers encoding config. See TOML serialization calls for details about each field. + */ + class ipc_config_flatbuffers final + { + public: + bool skip_unexpected_fields_in_json{ true }; + bool verify_buffers{ true }; + }; + /** Domain socket specific transport config */ class ipc_config_domain_socket : public ipc_config_transport { @@ -61,6 +71,7 @@ namespace ipc nano::error serialize_toml (nano::tomlconfig & toml) const; ipc_config_domain_socket transport_domain; ipc_config_tcp_socket transport_tcp; + ipc_config_flatbuffers flatbuffers; }; } } diff --git a/nano/node/ipc/ipc_server.cpp b/nano/node/ipc/ipc_server.cpp new file mode 100644 index 0000000000..2dc1aecebd --- /dev/null +++ b/nano/node/ipc/ipc_server.cpp @@ -0,0 +1,659 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace boost::log; + +namespace +{ +/** + * A session manages an inbound connection over which messages are exchanged. + */ +template +class session final : public nano::ipc::socket_base, public std::enable_shared_from_this> +{ +public: + session (nano::ipc::ipc_server & server_a, boost::asio::io_context & io_ctx_a, nano::ipc::ipc_config_transport & config_transport_a) : + socket_base (io_ctx_a), + server (server_a), node (server_a.node), session_id (server_a.id_dispenser.fetch_add (1)), + io_ctx (io_ctx_a), strand (io_ctx_a.get_executor ()), socket (io_ctx_a), config_transport (config_transport_a) + { + if (node.config.logging.log_ipc ()) + { + node.logger.always_log ("IPC: created session with id: ", session_id.load ()); + } + } + + ~session () + { + close (); + } + + SOCKET_TYPE & get_socket () + { + return socket; + } + + std::shared_ptr get_subscriber () + { + class subscriber_impl final : public nano::ipc::subscriber, public std::enable_shared_from_this + { + public: + subscriber_impl (std::shared_ptr const & session_a) : + session_m (session_a) + { + } + virtual void async_send_message (uint8_t const * data_a, size_t length_a, std::function broadcast_completion_handler_a) override + { + if (auto session_l = session_m.lock ()) + { + auto big_endian_length = std::make_shared (boost::endian::native_to_big (static_cast (length_a))); + boost::array buffers = { + boost::asio::buffer (big_endian_length.get (), sizeof (std::uint32_t)), + boost::asio::buffer (data_a, length_a) + }; + + session_l->queued_write (buffers, [broadcast_completion_handler_a, big_endian_length](boost::system::error_code const & ec_a, size_t size_a) { + if (broadcast_completion_handler_a) + { + nano::error error_l (ec_a); + broadcast_completion_handler_a (error_l); + } + }); + } + } + + uint64_t get_id () const override + { + uint64_t id{ 0 }; + if (auto session_l = session_m.lock ()) + { + id = session_l->session_id; + } + return id; + } + + std::string get_service_name () const override + { + std::string name{ 0 }; + if (auto session_l = session_m.lock ()) + { + name = session_l->service_name; + } + return name; + } + + void set_service_name (std::string const & service_name_a) override + { + if (auto session_l = session_m.lock ()) + { + session_l->service_name = service_name_a; + } + } + + nano::ipc::payload_encoding get_active_encoding () const override + { + nano::ipc::payload_encoding encoding{ nano::ipc::payload_encoding::flatbuffers }; + if (auto session_l = session_m.lock ()) + { + encoding = session_l->active_encoding; + } + return encoding; + } + + private: + std::weak_ptr session_m; + }; + + static std::mutex subscriber_mutex; + nano::unique_lock lock (subscriber_mutex); + + if (!subscriber) + { + subscriber = std::make_shared (this->shared_from_this ()); + } + return subscriber; + } + + /** Write a fixed array of buffers through the queue. Once the last item is completed, the callback is invoked */ + template + void queued_write (boost::array & buffers, std::function callback_a) + { + auto this_l (this->shared_from_this ()); + boost::asio::post (strand, boost::asio::bind_executor (strand, [buffers, callback_a, this_l]() { + bool write_in_progress = !this_l->send_queue.empty (); + auto queue_size = this_l->send_queue.size (); + if (queue_size < this_l->queue_size_max) + { + for (size_t i = 0; i < N - 1; i++) + { + this_l->send_queue.emplace_back (queue_item{ buffers[i], nullptr }); + } + this_l->send_queue.emplace_back (queue_item{ buffers[N - 1], callback_a }); + } + if (!write_in_progress) + { + this_l->write_queued_messages (); + } + })); + } + + /** + * Write to underlying socket. Writes goes through a queue protected by the strand. Thus, this function + * can be called concurrently with other writes. + * @note This function explicitely doesn't use nano::shared_const_buffer, as buffers usually originate from Flatbuffers + * and copying into the shared_const_buffer vector would impose a significant overhead for large requests and responses. + */ + void queued_write (boost::asio::const_buffer const & buffer_a, std::function callback_a) + { + auto this_l (this->shared_from_this ()); + boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, this_l]() { + bool write_in_progress = !this_l->send_queue.empty (); + auto queue_size = this_l->send_queue.size (); + if (queue_size < this_l->queue_size_max) + { + this_l->send_queue.emplace_back (queue_item{ buffer_a, callback_a }); + } + if (!write_in_progress) + { + this_l->write_queued_messages (); + } + })); + } + + void write_queued_messages () + { + std::weak_ptr this_w (this->shared_from_this ()); + auto msg (send_queue.front ()); + timer_start (std::chrono::seconds (config_transport.io_timeout)); + nano::unsafe_async_write (socket, msg.buffer, + boost::asio::bind_executor (strand, + [msg, this_w](boost::system::error_code ec, std::size_t size_a) { + if (auto this_l = this_w.lock ()) + { + this_l->timer_cancel (); + + if (msg.callback) + { + msg.callback (ec, size_a); + } + + this_l->send_queue.pop_front (); + if (!ec && !this_l->send_queue.empty ()) + { + this_l->write_queued_messages (); + } + } + })); + } + + /** + * Async read of exactly \p size_a bytes. The callback is invoked only when all the data is available and + * no error has occurred. On error, the error is logged, the read cycle stops and the session ends. Clients + * are expected to implement reconnect logic. + */ + void async_read_exactly (void * buff_a, size_t size_a, std::function const & callback_a) + { + async_read_exactly (buff_a, size_a, std::chrono::seconds (config_transport.io_timeout), callback_a); + } + + /** + * Async read of exactly \p size_a bytes and a specific \p timeout_a. + * @see async_read_exactly (void *, size_t, std::function) + */ + void async_read_exactly (void * buff_a, size_t size_a, std::chrono::seconds timeout_a, std::function const & callback_a) + { + timer_start (timeout_a); + auto this_l (this->shared_from_this ()); + boost::asio::async_read (socket, + boost::asio::buffer (buff_a, size_a), + boost::asio::transfer_exactly (size_a), + boost::asio::bind_executor (strand, + [this_l, callback_a](boost::system::error_code const & ec, size_t bytes_transferred_a) { + this_l->timer_cancel (); + if (ec == boost::asio::error::broken_pipe || ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset || ec == boost::asio::error::connection_refused) + { + if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log (boost::str (boost::format ("IPC: error reading %1% ") % ec.message ())); + } + } + else if (bytes_transferred_a > 0) + { + callback_a (); + } + })); + } + + /** Handler for payload_encoding::json_legacy */ + void handle_json_query (bool allow_unsafe) + { + session_timer.restart (); + auto request_id_l (std::to_string (server.id_dispenser.fetch_add (1))); + + // This is called when nano::rpc_handler#process_request is done. We convert to + // json and write the response to the ipc socket with a length prefix. + auto this_l (this->shared_from_this ()); + auto response_handler_l ([this_l, request_id_l](std::string const & body) { + auto big = boost::endian::native_to_big (static_cast (body.size ())); + auto buffer (std::make_shared> ()); + buffer->insert (buffer->end (), reinterpret_cast (&big), reinterpret_cast (&big) + sizeof (std::uint32_t)); + buffer->insert (buffer->end (), body.begin (), body.end ()); + if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log (boost::str (boost::format ("IPC/RPC request %1% completed in: %2% %3%") % request_id_l % this_l->session_timer.stop ().count () % this_l->session_timer.unit ())); + } + + this_l->timer_start (std::chrono::seconds (this_l->config_transport.io_timeout)); + this_l->queued_write (boost::asio::buffer (buffer->data (), buffer->size ()), [this_l, buffer](boost::system::error_code const & error_a, size_t size_a) { + this_l->timer_cancel (); + if (!error_a) + { + this_l->read_next_request (); + } + else if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log ("IPC: Write failed: ", error_a.message ()); + } + }); + + // Do not call any member variables here (like session_timer) as it's possible that the next request may already be underway. + }); + + node.stats.inc (nano::stat::type::ipc, nano::stat::detail::invocations); + auto body (std::string (reinterpret_cast (buffer.data ()), buffer.size ())); + + // Note that if the rpc action is async, the shared_ptr lifetime will be extended by the action handler + auto handler (std::make_shared (node, server.node_rpc_config, body, response_handler_l, [& server = server]() { + server.stop (); + server.node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (3), [& io_ctx = server.node.alarm.io_ctx]() { + io_ctx.stop (); + }); + })); + // For unsafe actions to be allowed, the unsafe encoding must be used AND the transport config must allow it + handler->process_request (allow_unsafe && config_transport.allow_unsafe); + } + + /** Async request reader */ + void read_next_request () + { + auto this_l = this->shared_from_this (); + + // Await next request indefinitely + buffer.resize (sizeof (buffer_size)); + async_read_exactly (buffer.data (), buffer.size (), std::chrono::seconds::max (), [this_l]() { + auto encoding (this_l->buffer[nano::ipc::preamble_offset::encoding]); + this_l->active_encoding = static_cast (encoding); + if (this_l->buffer[nano::ipc::preamble_offset::lead] != 'N' || this_l->buffer[nano::ipc::preamble_offset::reserved_1] != 0 || this_l->buffer[nano::ipc::preamble_offset::reserved_2] != 0) + { + if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log ("IPC: Invalid preamble"); + } + } + else if (encoding == static_cast (nano::ipc::payload_encoding::json_v1) || encoding == static_cast (nano::ipc::payload_encoding::json_v1_unsafe)) + { + auto allow_unsafe (encoding == static_cast (nano::ipc::payload_encoding::json_v1_unsafe)); + // Length of payload + this_l->async_read_exactly (&this_l->buffer_size, sizeof (this_l->buffer_size), [this_l, allow_unsafe]() { + boost::endian::big_to_native_inplace (this_l->buffer_size); + this_l->buffer.resize (this_l->buffer_size); + // Payload (ptree compliant JSON string) + this_l->async_read_exactly (this_l->buffer.data (), this_l->buffer_size, [this_l, allow_unsafe]() { + this_l->handle_json_query (allow_unsafe); + }); + }); + } + else if (encoding == static_cast (nano::ipc::payload_encoding::flatbuffers) || encoding == static_cast (nano::ipc::payload_encoding::flatbuffers_json)) + { + // Length of payload + this_l->async_read_exactly (&this_l->buffer_size, sizeof (this_l->buffer_size), [this_l, encoding]() { + boost::endian::big_to_native_inplace (this_l->buffer_size); + this_l->buffer.resize (this_l->buffer_size); + // Payload (flatbuffers or flatbuffers mappable json) + this_l->async_read_exactly (this_l->buffer.data (), this_l->buffer_size, [this_l, encoding]() { + this_l->session_timer.restart (); + + // Lazily create one Flatbuffers handler instance per session + if (!this_l->flatbuffers_handler) + { + this_l->flatbuffers_handler = std::make_shared (this_l->node, this_l->server, this_l->get_subscriber (), this_l->node.config.ipc_config); + } + + if (encoding == static_cast (nano::ipc::payload_encoding::flatbuffers_json)) + { + this_l->flatbuffers_handler->process_json (this_l->buffer.data (), this_l->buffer_size, [this_l](std::shared_ptr body) { + if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log (boost::str (boost::format ("IPC/Flatbuffer request completed in: %1% %2%") % this_l->session_timer.stop ().count () % this_l->session_timer.unit ())); + } + + auto big_endian_length = std::make_shared (boost::endian::native_to_big (static_cast (body->size ()))); + boost::array buffers = { + boost::asio::buffer (big_endian_length.get (), sizeof (std::uint32_t)), + boost::asio::buffer (body->data (), body->size ()) + }; + + this_l->queued_write (buffers, [this_l, body, big_endian_length](boost::system::error_code const & error_a, size_t size_a) { + if (!error_a) + { + this_l->read_next_request (); + } + else if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log ("IPC: Write failed: ", error_a.message ()); + } + }); + }); + } + else + { + this_l->flatbuffers_handler->process (this_l->buffer.data (), this_l->buffer_size, [this_l](std::shared_ptr fbb) { + if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log (boost::str (boost::format ("IPC/Flatbuffer request completed in: %1% %2%") % this_l->session_timer.stop ().count () % this_l->session_timer.unit ())); + } + + auto big_endian_length = std::make_shared (boost::endian::native_to_big (static_cast (fbb->GetSize ()))); + boost::array buffers = { + boost::asio::buffer (big_endian_length.get (), sizeof (std::uint32_t)), + boost::asio::buffer (fbb->GetBufferPointer (), fbb->GetSize ()) + }; + + this_l->queued_write (buffers, [this_l, fbb, big_endian_length](boost::system::error_code const & error_a, size_t size_a) { + if (!error_a) + { + this_l->read_next_request (); + } + else if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log ("IPC: Write failed: ", error_a.message ()); + } + }); + }); + } + }); + }); + } + else if (this_l->node.config.logging.log_ipc ()) + { + this_l->node.logger.always_log ("IPC: Unsupported payload encoding"); + } + }); + } + + /** Shut down and close socket. This is also called if the timer expires. */ + void close () + { + boost::system::error_code ec_ignored; + socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both, ec_ignored); + socket.close (ec_ignored); + } + +private: + /** Holds the buffer and callback for queued writes */ + class queue_item + { + public: + boost::asio::const_buffer buffer; + std::function callback; + }; + size_t const queue_size_max = 64 * 1024; + + nano::ipc::ipc_server & server; + nano::node & node; + + /** Unique session id */ + std::atomic session_id; + + /** Service name associated with this session. This is set through the ServiceRegister API */ + nano::locked service_name; + + /** + * The payload encoding currently in use by this session. This is set as requests are + * received and usually never changes (although a client technically can) + */ + std::atomic active_encoding; + + /** Timer for measuring the duration of ipc calls */ + nano::timer session_timer; + + /** + * IO context from node, or per-transport, depending on configuration. + * Certain transports may scale better if they use a separate context. + */ + boost::asio::io_context & io_ctx; + + /** IO strand for synchronizing */ + boost::asio::strand strand; + + /** The send queue is protected by always being accessed through the strand */ + std::deque send_queue; + + /** A socket of the given asio type */ + SOCKET_TYPE socket; + + /** Buffer sizes are read into this */ + uint32_t buffer_size{ 0 }; + + /** Buffer used to store data received from the client */ + std::vector buffer; + + /** Transport configuration */ + nano::ipc::ipc_config_transport & config_transport; + + /** Handler for Flatbuffers requests. This is created lazily on the first request. */ + std::shared_ptr flatbuffers_handler; + + /** Session subscriber */ + std::shared_ptr subscriber; +}; + +/** Domain and TCP socket transport */ +template +class socket_transport : public nano::ipc::transport +{ +public: + socket_transport (nano::ipc::ipc_server & server_a, ENDPOINT_TYPE endpoint_a, nano::ipc::ipc_config_transport & config_transport_a, int concurrency_a) : + server (server_a), config_transport (config_transport_a) + { + // Using a per-transport event dispatcher? + if (concurrency_a > 0) + { + io_ctx = std::make_unique (); + } + + boost::asio::socket_base::reuse_address option (true); + boost::asio::socket_base::keep_alive option_keepalive (true); + acceptor = std::make_unique (context (), endpoint_a); + acceptor->set_option (option); + acceptor->set_option (option_keepalive); + accept (); + + // Start serving IO requests. If concurrency_a is < 1, the node's thread pool/io_context is used instead. + // A separate io_context for domain sockets may facilitate better performance on some systems. + if (concurrency_a > 0) + { + runner = std::make_unique (*io_ctx, static_cast (concurrency_a)); + } + } + + boost::asio::io_context & context () const + { + return io_ctx ? *io_ctx : server.node.io_ctx; + } + + void accept () + { + // Prepare the next session + auto new_session (std::make_shared> (server, context (), config_transport)); + + acceptor->async_accept (new_session->get_socket (), [this, new_session](boost::system::error_code const & ec) { + if (!ec) + { + new_session->read_next_request (); + } + else + { + server.node.logger.always_log ("IPC: acceptor error: ", ec.message ()); + } + + if (ec != boost::asio::error::operation_aborted && acceptor->is_open ()) + { + this->accept (); + } + else + { + server.node.logger.always_log ("IPC: shutting down"); + } + }); + } + + void stop () + { + acceptor->close (); + if (io_ctx) + { + io_ctx->stop (); + } + + if (runner) + { + runner->join (); + } + } + +private: + nano::ipc::ipc_server & server; + nano::ipc::ipc_config_transport & config_transport; + std::unique_ptr runner; + std::unique_ptr io_ctx; + std::unique_ptr acceptor; +}; +} + +/** + * Awaits SIGHUP via signal_set instead of std::signal, as this allows the handler to escape the + * Posix signal handler restrictions + */ +void await_hup_signal (std::shared_ptr const & signals, nano::ipc::ipc_server & server_a) +{ + signals->async_wait ([signals, &server_a](const boost::system::error_code & ec, int signal_number) { + if (ec != boost::asio::error::operation_aborted) + { + std::cout << "Reloading access configuration..." << std::endl; + auto error (server_a.reload_access_config ()); + if (!error) + { + std::cout << "Reloaded access configuration successfully" << std::endl; + } + await_hup_signal (signals, server_a); + } + }); +} + +nano::ipc::ipc_server::ipc_server (nano::node & node_a, nano::node_rpc_config const & node_rpc_config_a) : +node (node_a), +node_rpc_config (node_rpc_config_a), +broker (node_a) +{ + try + { + nano::error access_config_error (reload_access_config ()); + if (access_config_error) + { + std::exit (1); + } +#ifndef _WIN32 + // Hook up config reloading through the HUP signal + auto signals (std::make_shared (node.io_ctx, SIGHUP)); + await_hup_signal (signals, *this); +#endif + if (node_a.config.ipc_config.transport_domain.enabled) + { +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + auto threads = node_a.config.ipc_config.transport_domain.io_threads; + file_remover = std::make_unique (node_a.config.ipc_config.transport_domain.path); + boost::asio::local::stream_protocol::endpoint ep{ node_a.config.ipc_config.transport_domain.path }; + transports.push_back (std::make_shared> (*this, ep, node_a.config.ipc_config.transport_domain, threads)); +#else + node.logger.always_log ("IPC: Domain sockets are not supported on this platform"); +#endif + } + + if (node_a.config.ipc_config.transport_tcp.enabled) + { + auto threads = node_a.config.ipc_config.transport_tcp.io_threads; + transports.push_back (std::make_shared> (*this, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v6 (), node_a.config.ipc_config.transport_tcp.port), node_a.config.ipc_config.transport_tcp, threads)); + } + + node.logger.always_log ("IPC: server started"); + + if (!transports.empty ()) + { + broker.start (); + } + } + catch (std::runtime_error const & ex) + { + node.logger.always_log ("IPC: ", ex.what ()); + } +} + +nano::ipc::ipc_server::~ipc_server () +{ + node.logger.always_log ("IPC: server stopped"); +} + +void nano::ipc::ipc_server::stop () +{ + for (auto & transport : transports) + { + transport->stop (); + } +} + +nano::ipc::broker & nano::ipc::ipc_server::get_broker () +{ + return broker; +} + +nano::ipc::access & nano::ipc::ipc_server::get_access () +{ + return access; +} + +nano::error nano::ipc::ipc_server::reload_access_config () +{ + nano::error access_config_error (nano::ipc::read_access_config_toml (node.application_path, access)); + if (access_config_error) + { + auto error (boost::str (boost::format ("IPC: invalid access configuration file: %1%") % access_config_error.get_message ())); + std::cerr << error << std::endl; + node.logger.always_log (error); + } + return access_config_error; +} diff --git a/nano/node/ipc/ipc_server.hpp b/nano/node/ipc/ipc_server.hpp new file mode 100644 index 0000000000..6815f90739 --- /dev/null +++ b/nano/node/ipc/ipc_server.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace flatbuffers +{ +class Parser; +} +namespace nano +{ +class node; +class error; +namespace ipc +{ + class access; + /** The IPC server accepts connections on one or more configured transports */ + class ipc_server final + { + public: + ipc_server (nano::node & node, nano::node_rpc_config const & node_rpc_config); + ~ipc_server (); + void stop (); + + nano::node & node; + nano::node_rpc_config const & node_rpc_config; + + /** Unique counter/id shared across sessions */ + std::atomic id_dispenser{ 1 }; + nano::ipc::broker & get_broker (); + nano::ipc::access & get_access (); + nano::error reload_access_config (); + + private: + void setup_callbacks (); + nano::ipc::broker broker; + nano::ipc::access access; + std::unique_ptr file_remover; + std::vector> transports; + }; +} +} diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 79c15279ce..bbec8f5ca5 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1,32 +1,24 @@ #include #include #include +#include #include -#include +#include #include #include #include #include +#include -#include -#include -#include -#include #include #include -#include #include #include -#include -#include -#include -#include -#include namespace { -void construct_json (nano::seq_con_info_component * component, boost::property_tree::ptree & parent); +void construct_json (nano::container_info_component * component, boost::property_tree::ptree & parent); using ipc_json_handler_no_arg_func_map = std::unordered_map>; ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map (); auto ipc_json_handler_no_arg_funcs = create_ipc_json_handler_no_arg_func_map (); @@ -43,6 +35,24 @@ node_rpc_config (node_rpc_config_a) { } +std::function nano::json_handler::create_worker_task (std::function const &)> const & action_a) +{ + return [rpc_l = shared_from_this (), action_a]() { + try + { + action_a (rpc_l); + } + catch (std::runtime_error const &) + { + json_error_response (rpc_l->response, "Unable to parse JSON"); + } + catch (...) + { + json_error_response (rpc_l->response, "Internal server error in RPC"); + } + }; +} + void nano::json_handler::process_request (bool unsafe_a) { try @@ -236,7 +246,7 @@ nano::account_info nano::json_handler::account_info_impl (nano::transaction cons if (node.store.account_get (transaction_a, account_a, result)) { ec = nano::error_common::account_not_found; - node.bootstrap_initiator.bootstrap_lazy (account_a, false, false); + node.bootstrap_initiator.bootstrap_lazy (account_a, false, false, account_a.to_account ()); } } return result; @@ -258,19 +268,27 @@ nano::amount nano::json_handler::amount_impl () std::shared_ptr nano::json_handler::block_impl (bool signature_work_required) { + const bool json_block_l = request.get ("json_block", false); std::shared_ptr result{ nullptr }; if (!ec) { - std::string block_text (request.get ("block")); boost::property_tree::ptree block_l; - std::stringstream block_stream (block_text); - try + if (json_block_l) { - boost::property_tree::read_json (block_stream, block_l); + block_l = request.get_child ("block"); } - catch (...) + else { - ec = nano::error_blocks::invalid_block; + std::string block_text (request.get ("block")); + std::stringstream block_stream (block_text); + try + { + boost::property_tree::read_json (block_stream, block_l); + } + catch (...) + { + ec = nano::error_blocks::invalid_block; + } } if (!ec) { @@ -289,26 +307,6 @@ std::shared_ptr nano::json_handler::block_impl (bool signature_work return result; } -std::shared_ptr nano::json_handler::block_json_impl (bool signature_work_required) -{ - std::shared_ptr result; - if (!ec) - { - auto block_l (request.get_child ("block")); - if (!signature_work_required) - { - block_l.put ("signature", "0"); - block_l.put ("work", "0"); - } - result = nano::deserialize_block_json (block_l); - if (result == nullptr) - { - ec = nano::error_blocks::invalid_block; - } - } - return result; -} - nano::block_hash nano::json_handler::hash_impl (std::string search_text) { nano::block_hash result (0); @@ -351,9 +349,9 @@ uint64_t nano::json_handler::work_optional_impl () return result; } -uint64_t nano::json_handler::difficulty_optional_impl () +uint64_t nano::json_handler::difficulty_optional_impl (nano::work_version const version_a) { - uint64_t difficulty (node.network_params.network.publish_threshold); + auto difficulty (node.default_difficulty (version_a)); boost::optional difficulty_text (request.get_optional ("difficulty")); if (!ec && difficulty_text.is_initialized ()) { @@ -365,7 +363,44 @@ uint64_t nano::json_handler::difficulty_optional_impl () return difficulty; } -double nano::json_handler::multiplier_optional_impl (uint64_t & difficulty) +uint64_t nano::json_handler::difficulty_ledger (nano::block const & block_a) +{ + nano::block_details details (nano::epoch::epoch_0, false, false, false); + bool details_found (false); + auto transaction (node.store.tx_begin_read ()); + // Previous block find + std::shared_ptr block_previous (nullptr); + auto previous (block_a.previous ()); + if (!previous.is_zero ()) + { + block_previous = node.store.block_get (transaction, previous); + } + // Send check + if (block_previous != nullptr) + { + details.is_send = node.store.block_balance (transaction, previous) > block_a.balance ().number (); + details_found = true; + } + // Epoch check + if (block_previous != nullptr) + { + details.epoch = block_previous->sideband ().details.epoch; + } + auto link (block_a.link ()); + if (!link.is_zero () && !details.is_send) + { + auto block_link (node.store.block_get (transaction, link)); + if (block_link != nullptr && node.store.pending_exists (transaction, nano::pending_key (block_a.account (), link))) + { + details.epoch = std::max (details.epoch, block_link->sideband ().details.epoch); + details.is_receive = true; + details_found = true; + } + } + return details_found ? nano::work_threshold (block_a.work_version (), details) : node.default_difficulty (block_a.work_version ()); +} + +double nano::json_handler::multiplier_optional_impl (nano::work_version const version_a, uint64_t & difficulty) { double multiplier (1.); boost::optional multiplier_text (request.get_optional ("multiplier")); @@ -374,7 +409,7 @@ double nano::json_handler::multiplier_optional_impl (uint64_t & difficulty) auto success = boost::conversion::try_lexical_convert (multiplier_text.get (), multiplier); if (success && multiplier > 0.) { - difficulty = nano::difficulty::from_multiplier (multiplier, node.network_params.network.publish_threshold); + difficulty = nano::difficulty::from_multiplier (multiplier, node.default_difficulty (version_a)); } else { @@ -384,6 +419,24 @@ double nano::json_handler::multiplier_optional_impl (uint64_t & difficulty) return multiplier; } +nano::work_version nano::json_handler::work_version_optional_impl (nano::work_version const default_a) +{ + nano::work_version result = default_a; + boost::optional version_text (request.get_optional ("version")); + if (!ec && version_text.is_initialized ()) + { + if (*version_text == nano::to_string (nano::work_version::work_1)) + { + result = nano::work_version::work_1; + } + else + { + ec = nano::error_rpc::bad_work_version; + } + } + return result; +} + namespace { bool decode_unsigned (std::string const & text, uint64_t & number) @@ -477,8 +530,7 @@ void nano::json_handler::account_block_count () void nano::json_handler::account_create () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -515,7 +567,7 @@ void nano::json_handler::account_create () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::account_get () @@ -543,8 +595,8 @@ void nano::json_handler::account_info () const bool pending = request.get ("pending", false); auto transaction (node.store.tx_begin_read ()); auto info (account_info_impl (transaction, account)); - uint64_t confirmation_height; - if (node.store.confirmation_height_get (transaction, account, confirmation_height)) + nano::confirmation_height_info confirmation_height_info; + if (node.store.confirmation_height_get (transaction, account, confirmation_height_info)) { ec = nano::error_common::account_not_found; } @@ -559,7 +611,8 @@ void nano::json_handler::account_info () response_l.put ("modified_timestamp", std::to_string (info.modified)); response_l.put ("block_count", std::to_string (info.block_count)); response_l.put ("account_version", epoch_as_string (info.epoch ())); - response_l.put ("confirmation_height", std::to_string (confirmation_height)); + response_l.put ("confirmation_height", std::to_string (confirmation_height_info.height)); + response_l.put ("confirmation_height_frontier", confirmation_height_info.frontier.to_string ()); if (representative) { response_l.put ("representative", info.representative.to_account ()); @@ -609,8 +662,7 @@ void nano::json_handler::account_list () void nano::json_handler::account_move () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -644,13 +696,12 @@ void nano::json_handler::account_move () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::account_remove () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); auto account (rpc_l->account_impl ()); if (!rpc_l->ec) @@ -665,7 +716,7 @@ void nano::json_handler::account_remove () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::account_representative () @@ -685,10 +736,7 @@ void nano::json_handler::account_representative () void nano::json_handler::account_representative_set () { - auto rpc_l (shared_from_this ()); - // clang-format off - node.worker.push_task ([ rpc_l, work_generation_enabled = node.work_generation_enabled () ]() { - // clang-format on + node.worker.push_task (create_worker_task ([work_generation_enabled = node.work_generation_enabled ()](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); auto account (rpc_l->account_impl ()); std::string representative_text (rpc_l->request.get ("representative")); @@ -707,7 +755,8 @@ void nano::json_handler::account_representative_set () auto info (rpc_l->account_info_impl (block_transaction, account)); if (!rpc_l->ec) { - if (nano::work_validate (info.head, work)) + nano::block_details details (info.epoch (), false, false, false); + if (nano::work_difficulty (nano::work_version::work_1, info.head, work) < nano::work_threshold (nano::work_version::work_1, details)) { rpc_l->ec = nano::error_common::invalid_work; } @@ -726,22 +775,21 @@ void nano::json_handler::account_representative_set () bool generate_work (work == 0); // Disable work generation if "work" option is provided auto response_a (rpc_l->response); auto response_data (std::make_shared (rpc_l->response_l)); - // clang-format off - wallet->change_async(account, representative, [response_a, response_data](std::shared_ptr block) { + wallet->change_async ( + account, representative, [response_a, response_data](std::shared_ptr block) { if (block != nullptr) { - response_data->put("block", block->hash().to_string()); + response_data->put ("block", block->hash ().to_string ()); std::stringstream ostream; - boost::property_tree::write_json(ostream, *response_data); - response_a(ostream.str()); + boost::property_tree::write_json (ostream, *response_data); + response_a (ostream.str ()); } else { - json_error_response(response_a, "Error generating block"); + json_error_response (response_a, "Error generating block"); } }, - work, generate_work); - // clang-format on + work, generate_work); } } // Because of change_async @@ -749,7 +797,7 @@ void nano::json_handler::account_representative_set () { rpc_l->response_errors (); } - }); + })); } void nano::json_handler::account_weight () @@ -784,8 +832,7 @@ void nano::json_handler::accounts_balances () void nano::json_handler::accounts_create () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); auto count (rpc_l->count_impl ()); if (!rpc_l->ec) @@ -805,7 +852,7 @@ void nano::json_handler::accounts_create () rpc_l->response_l.add_child ("accounts", accounts); } rpc_l->response_errors (); - }); + })); } void nano::json_handler::accounts_frontiers () @@ -845,7 +892,7 @@ void nano::json_handler::accounts_pending () if (!ec) { boost::property_tree::ptree peers_l; - for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))), n (node.store.pending_end ()); i != n && nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) { nano::pending_key const & key (i->first); if (block_confirmed (node, transaction, key.hash, include_active, include_only_confirmed)) @@ -901,11 +948,11 @@ void nano::json_handler::accounts_pending () void nano::json_handler::active_difficulty () { auto include_trend (request.get ("include_trend", false)); - response_l.put ("network_minimum", nano::to_string_hex (node.network_params.network.publish_threshold)); - auto difficulty_active = node.active.active_difficulty (); - response_l.put ("network_current", nano::to_string_hex (difficulty_active)); - auto multiplier = nano::difficulty::to_multiplier (difficulty_active, node.network_params.network.publish_threshold); - response_l.put ("multiplier", nano::to_string (multiplier)); + auto multiplier_active = node.active.active_multiplier (); + auto default_difficulty (node.default_difficulty (nano::work_version::work_1)); + response_l.put ("network_minimum", nano::to_string_hex (default_difficulty)); + response_l.put ("network_current", nano::to_string_hex (nano::difficulty::from_multiplier (multiplier_active, default_difficulty))); + response_l.put ("multiplier", multiplier_active); if (include_trend) { boost::property_tree::ptree trend_entry_l; @@ -932,49 +979,23 @@ void nano::json_handler::available_supply () response_errors (); } -void state_subtype (nano::transaction const & transaction_a, nano::node & node_a, std::shared_ptr block_a, nano::uint128_t const & balance_a, boost::property_tree::ptree & tree_a) -{ - // Subtype check - auto previous_balance (node_a.ledger.balance (transaction_a, block_a->previous ())); - if (balance_a < previous_balance) - { - tree_a.put ("subtype", "send"); - } - else - { - if (block_a->link ().is_zero ()) - { - tree_a.put ("subtype", "change"); - } - else if (balance_a == previous_balance && node_a.ledger.is_epoch_link (block_a->link ())) - { - tree_a.put ("subtype", "epoch"); - } - else - { - tree_a.put ("subtype", "receive"); - } - } -} - void nano::json_handler::block_info () { auto hash (hash_impl ()); if (!ec) { - nano::block_sideband sideband; auto transaction (node.store.tx_begin_read ()); - auto block (node.store.block_get (transaction, hash, &sideband)); + auto block (node.store.block_get (transaction, hash)); if (block != nullptr) { - nano::account account (block->account ().is_zero () ? sideband.account : block->account ()); + nano::account account (block->account ().is_zero () ? block->sideband ().account : block->account ()); response_l.put ("block_account", account.to_account ()); auto amount (node.ledger.amount (transaction, hash)); response_l.put ("amount", amount.convert_to ()); auto balance (node.ledger.balance (transaction, hash)); response_l.put ("balance", balance.convert_to ()); - response_l.put ("height", std::to_string (sideband.height)); - response_l.put ("local_timestamp", std::to_string (sideband.timestamp)); + response_l.put ("height", std::to_string (block->sideband ().height)); + response_l.put ("local_timestamp", std::to_string (block->sideband ().timestamp)); auto confirmed (node.ledger.block_confirmed (transaction, hash)); response_l.put ("confirmed", confirmed); @@ -993,7 +1014,8 @@ void nano::json_handler::block_info () } if (block->type () == nano::block_type::state) { - state_subtype (transaction, node, block, balance, response_l); + auto subtype (nano::state_subtype (block->sideband ().details)); + response_l.put ("subtype", subtype); } } else @@ -1015,20 +1037,19 @@ void nano::json_handler::block_confirm () { if (!node.ledger.block_confirmed (transaction, hash)) { - // Start new confirmation for unconfirmed block - node.block_confirm (std::move (block_l)); + // Start new confirmation for unconfirmed (or not being confirmed) block + if (!node.confirmation_height_processor.is_processing_block (hash)) + { + node.block_confirm (std::move (block_l)); + } } else { // Add record in confirmation history for confirmed block - nano::election_status status{ block_l, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, nano::election_status_type::active_confirmation_height }; + nano::election_status status{ block_l, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::active_confirmation_height }; { nano::lock_guard lock (node.active.mutex); - node.active.confirmed.push_back (status); - if (node.active.confirmed.size () > node.config.confirmation_history_size) - { - node.active.confirmed.pop_front (); - } + node.active.add_recently_cemented (status); } // Trigger callback for confirmed block node.block_arrival.add (hash); @@ -1113,19 +1134,18 @@ void nano::json_handler::blocks_info () nano::block_hash hash; if (!hash.decode_hex (hash_text)) { - nano::block_sideband sideband; - auto block (node.store.block_get (transaction, hash, &sideband)); + auto block (node.store.block_get (transaction, hash)); if (block != nullptr) { boost::property_tree::ptree entry; - nano::account account (block->account ().is_zero () ? sideband.account : block->account ()); + nano::account account (block->account ().is_zero () ? block->sideband ().account : block->account ()); entry.put ("block_account", account.to_account ()); auto amount (node.ledger.amount (transaction, hash)); entry.put ("amount", amount.convert_to ()); auto balance (node.ledger.balance (transaction, hash)); entry.put ("balance", balance.convert_to ()); - entry.put ("height", std::to_string (sideband.height)); - entry.put ("local_timestamp", std::to_string (sideband.timestamp)); + entry.put ("height", std::to_string (block->sideband ().height)); + entry.put ("local_timestamp", std::to_string (block->sideband ().timestamp)); auto confirmed (node.ledger.block_confirmed (transaction, hash)); entry.put ("confirmed", confirmed); @@ -1143,7 +1163,8 @@ void nano::json_handler::blocks_info () } if (block->type () == nano::block_type::state) { - state_subtype (transaction, node, block, balance, entry); + auto subtype (nano::state_subtype (block->sideband ().details)); + entry.put ("subtype", subtype); } if (pending) { @@ -1220,10 +1241,9 @@ void nano::json_handler::block_account () void nano::json_handler::block_count () { - auto transaction (node.store.tx_begin_read ()); - response_l.put ("count", std::to_string (node.store.block_count (transaction).sum ())); - response_l.put ("unchecked", std::to_string (node.store.unchecked_count (transaction))); - response_l.put ("cemented", std::to_string (node.ledger.cemented_count)); + response_l.put ("count", std::to_string (node.ledger.cache.block_count)); + response_l.put ("unchecked", std::to_string (node.ledger.cache.unchecked_count)); + response_l.put ("cemented", std::to_string (node.ledger.cache.cemented_count)); response_errors (); } @@ -1243,8 +1263,11 @@ void nano::json_handler::block_create () { std::string type (request.get ("type")); nano::wallet_id wallet (0); + // Default to work_1 if not specified + auto work_version (work_version_optional_impl (nano::work_version::work_1)); + auto difficulty_l (difficulty_optional_impl (work_version)); boost::optional wallet_text (request.get_optional ("wallet")); - if (wallet_text.is_initialized ()) + if (!ec && wallet_text.is_initialized ()) { if (wallet.decode_hex (wallet_text.get ())) { @@ -1372,6 +1395,7 @@ void nano::json_handler::block_create () auto block_response_put_l = [rpc_l, this](nano::block const & block_a) { boost::property_tree::ptree response_l; response_l.put ("hash", block_a.hash ().to_string ()); + response_l.put ("difficulty", nano::to_string_hex (block_a.difficulty ())); bool json_block_l = request.get ("json_block", false); if (json_block_l) { @@ -1390,9 +1414,9 @@ void nano::json_handler::block_create () rpc_l->response (ostream.str ()); }; // Wrapper from argument to lambda capture, to extend the block's scope - auto get_callback_l = [rpc_l, this, block_response_put_l](std::shared_ptr block_a) { + auto get_callback_l = [rpc_l, block_response_put_l](std::shared_ptr block_a) { // Callback upon work generation success or failure - return [block_a, rpc_l, this, block_response_put_l](boost::optional const & work_a) { + return [block_a, rpc_l, block_response_put_l](boost::optional const & work_a) { if (block_a != nullptr) { if (work_a.is_initialized ()) @@ -1553,7 +1577,12 @@ void nano::json_handler::block_create () { if (work == 0) { - node.work_generate (root_l, get_callback_l (block_l), nano::account (pub)); + // Difficulty calculation + if (request.count ("difficulty") == 0) + { + difficulty_l = difficulty_ledger (*block_l); + } + node.work_generate (work_version, root_l, difficulty_l, get_callback_l (block_l), nano::account (pub)); } else { @@ -1576,16 +1605,7 @@ void nano::json_handler::block_create () void nano::json_handler::block_hash () { - const bool json_block_l = request.get ("json_block", false); - std::shared_ptr block; - if (json_block_l) - { - block = block_json_impl (true); - } - else - { - block = block_impl (true); - } + auto block (block_impl (true)); if (!ec) { @@ -1600,7 +1620,7 @@ void nano::json_handler::bootstrap () std::string port_text = request.get ("port"); const bool bypass_frontier_confirmation = request.get ("bypass_frontier_confirmation", false); boost::system::error_code address_ec; - auto address (boost::asio::ip::address_v6::from_string (address_text, address_ec)); + auto address (boost::asio::ip::make_address_v6 (address_text, address_ec)); if (!address_ec) { uint16_t port; @@ -1608,7 +1628,8 @@ void nano::json_handler::bootstrap () { if (!node.flags.disable_legacy_bootstrap) { - node.bootstrap_initiator.bootstrap (nano::endpoint (address, port), true, bypass_frontier_confirmation); + std::string bootstrap_id (request.get ("id", "")); + node.bootstrap_initiator.bootstrap (nano::endpoint (address, port), true, bypass_frontier_confirmation, bootstrap_id); response_l.put ("success", ""); } else @@ -1633,7 +1654,8 @@ void nano::json_handler::bootstrap_any () const bool force = request.get ("force", false); if (!node.flags.disable_legacy_bootstrap) { - node.bootstrap_initiator.bootstrap (force); + std::string bootstrap_id (request.get ("id", "")); + node.bootstrap_initiator.bootstrap (force, bootstrap_id); response_l.put ("success", ""); } else @@ -1651,7 +1673,8 @@ void nano::json_handler::bootstrap_lazy () { if (!node.flags.disable_lazy_bootstrap) { - node.bootstrap_initiator.bootstrap_lazy (hash, force); + std::string bootstrap_id (request.get ("id", "")); + node.bootstrap_initiator.bootstrap_lazy (hash, force, true, bootstrap_id); response_l.put ("started", "1"); } else @@ -1667,53 +1690,39 @@ void nano::json_handler::bootstrap_lazy () */ void nano::json_handler::bootstrap_status () { - auto attempt (node.bootstrap_initiator.current_attempt ()); - if (attempt != nullptr) + auto attempts_count (node.bootstrap_initiator.attempts.size ()); + response_l.put ("bootstrap_threads", std::to_string (node.config.bootstrap_initiator_threads)); + response_l.put ("running_attempts_count", std::to_string (attempts_count)); + response_l.put ("total_attempts_count", std::to_string (node.bootstrap_initiator.attempts.incremental)); + boost::property_tree::ptree connections; { - nano::lock_guard lock (attempt->mutex); - nano::lock_guard lazy_lock (attempt->lazy_mutex); - response_l.put ("clients", std::to_string (attempt->clients.size ())); - response_l.put ("pulls", std::to_string (attempt->pulls.size ())); - response_l.put ("pulling", std::to_string (attempt->pulling)); - response_l.put ("connections", std::to_string (attempt->connections)); - response_l.put ("idle", std::to_string (attempt->idle.size ())); - response_l.put ("target_connections", std::to_string (attempt->target_connections (attempt->pulls.size ()))); - response_l.put ("total_blocks", std::to_string (attempt->total_blocks)); - response_l.put ("runs_count", std::to_string (attempt->runs_count)); - response_l.put ("requeued_pulls", std::to_string (attempt->requeued_pulls)); - response_l.put ("frontiers_received", static_cast (attempt->frontiers_received)); - response_l.put ("frontiers_confirmed", static_cast (attempt->frontiers_confirmed)); - std::string mode_text; - if (attempt->mode == nano::bootstrap_mode::legacy) - { - mode_text = "legacy"; - } - else if (attempt->mode == nano::bootstrap_mode::lazy) - { - mode_text = "lazy"; - } - else if (attempt->mode == nano::bootstrap_mode::wallet_lazy) - { - mode_text = "wallet_lazy"; - } - response_l.put ("mode", mode_text); - response_l.put ("lazy_blocks", std::to_string (attempt->lazy_blocks.size ())); - response_l.put ("lazy_state_backlog", std::to_string (attempt->lazy_state_backlog.size ())); - response_l.put ("lazy_balances", std::to_string (attempt->lazy_balances.size ())); - response_l.put ("lazy_destinations", std::to_string (attempt->lazy_destinations.size ())); - response_l.put ("lazy_undefined_links", std::to_string (attempt->lazy_undefined_links.size ())); - response_l.put ("lazy_pulls", std::to_string (attempt->lazy_pulls.size ())); - response_l.put ("lazy_keys", std::to_string (attempt->lazy_keys.size ())); - if (!attempt->lazy_keys.empty ()) - { - response_l.put ("lazy_key_1", (*(attempt->lazy_keys.begin ())).to_string ()); - } - response_l.put ("duration", std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt->attempt_start).count ()); + nano::lock_guard connections_lock (node.bootstrap_initiator.connections->mutex); + connections.put ("clients", std::to_string (node.bootstrap_initiator.connections->clients.size ())); + connections.put ("connections", std::to_string (node.bootstrap_initiator.connections->connections_count)); + connections.put ("idle", std::to_string (node.bootstrap_initiator.connections->idle.size ())); + connections.put ("target_connections", std::to_string (node.bootstrap_initiator.connections->target_connections (node.bootstrap_initiator.connections->pulls.size (), attempts_count))); + connections.put ("pulls", std::to_string (node.bootstrap_initiator.connections->pulls.size ())); } - else + response_l.add_child ("connections", connections); + boost::property_tree::ptree attempts; { - response_l.put ("active", "0"); - } + nano::lock_guard attempts_lock (node.bootstrap_initiator.attempts.bootstrap_attempts_mutex); + for (auto i : node.bootstrap_initiator.attempts.attempts) + { + boost::property_tree::ptree entry; + auto & attempt (i.second); + entry.put ("id", attempt->id); + entry.put ("mode", attempt->mode_text ()); + entry.put ("started", static_cast (attempt->started)); + entry.put ("pulling", std::to_string (attempt->pulling)); + entry.put ("total_blocks", std::to_string (attempt->total_blocks)); + entry.put ("requeued_pulls", std::to_string (attempt->requeued_pulls)); + attempt->get_information (entry); + entry.put ("duration", std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt->attempt_start).count ()); + attempts.push_back (std::make_pair ("", entry)); + } + } + response_l.add_child ("attempts", attempts); response_errors (); } @@ -1757,6 +1766,7 @@ void nano::json_handler::chain (bool successors) void nano::json_handler::confirmation_active () { uint64_t announcements (0); + uint64_t confirmed (0); boost::optional announcements_text (request.get_optional ("announcements")); if (announcements_text.is_initialized ()) { @@ -1767,24 +1777,33 @@ void nano::json_handler::confirmation_active () nano::lock_guard lock (node.active.mutex); for (auto i (node.active.roots.begin ()), n (node.active.roots.end ()); i != n; ++i) { - if (i->election->confirmation_request_count >= announcements && !i->election->confirmed && !i->election->stopped) + if (i->election->confirmation_request_count >= announcements) { - boost::property_tree::ptree entry; - entry.put ("", i->root.to_string ()); - elections.push_back (std::make_pair ("", entry)); + if (!i->election->confirmed ()) + { + boost::property_tree::ptree entry; + entry.put ("", i->root.to_string ()); + elections.push_back (std::make_pair ("", entry)); + } + else + { + ++confirmed; + } } } } response_l.add_child ("confirmations", elections); + response_l.put ("unconfirmed", elections.size ()); + response_l.put ("confirmed", confirmed); response_errors (); } void nano::json_handler::confirmation_height_currently_processing () { - auto hash = node.pending_confirmation_height.current (); + auto hash = node.confirmation_height_processor.current (); if (!hash.is_zero ()) { - response_l.put ("hash", node.pending_confirmation_height.current ().to_string ()); + response_l.put ("hash", hash.to_string ()); } else { @@ -1806,7 +1825,7 @@ void nano::json_handler::confirmation_history () } if (!ec) { - auto confirmed (node.active.list_confirmed ()); + auto confirmed (node.active.list_recently_cemented ()); for (auto i (confirmed.begin ()), n (confirmed.end ()); i != n; ++i) { if (hash.is_zero () || i->winner->hash () == hash) @@ -1816,7 +1835,9 @@ void nano::json_handler::confirmation_history () election.put ("duration", i->election_duration.count ()); election.put ("time", i->election_end.count ()); election.put ("tally", i->tally.to_string_dec ()); - election.put ("request_count", i->confirmation_request_count); + election.put ("blocks", std::to_string (i->block_count)); + election.put ("voters", std::to_string (i->voter_count)); + election.put ("request_count", std::to_string (i->confirmation_request_count)); elections.push_back (std::make_pair ("", election)); } running_total += i->election_duration; @@ -1841,14 +1862,14 @@ void nano::json_handler::confirmation_info () nano::qualified_root root; if (!root.decode_hex (root_text)) { - nano::lock_guard lock (node.active.mutex); - auto conflict_info (node.active.roots.find (root)); - if (conflict_info != node.active.roots.end ()) + auto election (node.active.election (root)); + nano::lock_guard guard (node.active.mutex); + if (election != nullptr && !election->confirmed ()) { - response_l.put ("announcements", std::to_string (conflict_info->election->confirmation_request_count)); - auto election (conflict_info->election); - nano::uint128_t total (0); + response_l.put ("announcements", std::to_string (election->confirmation_request_count)); + response_l.put ("voters", std::to_string (election->last_votes.size ())); response_l.put ("last_winner", election->status.winner->hash ().to_string ()); + nano::uint128_t total (0); auto tally_l (election->tally ()); boost::property_tree::ptree blocks; for (auto i (tally_l.begin ()), n (tally_l.end ()); i != n; ++i) @@ -1880,7 +1901,7 @@ void nano::json_handler::confirmation_info () if (i->second->hash () == ii->second.hash) { nano::account const & representative (ii->first); - auto amount (node.ledger.rep_weights.representation_get (representative)); + auto amount (node.ledger.cache.rep_weights.representation_get (representative)); representatives.emplace (std::move (amount), representative); } } @@ -2048,197 +2069,6 @@ void nano::json_handler::deterministic_key () response_errors (); } -void epoch_upgrader (std::shared_ptr node_a, nano::private_key const & prv_a, nano::epoch epoch_a, uint64_t count_limit) -{ - uint64_t const upgrade_batch_size = 1000; - nano::block_builder builder; - auto link (node_a->ledger.epoch_link (epoch_a)); - nano::raw_key raw_key; - raw_key.data = prv_a; - auto signer (nano::pub_key (prv_a)); - assert (signer == node_a->ledger.epoch_signer (link)); - - class account_upgrade_item final - { - public: - nano::account account{ 0 }; - uint64_t modified{ 0 }; - }; - class account_tag - { - }; - class modified_tag - { - }; - boost::multi_index_container< - account_upgrade_item, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique, boost::multi_index::member, std::greater>, - boost::multi_index::hashed_unique, boost::multi_index::member>>> - accounts_list; - - bool finished_upgrade (false); - - while (!finished_upgrade && !node_a->stopped) - { - bool finished_accounts (false); - uint64_t total_upgraded_accounts (0); - while (!finished_accounts && count_limit != 0 && !node_a->stopped) - { - { - auto transaction (node_a->store.tx_begin_read ()); - // Collect accounts to upgrade - for (auto i (node_a->store.latest_begin (transaction)), n (node_a->store.latest_end ()); i != n; ++i) - { - nano::account const & account (i->first); - nano::account_info const & info (i->second); - if (info.epoch () < epoch_a) - { - release_assert (nano::epochs::is_sequential (info.epoch (), epoch_a)); - accounts_list.insert (account_upgrade_item{ account, info.modified }); - } - } - } - - /* Upgrade accounts - Repeat until accounts with previous epoch exist in latest table */ - uint64_t upgraded_accounts (0); - for (auto i (accounts_list.get ().begin ()), n (accounts_list.get ().end ()); i != n && upgraded_accounts < upgrade_batch_size && upgraded_accounts < count_limit && !node_a->stopped; ++i) - { - auto transaction (node_a->store.tx_begin_read ()); - nano::account_info info; - if (!node_a->store.account_get (transaction, i->account, info) && info.epoch () < epoch_a) - { - auto epoch = builder.state () - .account (i->account) - .previous (info.head) - .representative (info.representative) - .balance (info.balance) - .link (link) - .sign (raw_key, signer) - .work (node_a->work_generate_blocking (info.head).value_or (0)) - .build (); - bool valid_signature (!nano::validate_message (signer, epoch->hash (), epoch->block_signature ())); - bool valid_work (!nano::work_validate (*epoch.get ())); - nano::process_result result (nano::process_result::old); - if (valid_signature && valid_work) - { - result = node_a->process_local (std::move (epoch)).code; - } - if (result == nano::process_result::progress) - { - ++upgraded_accounts; - } - else - { - bool fork (result == nano::process_result::fork); - node_a->logger.always_log (boost::str (boost::format ("Failed to upgrade account %1%. Valid signature: %2%. Valid work: %3%. Block processor fork: %4%") % i->account.to_account () % valid_signature % valid_work % fork)); - } - } - } - total_upgraded_accounts += upgraded_accounts; - count_limit -= upgraded_accounts; - - if (!accounts_list.empty ()) - { - node_a->logger.always_log (boost::str (boost::format ("%1% accounts were upgraded to new epoch, %2% remain...") % total_upgraded_accounts % (accounts_list.size () - upgraded_accounts))); - accounts_list.clear (); - } - else - { - node_a->logger.always_log (boost::str (boost::format ("%1% total accounts were upgraded to new epoch") % total_upgraded_accounts)); - finished_accounts = true; - } - } - - // Pending blocks upgrade - bool finished_pending (false); - uint64_t total_upgraded_pending (0); - while (!finished_pending && count_limit != 0 && !node_a->stopped) - { - uint64_t upgraded_pending (0); - auto transaction (node_a->store.tx_begin_read ()); - for (auto i (node_a->store.pending_begin (transaction, nano::pending_key (1, 0))), n (node_a->store.pending_end ()); i != n && upgraded_pending < upgrade_batch_size && upgraded_pending < count_limit && !node_a->stopped;) - { - bool to_next_account (false); - nano::pending_key const & key (i->first); - if (!node_a->store.account_exists (transaction, key.account)) - { - nano::pending_info const & info (i->second); - if (info.epoch < epoch_a) - { - release_assert (nano::epochs::is_sequential (info.epoch, epoch_a)); - auto epoch = builder.state () - .account (key.account) - .previous (0) - .representative (0) - .balance (0) - .link (link) - .sign (raw_key, signer) - .work (node_a->work_generate_blocking (key.account).value_or (0)) - .build (); - bool valid_signature (!nano::validate_message (signer, epoch->hash (), epoch->block_signature ())); - bool valid_work (!nano::work_validate (*epoch.get ())); - nano::process_result result (nano::process_result::old); - if (valid_signature && valid_work) - { - result = node_a->process_local (std::move (epoch)).code; - } - if (result == nano::process_result::progress) - { - ++upgraded_pending; - to_next_account = true; - } - else - { - bool fork (result == nano::process_result::fork); - node_a->logger.always_log (boost::str (boost::format ("Failed to upgrade account with pending blocks %1%. Valid signature: %2%. Valid work: %3%. Block processor fork: %4%") % key.account.to_account () % valid_signature % valid_work % fork)); - } - } - } - else - { - to_next_account = true; - } - if (to_next_account) - { - // Move to next account if pending account exists or was upgraded - if (key.account.number () == std::numeric_limits::max ()) - { - break; - } - else - { - i = node_a->store.pending_begin (transaction, nano::pending_key (key.account.number () + 1, 0)); - } - } - else - { - // Move to next pending item - ++i; - } - } - total_upgraded_pending += upgraded_pending; - count_limit -= upgraded_pending; - - // Repeat if some pending accounts were upgraded - if (upgraded_pending != 0) - { - node_a->logger.always_log (boost::str (boost::format ("%1% unopened accounts with pending blocks were upgraded to new epoch...") % total_upgraded_pending)); - } - else - { - node_a->logger.always_log (boost::str (boost::format ("%1% total unopened accounts with pending blocks were upgraded to new epoch") % total_upgraded_pending)); - finished_pending = true; - } - } - - finished_upgrade = (total_upgraded_accounts == 0) && (total_upgraded_pending == 0); - } - - node_a->logger.always_log ("Epoch upgrade is completed"); -} - /* * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable */ @@ -2260,17 +2090,29 @@ void nano::json_handler::epoch_upgrade () if (epoch != nano::epoch::invalid) { uint64_t count_limit (count_optional_impl ()); + uint64_t threads (0); + boost::optional threads_text (request.get_optional ("threads")); + if (!ec && threads_text.is_initialized ()) + { + if (decode_unsigned (threads_text.get (), threads)) + { + ec = nano::error_rpc::invalid_threads_count; + } + } std::string key_text (request.get ("key")); nano::private_key prv; if (!prv.decode_hex (key_text)) { if (nano::pub_key (prv) == node.ledger.epoch_signer (node.ledger.epoch_link (epoch))) { - auto node_l (node.shared ()); - node.worker.push_task ([node_l, prv, epoch, count_limit]() { - epoch_upgrader (node_l, prv, epoch, count_limit); - }); - response_l.put ("started", "1"); + if (!node.epoch_upgrader (prv, epoch, count_limit, threads)) + { + response_l.put ("started", "1"); + } + else + { + response_l.put ("started", "0"); + } } else { @@ -2308,8 +2150,7 @@ void nano::json_handler::frontiers () void nano::json_handler::account_count () { - auto transaction (node.store.tx_begin_read ()); - auto size (node.store.account_count (transaction)); + auto size (node.ledger.cache.account_count.load ()); response_l.put ("count", std::to_string (size)); response_errors (); } @@ -2552,8 +2393,7 @@ void nano::json_handler::account_history () boost::property_tree::ptree history; bool output_raw (request.get_optional ("raw") == true); response_l.put ("account", account.to_account ()); - nano::block_sideband sideband; - auto block (node.store.block_get (transaction, hash, &sideband)); + auto block (node.store.block_get (transaction, hash)); while (block != nullptr && count > 0) { if (offset > 0) @@ -2567,8 +2407,8 @@ void nano::json_handler::account_history () block->visit (visitor); if (!entry.empty ()) { - entry.put ("local_timestamp", std::to_string (sideband.timestamp)); - entry.put ("height", std::to_string (sideband.height)); + entry.put ("local_timestamp", std::to_string (block->sideband ().timestamp)); + entry.put ("height", std::to_string (block->sideband ().height)); entry.put ("hash", hash.to_string ()); if (output_raw) { @@ -2580,7 +2420,7 @@ void nano::json_handler::account_history () } } hash = reverse ? node.store.block_successor (transaction, hash) : block->previous (); - block = node.store.block_get (transaction, hash, &sideband); + block = node.store.block_get (transaction, hash); } response_l.add_child ("history", history); if (!hash.is_zero ()) @@ -2816,8 +2656,7 @@ void nano::json_handler::node_id_delete () void nano::json_handler::password_change () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -2835,13 +2674,12 @@ void nano::json_handler::password_change () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::password_enter () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -2851,7 +2689,7 @@ void nano::json_handler::password_enter () rpc_l->response_l.put ("valid", error ? "0" : "1"); } rpc_l->response_errors (); - }); + })); } void nano::json_handler::password_valid (bool wallet_locked) @@ -2926,7 +2764,7 @@ void nano::json_handler::pending () { boost::property_tree::ptree peers_l; auto transaction (node.store.tx_begin_read ()); - for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))), n (node.store.pending_end ()); i != n && nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) { nano::pending_key const & key (i->first); if (block_confirmed (node, transaction, key.hash, include_active, include_only_confirmed)) @@ -3014,8 +2852,7 @@ void nano::json_handler::pending_exists () void nano::json_handler::payment_begin () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { std::string id_text (rpc_l->request.get ("wallet")); nano::wallet_id id; if (!id.decode_hex (id_text)) @@ -3081,13 +2918,12 @@ void nano::json_handler::payment_begin () rpc_l->ec = nano::error_common::bad_wallet_number; } rpc_l->response_errors (); - }); + })); } void nano::json_handler::payment_init () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -3104,7 +2940,7 @@ void nano::json_handler::payment_init () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::payment_end () @@ -3163,19 +2999,9 @@ void nano::json_handler::payment_wait () void nano::json_handler::process () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { - const bool json_block_l = rpc_l->request.get ("json_block", false); + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { const bool watch_work_l = rpc_l->request.get ("watch_work", true); - std::shared_ptr block; - if (json_block_l) - { - block = rpc_l->block_json_impl (true); - } - else - { - block = rpc_l->block_impl (true); - } + auto block (rpc_l->block_impl (true)); // State blocks subtype check if (!rpc_l->ec && block->type () == nano::block_type::state) @@ -3246,7 +3072,7 @@ void nano::json_handler::process () } if (!rpc_l->ec) { - if (!nano::work_validate (*block)) + if (!nano::work_validate_entry (*block)) { auto result (rpc_l->node.process_local (block, watch_work_l)); switch (result.code) @@ -3312,6 +3138,11 @@ void nano::json_handler::process () } break; } + case nano::process_result::insufficient_work: + { + rpc_l->ec = nano::error_process::insufficient_work; + break; + } default: { rpc_l->ec = nano::error_process::other; @@ -3325,7 +3156,7 @@ void nano::json_handler::process () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::receive () @@ -3335,9 +3166,9 @@ void nano::json_handler::receive () auto hash (hash_impl ("block")); if (!ec) { - auto transaction (node.wallets.tx_begin_read ()); - wallet_locked_impl (transaction, wallet); - wallet_account_impl (transaction, wallet, account); + auto wallet_transaction (node.wallets.tx_begin_read ()); + wallet_locked_impl (wallet_transaction, wallet); + wallet_account_impl (wallet_transaction, wallet, account); if (!ec) { auto block_transaction (node.store.tx_begin_read ()); @@ -3351,15 +3182,19 @@ void nano::json_handler::receive () { nano::account_info info; nano::root head; + nano::epoch epoch = block->sideband ().details.epoch; if (!node.store.account_get (block_transaction, account, info)) { head = info.head; + // When receiving, epoch version is the higher between the previous and the source blocks + epoch = std::max (info.epoch (), epoch); } else { head = account; } - if (nano::work_validate (head, work)) + nano::block_details details (epoch, false, true, false); + if (nano::work_difficulty (nano::work_version::work_1, head, work) < nano::work_threshold (nano::work_version::work_1, details)) { ec = nano::error_common::invalid_work; } @@ -3373,25 +3208,27 @@ void nano::json_handler::receive () } if (!ec) { + // Representative is only used by receive_action when opening accounts + // Set a wallet default representative for new accounts + nano::account representative (wallet->store.representative (wallet_transaction)); bool generate_work (work == 0); // Disable work generation if "work" option is provided auto response_a (response); - // clang-format off - wallet->receive_async(std::move(block), account, node.network_params.ledger.genesis_amount, [response_a](std::shared_ptr block_a) { + wallet->receive_async ( + std::move (block), representative, node.network_params.ledger.genesis_amount, [response_a](std::shared_ptr block_a) { if (block_a != nullptr) { boost::property_tree::ptree response_l; - response_l.put("block", block_a->hash().to_string()); + response_l.put ("block", block_a->hash ().to_string ()); std::stringstream ostream; - boost::property_tree::write_json(ostream, response_l); - response_a(ostream.str()); + boost::property_tree::write_json (ostream, response_l); + response_a (ostream.str ()); } else { - json_error_response(response_a, "Error generating block"); + json_error_response (response_a, "Error generating block"); } }, - work, generate_work); - // clang-format on + work, generate_work); } } else @@ -3439,7 +3276,7 @@ void nano::json_handler::representatives () { const bool sorting = request.get ("sorting", false); boost::property_tree::ptree representatives; - auto rep_amounts = node.ledger.rep_weights.get_rep_amounts (); + auto rep_amounts = node.ledger.cache.rep_weights.get_rep_amounts (); if (!sorting) // Simple { std::map ordered (rep_amounts.begin (), rep_amounts.end ()); @@ -3700,7 +3537,8 @@ void nano::json_handler::send () } if (!ec && work) { - if (nano::work_validate (info.head, work)) + nano::block_details details (info.epoch (), true, false, false); + if (nano::work_difficulty (nano::work_version::work_1, info.head, work) < nano::work_threshold (nano::work_version::work_1, details)) { ec = nano::error_common::invalid_work; } @@ -3712,30 +3550,29 @@ void nano::json_handler::send () boost::optional send_id (request.get_optional ("id")); auto response_a (response); auto response_data (std::make_shared (response_l)); - // clang-format off - wallet->send_async(source, destination, amount.number(), [balance, amount, response_a, response_data](std::shared_ptr block_a) { + wallet->send_async ( + source, destination, amount.number (), [balance, amount, response_a, response_data](std::shared_ptr block_a) { if (block_a != nullptr) { - response_data->put("block", block_a->hash().to_string()); + response_data->put ("block", block_a->hash ().to_string ()); std::stringstream ostream; - boost::property_tree::write_json(ostream, *response_data); - response_a(ostream.str()); + boost::property_tree::write_json (ostream, *response_data); + response_a (ostream.str ()); } else { - if (balance >= amount.number()) + if (balance >= amount.number ()) { - json_error_response(response_a, "Error generating block"); + json_error_response (response_a, "Error generating block"); } else { - std::error_code ec(nano::error_common::insufficient_balance); - json_error_response(response_a, ec.message()); + std::error_code ec (nano::error_common::insufficient_balance); + json_error_response (response_a, ec.message ()); } } }, - work, generate_work, send_id); - // clang-format on + work, generate_work, send_id); } } // Because of send_async @@ -3757,17 +3594,9 @@ void nano::json_handler::sign () } // Retrieving block std::shared_ptr block; - boost::optional block_text (request.get_optional ("block")); - if (!ec && block_text.is_initialized ()) + if (!ec && request.count ("block")) { - if (json_block_l) - { - block = block_json_impl (true); - } - else - { - block = block_impl (true); - } + block = block_impl (true); if (block != nullptr) { hash = block->hash (); @@ -3862,7 +3691,7 @@ void nano::json_handler::stats () } else if (type == "objects") { - construct_json (collect_seq_con_info (node, "node").get (), response_l); + construct_json (collect_container_info (node, "node").get (), response_l); } else if (type == "samples") { @@ -3907,6 +3736,169 @@ void nano::json_handler::stop () } } +void nano::json_handler::telemetry () +{ + auto rpc_l (shared_from_this ()); + + auto address_text (request.get_optional ("address")); + auto port_text (request.get_optional ("port")); + + if (address_text.is_initialized () || port_text.is_initialized ()) + { + // Check both are specified + std::shared_ptr channel; + if (address_text.is_initialized () && port_text.is_initialized ()) + { + uint16_t port; + if (!nano::parse_port (*port_text, port)) + { + boost::asio::ip::address address; + if (!nano::parse_address (*address_text, address)) + { + nano::endpoint endpoint (address, port); + if (address.is_loopback () && port == rpc_l->node.network.endpoint ().port ()) + { + // Requesting telemetry metrics locally + auto telemetry_data = nano::local_telemetry_data (rpc_l->node.ledger.cache, rpc_l->node.network, rpc_l->node.config.bandwidth_limit, rpc_l->node.network_params, rpc_l->node.startup_time, rpc_l->node.active.active_difficulty (), rpc_l->node.node_id); + + nano::jsonconfig config_l; + auto const should_ignore_identification_metrics = false; + auto err = telemetry_data.serialize_json (config_l, should_ignore_identification_metrics); + auto const & ptree = config_l.get_tree (); + + if (!err) + { + rpc_l->response_l.insert (rpc_l->response_l.begin (), ptree.begin (), ptree.end ()); + } + + rpc_l->response_errors (); + return; + } + else + { + channel = node.network.find_channel (nano::transport::map_endpoint_to_v6 (endpoint)); + if (!channel) + { + ec = nano::error_rpc::peer_not_found; + } + } + } + else + { + ec = nano::error_common::invalid_ip_address; + } + } + else + { + ec = nano::error_common::invalid_port; + } + } + else + { + ec = nano::error_rpc::requires_port_and_address; + } + + if (!ec) + { + debug_assert (channel); + if (node.telemetry) + { + node.telemetry->get_metrics_single_peer_async (channel, [rpc_l](auto const & telemetry_response_a) { + if (!telemetry_response_a.error) + { + nano::jsonconfig config_l; + auto const should_ignore_identification_metrics = false; + auto err = telemetry_response_a.telemetry_data.serialize_json (config_l, should_ignore_identification_metrics); + auto const & ptree = config_l.get_tree (); + + if (!err) + { + rpc_l->response_l.insert (rpc_l->response_l.begin (), ptree.begin (), ptree.end ()); + } + else + { + rpc_l->ec = nano::error_rpc::generic; + } + } + else + { + rpc_l->ec = nano::error_rpc::generic; + } + + rpc_l->response_errors (); + }); + } + else + { + response_errors (); + } + } + else + { + response_errors (); + } + } + else + { + // By default, consolidated (average or mode) telemetry metrics are returned, + // setting "raw" to true returns metrics from all nodes requested. + auto raw = request.get_optional ("raw"); + auto output_raw = raw.value_or (false); + if (node.telemetry) + { + auto telemetry_responses = node.telemetry->get_metrics (); + if (output_raw) + { + boost::property_tree::ptree metrics; + for (auto & telemetry_metrics : telemetry_responses) + { + nano::jsonconfig config_l; + auto const should_ignore_identification_metrics = false; + auto err = telemetry_metrics.second.serialize_json (config_l, should_ignore_identification_metrics); + config_l.put ("address", telemetry_metrics.first.address ()); + config_l.put ("port", telemetry_metrics.first.port ()); + if (!err) + { + metrics.push_back (std::make_pair ("", config_l.get_tree ())); + } + else + { + ec = nano::error_rpc::generic; + } + } + + response_l.put_child ("metrics", metrics); + } + else + { + nano::jsonconfig config_l; + std::vector telemetry_datas; + telemetry_datas.reserve (telemetry_responses.size ()); + std::transform (telemetry_responses.begin (), telemetry_responses.end (), std::back_inserter (telemetry_datas), [](auto const & endpoint_telemetry_data) { + return endpoint_telemetry_data.second; + }); + + auto average_telemetry_metrics = nano::consolidate_telemetry_data (telemetry_datas); + // Don't add node_id/signature in consolidated metrics + auto const should_ignore_identification_metrics = true; + auto err = average_telemetry_metrics.serialize_json (config_l, should_ignore_identification_metrics); + auto const & ptree = config_l.get_tree (); + + if (!err) + { + response_l.insert (response_l.begin (), ptree.begin (), ptree.end ()); + } + else + { + ec = nano::error_rpc::generic; + } + } + } + + response_errors (); + } +} + void nano::json_handler::unchecked () { const bool json_block_l = request.get ("json_block", false); @@ -3938,13 +3930,13 @@ void nano::json_handler::unchecked () void nano::json_handler::unchecked_clear () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { - auto transaction (rpc_l->node.store.tx_begin_write ()); + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { + auto transaction (rpc_l->node.store.tx_begin_write ({ tables::unchecked })); rpc_l->node.store.unchecked_clear (transaction); + rpc_l->node.ledger.cache.unchecked_count = 0; rpc_l->response_l.put ("success", ""); rpc_l->response_errors (); - }); + })); } void nano::json_handler::unchecked_get () @@ -4100,8 +4092,9 @@ void nano::json_handler::version () response_l.put ("store_version", std::to_string (node.store_version ())); response_l.put ("protocol_version", std::to_string (node.network_params.protocol.protocol_version)); response_l.put ("node_vendor", boost::str (boost::format ("Nano %1%") % NANO_VERSION_STRING)); + response_l.put ("store_vendor", node.store.vendor_get ()); response_l.put ("network", node.network_params.network.get_current_network_as_string ()); - response_l.put ("network_identifier", nano::genesis ().hash ().to_string ()); + response_l.put ("network_identifier", node.network_params.ledger.genesis_hash.to_string ()); response_l.put ("build_info", BUILD_INFO); response_errors (); } @@ -4117,8 +4110,7 @@ void nano::json_handler::validate_account_number () void nano::json_handler::wallet_add () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -4143,13 +4135,12 @@ void nano::json_handler::wallet_add () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_add_watch () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -4178,7 +4169,7 @@ void nano::json_handler::wallet_add_watch () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_info () @@ -4249,8 +4240,7 @@ void nano::json_handler::wallet_balances () void nano::json_handler::wallet_change_seed () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); if (!rpc_l->ec) { @@ -4266,7 +4256,7 @@ void nano::json_handler::wallet_change_seed () rpc_l->response_l.put ("success", ""); rpc_l->response_l.put ("last_restored_account", account.to_account ()); auto index (wallet->store.deterministic_index_get (transaction)); - assert (index > 0); + debug_assert (index > 0); rpc_l->response_l.put ("restored_count", std::to_string (index)); } else @@ -4280,7 +4270,7 @@ void nano::json_handler::wallet_change_seed () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_contains () @@ -4298,8 +4288,7 @@ void nano::json_handler::wallet_contains () void nano::json_handler::wallet_create () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { nano::raw_key seed; auto seed_text (rpc_l->request.get_optional ("seed")); if (seed_text.is_initialized () && seed.data.decode_hex (seed_text.get ())) @@ -4325,18 +4314,17 @@ void nano::json_handler::wallet_create () nano::public_key account (wallet->change_seed (transaction, seed)); rpc_l->response_l.put ("last_restored_account", account.to_account ()); auto index (wallet->store.deterministic_index_get (transaction)); - assert (index > 0); + debug_assert (index > 0); rpc_l->response_l.put ("restored_count", std::to_string (index)); } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_destroy () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { std::string wallet_text (rpc_l->request.get ("wallet")); nano::wallet_id wallet; if (!wallet.decode_hex (wallet_text)) @@ -4358,7 +4346,7 @@ void nano::json_handler::wallet_destroy () rpc_l->ec = nano::error_common::bad_wallet_number; } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_export () @@ -4423,9 +4411,8 @@ void nano::json_handler::wallet_history () auto hash (info.head); while (timestamp >= modified_since && !hash.is_zero ()) { - nano::block_sideband sideband; - auto block (node.store.block_get (block_transaction, hash, &sideband)); - timestamp = sideband.timestamp; + auto block (node.store.block_get (block_transaction, hash)); + timestamp = block->sideband ().timestamp; if (block != nullptr && timestamp >= modified_since) { boost::property_tree::ptree entry; @@ -4559,7 +4546,7 @@ void nano::json_handler::wallet_pending () { nano::account const & account (i->first); boost::property_tree::ptree peers_l; - for (auto ii (node.store.pending_begin (block_transaction, nano::pending_key (account, 0))); nano::pending_key (ii->first).account == account && peers_l.size () < count; ++ii) + for (auto ii (node.store.pending_begin (block_transaction, nano::pending_key (account, 0))), nn (node.store.pending_end ()); ii != nn && nano::pending_key (ii->first).account == account && peers_l.size () < count; ++ii) { nano::pending_key key (ii->first); if (block_confirmed (node, block_transaction, key.hash, include_active, include_only_confirmed)) @@ -4620,8 +4607,7 @@ void nano::json_handler::wallet_representative () void nano::json_handler::wallet_representative_set () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); std::string representative_text (rpc_l->request.get ("representative")); auto representative (rpc_l->account_impl (representative_text, nano::error_rpc::bad_representative_number)); @@ -4662,14 +4648,13 @@ void nano::json_handler::wallet_representative_set () } for (auto & account : accounts) { - // clang-format off - wallet->change_async(account, representative, [](std::shared_ptr) {}, 0, false); - // clang-format on + wallet->change_async ( + account, representative, [](std::shared_ptr) {}, 0, false); } } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::wallet_republish () @@ -4754,24 +4739,57 @@ void nano::json_handler::work_generate () { boost::optional account; auto account_opt (request.get_optional ("account")); - if (account_opt.is_initialized ()) + // Default to work_1 if not specified + auto work_version (work_version_optional_impl (nano::work_version::work_1)); + if (!ec && account_opt.is_initialized ()) { account = account_impl (account_opt.get ()); } if (!ec) { auto hash (hash_impl ()); - auto difficulty (difficulty_optional_impl ()); - multiplier_optional_impl (difficulty); - if (!ec && (difficulty > node.config.max_work_generate_difficulty || difficulty < node.network_params.network.publish_threshold)) + auto difficulty (difficulty_optional_impl (work_version)); + multiplier_optional_impl (work_version, difficulty); + if (!ec && (difficulty > node.max_work_generate_difficulty (work_version) || difficulty < nano::work_threshold_entry (work_version))) { ec = nano::error_rpc::difficulty_limit; } - if (!ec) + // Retrieving optional block + std::shared_ptr block; + if (!ec && request.count ("block")) + { + block = block_impl (true); + if (block != nullptr) + { + if (hash != block->root ()) + { + ec = nano::error_rpc::block_root_mismatch; + } + if (request.count ("version") == 0) + { + work_version = block->work_version (); + } + else if (!ec && work_version != block->work_version ()) + { + ec = nano::error_rpc::block_work_version_mismatch; + } + // Difficulty calculation + if (!ec && request.count ("difficulty") == 0 && request.count ("multiplier") == 0) + { + difficulty = difficulty_ledger (*block); + } + // If optional block difficulty is higher than requested difficulty, send error + if (!ec && block->difficulty () >= difficulty) + { + ec = nano::error_rpc::block_work_enough; + } + } + } + if (!ec && response_l.empty ()) { auto use_peers (request.get ("use_peers", false)); auto rpc_l (shared_from_this ()); - auto callback = [rpc_l, hash, this](boost::optional const & work_a) { + auto callback = [rpc_l, hash, work_version, this](boost::optional const & work_a) { if (work_a) { boost::property_tree::ptree response_l; @@ -4779,10 +4797,9 @@ void nano::json_handler::work_generate () uint64_t work (work_a.value ()); response_l.put ("work", nano::to_string_hex (work)); std::stringstream ostream; - uint64_t result_difficulty; - nano::work_validate (hash, work, &result_difficulty); + auto result_difficulty (nano::work_difficulty (work_version, hash, work)); response_l.put ("difficulty", nano::to_string_hex (result_difficulty)); - auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, this->node.network_params.network.publish_threshold); + auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, node.default_difficulty (work_version)); response_l.put ("multiplier", nano::to_string (result_multiplier)); boost::property_tree::write_json (ostream, response_l); rpc_l->response (ostream.str ()); @@ -4796,7 +4813,11 @@ void nano::json_handler::work_generate () { if (node.local_work_generation_enabled ()) { - node.work.generate (hash, callback, difficulty); + auto error = node.distributed_work.make (work_version, hash, {}, difficulty, callback, {}); + if (error) + { + ec = nano::error_common::failure_work_generation; + } } else { @@ -4818,7 +4839,7 @@ void nano::json_handler::work_generate () auto const & peers_l (secondary_work_peers_l ? node.config.secondary_work_peers : node.config.work_peers); if (node.work_generation_enabled (peers_l)) { - node.work_generate (hash, callback, difficulty, account, secondary_work_peers_l); + node.work_generate (work_version, hash, difficulty, callback, account, secondary_work_peers_l); } else { @@ -4865,8 +4886,7 @@ void nano::json_handler::work_get () void nano::json_handler::work_set () { - auto rpc_l (shared_from_this ()); - node.worker.push_task ([rpc_l]() { + node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { auto wallet (rpc_l->wallet_impl ()); auto account (rpc_l->account_impl ()); auto work (rpc_l->work_optional_impl ()); @@ -4881,22 +4901,36 @@ void nano::json_handler::work_set () } } rpc_l->response_errors (); - }); + })); } void nano::json_handler::work_validate () { auto hash (hash_impl ()); auto work (work_optional_impl ()); - auto difficulty (difficulty_optional_impl ()); - multiplier_optional_impl (difficulty); + // Default to work_1 if not specified + auto work_version (work_version_optional_impl (nano::work_version::work_1)); + auto difficulty (difficulty_optional_impl (work_version)); + multiplier_optional_impl (work_version, difficulty); if (!ec) { - uint64_t result_difficulty (0); - nano::work_validate (hash, work, &result_difficulty); - response_l.put ("valid", (result_difficulty >= difficulty) ? "1" : "0"); + /* Transition to epoch_2 difficulty levels breaks previous behavior. + * When difficulty is not given, the default difficulty to validate changes when the first epoch_2 block is seen, breaking previous behavior. + * For this reason, when difficulty is not given, the "valid" field is no longer included in the response to break loudly any client expecting it. + * Instead, use the new fields: + * * valid_all: the work is valid at the current highest difficulty threshold + * * valid_receive: the work is valid for a receive block in an epoch_2 upgraded account + */ + + auto result_difficulty (nano::work_difficulty (work_version, hash, work)); + if (request.count ("difficulty")) + { + response_l.put ("valid", (result_difficulty >= difficulty) ? "1" : "0"); + } + response_l.put ("valid_all", (result_difficulty >= node.default_difficulty (work_version)) ? "1" : "0"); + response_l.put ("valid_receive", (result_difficulty >= nano::work_threshold (work_version, nano::block_details (nano::epoch::epoch_2, false, true, false))) ? "1" : "0"); response_l.put ("difficulty", nano::to_string_hex (result_difficulty)); - auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, node.network_params.network.publish_threshold); + auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, node.default_difficulty (work_version)); response_l.put ("multiplier", nano::to_string (result_multiplier)); } response_errors (); @@ -4939,14 +4973,31 @@ void nano::json_handler::work_peers_clear () response_errors (); } +void nano::inprocess_rpc_handler::process_request (std::string const &, std::string const & body_a, std::function response_a) +{ + // Note that if the rpc action is async, the shared_ptr lifetime will be extended by the action handler + auto handler (std::make_shared (node, node_rpc_config, body_a, response_a, [this]() { + this->stop_callback (); + this->stop (); + })); + handler->process_request (); +} + +void nano::inprocess_rpc_handler::process_request_v2 (rpc_handler_request_params const & params_a, std::string const & body_a, std::function)> response_a) +{ + std::string body_l = params_a.json_envelope (body_a); + auto handler (std::make_shared (node, ipc_server, nullptr, node.config.ipc_config)); + handler->process_json (reinterpret_cast (body_l.data ()), body_l.size (), response_a); +} + namespace { -void construct_json (nano::seq_con_info_component * component, boost::property_tree::ptree & parent) +void construct_json (nano::container_info_component * component, boost::property_tree::ptree & parent) { // We are a leaf node, print name and exit if (!component->is_composite ()) { - auto & leaf_info = static_cast (component)->get_info (); + auto & leaf_info = static_cast (component)->get_info (); boost::property_tree::ptree child; child.put ("count", leaf_info.count); child.put ("size", leaf_info.count * leaf_info.sizeof_element); @@ -4954,7 +5005,7 @@ void construct_json (nano::seq_con_info_component * component, boost::property_t return; } - auto composite = static_cast (component); + auto composite = static_cast (component); boost::property_tree::ptree current; for (auto & child : composite->get_children ()) @@ -5046,6 +5097,7 @@ ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map () no_arg_funcs.emplace ("stats", &nano::json_handler::stats); no_arg_funcs.emplace ("stats_clear", &nano::json_handler::stats_clear); no_arg_funcs.emplace ("stop", &nano::json_handler::stop); + no_arg_funcs.emplace ("telemetry", &nano::json_handler::telemetry); no_arg_funcs.emplace ("unchecked", &nano::json_handler::unchecked); no_arg_funcs.emplace ("unchecked_clear", &nano::json_handler::unchecked_clear); no_arg_funcs.emplace ("unchecked_get", &nano::json_handler::unchecked_get); diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp index c9ee197c0d..f739ab6eca 100644 --- a/nano/node/json_handler.hpp +++ b/nano/node/json_handler.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -11,6 +12,10 @@ namespace nano { +namespace ipc +{ + class ipc_server; +} class node; class node_rpc_config; @@ -97,6 +102,7 @@ class json_handler : public std::enable_shared_from_this void stats (); void stats_clear (); void stop (); + void telemetry (); void unchecked (); void unchecked_clear (); void unchecked_get (); @@ -148,40 +154,36 @@ class json_handler : public std::enable_shared_from_this nano::account_info account_info_impl (nano::transaction const &, nano::account const &); nano::amount amount_impl (); std::shared_ptr block_impl (bool = true); - std::shared_ptr block_json_impl (bool = true); nano::block_hash hash_impl (std::string = "hash"); nano::amount threshold_optional_impl (); uint64_t work_optional_impl (); uint64_t count_impl (); uint64_t count_optional_impl (uint64_t = std::numeric_limits::max ()); uint64_t offset_optional_impl (uint64_t = 0); - uint64_t difficulty_optional_impl (); - double multiplier_optional_impl (uint64_t &); + uint64_t difficulty_optional_impl (nano::work_version const); + uint64_t difficulty_ledger (nano::block const &); + double multiplier_optional_impl (nano::work_version const, uint64_t &); + nano::work_version work_version_optional_impl (nano::work_version const default_a); bool enable_sign_hash{ false }; std::function stop_callback; nano::node_rpc_config const & node_rpc_config; + std::function create_worker_task (std::function const &)> const &); }; class inprocess_rpc_handler final : public nano::rpc_handler_interface { public: inprocess_rpc_handler ( - nano::node & node_a, nano::node_rpc_config const & node_rpc_config_a, std::function stop_callback_a = []() {}) : + nano::node & node_a, nano::ipc::ipc_server & ipc_server_a, nano::node_rpc_config const & node_rpc_config_a, std::function stop_callback_a = []() {}) : node (node_a), + ipc_server (ipc_server_a), stop_callback (stop_callback_a), node_rpc_config (node_rpc_config_a) { } - void process_request (std::string const &, std::string const & body_a, std::function response_a) override - { - // Note that if the rpc action is async, the shared_ptr lifetime will be extended by the action handler - auto handler (std::make_shared (node, node_rpc_config, body_a, response_a, [this]() { - this->stop_callback (); - this->stop (); - })); - handler->process_request (); - } + void process_request (std::string const &, std::string const & body_a, std::function response_a) override; + void process_request_v2 (rpc_handler_request_params const & params_a, std::string const & body_a, std::function)> response_a) override; void stop () override { @@ -198,6 +200,7 @@ class inprocess_rpc_handler final : public nano::rpc_handler_interface private: nano::node & node; + nano::ipc::ipc_server & ipc_server; boost::optional rpc; std::function stop_callback; nano::node_rpc_config const & node_rpc_config; diff --git a/nano/node/json_payment_observer.cpp b/nano/node/json_payment_observer.cpp index 929aee89eb..26e51e2a6b 100644 --- a/nano/node/json_payment_observer.cpp +++ b/nano/node/json_payment_observer.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/nano/node/json_payment_observer.hpp b/nano/node/json_payment_observer.hpp index 257de782bd..cd755ea07e 100644 --- a/nano/node/json_payment_observer.hpp +++ b/nano/node/json_payment_observer.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include #include -#include namespace nano { diff --git a/nano/node/lmdb/lmdb.cpp b/nano/node/lmdb/lmdb.cpp index b53e282b4d..5b54eac32f 100644 --- a/nano/node/lmdb/lmdb.cpp +++ b/nano/node/lmdb/lmdb.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include -#include +#include +#include #include #include @@ -38,18 +40,20 @@ void mdb_val::convert_buffer_to_value () } } -nano::mdb_store::mdb_store (nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, int lmdb_max_dbs, size_t const batch_size, bool backup_before_upgrade) : +nano::mdb_store::mdb_store (nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, nano::lmdb_config const & lmdb_config_a, size_t const batch_size_a, bool backup_before_upgrade_a) : logger (logger_a), -env (error, path_a, lmdb_max_dbs, true), +env (error, path_a, nano::mdb_env::options::make ().set_config (lmdb_config_a).set_use_no_mem_init (true)), mdb_txn_tracker (logger_a, txn_tracking_config_a, block_processor_batch_max_time_a), txn_tracking_enabled (txn_tracking_config_a.enable) { if (!error) { auto is_fully_upgraded (false); + auto is_fresh_db (false); { auto transaction (tx_begin_read ()); auto err = mdb_dbi_open (env.tx (transaction), "meta", 0, &meta); + is_fresh_db = err != MDB_SUCCESS; if (err == MDB_SUCCESS) { is_fully_upgraded = (version_get (transaction) == version); @@ -62,9 +66,17 @@ txn_tracking_enabled (txn_tracking_config_a.enable) // (can be a few minutes with the --fast_bootstrap flag for instance) if (!is_fully_upgraded) { - if (backup_before_upgrade) + nano::network_constants network_constants; + if (!is_fresh_db) { - create_backup_file (env, path_a, logger_a); + if (!network_constants.is_test_network ()) + { + std::cout << "Upgrade in progress..." << std::endl; + } + if (backup_before_upgrade_a) + { + create_backup_file (env, path_a, logger_a); + } } auto needs_vacuuming = false; { @@ -72,13 +84,14 @@ txn_tracking_enabled (txn_tracking_config_a.enable) open_databases (error, transaction, MDB_CREATE); if (!error) { - error |= do_upgrades (transaction, needs_vacuuming, batch_size); + error |= do_upgrades (transaction, needs_vacuuming, batch_size_a); } } - if (needs_vacuuming) + if (needs_vacuuming && !network_constants.is_test_network ()) { - auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_max_dbs); + logger.always_log ("Preparing vacuum..."); + auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_config_a); logger.always_log (vacuum_success ? "Vacuum succeeded." : "Failed to vacuum. (Optional) Ensure enough disk space is available for a copy of the database and try to vacuum after shutting down the node"); } } @@ -90,7 +103,7 @@ txn_tracking_enabled (txn_tracking_config_a.enable) } } -bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path_a, int lmdb_max_dbs) +bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a) { // Vacuum the database. This is not a required step and may actually fail if there isn't enough storage space. auto vacuum_path = path_a.parent_path () / "vacuumed.ldb"; @@ -99,6 +112,7 @@ bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path if (vacuum_success) { // Need to close the database to release the file handle + mdb_env_sync (env.environment, true); mdb_env_close (env.environment); env.environment = nullptr; @@ -106,7 +120,10 @@ bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path boost::filesystem::rename (vacuum_path, path_a); // Set up the environment again - env.init (error, path_a, lmdb_max_dbs, true); + auto options = nano::mdb_env::options::make () + .set_config (lmdb_config_a) + .set_use_no_mem_init (true); + env.init (error, path_a, options); if (!error) { auto transaction (tx_begin_read ()); @@ -136,19 +153,22 @@ nano::read_transaction nano::mdb_store::tx_begin_read () return env.tx_begin_read (create_txn_callbacks ()); } +std::string nano::mdb_store::vendor_get () const +{ + return boost::str (boost::format ("LMDB %1%.%2%.%3%") % MDB_VERSION_MAJOR % MDB_VERSION_MINOR % MDB_VERSION_PATCH); +} + nano::mdb_txn_callbacks nano::mdb_store::create_txn_callbacks () { nano::mdb_txn_callbacks mdb_txn_callbacks; if (txn_tracking_enabled) { - // clang-format off - mdb_txn_callbacks.txn_start = ([&mdb_txn_tracker = mdb_txn_tracker](const nano::transaction_impl * transaction_impl) { + mdb_txn_callbacks.txn_start = ([& mdb_txn_tracker = mdb_txn_tracker](const nano::transaction_impl * transaction_impl) { mdb_txn_tracker.add (transaction_impl); }); - mdb_txn_callbacks.txn_end = ([&mdb_txn_tracker = mdb_txn_tracker](const nano::transaction_impl * transaction_impl) { + mdb_txn_callbacks.txn_end = ([& mdb_txn_tracker = mdb_txn_tracker](const nano::transaction_impl * transaction_impl) { mdb_txn_tracker.erase (transaction_impl); }); - // clang-format on } return mdb_txn_callbacks; } @@ -168,6 +188,7 @@ void nano::mdb_store::open_databases (bool & error_a, nano::transaction const & error_a |= mdb_dbi_open (env.tx (transaction_a), "confirmation_height", flags, &confirmation_height) != 0; if (!full_sideband (transaction_a)) { + // The blocks_info database is no longer used, but need opening so that it can be deleted during an upgrade error_a |= mdb_dbi_open (env.tx (transaction_a), "blocks_info", flags, &blocks_info) != 0; } error_a |= mdb_dbi_open (env.tx (transaction_a), "accounts", flags, &accounts_v0) != 0; @@ -175,8 +196,15 @@ void nano::mdb_store::open_databases (bool & error_a, nano::transaction const & error_a |= mdb_dbi_open (env.tx (transaction_a), "pending", flags, &pending_v0) != 0; pending = pending_v0; + if (version_get (transaction_a) < 16) + { + // The representation database is no longer used, but needs opening so that it can be deleted during an upgrade + error_a |= mdb_dbi_open (env.tx (transaction_a), "representation", flags, &representation) != 0; + } + if (version_get (transaction_a) < 15) { + // These databases are no longer used, but need opening so they can be deleted during an upgrade error_a |= mdb_dbi_open (env.tx (transaction_a), "state", flags, &state_blocks_v0) != 0; state_blocks = state_blocks_v0; error_a |= mdb_dbi_open (env.tx (transaction_a), "accounts_v1", flags, &accounts_v1) != 0; @@ -225,6 +253,14 @@ bool nano::mdb_store::do_upgrades (nano::write_transaction & transaction_a, bool upgrade_v14_to_v15 (transaction_a); needs_vacuuming = true; case 15: + // Upgrades to v16, v17 & v18 are all part of the v21 node release + upgrade_v15_to_v16 (transaction_a); + case 16: + upgrade_v16_to_v17 (transaction_a); + case 17: + upgrade_v17_to_v18 (transaction_a); + needs_vacuuming = true; + case 18: break; default: logger.always_log (boost::str (boost::format ("The version of the ledger (%1%) is too high for this node") % version_l)); @@ -278,7 +314,7 @@ void nano::mdb_store::upgrade_v2_to_v3 (nano::write_transaction const & transact nano::account_info_v5 info ((*i)->second); representative_visitor visitor (transaction_a, *this); visitor.compute (info.head); - assert (!visitor.result.is_zero ()); + debug_assert (!visitor.result.is_zero ()); info.rep_block = visitor.result; auto impl (boost::polymorphic_downcast *> (i.get ())); mdb_cursor_put (impl->cursor, nano::mdb_val (account_l), nano::mdb_val (sizeof (info), &info), MDB_CURRENT); @@ -293,13 +329,13 @@ void nano::mdb_store::upgrade_v3_to_v4 (nano::write_transaction const & transact { nano::block_hash const & hash (i->first); nano::pending_info_v3 const & info (i->second); - items.push (std::make_pair (nano::pending_key (info.destination, hash), nano::pending_info_v14 (info.source, info.amount, nano::epoch::epoch_0))); + items.emplace (nano::pending_key (info.destination, hash), nano::pending_info_v14 (info.source, info.amount, nano::epoch::epoch_0)); } mdb_drop (env.tx (transaction_a), pending_v0, 0); while (!items.empty ()) { auto status (mdb_put (env.tx (transaction_a), pending, nano::mdb_val (items.front ().first), nano::mdb_val (items.front ().second), 0)); - assert (success (status)); + debug_assert (success (status)); items.pop (); } } @@ -328,7 +364,7 @@ void nano::mdb_store::upgrade_v4_to_v5 (nano::write_transaction const & transact { nano::block_type type; auto value (block_raw_get (transaction_a, block->previous (), type)); - assert (value.size () != 0); + debug_assert (value.size () != 0); std::vector data (static_cast (value.data ()), static_cast (value.data ()) + value.size ()); std::copy (hash.bytes.begin (), hash.bytes.end (), data.end () - nano::block_sideband::size (type)); block_raw_put (transaction_a, data, type, block->previous ()); @@ -354,7 +390,7 @@ void nano::mdb_store::upgrade_v5_to_v6 (nano::write_transaction const & transact { ++block_count; auto block (block_get (transaction_a, hash)); - assert (block != nullptr); + debug_assert (block != nullptr); hash = block->previous (); } headers.emplace_back (account, nano::account_info_v13{ info_old.head, info_old.rep_block, info_old.open_block, info_old.balance, info_old.modified, block_count, nano::epoch::epoch_0 }); @@ -402,7 +438,7 @@ void nano::mdb_store::upgrade_v8_to_v9 (nano::write_transaction const & transact } auto status1 (mdb_put (env.tx (transaction_a), vote, nano::mdb_val (i->first), nano::mdb_val (vector.size (), vector.data ()), 0)); release_assert (status1 == 0); - assert (!error); + debug_assert (!error); } mdb_drop (env.tx (transaction_a), sequence, 1); } @@ -462,7 +498,7 @@ void nano::mdb_store::upgrade_v12_to_v13 (nano::write_transaction & transaction_ bool is_state_block_v1 = false; auto block = block_get_v14 (transaction_a, hash, &sideband, &is_state_block_v1); - assert (block != nullptr); + debug_assert (block != nullptr); if (sideband.height == 0) { sideband.height = height; @@ -475,14 +511,14 @@ void nano::mdb_store::upgrade_v12_to_v13 (nano::write_transaction & transaction_ } nano::mdb_val value{ vector.size (), (void *)vector.data () }; - MDB_dbi database = is_state_block_v1 ? state_blocks_v1 : table_to_dbi (block_database (sideband.type)); + MDB_dbi database = is_state_block_v1 ? state_blocks_v1 : table_to_dbi (block_database (block->type ())); auto status = mdb_put (env.tx (transaction_a), database, nano::mdb_val (hash), value, 0); release_assert (success (status)); nano::block_predecessor_set predecessor (transaction_a, *this); block->visit (predecessor); - assert (block->previous ().is_zero () || block_successor (transaction_a, block->previous ()) == hash); + debug_assert (block->previous ().is_zero () || block_successor (transaction_a, block->previous ()) == hash); cost += 16; } else @@ -542,7 +578,7 @@ void nano::mdb_store::upgrade_v13_to_v14 (nano::write_transaction const & transa void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_a) { - logger.always_log ("Preparing v14 to v15 upgrade..."); + logger.always_log ("Preparing v14 to v15 database upgrade..."); std::vector> account_infos; upgrade_counters account_counters (count (transaction_a, accounts_v0), count (transaction_a, accounts_v1)); @@ -560,13 +596,13 @@ void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_ release_assert (rep_block != nullptr); account_infos.emplace_back (account, nano::account_info{ account_info_v14.head, rep_block->representative (), account_info_v14.open_block, account_info_v14.balance, account_info_v14.modified, account_info_v14.block_count, i_account.from_first_database ? nano::epoch::epoch_0 : nano::epoch::epoch_1 }); // Move confirmation height from account_info database to its own table - confirmation_height_put (transaction_a, account, account_info_v14.confirmation_height); + mdb_put (env.tx (transaction_a), confirmation_height, nano::mdb_val (account), nano::mdb_val (account_info_v14.confirmation_height), MDB_APPEND); i_account.from_first_database ? ++account_counters.after_v0 : ++account_counters.after_v1; } logger.always_log ("Finished extracting confirmation height to its own database"); - assert (account_counters.are_equal ()); + debug_assert (account_counters.are_equal ()); // No longer need accounts_v1, keep v0 but clear it mdb_drop (env.tx (transaction_a), accounts_v1, 1); mdb_drop (env.tx (transaction_a), accounts_v0, 0); @@ -596,14 +632,14 @@ void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_ nano::state_block_w_sideband_v14 state_block_w_sideband_v14 (i_state->second); auto & sideband_v14 = state_block_w_sideband_v14.sideband; - nano::block_sideband sideband{ sideband_v14.type, sideband_v14.account, sideband_v14.successor, sideband_v14.balance, sideband_v14.height, sideband_v14.timestamp, i_state.from_first_database ? nano::epoch::epoch_0 : nano::epoch::epoch_1 }; + nano::block_sideband sideband (sideband_v14.account, sideband_v14.successor, sideband_v14.balance, sideband_v14.height, sideband_v14.timestamp, i_state.from_first_database ? nano::epoch::epoch_0 : nano::epoch::epoch_1, false, false, false); // Write these out std::vector data; { nano::vectorstream stream (data); state_block_w_sideband_v14.state_block->serialize (stream); - sideband.serialize (stream); + sideband.serialize (stream, sideband_v14.type); } nano::mdb_val value{ data.size (), (void *)data.data () }; @@ -619,7 +655,7 @@ void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_ i_state.from_first_database ? ++state_counters.after_v0 : ++state_counters.after_v1; } - assert (state_counters.are_equal ()); + debug_assert (state_counters.are_equal ()); logger.always_log ("Epoch merge upgrade: Finished state blocks, now doing pending blocks"); state_blocks = state_blocks_new; @@ -643,7 +679,7 @@ void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_ i_pending.from_first_database ? ++pending_counters.after_v0 : ++pending_counters.after_v1; } - assert (pending_counters.are_equal ()); + debug_assert (pending_counters.are_equal ()); // No longer need the pending v1 table mdb_drop (env.tx (transaction_a), pending_v1, 1); @@ -654,15 +690,162 @@ void nano::mdb_store::upgrade_v14_to_v15 (nano::write_transaction & transaction_ mdb_put (env.tx (transaction_a), pending, nano::mdb_val (pending_key_pending_info_pair.first), nano::mdb_val (pending_key_pending_info_pair.second), MDB_APPEND); } + version_put (transaction_a, 15); + logger.always_log ("Finished epoch merge upgrade"); +} + +void nano::mdb_store::upgrade_v15_to_v16 (nano::write_transaction const & transaction_a) +{ // Representation table is no longer used + debug_assert (representation != 0); if (representation != 0) { auto status (mdb_drop (env.tx (transaction_a), representation, 1)); release_assert (status == MDB_SUCCESS); representation = 0; } - version_put (transaction_a, 15); - logger.always_log ("Finished epoch merge upgrade. Preparing vacuum..."); + version_put (transaction_a, 16); +} + +void nano::mdb_store::upgrade_v16_to_v17 (nano::write_transaction const & transaction_a) +{ + logger.always_log ("Preparing v16 to v17 database upgrade..."); + + auto account_info_i = latest_begin (transaction_a); + auto account_info_n = latest_end (); + + // Set the confirmed frontier for each account in the confirmation height table + std::vector> confirmation_height_infos; + auto num = 0u; + for (nano::mdb_iterator i (transaction_a, confirmation_height), n (nano::mdb_iterator{}); i != n; ++i, ++account_info_i, ++num) + { + nano::account account (i->first); + uint64_t confirmation_height (i->second); + + // Check account hashes matches both the accounts table and confirmation height table + debug_assert (account == account_info_i->first); + + auto const & account_info = account_info_i->second; + + if (confirmation_height == 0) + { + confirmation_height_infos.emplace_back (account, confirmation_height_info{ 0, nano::block_hash (0) }); + } + else + { + if (account_info_i->second.block_count / 2 >= confirmation_height) + { + // The confirmation height of the account is closer to the bottom of the chain, so start there and work up + auto block = block_get (transaction_a, account_info.open_block); + debug_assert (block); + auto height = 1; + + while (height != confirmation_height) + { + block = block_get (transaction_a, block->sideband ().successor); + debug_assert (block); + ++height; + } + + debug_assert (block->sideband ().height == confirmation_height); + confirmation_height_infos.emplace_back (account, confirmation_height_info{ confirmation_height, block->hash () }); + } + else + { + // The confirmation height of the account is closer to the top of the chain so start there and work down + auto block = block_get (transaction_a, account_info.head); + auto height = block->sideband ().height; + while (height != confirmation_height) + { + block = block_get (transaction_a, block->previous ()); + debug_assert (block); + --height; + } + confirmation_height_infos.emplace_back (account, confirmation_height_info{ confirmation_height, block->hash () }); + } + } + + // Every so often output to the log to indicate progress (every 200k accounts) + constexpr auto output_cutoff = 200000; + if (num % output_cutoff == 0 && num != 0) + { + logger.always_log (boost::str (boost::format ("Confirmation height frontier set for %1%00k accounts") % ((num / output_cutoff) * 2))); + } + } + + // Clear it then append + auto status (mdb_drop (env.tx (transaction_a), confirmation_height, 0)); + release_assert (status == MDB_SUCCESS); + + for (auto const & confirmation_height_info_pair : confirmation_height_infos) + { + mdb_put (env.tx (transaction_a), confirmation_height, nano::mdb_val (confirmation_height_info_pair.first), nano::mdb_val (confirmation_height_info_pair.second), MDB_APPEND); + } + + version_put (transaction_a, 17); + logger.always_log ("Finished upgrading confirmation height frontiers"); +} + +void nano::mdb_store::upgrade_v17_to_v18 (nano::write_transaction const & transaction_a) +{ + logger.always_log ("Preparing v17 to v18 database upgrade..."); + + auto count_pre (count (transaction_a, state_blocks)); + + auto num = 0u; + for (nano::mdb_iterator state_i (transaction_a, state_blocks), state_n{}; state_i != state_n; ++state_i, ++num) + { + nano::state_block_w_sideband block_sideband (state_i->second); + auto & block (block_sideband.state_block); + auto & sideband (block_sideband.sideband); + + bool is_send{ false }; + bool is_receive{ false }; + bool is_epoch{ false }; + + nano::amount prev_balance (0); + if (!block->hashables.previous.is_zero ()) + { + prev_balance = block_balance (transaction_a, block->hashables.previous); + } + if (block->hashables.balance == prev_balance && network_params.ledger.epochs.is_epoch_link (block->hashables.link)) + { + is_epoch = true; + } + else if (block->hashables.balance < prev_balance) + { + is_send = true; + } + else if (!block->hashables.link.is_zero ()) + { + is_receive = true; + } + + nano::block_sideband new_sideband (sideband.account, sideband.successor, sideband.balance, sideband.height, sideband.timestamp, sideband.details.epoch, is_send, is_receive, is_epoch); + // Write these out + std::vector data; + { + nano::vectorstream stream (data); + block->serialize (stream); + new_sideband.serialize (stream, block->type ()); + } + nano::mdb_val value{ data.size (), (void *)data.data () }; + auto s = mdb_cursor_put (state_i.cursor, state_i->first, value, MDB_CURRENT); + release_assert (success (s)); + + // Every so often output to the log to indicate progress + constexpr auto output_cutoff = 1000000; + if (num > 0 && num % output_cutoff == 0) + { + logger.always_log (boost::str (boost::format ("Database sideband upgrade %1% million state blocks upgraded (out of %2%)") % (num / output_cutoff) % count_pre)); + } + } + + auto count_post (count (transaction_a, state_blocks)); + release_assert (count_pre == count_post); + + version_put (transaction_a, 18); + logger.always_log ("Finished upgrading the sideband"); } /** Takes a filepath, appends '_backup_' to the end (but before any extension) and saves that file in the same directory */ @@ -717,7 +900,7 @@ void nano::mdb_store::version_put (nano::write_transaction const & transaction_a bool nano::mdb_store::block_info_get (nano::transaction const & transaction_a, nano::block_hash const & hash_a, nano::block_info & block_info_a) const { - assert (!full_sideband (transaction_a)); + debug_assert (!full_sideband (transaction_a)); nano::mdb_val value; auto status (mdb_get (env.tx (transaction_a), blocks_info, nano::mdb_val (hash_a), value)); release_assert (status == 0 || status == MDB_NOTFOUND); @@ -725,14 +908,14 @@ bool nano::mdb_store::block_info_get (nano::transaction const & transaction_a, n if (status != MDB_NOTFOUND) { result = false; - assert (value.size () == sizeof (block_info_a.account.bytes) + sizeof (block_info_a.balance.bytes)); + debug_assert (value.size () == sizeof (block_info_a.account.bytes) + sizeof (block_info_a.balance.bytes)); nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); auto error1 (nano::try_read (stream, block_info_a.account)); (void)error1; - assert (!error1); + debug_assert (!error1); auto error2 (nano::try_read (stream, block_info_a.balance)); (void)error2; - assert (!error2); + debug_assert (!error2); } return result; } @@ -843,6 +1026,56 @@ bool nano::mdb_store::copy_db (boost::filesystem::path const & destination_file) return !mdb_env_copy2 (env.environment, destination_file.string ().c_str (), MDB_CP_COMPACT); } +void nano::mdb_store::rebuild_db (nano::write_transaction const & transaction_a) +{ + // Tables with uint256_union key + std::vector tables = { accounts, send_blocks, receive_blocks, open_blocks, change_blocks, state_blocks, vote, confirmation_height }; + for (auto const & table : tables) + { + MDB_dbi temp; + mdb_dbi_open (env.tx (transaction_a), "temp_table", MDB_CREATE, &temp); + // Copy all values to temporary table + for (auto i (nano::store_iterator (std::make_unique> (transaction_a, table))), n (nano::store_iterator (nullptr)); i != n; ++i) + { + auto s = mdb_put (env.tx (transaction_a), temp, nano::mdb_val (i->first), i->second, MDB_APPEND); + release_assert (success (s)); + } + release_assert (count (transaction_a, table) == count (transaction_a, temp)); + // Clear existing table + mdb_drop (env.tx (transaction_a), table, 0); + // Put values from copy + for (auto i (nano::store_iterator (std::make_unique> (transaction_a, temp))), n (nano::store_iterator (nullptr)); i != n; ++i) + { + auto s = mdb_put (env.tx (transaction_a), table, nano::mdb_val (i->first), i->second, MDB_APPEND); + release_assert (success (s)); + } + release_assert (count (transaction_a, table) == count (transaction_a, temp)); + // Remove temporary table + mdb_drop (env.tx (transaction_a), temp, 1); + } + // Pending table + { + MDB_dbi temp; + mdb_dbi_open (env.tx (transaction_a), "temp_table", MDB_CREATE, &temp); + // Copy all values to temporary table + for (auto i (nano::store_iterator (std::make_unique> (transaction_a, pending))), n (nano::store_iterator (nullptr)); i != n; ++i) + { + auto s = mdb_put (env.tx (transaction_a), temp, nano::mdb_val (i->first), nano::mdb_val (i->second), MDB_APPEND); + release_assert (success (s)); + } + release_assert (count (transaction_a, pending) == count (transaction_a, temp)); + mdb_drop (env.tx (transaction_a), pending, 0); + // Put values from copy + for (auto i (nano::store_iterator (std::make_unique> (transaction_a, temp))), n (nano::store_iterator (nullptr)); i != n; ++i) + { + auto s = mdb_put (env.tx (transaction_a), pending, nano::mdb_val (i->first), nano::mdb_val (i->second), MDB_APPEND); + release_assert (success (s)); + } + release_assert (count (transaction_a, pending) == count (transaction_a, temp)); + mdb_drop (env.tx (transaction_a), temp, 1); + } +} + bool nano::mdb_store::init_error () const { return error; @@ -864,7 +1097,7 @@ size_t nano::mdb_store::block_successor_offset_v14 (nano::transaction const & tr else { // Read old successor-only sideband - assert (entry_size_a == nano::block::size (type_a) + sizeof (nano::uint256_union)); + debug_assert (entry_size_a == nano::block::size (type_a) + sizeof (nano::uint256_union)); result = entry_size_a - sizeof (nano::uint256_union); } return result; @@ -877,11 +1110,11 @@ nano::block_hash nano::mdb_store::block_successor_v14 (nano::transaction const & nano::block_hash result; if (value.size () != 0) { - assert (value.size () >= result.bytes.size ()); + debug_assert (value.size () >= result.bytes.size ()); nano::bufferstream stream (reinterpret_cast (value.data ()) + block_successor_offset_v14 (transaction_a, value.size (), type), result.bytes.size ()); auto error (nano::try_read (stream, result.bytes)); (void)error; - assert (!error); + debug_assert (!error); } else { @@ -974,20 +1207,20 @@ nano::account nano::mdb_store::block_account_v14 (nano::transaction const & tran { result = sideband.account; } - assert (!result.is_zero ()); + debug_assert (!result.is_zero ()); return result; } // Return account containing hash nano::account nano::mdb_store::block_account_computed_v14 (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { - assert (!full_sideband (transaction_a)); + debug_assert (!full_sideband (transaction_a)); nano::account result (0); auto hash (hash_a); while (result.is_zero ()) { auto block (block_get_v14 (transaction_a, hash)); - assert (block); + debug_assert (block); result = block->account (); if (result.is_zero ()) { @@ -1010,20 +1243,20 @@ nano::account nano::mdb_store::block_account_computed_v14 (nano::transaction con if (result.is_zero ()) { auto successor (block_successor_v14 (transaction_a, hash)); - assert (!successor.is_zero ()); + debug_assert (!successor.is_zero ()); hash = successor; } } } } } - assert (!result.is_zero ()); + debug_assert (!result.is_zero ()); return result; } nano::uint128_t nano::mdb_store::block_balance_computed_v14 (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { - assert (!full_sideband (transaction_a)); + debug_assert (!full_sideband (transaction_a)); summation_visitor visitor (transaction_a, *this, true); return visitor.compute_balance (hash_a); } @@ -1037,7 +1270,7 @@ std::shared_ptr nano::mdb_store::block_get_v14 (nano::transaction c { nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); result = nano::deserialize_block (stream, type); - assert (result != nullptr); + debug_assert (result != nullptr); if (sideband_a) { sideband_a->type = type; @@ -1045,7 +1278,7 @@ std::shared_ptr nano::mdb_store::block_get_v14 (nano::transaction c { bool error = sideband_a->deserialize (stream); (void)error; - assert (!error); + debug_assert (!error); } else { @@ -1071,3 +1304,6 @@ bool nano::mdb_store::upgrade_counters::are_equal () const { return (before_v0 == after_v0) && (before_v1 == after_v1); } + +// Explicitly instantiate +template class nano::block_store_partial; diff --git a/nano/node/lmdb/lmdb.hpp b/nano/node/lmdb/lmdb.hpp index 0713e37f09..e46ebdf203 100644 --- a/nano/node/lmdb/lmdb.hpp +++ b/nano/node/lmdb/lmdb.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include #include #include @@ -11,13 +11,18 @@ #include #include -#include #include -#include - #include +namespace boost +{ +namespace filesystem +{ + class path; +} +} + namespace nano { using mdb_val = db_val; @@ -32,10 +37,12 @@ class mdb_store : public block_store_partial using block_store_partial::block_exists; using block_store_partial::unchecked_put; - mdb_store (nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), int lmdb_max_dbs = 128, size_t batch_size = 512, bool backup_before_upgrade = false); + mdb_store (nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), nano::lmdb_config const & lmdb_config_a = nano::lmdb_config{}, size_t batch_size = 512, bool backup_before_upgrade = false); nano::write_transaction tx_begin_write (std::vector const & tables_requiring_lock = {}, std::vector const & tables_no_lock = {}) override; nano::read_transaction tx_begin_read () override; + std::string vendor_get () const override; + bool block_info_get (nano::transaction const &, nano::block_hash const &, nano::block_info &) const override; void version_put (nano::write_transaction const &, int) override; @@ -178,8 +185,8 @@ class mdb_store : public block_store_partial MDB_dbi peers{ 0 }; /* - * Confirmation height of an account - * nano::account -> uint64_t + * Confirmation height of an account, and the hash for the block at that height + * nano::account -> uint64_t, nano::block_hash */ MDB_dbi confirmation_height{ 0 }; @@ -190,6 +197,7 @@ class mdb_store : public block_store_partial int del (nano::write_transaction const & transaction_a, tables table_a, nano::mdb_val const & key_a) const; bool copy_db (boost::filesystem::path const & destination_file) override; + void rebuild_db (nano::write_transaction const & transaction_a) override; template nano::store_iterator make_iterator (nano::transaction const & transaction_a, tables table_a) const @@ -233,6 +241,10 @@ class mdb_store : public block_store_partial void upgrade_v12_to_v13 (nano::write_transaction &, size_t); void upgrade_v13_to_v14 (nano::write_transaction const &); void upgrade_v14_to_v15 (nano::write_transaction &); + void upgrade_v15_to_v16 (nano::write_transaction const &); + void upgrade_v16_to_v17 (nano::write_transaction const &); + void upgrade_v17_to_v18 (nano::write_transaction const &); + void open_databases (bool &, nano::transaction const &, unsigned); int drop (nano::write_transaction const & transaction_a, tables table_a) override; @@ -250,7 +262,7 @@ class mdb_store : public block_store_partial size_t count (nano::transaction const & transaction_a, tables table_a) const override; - bool vacuum_after_upgrade (boost::filesystem::path const & path_a, int lmdb_max_dbs); + bool vacuum_after_upgrade (boost::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a); class upgrade_counters { @@ -273,4 +285,6 @@ template <> mdb_val::db_val (size_t size_a, void * data_a); template <> void mdb_val::convert_buffer_to_value (); + +extern template class block_store_partial; } diff --git a/nano/node/lmdb/lmdb_env.cpp b/nano/node/lmdb/lmdb_env.cpp index cf30712579..2c474ac200 100644 --- a/nano/node/lmdb/lmdb_env.cpp +++ b/nano/node/lmdb/lmdb_env.cpp @@ -1,11 +1,13 @@ #include -nano::mdb_env::mdb_env (bool & error_a, boost::filesystem::path const & path_a, int max_dbs_a, bool use_no_mem_init_a, size_t map_size_a) +#include + +nano::mdb_env::mdb_env (bool & error_a, boost::filesystem::path const & path_a, nano::mdb_env::options options_a) { - init (error_a, path_a, max_dbs_a, use_no_mem_init_a, map_size_a); + init (error_a, path_a, options_a); } -void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a, int max_dbs_a, bool use_no_mem_init_a, size_t map_size_a) +void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a, nano::mdb_env::options options_a) { boost::system::error_code error_mkdir, error_chmod; if (path_a.has_parent_path ()) @@ -16,11 +18,11 @@ void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a { auto status1 (mdb_env_create (&environment)); release_assert (status1 == 0); - auto status2 (mdb_env_set_maxdbs (environment, max_dbs_a)); + auto status2 (mdb_env_set_maxdbs (environment, options_a.config.max_databases)); release_assert (status2 == 0); - auto map_size = map_size_a; + auto map_size = options_a.config.map_size; auto max_valgrind_map_size = 16 * 1024 * 1024; - if (running_within_valgrind () && map_size_a > max_valgrind_map_size) + if (running_within_valgrind () && map_size > max_valgrind_map_size) { // In order to run LMDB under Valgrind, the maximum map size must be smaller than half your available RAM map_size = max_valgrind_map_size; @@ -32,7 +34,20 @@ void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a // MDB_NORDAHEAD will allow platforms that support it to load the DB in memory as needed. // MDB_NOMEMINIT prevents zeroing malloc'ed pages. Can provide improvement for non-sensitive data but may make memory checkers noisy (e.g valgrind). auto environment_flags = MDB_NOSUBDIR | MDB_NOTLS | MDB_NORDAHEAD; - if (!running_within_valgrind () && use_no_mem_init_a) + if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_safe) + { + environment_flags |= MDB_NOMETASYNC; + } + else if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_unsafe) + { + environment_flags |= MDB_NOSYNC; + } + else if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory) + { + environment_flags |= MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC; + } + + if (!running_within_valgrind () && options_a.use_no_mem_init) { environment_flags |= MDB_NOMEMINIT; } @@ -67,6 +82,8 @@ nano::mdb_env::~mdb_env () { if (environment != nullptr) { + // Make sure the commits are flushed. This is a no-op unless MDB_NOSYNC is used. + mdb_env_sync (environment, true); mdb_env_close (environment); } } diff --git a/nano/node/lmdb/lmdb_env.hpp b/nano/node/lmdb/lmdb_env.hpp index 5337f24999..139124ce09 100644 --- a/nano/node/lmdb/lmdb_env.hpp +++ b/nano/node/lmdb/lmdb_env.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -11,15 +12,55 @@ namespace nano class mdb_env final { public: - mdb_env (bool &, boost::filesystem::path const &, int max_dbs = 128, bool use_no_mem_init = false, size_t map_size = 128ULL * 1024 * 1024 * 1024); - void init (bool &, boost::filesystem::path const &, int max_dbs, bool use_no_mem_init, size_t map_size = 128ULL * 1024 * 1024 * 1024); + /** Environment options, most of which originates from the config file. */ + class options final + { + friend class mdb_env; + + public: + static options make () + { + return options (); + } + + options & set_config (nano::lmdb_config config_a) + { + config = config_a; + return *this; + } + + options & set_use_no_mem_init (int use_no_mem_init_a) + { + use_no_mem_init = use_no_mem_init_a; + return *this; + } + + /** Used by the wallet to override the config map size */ + options & override_config_map_size (size_t map_size_a) + { + config.map_size = map_size_a; + return *this; + } + + /** Used by the wallet to override the sync strategy */ + options & override_config_sync (nano::lmdb_config::sync_strategy sync_a) + { + config.sync = sync_a; + return *this; + } + + private: + bool use_no_mem_init{ false }; + nano::lmdb_config config; + }; + + mdb_env (bool &, boost::filesystem::path const &, nano::mdb_env::options options_a = nano::mdb_env::options::make ()); + void init (bool &, boost::filesystem::path const &, nano::mdb_env::options options_a = nano::mdb_env::options::make ()); ~mdb_env (); operator MDB_env * () const; - // clang-format off nano::read_transaction tx_begin_read (mdb_txn_callbacks txn_callbacks = mdb_txn_callbacks{}) const; nano::write_transaction tx_begin_write (mdb_txn_callbacks txn_callbacks = mdb_txn_callbacks{}) const; MDB_txn * tx (nano::transaction const & transaction_a) const; - // clang-format on MDB_env * environment; }; } diff --git a/nano/node/lmdb/lmdb_iterator.hpp b/nano/node/lmdb/lmdb_iterator.hpp index 6f1e7e1305..d225cb4136 100644 --- a/nano/node/lmdb/lmdb_iterator.hpp +++ b/nano/node/lmdb/lmdb_iterator.hpp @@ -74,7 +74,7 @@ class mdb_iterator : public store_iterator_impl nano::store_iterator_impl & operator++ () override { - assert (cursor != nullptr); + debug_assert (cursor != nullptr); auto status (mdb_cursor_get (cursor, ¤t.first.value, ¤t.second.value, MDB_NEXT)); release_assert (status == 0 || status == MDB_NOTFOUND); if (status == MDB_NOTFOUND) @@ -97,9 +97,9 @@ class mdb_iterator : public store_iterator_impl { auto const other_a (boost::polymorphic_downcast const *> (&base_a)); auto result (current.first.data () == other_a->current.first.data ()); - assert (!result || (current.first.size () == other_a->current.first.size ())); - assert (!result || (current.second.data () == other_a->current.second.data ())); - assert (!result || (current.second.size () == other_a->current.second.size ())); + debug_assert (!result || (current.first.size () == other_a->current.first.size ())); + debug_assert (!result || (current.second.data () == other_a->current.second.data ())); + debug_assert (!result || (current.second.size () == other_a->current.second.size ())); return result; } @@ -130,7 +130,7 @@ class mdb_iterator : public store_iterator_impl { current.first = nano::db_val (); current.second = nano::db_val (); - assert (is_end_sentinal ()); + debug_assert (is_end_sentinal ()); } nano::mdb_iterator & operator= (nano::mdb_iterator && other_a) @@ -203,7 +203,7 @@ class mdb_merge_iterator : public store_iterator_impl bool operator== (nano::store_iterator_impl const & base_a) const override { - assert ((dynamic_cast const *> (&base_a) != nullptr) && "Incompatible iterator comparison"); + debug_assert ((dynamic_cast const *> (&base_a) != nullptr) && "Incompatible iterator comparison"); auto & other (static_cast const &> (base_a)); return *impl1 == *other.impl1 && *impl2 == *other.impl2; } diff --git a/nano/node/lmdb/lmdb_txn.cpp b/nano/node/lmdb/lmdb_txn.cpp index 1b0a705ad7..405fd84e26 100644 --- a/nano/node/lmdb/lmdb_txn.cpp +++ b/nano/node/lmdb/lmdb_txn.cpp @@ -1,11 +1,12 @@ #include #include +#include #include #include #include #include -#include +#include // Some builds (mac) fail due to "Boost.Stacktrace requires `_Unwind_Backtrace` function". #ifndef _WIN32 @@ -130,33 +131,36 @@ void nano::mdb_txn_tracker::serialize_json (boost::property_tree::ptree & json, { // Copying is cheap compared to generating the stack trace strings, so reduce time holding the mutex std::vector copy_stats; + std::vector are_writes; { nano::lock_guard guard (mutex); copy_stats = stats; + are_writes.reserve (stats.size ()); + std::transform (stats.cbegin (), stats.cend (), std::back_inserter (are_writes), [](auto & mdb_txn_stat) { + return mdb_txn_stat.is_write (); + }); } // Get the time difference now as creating stacktraces (Debug/Windows for instance) can take a while so results won't be as accurate std::vector times_since_start; times_since_start.reserve (copy_stats.size ()); - // clang-format off - std::transform (copy_stats.cbegin (), copy_stats.cend (), std::back_inserter (times_since_start), [] (const auto & stat) { + std::transform (copy_stats.cbegin (), copy_stats.cend (), std::back_inserter (times_since_start), [](const auto & stat) { return stat.timer.since_start (); }); - // clang-format on - assert (times_since_start.size () == copy_stats.size ()); + debug_assert (times_since_start.size () == copy_stats.size ()); for (size_t i = 0; i < times_since_start.size (); ++i) { auto const & stat = copy_stats[i]; auto time_held_open = times_since_start[i]; - if ((stat.is_write () && time_held_open >= min_write_time) || (!stat.is_write () && time_held_open >= min_read_time)) + if ((are_writes[i] && time_held_open >= min_write_time) || (!are_writes[i] && time_held_open >= min_read_time)) { nano::jsonconfig mdb_lock_config; mdb_lock_config.put ("thread", stat.thread_name); mdb_lock_config.put ("time_held_open", time_held_open.count ()); - mdb_lock_config.put ("write", stat.is_write ()); + mdb_lock_config.put ("write", !!are_writes[i]); boost::property_tree::ptree stacktrace_config; for (auto frame : *stat.stacktrace) @@ -193,7 +197,7 @@ void nano::mdb_txn_tracker::output_finished (nano::mdb_txn_stats const & mdb_txn if (!should_ignore && ((is_write && time_open >= txn_tracking_config.min_write_txn_time) || (!is_write && time_open >= txn_tracking_config.min_read_txn_time))) { - assert (mdb_txn_stats.stacktrace); + debug_assert (mdb_txn_stats.stacktrace); logger.always_log (boost::str (boost::format ("%1%ms %2% held on thread %3%\n%4%") % mdb_txn_stats.timer.since_start ().count () % (is_write ? "write lock" : "read") % mdb_txn_stats.thread_name % *mdb_txn_stats.stacktrace)); } } @@ -201,9 +205,7 @@ void nano::mdb_txn_tracker::output_finished (nano::mdb_txn_stats const & mdb_txn void nano::mdb_txn_tracker::add (const nano::transaction_impl * transaction_impl) { nano::lock_guard guard (mutex); - // clang-format off - assert (std::find_if (stats.cbegin (), stats.cend (), matches_txn (transaction_impl)) == stats.cend ()); - // clang-format on + debug_assert (std::find_if (stats.cbegin (), stats.cend (), matches_txn (transaction_impl)) == stats.cend ()); stats.emplace_back (transaction_impl); } @@ -211,9 +213,7 @@ void nano::mdb_txn_tracker::add (const nano::transaction_impl * transaction_impl void nano::mdb_txn_tracker::erase (const nano::transaction_impl * transaction_impl) { nano::lock_guard guard (mutex); - // clang-format off auto it = std::find_if (stats.begin (), stats.end (), matches_txn (transaction_impl)); - // clang-format on if (it != stats.end ()) { output_finished (*it); diff --git a/nano/node/lmdb/lmdb_txn.hpp b/nano/node/lmdb/lmdb_txn.hpp index a6ca82c166..0ec1767c26 100644 --- a/nano/node/lmdb/lmdb_txn.hpp +++ b/nano/node/lmdb/lmdb_txn.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -20,10 +20,8 @@ class mdb_env; class mdb_txn_callbacks { public: - // clang-format off - std::function txn_start{ [] (const nano::transaction_impl *) {} }; - std::function txn_end{ [] (const nano::transaction_impl *) {} }; - // clang-format on + std::function txn_start{ [](const nano::transaction_impl *) {} }; + std::function txn_end{ [](const nano::transaction_impl *) {} }; }; class read_mdb_txn final : public read_transaction_impl diff --git a/nano/node/lmdb/wallet_value.cpp b/nano/node/lmdb/wallet_value.cpp index 91c8a6797a..1e5aa315ea 100644 --- a/nano/node/lmdb/wallet_value.cpp +++ b/nano/node/lmdb/wallet_value.cpp @@ -2,7 +2,7 @@ nano::wallet_value::wallet_value (nano::db_val const & val_a) { - assert (val_a.size () == sizeof (*this)); + debug_assert (val_a.size () == sizeof (*this)); std::copy (reinterpret_cast (val_a.data ()), reinterpret_cast (val_a.data ()) + sizeof (key), key.chars.begin ()); std::copy (reinterpret_cast (val_a.data ()) + sizeof (key), reinterpret_cast (val_a.data ()) + sizeof (key) + sizeof (work), reinterpret_cast (&work)); } diff --git a/nano/node/logging.cpp b/nano/node/logging.cpp index b296203de1..8934e2028b 100644 --- a/nano/node/logging.cpp +++ b/nano/node/logging.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #ifdef BOOST_WINDOWS #include @@ -68,9 +68,48 @@ void nano::logging::init (boost::filesystem::path const & application_path_a) #endif } +//clang-format off +#if BOOST_VERSION < 107000 + if (stable_log_filename) + { + stable_log_filename = false; + std::cerr << "The stable_log_filename config setting is only available when building with Boost 1.70 or later. Reverting to old behavior." << std::endl; + } +#endif + auto path = application_path_a / "log"; - file_sink = boost::log::add_file_log (boost::log::keywords::target = path, boost::log::keywords::file_name = path / "log_%Y-%m-%d_%H-%M-%S.%N.log", boost::log::keywords::rotation_size = rotation_size, boost::log::keywords::auto_flush = flush, boost::log::keywords::scan_method = boost::log::sinks::file::scan_method::scan_matching, boost::log::keywords::max_size = max_size, boost::log::keywords::format = format_with_timestamp); + if (stable_log_filename) + { +#if BOOST_VERSION >= 107000 + // Logging to node.log and node_ instead of log_.log is deliberate. This way, + // existing log monitoring scripts expecting the old logfile structure will fail immediately instead + // of reading only rotated files with old entries. + file_sink = boost::log::add_file_log (boost::log::keywords::target = path, + boost::log::keywords::file_name = path / "node.log", + boost::log::keywords::target_file_name = path / "node_%Y-%m-%d_%H-%M-%S.%N.log", + boost::log::keywords::open_mode = std::ios_base::out | std::ios_base::app, // append to node.log if it exists + boost::log::keywords::enable_final_rotation = false, // for stable log filenames, don't rotate on destruction + boost::log::keywords::rotation_size = rotation_size, // max file size in bytes before rotation + boost::log::keywords::auto_flush = flush, + boost::log::keywords::scan_method = boost::log::sinks::file::scan_method::scan_matching, + boost::log::keywords::max_size = max_size, // max total size in bytes of all log files + boost::log::keywords::format = format_with_timestamp); +#else + debug_assert (false); +#endif + } + else + { + file_sink = boost::log::add_file_log (boost::log::keywords::target = path, + boost::log::keywords::file_name = path / "log_%Y-%m-%d_%H-%M-%S.%N.log", + boost::log::keywords::rotation_size = rotation_size, + boost::log::keywords::auto_flush = flush, + boost::log::keywords::scan_method = boost::log::sinks::file::scan_method::scan_matching, + boost::log::keywords::max_size = max_size, + boost::log::keywords::format = format_with_timestamp); + } } + //clang-format on } void nano::logging::release_file_sink () @@ -94,6 +133,8 @@ nano::error nano::logging::serialize_toml (nano::tomlconfig & toml) const toml.put ("network_packet", network_packet_logging_value, "Log network packet activity.\ntype:bool"); toml.put ("network_keepalive", network_keepalive_logging_value, "Log keepalive related messages.\ntype:bool"); toml.put ("network_node_id_handshake", network_node_id_handshake_logging_value, "Log node-id handshake related messages.\ntype:bool"); + toml.put ("network_telemetry", network_telemetry_logging_value, "Log telemetry related messages.\ntype:bool"); + toml.put ("network_rejected", network_rejected_logging_value, "Log message when a connection is rejected.\ntype:bool"); toml.put ("node_lifetime_tracing", node_lifetime_tracing_value, "Log node startup and shutdown messages.\ntype:bool"); toml.put ("insufficient_work", insufficient_work_logging_value, "Log if insufficient work is detected.\ntype:bool"); toml.put ("log_ipc", log_ipc_value, "Log IPC related activity.\ntype:bool"); @@ -108,6 +149,7 @@ nano::error nano::logging::serialize_toml (nano::tomlconfig & toml) const toml.put ("flush", flush, "If enabled, immediately flush new entries to log file.\nWarning: this may negatively affect logging performance.\ntype:bool"); toml.put ("min_time_between_output", min_time_between_log_output.count (), "Minimum time that must pass for low priority entries to be logged.\nWarning: decreasing this value may result in a very large amount of logs.\ntype:milliseconds"); toml.put ("single_line_record", single_line_record_value, "Keep log entries on single lines.\ntype:bool"); + toml.put ("stable_log_filename", stable_log_filename, "Append to log/node.log without a timestamp in the filename.\nThe file is not emptied on startup if it exists, but appended to.\ntype:bool"); return toml.get_error (); } @@ -124,6 +166,8 @@ nano::error nano::logging::deserialize_toml (nano::tomlconfig & toml) toml.get ("network_packet", network_packet_logging_value); toml.get ("network_keepalive", network_keepalive_logging_value); toml.get ("network_node_id_handshake", network_node_id_handshake_logging_value); + toml.get ("network_telemetry_logging", network_telemetry_logging_value); + toml.get ("network_rejected_logging", network_rejected_logging_value); toml.get ("node_lifetime_tracing", node_lifetime_tracing_value); toml.get ("insufficient_work", insufficient_work_logging_value); toml.get ("log_ipc", log_ipc_value); @@ -140,6 +184,7 @@ nano::error nano::logging::deserialize_toml (nano::tomlconfig & toml) auto min_time_between_log_output_l = min_time_between_log_output.count (); toml.get ("min_time_between_output", min_time_between_log_output_l); min_time_between_log_output = std::chrono::milliseconds (min_time_between_log_output_l); + toml.get ("stable_log_filename", stable_log_filename); return toml.get_error (); } @@ -157,6 +202,8 @@ nano::error nano::logging::serialize_json (nano::jsonconfig & json) const json.put ("network_packet", network_packet_logging_value); json.put ("network_keepalive", network_keepalive_logging_value); json.put ("network_node_id_handshake", network_node_id_handshake_logging_value); + json.put ("network_telemetry_logging", network_telemetry_logging_value); + json.put ("network_rejected_logging", network_rejected_logging_value); json.put ("node_lifetime_tracing", node_lifetime_tracing_value); json.put ("insufficient_work", insufficient_work_logging_value); json.put ("log_ipc", log_ipc_value); @@ -309,6 +356,16 @@ bool nano::logging::network_node_id_handshake_logging () const return network_logging () && network_node_id_handshake_logging_value; } +bool nano::logging::network_telemetry_logging () const +{ + return network_logging () && network_telemetry_logging_value; +} + +bool nano::logging::network_rejected_logging () const +{ + return network_logging () && network_rejected_logging_value; +} + bool nano::logging::node_lifetime_tracing () const { return node_lifetime_tracing_value; diff --git a/nano/node/logging.hpp b/nano/node/logging.hpp index 1661aa9857..c52290b08c 100644 --- a/nano/node/logging.hpp +++ b/nano/node/logging.hpp @@ -1,21 +1,39 @@ #pragma once #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #define FATAL_LOG_PREFIX "FATAL ERROR: " +namespace boost +{ +BOOST_LOG_OPEN_NAMESPACE +namespace sinks +{ + class text_file_backend; + + template + class synchronous_sink; +} + +BOOST_LOG_CLOSE_NAMESPACE + +namespace filesystem +{ + class path; +} +} + namespace nano { class tomlconfig; +class jsonconfig; class logging final { public: @@ -34,6 +52,8 @@ class logging final bool network_packet_logging () const; bool network_keepalive_logging () const; bool network_node_id_handshake_logging () const; + bool network_telemetry_logging () const; + bool network_rejected_logging () const; bool node_lifetime_tracing () const; bool insufficient_work_logging () const; bool upnp_details_logging () const; @@ -57,6 +77,8 @@ class logging final bool network_packet_logging_value{ false }; bool network_keepalive_logging_value{ false }; bool network_node_id_handshake_logging_value{ false }; + bool network_telemetry_logging_value{ false }; + bool network_rejected_logging_value{ false }; bool node_lifetime_tracing_value{ false }; bool insufficient_work_logging_value{ true }; bool log_ipc_value{ true }; @@ -69,6 +91,7 @@ class logging final bool flush{ true }; uintmax_t max_size{ 128 * 1024 * 1024 }; uintmax_t rotation_size{ 4 * 1024 * 1024 }; + bool stable_log_filename{ false }; std::chrono::milliseconds min_time_between_log_output{ 5 }; bool single_line_record_value{ false }; static void release_file_sink (); diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 4f4f61edea..58a5718990 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -1,22 +1,34 @@ +#include +#include #include #include +#include +#include + +#include +#include #include -#include nano::network::network (nano::node & node_a, uint16_t port_a) : +syn_cookies (node_a.network_params.node.max_peers_per_ip), buffer_container (node_a.stats, nano::network::buffer_size, 4096), // 2Mb receive buffer resolver (node_a.io_ctx), +limiter (node_a.config.bandwidth_limit_burst_ratio, node_a.config.bandwidth_limit), +tcp_message_manager (node_a.config.tcp_incoming_connections_max), node (node_a), +publish_filter (256 * 1024), udp_channels (node_a, port_a), tcp_channels (node_a), +port (port_a), disconnect_observer ([]() {}) { boost::thread::attributes attrs; nano::thread_attributes::set (attrs); - for (size_t i = 0; i < node.config.network_threads; ++i) + // UDP + for (size_t i = 0; i < node.config.network_threads && !node.flags.disable_udp; ++i) { - packet_processing_threads.push_back (boost::thread (attrs, [this]() { + packet_processing_threads.emplace_back (attrs, [this]() { nano::thread_role::set (nano::thread_role::name::packet_processing); try { @@ -24,29 +36,64 @@ disconnect_observer ([]() {}) } catch (boost::system::error_code & ec) { - this->node.logger.try_log (FATAL_LOG_PREFIX, ec.message ()); + this->node.logger.always_log (FATAL_LOG_PREFIX, ec.message ()); release_assert (false); } catch (std::error_code & ec) { - this->node.logger.try_log (FATAL_LOG_PREFIX, ec.message ()); + this->node.logger.always_log (FATAL_LOG_PREFIX, ec.message ()); release_assert (false); } catch (std::runtime_error & err) { - this->node.logger.try_log (FATAL_LOG_PREFIX, err.what ()); + this->node.logger.always_log (FATAL_LOG_PREFIX, err.what ()); release_assert (false); } catch (...) { - this->node.logger.try_log (FATAL_LOG_PREFIX, "Unknown exception"); + this->node.logger.always_log (FATAL_LOG_PREFIX, "Unknown exception"); release_assert (false); } if (this->node.config.logging.network_packet_logging ()) { - this->node.logger.try_log ("Exiting packet processing thread"); + this->node.logger.try_log ("Exiting UDP packet processing thread"); } - })); + }); + } + // TCP + for (size_t i = 0; i < node.config.network_threads && !node.flags.disable_tcp_realtime; ++i) + { + packet_processing_threads.emplace_back (attrs, [this]() { + nano::thread_role::set (nano::thread_role::name::packet_processing); + try + { + tcp_channels.process_messages (); + } + catch (boost::system::error_code & ec) + { + this->node.logger.always_log (FATAL_LOG_PREFIX, ec.message ()); + release_assert (false); + } + catch (std::error_code & ec) + { + this->node.logger.always_log (FATAL_LOG_PREFIX, ec.message ()); + release_assert (false); + } + catch (std::runtime_error & err) + { + this->node.logger.always_log (FATAL_LOG_PREFIX, err.what ()); + release_assert (false); + } + catch (...) + { + this->node.logger.always_log (FATAL_LOG_PREFIX, "Unknown exception"); + release_assert (false); + } + if (this->node.config.logging.network_packet_logging ()) + { + this->node.logger.try_log ("Exiting TCP packet processing thread"); + } + }); } } @@ -62,6 +109,7 @@ void nano::network::start () if (!node.flags.disable_udp) { udp_channels.start (); + debug_assert (udp_channels.get_local_endpoint ().port () == port); } if (!node.flags.disable_tcp_realtime) { @@ -78,6 +126,8 @@ void nano::network::stop () tcp_channels.stop (); resolver.cancel (); buffer_container.stop (); + tcp_message_manager.stop (); + port = 0; for (auto & thread : packet_processing_threads) { thread.join (); @@ -95,9 +145,12 @@ void nano::network::send_keepalive (std::shared_ptr ch void nano::network::send_keepalive_self (std::shared_ptr channel_a) { nano::keepalive message; - if (node.config.external_address != boost::asio::ip::address_v6{} && node.config.external_port != 0) + random_fill (message.peers); + // Replace part of message with node external address or listening port + message.peers[1] = nano::endpoint (boost::asio::ip::address_v6{}, 0); // For node v19 (response channels) + if (node.config.external_address != boost::asio::ip::address_v6{}.to_string () && node.config.external_port != 0) { - message.peers[0] = nano::endpoint (node.config.external_address, node.config.external_port); + message.peers[0] = nano::endpoint (boost::asio::ip::make_address_v6 (node.config.external_address), node.config.external_port); } else { @@ -106,7 +159,7 @@ void nano::network::send_keepalive_self (std::shared_ptrfirst, *respond_to, response->second)); + debug_assert (!nano::validate_message (response->first, *respond_to, response->second)); } nano::node_id_handshake message (query, response); if (node.config.logging.network_node_id_handshake_logging ()) @@ -133,100 +186,48 @@ void nano::network::send_node_id_handshake (std::shared_ptrsend (message); } -template -bool confirm_block (nano::transaction const & transaction_a, nano::node & node_a, T & list_a, std::shared_ptr block_a, bool also_publish) +void nano::network::flood_message (nano::message const & message_a, nano::buffer_drop_policy const drop_policy_a, float const scale_a) { - bool result (false); - if (node_a.config.enable_voting) + for (auto & i : list (fanout (scale_a))) { - auto hash (block_a->hash ()); - // Search in cache - auto votes (node_a.votes_cache.find (hash)); - if (votes.empty ()) - { - // Generate new vote - node_a.wallets.foreach_representative ([&result, &list_a, &node_a, &transaction_a, &hash](nano::public_key const & pub_a, nano::raw_key const & prv_a) { - result = true; - auto vote (node_a.store.vote_generate (transaction_a, pub_a, prv_a, std::vector (1, hash))); - nano::confirm_ack confirm (vote); - for (auto j (list_a.begin ()), m (list_a.end ()); j != m; ++j) - { - j->get ()->send (confirm); - } - node_a.votes_cache.add (vote); - }); - } - else - { - // Send from cache - for (auto & vote : votes) - { - nano::confirm_ack confirm (vote); - for (auto j (list_a.begin ()), m (list_a.end ()); j != m; ++j) - { - j->get ()->send (confirm); - } - } - } - // Republish if required - if (also_publish) - { - nano::publish publish (block_a); - for (auto j (list_a.begin ()), m (list_a.end ()); j != m; ++j) - { - j->get ()->send (publish); - } - } + i->send (message_a, nullptr, drop_policy_a); } - return result; } -bool confirm_block (nano::transaction const & transaction_a, nano::node & node_a, std::shared_ptr channel_a, std::shared_ptr block_a, bool also_publish) +void nano::network::flood_block (std::shared_ptr const & block_a, nano::buffer_drop_policy const drop_policy_a) { - std::array, 1> endpoints = { channel_a }; - auto result (confirm_block (transaction_a, node_a, endpoints, std::move (block_a), also_publish)); - return result; + nano::publish message (block_a); + flood_message (message, drop_policy_a); } -void nano::network::confirm_hashes (nano::transaction const & transaction_a, std::shared_ptr channel_a, std::vector blocks_bundle_a) +void nano::network::flood_block_initial (std::shared_ptr const & block_a) { - if (node.config.enable_voting) + nano::publish message (block_a); + for (auto const & i : node.rep_crawler.principal_representatives ()) { - node.wallets.foreach_representative ([this, &blocks_bundle_a, &channel_a, &transaction_a](nano::public_key const & pub_a, nano::raw_key const & prv_a) { - auto vote (this->node.store.vote_generate (transaction_a, pub_a, prv_a, blocks_bundle_a)); - nano::confirm_ack confirm (vote); - std::shared_ptr> bytes (new std::vector); - { - nano::vectorstream stream (*bytes); - confirm.serialize (stream); - } - channel_a->send (confirm); - this->node.votes_cache.add (vote); - }); + i.channel->send (message, nullptr, nano::buffer_drop_policy::no_limiter_drop); + } + for (auto & i : list_non_pr (fanout (1.0))) + { + i->send (message, nullptr, nano::buffer_drop_policy::no_limiter_drop); } } -bool nano::network::send_votes_cache (std::shared_ptr channel_a, nano::block_hash const & hash_a) +void nano::network::flood_vote (std::shared_ptr const & vote_a, float scale) { - // Search in cache - auto votes (node.votes_cache.find (hash_a)); - // Send from cache - for (auto & vote : votes) + nano::confirm_ack message (vote_a); + for (auto & i : list (fanout (scale))) { - nano::confirm_ack confirm (vote); - channel_a->send (confirm); + i->send (message, nullptr); } - // Returns true if votes were sent - bool result (!votes.empty ()); - return result; } -void nano::network::flood_message (nano::message const & message_a, bool const is_droppable_a) +void nano::network::flood_vote_pr (std::shared_ptr const & vote_a) { - auto list (list_fanout ()); - for (auto i (list.begin ()), n (list.end ()); i != n; ++i) + nano::confirm_ack message (vote_a); + for (auto const & i : node.rep_crawler.principal_representatives ()) { - (*i)->send (message_a, nullptr, is_droppable_a); + i.channel->send (message, nullptr, nano::buffer_drop_policy::no_limiter_drop); } } @@ -238,14 +239,12 @@ void nano::network::flood_block_many (std::deque> b if (!blocks_a.empty ()) { std::weak_ptr node_w (node.shared ()); - // clang-format off node.alarm.add (std::chrono::steady_clock::now () + std::chrono::milliseconds (delay_a + std::rand () % delay_a), [node_w, blocks (std::move (blocks_a)), callback_a, delay_a]() { if (auto node_l = node_w.lock ()) { node_l->network.flood_block_many (std::move (blocks), callback_a, delay_a); } }); - // clang-format on } else if (callback_a) { @@ -256,17 +255,8 @@ void nano::network::flood_block_many (std::deque> b void nano::network::send_confirm_req (std::shared_ptr channel_a, std::shared_ptr block_a) { // Confirmation request with hash + root - if (channel_a->get_network_version () >= node.network_params.protocol.tcp_realtime_protocol_version_min) - { - nano::confirm_req req (block_a->hash (), block_a->root ()); - channel_a->send (req); - } - // Confirmation request with full block - else - { - nano::confirm_req req (block_a); - channel_a->send (req); - } + nano::confirm_req req (block_a->hash (), block_a->root ()); + channel_a->send (req); } void nano::network::broadcast_confirm_req (std::shared_ptr block_a) @@ -275,12 +265,9 @@ void nano::network::broadcast_confirm_req (std::shared_ptr block_a) if (list->empty () || node.rep_crawler.total_weight () < node.config.online_weight_minimum.number ()) { // broadcast request to all peers (with max limit 2 * sqrt (peers count)) - auto peers (node.network.list (std::min (static_cast (100), 2 * node.network.size_sqrt ()))); + auto peers (node.network.list (std::min (100, node.network.fanout (2.0)))); list->clear (); - for (auto & peer : peers) - { - list->push_back (peer); - } + list->insert (list->end (), peers.begin (), peers.end ()); } /* @@ -291,7 +278,7 @@ void nano::network::broadcast_confirm_req (std::shared_ptr block_a) * if the votes for a block have not arrived in time. */ const size_t max_endpoints = 32; - random_pool::shuffle (list->begin (), list->end ()); + nano::random_pool_shuffle (list->begin (), list->end ()); if (list->size () > max_endpoints) { list->erase (list->begin () + max_endpoints, list->end ()); @@ -420,6 +407,13 @@ class network_message_visitor : public nano::message_visitor } node.stats.inc (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in); node.network.merge_peers (message_a.peers); + // Check for special node port data + auto peer0 (message_a.peers[0]); + if (peer0.address () == boost::asio::ip::address_v6{} && peer0.port () != 0) + { + nano::endpoint new_endpoint (channel->get_tcp_endpoint ().address (), peer0.port ()); + node.network.merge_peer (new_endpoint); + } } void publish (nano::publish const & message_a) override { @@ -434,9 +428,9 @@ class network_message_visitor : public nano::message_visitor } else { + node.network.publish_filter.clear (message_a.digest); node.stats.inc (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::in); } - node.active.publish (message_a.block); } void confirm_req (nano::confirm_req const & message_a) override { @@ -453,87 +447,15 @@ class network_message_visitor : public nano::message_visitor } node.stats.inc (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::in); // Don't load nodes with disabled voting - if (node.config.enable_voting && node.wallets.reps_count) + if (node.config.enable_voting && node.wallets.reps ().voting > 0) { if (message_a.block != nullptr) { - auto hash (message_a.block->hash ()); - if (!node.network.send_votes_cache (channel, hash)) - { - auto transaction (node.store.tx_begin_read ()); - auto successor (node.ledger.successor (transaction, message_a.block->qualified_root ())); - if (successor != nullptr) - { - auto same_block (successor->hash () == hash); - confirm_block (transaction, node, channel, std::move (successor), !same_block); - } - } + node.aggregator.add (channel, { { message_a.block->hash (), message_a.block->root () } }); } else if (!message_a.roots_hashes.empty ()) { - auto transaction (node.store.tx_begin_read ()); - std::vector blocks_bundle; - std::vector> cached_votes; - size_t cached_count (0); - for (auto & root_hash : message_a.roots_hashes) - { - auto find_votes (node.votes_cache.find (root_hash.first)); - if (!find_votes.empty ()) - { - ++cached_count; - cached_votes.insert (cached_votes.end (), find_votes.begin (), find_votes.end ()); - } - if (!find_votes.empty () || (!root_hash.first.is_zero () && node.store.block_exists (transaction, root_hash.first))) - { - blocks_bundle.push_back (root_hash.first); - } - else if (!root_hash.second.is_zero ()) - { - nano::block_hash successor (0); - // Search for block root - successor = node.store.block_successor (transaction, root_hash.second); - // Search for account root - if (successor.is_zero ()) - { - nano::account_info info; - auto error (node.store.account_get (transaction, root_hash.second, info)); - if (!error) - { - successor = info.open_block; - } - } - if (!successor.is_zero ()) - { - auto find_successor_votes (node.votes_cache.find (successor)); - if (!find_successor_votes.empty ()) - { - ++cached_count; - cached_votes.insert (cached_votes.end (), find_successor_votes.begin (), find_successor_votes.end ()); - } - blocks_bundle.push_back (successor); - auto successor_block (node.store.block_get (transaction, successor)); - assert (successor_block != nullptr); - nano::publish publish (successor_block); - channel->send (publish); - } - } - } - /* Decide to send cached votes or to create new vote - If there is at least one new hash to confirm, then create new batch vote - Otherwise use more bandwidth & save local resources required to sign vote */ - if (!blocks_bundle.empty () && cached_count < blocks_bundle.size ()) - { - node.network.confirm_hashes (transaction, channel, blocks_bundle); - } - else - { - // Send from cache - for (auto & vote : cached_votes) - { - nano::confirm_ack confirm (vote); - channel->send (confirm); - } - } + node.aggregator.add (channel, message_a.roots_hashes); } } } @@ -544,44 +466,76 @@ class network_message_visitor : public nano::message_visitor node.logger.try_log (boost::str (boost::format ("Received confirm_ack message from %1% for %2%sequence %3%") % channel->to_string () % message_a.vote->hashes_string () % std::to_string (message_a.vote->sequence))); } node.stats.inc (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::in); - for (auto & vote_block : message_a.vote->blocks) + if (!message_a.vote->account.is_zero ()) { - if (!vote_block.which ()) + for (auto & vote_block : message_a.vote->blocks) { - auto block (boost::get> (vote_block)); - if (!node.block_processor.full ()) - { - node.process_active (block); - } - else + if (!vote_block.which ()) { - node.stats.inc (nano::stat::type::drop, nano::stat::detail::confirm_ack, nano::stat::dir::in); + auto block (boost::get> (vote_block)); + if (!node.block_processor.full ()) + { + node.process_active (block); + } + else + { + node.stats.inc (nano::stat::type::drop, nano::stat::detail::confirm_ack, nano::stat::dir::in); + } } - node.active.publish (block); } + node.vote_processor.vote (message_a.vote, channel); } - node.vote_processor.vote (message_a.vote, channel); } void bulk_pull (nano::bulk_pull const &) override { - assert (false); + debug_assert (false); } void bulk_pull_account (nano::bulk_pull_account const &) override { - assert (false); + debug_assert (false); } void bulk_push (nano::bulk_push const &) override { - assert (false); + debug_assert (false); } void frontier_req (nano::frontier_req const &) override { - assert (false); + debug_assert (false); } void node_id_handshake (nano::node_id_handshake const & message_a) override { node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } + void telemetry_req (nano::telemetry_req const & message_a) override + { + if (node.config.logging.network_telemetry_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Telemetry_req message from %1%") % channel->to_string ())); + } + node.stats.inc (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in); + + // Send an empty telemetry_ack if we do not want, just to acknowledge that we have received the message to + // remove any timeouts on the server side waiting for a message. + nano::telemetry_ack telemetry_ack; + if (!node.flags.disable_providing_telemetry_metrics) + { + auto telemetry_data = nano::local_telemetry_data (node.ledger.cache, node.network, node.config.bandwidth_limit, node.network_params, node.startup_time, node.active.active_difficulty (), node.node_id); + telemetry_ack = nano::telemetry_ack (telemetry_data); + } + channel->send (telemetry_ack, nullptr, nano::buffer_drop_policy::no_socket_drop); + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + if (node.config.logging.network_telemetry_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Received telemetry_ack message from %1%") % channel->to_string ())); + } + node.stats.inc (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in); + if (node.telemetry) + { + node.telemetry->set (message_a, *channel); + } + } nano::node & node; std::shared_ptr channel; }; @@ -646,12 +600,29 @@ bool nano::network::reachout (nano::endpoint const & endpoint_a, bool allow_loca return error; } -std::deque> nano::network::list (size_t count_a) +std::deque> nano::network::list (size_t count_a, uint8_t minimum_version_a, bool include_tcp_temporary_channels_a) +{ + std::deque> result; + tcp_channels.list (result, minimum_version_a, include_tcp_temporary_channels_a); + udp_channels.list (result, minimum_version_a); + nano::random_pool_shuffle (result.begin (), result.end ()); + if (result.size () > count_a) + { + result.resize (count_a, nullptr); + } + return result; +} + +std::deque> nano::network::list_non_pr (size_t count_a) { std::deque> result; tcp_channels.list (result); udp_channels.list (result); - random_pool::shuffle (result.begin (), result.end ()); + nano::random_pool_shuffle (result.begin (), result.end ()); + result.erase (std::remove_if (result.begin (), result.end (), [this](std::shared_ptr const & channel) { + return this->node.rep_crawler.is_pr (*channel); + }), + result.end ()); if (result.size () > count_a) { result.resize (count_a, nullptr); @@ -660,16 +631,15 @@ std::deque> nano::network::list (size_ } // Simulating with sqrt_broadcast_simulate shows we only need to broadcast to sqrt(total_peers) random peers in order to successfully publish to everyone with high probability -std::deque> nano::network::list_fanout () +size_t nano::network::fanout (float scale) const { - auto result (list (size_sqrt ())); - return result; + return static_cast (std::ceil (scale * size_sqrt ())); } -std::unordered_set> nano::network::random_set (size_t count_a) const +std::unordered_set> nano::network::random_set (size_t count_a, uint8_t min_version_a, bool include_temporary_channels_a) const { - std::unordered_set> result (tcp_channels.random_set (count_a)); - std::unordered_set> udp_random (udp_channels.random_set (count_a)); + std::unordered_set> result (tcp_channels.random_set (count_a, min_version_a, include_temporary_channels_a)); + std::unordered_set> udp_random (udp_channels.random_set (count_a, min_version_a)); for (auto i (udp_random.begin ()), n (udp_random.end ()); i != n && result.size () < count_a * 1.5; ++i) { result.insert (*i); @@ -683,16 +653,16 @@ std::unordered_set> nano::network::ran void nano::network::random_fill (std::array & target_a) const { - auto peers (random_set (target_a.size ())); - assert (peers.size () <= target_a.size ()); + auto peers (random_set (target_a.size (), 0, false)); // Don't include channels with ephemeral remote ports + debug_assert (peers.size () <= target_a.size ()); auto endpoint (nano::endpoint (boost::asio::ip::address_v6{}, 0)); - assert (endpoint.address ().is_v6 ()); + debug_assert (endpoint.address ().is_v6 ()); std::fill (target_a.begin (), target_a.end (), endpoint); auto j (target_a.begin ()); for (auto i (peers.begin ()), n (peers.end ()); i != n; ++i, ++j) { - assert ((*i)->get_endpoint ().address ().is_v6 ()); - assert (j < target_a.end ()); + debug_assert ((*i)->get_endpoint ().address ().is_v6 ()); + debug_assert (j < target_a.end ()); *j = (*i)->get_endpoint (); } } @@ -701,14 +671,13 @@ nano::tcp_endpoint nano::network::bootstrap_peer (bool lazy_bootstrap) { nano::tcp_endpoint result (boost::asio::ip::address_v6::any (), 0); bool use_udp_peer (nano::random_pool::generate_word32 (0, 1)); - auto protocol_min (lazy_bootstrap ? node.network_params.protocol.protocol_version_bootstrap_lazy_min : node.network_params.protocol.protocol_version_bootstrap_min); if (use_udp_peer || tcp_channels.size () == 0) { - result = udp_channels.bootstrap_peer (protocol_min); + result = udp_channels.bootstrap_peer (node.network_params.protocol.protocol_version_min (node.ledger.cache.epoch_2_started)); } if (result == nano::tcp_endpoint (boost::asio::ip::address_v6::any (), 0)) { - result = tcp_channels.bootstrap_peer (protocol_min); + result = tcp_channels.bootstrap_peer (node.network_params.protocol.protocol_version_min (node.ledger.cache.epoch_2_started)); } return result; } @@ -735,7 +704,7 @@ std::shared_ptr nano::network::find_node_id (nano::acc nano::endpoint nano::network::endpoint () { - return udp_channels.get_local_endpoint (); + return nano::endpoint (boost::asio::ip::address_v6::loopback (), port); } void nano::network::cleanup (std::chrono::steady_clock::time_point const & cutoff_a) @@ -789,9 +758,9 @@ size_t nano::network::size () const return tcp_channels.size () + udp_channels.size (); } -size_t nano::network::size_sqrt () const +float nano::network::size_sqrt () const { - return (static_cast (std::ceil (std::sqrt (size ())))); + return static_cast (std::sqrt (size ())); } bool nano::network::empty () const @@ -799,6 +768,31 @@ bool nano::network::empty () const return size () == 0; } +void nano::network::erase_below_version (uint8_t cutoff_version_a) +{ + std::vector> channels_to_remove; + tcp_channels.list_below_version (channels_to_remove, cutoff_version_a); + udp_channels.list_below_version (channels_to_remove, cutoff_version_a); + for (auto const & channel_to_remove : channels_to_remove) + { + debug_assert (channel_to_remove->get_network_version () < cutoff_version_a); + erase (*channel_to_remove); + } +} + +void nano::network::erase (nano::transport::channel const & channel_a) +{ + if (channel_a.get_type () == nano::transport::transport_type::tcp) + { + tcp_channels.erase (channel_a.get_tcp_endpoint ()); + } + else + { + udp_channels.erase (channel_a.get_endpoint ()); + udp_channels.clean_node_id (channel_a.get_node_id ()); + } +} + nano::message_buffer_manager::message_buffer_manager (nano::stat & stats_a, size_t size, size_t count) : stats (stats_a), free (count), @@ -807,8 +801,8 @@ slab (size * count), entries (count), stopped (false) { - assert (count > 0); - assert (size > 0); + debug_assert (count > 0); + debug_assert (size > 0); auto slab_data (slab.data ()); auto entry_data (entries.data ()); for (auto i (0); i < count; ++i, ++entry_data) @@ -824,9 +818,7 @@ nano::message_buffer * nano::message_buffer_manager::allocate () if (!stopped && free.empty () && full.empty ()) { stats.inc (nano::stat::type::udp, nano::stat::detail::blocking, nano::stat::dir::in); - // clang-format off condition.wait (lock, [& stopped = stopped, &free = free, &full = full] { return stopped || !free.empty () || !full.empty (); }); - // clang-format on } nano::message_buffer * result (nullptr); if (!free.empty ()) @@ -846,7 +838,7 @@ nano::message_buffer * nano::message_buffer_manager::allocate () void nano::message_buffer_manager::enqueue (nano::message_buffer * data_a) { - assert (data_a != nullptr); + debug_assert (data_a != nullptr); { nano::lock_guard lock (mutex); full.push_back (data_a); @@ -872,7 +864,7 @@ nano::message_buffer * nano::message_buffer_manager::dequeue () void nano::message_buffer_manager::release (nano::message_buffer * data_a) { - assert (data_a != nullptr); + debug_assert (data_a != nullptr); { nano::lock_guard lock (mutex); free.push_back (data_a); @@ -889,14 +881,66 @@ void nano::message_buffer_manager::stop () condition.notify_all (); } +nano::tcp_message_manager::tcp_message_manager (unsigned incoming_connections_max_a) : +max_entries (incoming_connections_max_a * nano::tcp_message_manager::max_entries_per_connection + 1) +{ + debug_assert (max_entries > 0); +} + +void nano::tcp_message_manager::put_message (nano::tcp_message_item const & item_a) +{ + { + nano::unique_lock lock (mutex); + while (entries.size () > max_entries && !stopped) + { + condition.wait (lock); + } + entries.push_back (item_a); + } + condition.notify_all (); +} + +nano::tcp_message_item nano::tcp_message_manager::get_message () +{ + nano::unique_lock lock (mutex); + while (entries.empty () && !stopped) + { + condition.wait (lock); + } + if (!entries.empty ()) + { + auto result (entries.front ()); + entries.pop_front (); + return result; + } + else + { + return nano::tcp_message_item{ std::make_shared (), nano::tcp_endpoint (boost::asio::ip::address_v6::any (), 0), 0, nullptr, nano::bootstrap_server_type::undefined }; + } +} + +void nano::tcp_message_manager::stop () +{ + { + nano::lock_guard lock (mutex); + stopped = true; + } + condition.notify_all (); +} + +nano::syn_cookies::syn_cookies (size_t max_cookies_per_ip_a) : +max_cookies_per_ip (max_cookies_per_ip_a) +{ +} + boost::optional nano::syn_cookies::assign (nano::endpoint const & endpoint_a) { auto ip_addr (endpoint_a.address ()); - assert (ip_addr.is_v6 ()); + debug_assert (ip_addr.is_v6 ()); nano::lock_guard lock (syn_cookie_mutex); unsigned & ip_cookies = cookies_per_ip[ip_addr]; boost::optional result; - if (ip_cookies < nano::transport::max_peers_per_ip) + if (ip_cookies < max_cookies_per_ip) { if (cookies.find (endpoint_a) == cookies.end ()) { @@ -914,7 +958,7 @@ boost::optional nano::syn_cookies::assign (nano::endpoint c bool nano::syn_cookies::validate (nano::endpoint const & endpoint_a, nano::account const & node_id, nano::signature const & sig) { auto ip_addr (endpoint_a.address ()); - assert (ip_addr.is_v6 ()); + debug_assert (ip_addr.is_v6 ()); nano::lock_guard lock (syn_cookie_mutex); auto result (true); auto cookie_it (cookies.find (endpoint_a)); @@ -929,7 +973,7 @@ bool nano::syn_cookies::validate (nano::endpoint const & endpoint_a, nano::accou } else { - assert (false && "More SYN cookies deleted than created for IP"); + debug_assert (false && "More SYN cookies deleted than created for IP"); } } return result; @@ -951,7 +995,7 @@ void nano::syn_cookies::purge (std::chrono::steady_clock::time_point const & cut } else { - assert (false && "More SYN cookies deleted than created for IP"); + debug_assert (false && "More SYN cookies deleted than created for IP"); } it = cookies.erase (it); } @@ -962,17 +1006,33 @@ void nano::syn_cookies::purge (std::chrono::steady_clock::time_point const & cut } } -std::unique_ptr nano::syn_cookies::collect_seq_con_info (std::string const & name) +size_t nano::syn_cookies::cookies_size () +{ + nano::lock_guard lock (syn_cookie_mutex); + return cookies.size (); +} + +std::unique_ptr nano::collect_container_info (network & network, const std::string & name) +{ + auto composite = std::make_unique (name); + composite->add_component (network.tcp_channels.collect_container_info ("tcp_channels")); + composite->add_component (network.udp_channels.collect_container_info ("udp_channels")); + composite->add_component (network.syn_cookies.collect_container_info ("syn_cookies")); + composite->add_component (collect_container_info (network.excluded_peers, "excluded_peers")); + return composite; +} + +std::unique_ptr nano::syn_cookies::collect_container_info (std::string const & name) { - size_t syn_cookies_count = 0; - size_t syn_cookies_per_ip_count = 0; + size_t syn_cookies_count; + size_t syn_cookies_per_ip_count; { nano::lock_guard syn_cookie_guard (syn_cookie_mutex); syn_cookies_count = cookies.size (); syn_cookies_per_ip_count = cookies_per_ip.size (); } - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "syn_cookies", syn_cookies_count, sizeof (decltype (cookies)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "syn_cookies_per_ip", syn_cookies_per_ip_count, sizeof (decltype (cookies_per_ip)::value_type) })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "syn_cookies", syn_cookies_count, sizeof (decltype (cookies)::value_type) })); + composite->add_component (std::make_unique (container_info{ "syn_cookies_per_ip", syn_cookies_per_ip_count, sizeof (decltype (cookies_per_ip)::value_type) })); return composite; } diff --git a/nano/node/network.hpp b/nano/node/network.hpp index da2850a113..780da5460a 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -1,15 +1,16 @@ #pragma once -#include #include +#include #include #include +#include #include #include #include - +#include namespace nano { class channel; @@ -65,12 +66,30 @@ class message_buffer_manager final std::vector entries; bool stopped; }; +class tcp_message_manager final +{ +public: + tcp_message_manager (unsigned incoming_connections_max_a); + void put_message (nano::tcp_message_item const & item_a); + nano::tcp_message_item get_message (); + // Stop container and notify waiting threads + void stop (); + +private: + std::mutex mutex; + nano::condition_variable condition; + std::deque entries; + unsigned max_entries; + static unsigned const max_entries_per_connection = 16; + bool stopped{ false }; +}; /** * Node ID cookies for node ID handshakes */ class syn_cookies final { public: + syn_cookies (size_t); void purge (std::chrono::steady_clock::time_point const &); // Returns boost::none if the IP is rate capped on syn cookie requests, // or if the endpoint already has a syn cookie query @@ -78,7 +97,8 @@ class syn_cookies final // Returns false if valid, true if invalid (true on error convention) // Also removes the syn cookie from the store if valid bool validate (nano::endpoint const &, nano::account const &, nano::signature const &); - std::unique_ptr collect_seq_con_info (std::string const &); + std::unique_ptr collect_container_info (std::string const &); + size_t cookies_size (); private: class syn_cookie_info final @@ -90,6 +110,7 @@ class syn_cookies final mutable std::mutex syn_cookie_mutex; std::unordered_map cookies; std::unordered_map cookies_per_ip; + size_t max_cookies_per_ip; }; class network final { @@ -98,24 +119,19 @@ class network final ~network (); void start (); void stop (); - void flood_message (nano::message const &, bool const = true); + void flood_message (nano::message const &, nano::buffer_drop_policy const = nano::buffer_drop_policy::limiter, float const = 1.0f); void flood_keepalive () { nano::keepalive message; random_fill (message.peers); flood_message (message); } - void flood_vote (std::shared_ptr vote_a) - { - nano::confirm_ack message (vote_a); - flood_message (message); - } - void flood_block (std::shared_ptr block_a, bool const is_droppable_a = true) - { - nano::publish publish (block_a); - flood_message (publish, is_droppable_a); - } - + void flood_vote (std::shared_ptr const &, float scale); + void flood_vote_pr (std::shared_ptr const &); + // Flood block to all PRs and a random selection of non-PRs + void flood_block_initial (std::shared_ptr const &); + // Flood block to a random selection of peers + void flood_block (std::shared_ptr const &, nano::buffer_drop_policy const = nano::buffer_drop_policy::limiter); void flood_block_many (std::deque>, std::function = nullptr, unsigned = broadcast_interval_ms); void merge_peers (std::array const &); void merge_peer (nano::endpoint const &); @@ -127,19 +143,19 @@ class network final void broadcast_confirm_req_base (std::shared_ptr, std::shared_ptr>>, unsigned, bool = false); void broadcast_confirm_req_batched_many (std::unordered_map, std::deque>>, std::function = nullptr, unsigned = broadcast_interval_ms, bool = false); void broadcast_confirm_req_many (std::deque, std::shared_ptr>>>>, std::function = nullptr, unsigned = broadcast_interval_ms); - void confirm_hashes (nano::transaction const &, std::shared_ptr, std::vector); - bool send_votes_cache (std::shared_ptr, nano::block_hash const &); std::shared_ptr find_node_id (nano::account const &); std::shared_ptr find_channel (nano::endpoint const &); void process_message (nano::message const &, std::shared_ptr); bool not_a_peer (nano::endpoint const &, bool); // Should we reach out to this endpoint with a keepalive message bool reachout (nano::endpoint const &, bool = false); - std::deque> list (size_t); - // A list of random peers sized for the configured rebroadcast fanout - std::deque> list_fanout (); + std::deque> list (size_t, uint8_t = 0, bool = true); + std::deque> list_non_pr (size_t); + // Desired fanout for a given scale + size_t fanout (float scale = 1.0f) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + // Note: The minimum protocol version is used after the random selection, so number of peers can be less than expected. + std::unordered_set> random_set (size_t, uint8_t = 0, bool = false) const; // Get the next peer for attempting a tcp bootstrap connection nano::tcp_endpoint bootstrap_peer (bool = false); nano::endpoint endpoint (); @@ -150,14 +166,21 @@ class network final void ongoing_syn_cookie_cleanup (); void ongoing_keepalive (); size_t size () const; - size_t size_sqrt () const; + float size_sqrt () const; bool empty () const; + void erase (nano::transport::channel const &); + void erase_below_version (uint8_t); nano::message_buffer_manager buffer_container; boost::asio::ip::udp::resolver resolver; std::vector packet_processing_threads; + nano::bandwidth_limiter limiter; + nano::peer_exclusion excluded_peers; + nano::tcp_message_manager tcp_message_manager; nano::node & node; + nano::network_filter publish_filter; nano::transport::udp_channels udp_channels; nano::transport::tcp_channels tcp_channels; + std::atomic port{ 0 }; std::function disconnect_observer; // Called when a new channel is observed std::function)> channel_observer; @@ -165,5 +188,7 @@ class network final static unsigned const broadcast_interval_ms = 10; static size_t const buffer_size = 512; static size_t const confirm_req_hashes_max = 7; + static size_t const confirm_ack_hashes_max = 12; }; +std::unique_ptr collect_container_info (network & network, const std::string & name); } diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 1e6c599a57..2c1af35bb4 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -1,21 +1,26 @@ -#include -#include +#define IGNORE_GTEST_INCL +#include +#include +#include #include #include +#include #include +#include +#include #include +#include #if NANO_ROCKSDB #include #endif -#include +#include #include #include #include #include -#include #include double constexpr nano::node::price_max; @@ -64,56 +69,26 @@ void nano::node::keepalive (std::string const & address_a, uint16_t port_a) }); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (rep_crawler & rep_crawler, const std::string & name) +std::unique_ptr nano::collect_container_info (rep_crawler & rep_crawler, const std::string & name) { - size_t count = 0; + size_t count; { nano::lock_guard guard (rep_crawler.active_mutex); count = rep_crawler.active.size (); } auto sizeof_element = sizeof (decltype (rep_crawler.active)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "active", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "active", count, sizeof_element })); return composite; } -std::unique_ptr collect_seq_con_info (block_processor & block_processor, const std::string & name) +nano::node::node (boost::asio::io_context & io_ctx_a, uint16_t peering_port_a, boost::filesystem::path const & application_path_a, nano::alarm & alarm_a, nano::logging const & logging_a, nano::work_pool & work_a, nano::node_flags flags_a, unsigned seq) : +node (io_ctx_a, application_path_a, alarm_a, nano::node_config (peering_port_a, logging_a), work_a, flags_a, seq) { - size_t state_blocks_count = 0; - size_t blocks_count = 0; - size_t blocks_filter_count = 0; - size_t forced_count = 0; - size_t rolled_back_count = 0; - - { - nano::lock_guard guard (block_processor.mutex); - state_blocks_count = block_processor.state_blocks.size (); - blocks_count = block_processor.blocks.size (); - blocks_filter_count = block_processor.blocks_filter.size (); - forced_count = block_processor.forced.size (); - rolled_back_count = block_processor.rolled_back.size (); - } - - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "state_blocks", state_blocks_count, sizeof (decltype (block_processor.state_blocks)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "blocks", blocks_count, sizeof (decltype (block_processor.blocks)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "blocks_filter", blocks_filter_count, sizeof (decltype (block_processor.blocks_filter)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "forced", forced_count, sizeof (decltype (block_processor.forced)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "rolled_back", rolled_back_count, sizeof (decltype (block_processor.rolled_back)::value_type) })); - composite->add_component (collect_seq_con_info (block_processor.generator, "generator")); - return composite; -} } -nano::node::node (boost::asio::io_context & io_ctx_a, uint16_t peering_port_a, boost::filesystem::path const & application_path_a, nano::alarm & alarm_a, nano::logging const & logging_a, nano::work_pool & work_a, nano::node_flags flags_a) : -node (io_ctx_a, application_path_a, alarm_a, nano::node_config (peering_port_a, logging_a), work_a, flags_a) -{ -} - -nano::node::node (boost::asio::io_context & io_ctx_a, boost::filesystem::path const & application_path_a, nano::alarm & alarm_a, nano::node_config const & config_a, nano::work_pool & work_a, nano::node_flags flags_a) : +nano::node::node (boost::asio::io_context & io_ctx_a, boost::filesystem::path const & application_path_a, nano::alarm & alarm_a, nano::node_config const & config_a, nano::work_pool & work_a, nano::node_flags flags_a, unsigned seq) : io_ctx (io_ctx_a), node_initialized_latch (1), config (config_a), @@ -123,40 +98,48 @@ alarm (alarm_a), work (work_a), distributed_work (*this), logger (config_a.logging.min_time_between_log_output), -store_impl (nano::make_store (logger, application_path_a, flags.read_only, true, config_a.rocksdb_config, config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_max_dbs, flags.sideband_batch_size, config_a.backup_before_upgrade, config_a.rocksdb_config.enable)), +store_impl (nano::make_store (logger, application_path_a, flags.read_only, true, config_a.rocksdb_config, config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_config, flags.sideband_batch_size, config_a.backup_before_upgrade, config_a.rocksdb_config.enable)), store (*store_impl), -wallets_store_impl (std::make_unique (application_path_a / "wallets.ldb", config_a.lmdb_max_dbs)), +wallets_store_impl (std::make_unique (application_path_a / "wallets.ldb", config_a.lmdb_config)), wallets_store (*wallets_store_impl), gap_cache (*this), -ledger (store, stats, flags_a.cache_representative_weights_from_frontiers), +ledger (store, stats, flags_a.generate_cache, [this]() { this->network.erase_below_version (network_params.protocol.protocol_version_min (true)); }), checker (config.signature_checker_threads), network (*this, config.peering_port), +telemetry (std::make_shared (network, alarm, worker, observers.telemetry, stats, network_params, flags.disable_ongoing_telemetry_requests)), bootstrap_initiator (*this), bootstrap (config.peering_port, *this), application_path (application_path_a), port_mapping (*this), -vote_processor (*this), +vote_processor (checker, active, observers, stats, config, flags, logger, online_reps, ledger, network_params), rep_crawler (*this), warmed_up (0), block_processor (*this, write_database_queue), +// clang-format off block_processor_thread ([this]() { nano::thread_role::set (nano::thread_role::name::block_processing); this->block_processor.process_blocks (); }), -online_reps (*this, config.online_weight_minimum.number ()), +// clang-format on +online_reps (ledger, network_params, config.online_weight_minimum.number ()), +votes_cache (wallets), vote_uniquer (block_uniquer), -active (*this), -confirmation_height_processor (pending_confirmation_height, ledger, active, write_database_queue, config.conf_height_processor_batch_min_time, logger), +confirmation_height_processor (ledger, write_database_queue, config.conf_height_processor_batch_min_time, logger, node_initialized_latch, flags.confirmation_height_processor_mode), +active (*this, confirmation_height_processor), +aggregator (network_params.network, config, stats, votes_cache, ledger, wallets, active), payment_observer_processor (observers.blocks), wallets (wallets_store.init_error (), *this), -startup_time (std::chrono::steady_clock::now ()) +startup_time (std::chrono::steady_clock::now ()), +node_seq (seq) { if (!init_error ()) { + telemetry->start (); + if (config.websocket_config.enabled) { - auto endpoint_l (nano::tcp_endpoint (config.websocket_config.address, config.websocket_config.port)); - websocket_server = std::make_shared (*this, endpoint_l); + auto endpoint_l (nano::tcp_endpoint (boost::asio::ip::make_address_v6 (config.websocket_config.address), config.websocket_config.port)); + websocket_server = std::make_shared (logger, wallets, io_ctx, endpoint_l); this->websocket_server->run (); } @@ -164,6 +147,7 @@ startup_time (std::chrono::steady_clock::now ()) observers.wallet.notify (active); }; network.channel_observer = [this](std::shared_ptr channel_a) { + debug_assert (channel_a != nullptr); observers.endpoint.notify (channel_a); }; network.disconnect_observer = [this]() { @@ -234,7 +218,7 @@ startup_time (std::chrono::steady_clock::now ()) if (websocket_server) { observers.blocks.add ([this](nano::election_status const & status_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { - assert (status_a.type != nano::election_status_type::ongoing); + debug_assert (status_a.type != nano::election_status_type::ongoing); if (this->websocket_server->any_subscriber (nano::websocket::topic::confirmation)) { @@ -276,24 +260,32 @@ startup_time (std::chrono::steady_clock::now ()) if (this->websocket_server->any_subscriber (nano::websocket::topic::active_difficulty)) { nano::websocket::message_builder builder; - auto msg (builder.difficulty_changed (network_params.network.publish_threshold, active_difficulty)); + auto msg (builder.difficulty_changed (this->default_difficulty (nano::work_version::work_1), active_difficulty)); this->websocket_server->broadcast (msg); } }); + + observers.telemetry.add ([this](nano::telemetry_data const & telemetry_data, nano::endpoint const & endpoint) { + if (this->websocket_server->any_subscriber (nano::websocket::topic::telemetry)) + { + nano::websocket::message_builder builder; + this->websocket_server->broadcast (builder.telemetry_received (telemetry_data, endpoint)); + } + }); } // Add block confirmation type stats regardless of http-callback and websocket subscriptions observers.blocks.add ([this](nano::election_status const & status_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { - assert (status_a.type != nano::election_status_type::ongoing); + debug_assert (status_a.type != nano::election_status_type::ongoing); switch (status_a.type) { case nano::election_status_type::active_confirmed_quorum: - this->stats.inc (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out); + this->stats.inc (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out); break; case nano::election_status_type::active_confirmation_height: - this->stats.inc (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_conf_height, nano::stat::dir::out); + this->stats.inc (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out); break; case nano::election_status_type::inactive_confirmation_height: - this->stats.inc (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out); + this->stats.inc (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out); break; default: break; @@ -309,51 +301,29 @@ startup_time (std::chrono::steady_clock::now ()) this->network.send_keepalive_self (channel_a); } }); - observers.vote.add ([this](std::shared_ptr vote_a, std::shared_ptr channel_a) { - this->gap_cache.vote (vote_a); - this->online_reps.observe (vote_a->account); - nano::uint128_t rep_weight; - { - rep_weight = ledger.weight (vote_a->account); - } - if (rep_weight > minimum_principal_weight ()) + observers.vote.add ([this](std::shared_ptr vote_a, std::shared_ptr channel_a, nano::vote_code code_a) { + debug_assert (code_a != nano::vote_code::invalid); + if (code_a != nano::vote_code::replay) { - bool rep_crawler_exists (false); - for (auto hash : *vote_a) + auto active_in_rep_crawler (!this->rep_crawler.response (channel_a, vote_a)); + if (active_in_rep_crawler || code_a == nano::vote_code::vote) { - if (this->rep_crawler.exists (hash)) - { - rep_crawler_exists = true; - break; - } - } - if (rep_crawler_exists) - { - // We see a valid non-replay vote for a block we requested, this node is probably a representative - if (this->rep_crawler.response (channel_a, vote_a->account, rep_weight)) - { - logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel_a->to_string ())); - // Rebroadcasting all active votes to new representative - auto blocks (this->active.list_blocks (true)); - for (auto i (blocks.begin ()), n (blocks.end ()); i != n; ++i) - { - if (*i != nullptr) - { - nano::confirm_req req (*i); - channel_a->send (req); - } - } - } + // Representative is defined as online if replying to live votes or rep_crawler queries + this->online_reps.observe (vote_a->account); } } + if (code_a == nano::vote_code::indeterminate) + { + this->gap_cache.vote (vote_a); + } }); if (websocket_server) { - observers.vote.add ([this](std::shared_ptr vote_a, std::shared_ptr channel_a) { + observers.vote.add ([this](std::shared_ptr vote_a, std::shared_ptr channel_a, nano::vote_code code_a) { if (this->websocket_server->any_subscriber (nano::websocket::topic::vote)) { nano::websocket::message_builder builder; - auto msg (builder.vote_received (vote_a)); + auto msg (builder.vote_received (vote_a, code_a)); this->websocket_server->broadcast (msg); } }); @@ -366,6 +336,7 @@ startup_time (std::chrono::steady_clock::now ()) logger.always_log ("Node starting, version: ", NANO_VERSION_STRING); logger.always_log ("Build information: ", BUILD_INFO); + logger.always_log ("Database backend: ", store.vendor_get ()); auto network_label = network_params.network.get_current_network_as_string (); logger.always_log ("Active network: ", network_label); @@ -382,7 +353,7 @@ startup_time (std::chrono::steady_clock::now ()) logger.always_log ("Constructing node"); } - logger.always_log (boost::str (boost::format ("Outbound Voting Bandwidth limited to %1% bytes per second") % config.bandwidth_limit)); + logger.always_log (boost::str (boost::format ("Outbound Voting Bandwidth limited to %1% bytes per second, burst ratio %2%") % config.bandwidth_limit % config.bandwidth_limit_burst_ratio)); // First do a pass with a read to see if any writing needs doing, this saves needing to open a write lock (and potentially blocking) auto is_initialized (false); @@ -395,9 +366,9 @@ startup_time (std::chrono::steady_clock::now ()) if (!is_initialized) { release_assert (!flags.read_only); - auto transaction (store.tx_begin_write ()); + auto transaction (store.tx_begin_write ({ tables::accounts, tables::cached_counts, tables::confirmation_height, tables::frontiers, tables::open_blocks })); // Store was empty meaning we just created it, add the genesis block - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); } if (!ledger.block_exists (genesis.hash ())) @@ -415,46 +386,46 @@ startup_time (std::chrono::steady_clock::now ()) std::exit (1); } + if (config.enable_voting) + { + std::ostringstream stream; + stream << "Voting is enabled, more system resources will be used"; + auto voting (wallets.reps ().voting); + if (voting > 0) + { + stream << ". " << voting << " representative(s) are configured"; + if (voting > 1) + { + stream << ". Voting with more than one representative can limit performance"; + } + } + logger.always_log (stream.str ()); + } + node_id = nano::keypair (); logger.always_log ("Node ID: ", node_id.pub.to_node_id ()); if ((network_params.network.is_live_network () || network_params.network.is_beta_network ()) && !flags.inactive_node) { + auto bootstrap_weights = get_bootstrap_weights (); // Use bootstrap weights if initial bootstrap is not completed - bool use_bootstrap_weight (false); - const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta; - size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size; - nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size); - nano::uint128_union block_height; - if (!nano::try_read (weight_stream, block_height)) + bool use_bootstrap_weight = ledger.cache.block_count < bootstrap_weights.first; + if (use_bootstrap_weight) { - auto max_blocks = (uint64_t)block_height.number (); - use_bootstrap_weight = ledger.block_count_cache < max_blocks; - if (use_bootstrap_weight) + ledger.bootstrap_weight_max_blocks = bootstrap_weights.first; + ledger.bootstrap_weights = bootstrap_weights.second; + for (auto const & rep : ledger.bootstrap_weights) { - ledger.bootstrap_weight_max_blocks = max_blocks; - while (true) - { - nano::account account; - if (nano::try_read (weight_stream, account.bytes)) - { - break; - } - nano::amount weight; - if (nano::try_read (weight_stream, weight.bytes)) - { - break; - } - logger.always_log ("Using bootstrap rep weight: ", account.to_account (), " -> ", weight.format_balance (Mxrb_ratio, 0, true), " XRB"); - ledger.bootstrap_weights[account] = weight.number (); - } + logger.always_log ("Using bootstrap rep weight: ", rep.first.to_account (), " -> ", nano::uint128_union (rep.second).format_balance (Mxrb_ratio, 0, true), " XRB"); } } + // Drop unchecked blocks if initial bootstrap is completed if (!flags.disable_unchecked_drop && !use_bootstrap_weight && !flags.read_only) { - auto transaction (store.tx_begin_write ()); + auto transaction (store.tx_begin_write ({ tables::unchecked })); store.unchecked_clear (transaction); + ledger.cache.unchecked_count = 0; logger.always_log ("Dropping unchecked blocks"); } } @@ -554,68 +525,69 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha if (!store.block_exists (transaction_a, block_a->type (), block_a->hash ()) && store.root_exists (transaction_a, block_a->root ())) { std::shared_ptr ledger_block (ledger.forked_block (transaction_a, *block_a)); - if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ())) + if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ()) && ledger.can_vote (transaction_a, *ledger_block)) { std::weak_ptr this_w (shared_from_this ()); - if (!active.start (ledger_block, false, [this_w, root](std::shared_ptr) { - if (auto this_l = this_w.lock ()) - { - auto attempt (this_l->bootstrap_initiator.current_attempt ()); - if (attempt && attempt->mode == nano::bootstrap_mode::legacy) - { - auto transaction (this_l->store.tx_begin_read ()); - auto account (this_l->ledger.store.frontier_get (transaction, root)); - if (!account.is_zero ()) - { - attempt->requeue_pull (nano::pull_info (account, root, root)); - } - else if (this_l->ledger.store.account_exists (transaction, root)) - { - attempt->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0))); - } - } - } - })) + auto election = active.insert (ledger_block, boost::none, [this_w, root](std::shared_ptr) { + if (auto this_l = this_w.lock ()) + { + auto attempt (this_l->bootstrap_initiator.current_attempt ()); + if (attempt && attempt->mode == nano::bootstrap_mode::legacy) + { + auto transaction (this_l->store.tx_begin_read ()); + auto account (this_l->ledger.store.frontier_get (transaction, root)); + if (!account.is_zero ()) + { + this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (account, root, root, attempt->incremental_id)); + } + else if (this_l->ledger.store.account_exists (transaction, root)) + { + this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0), attempt->incremental_id)); + } + } + } + }); + if (election.inserted) { logger.always_log (boost::str (boost::format ("Resolving fork between our block: %1% and block %2% both with root %3%") % ledger_block->hash ().to_string () % block_a->hash ().to_string () % block_a->root ().to_string ())); - network.broadcast_confirm_req (ledger_block); + election.election->transition_active (); } } + active.publish (block_a); } } -namespace nano +std::unique_ptr nano::collect_container_info (node & node, const std::string & name) { -std::unique_ptr collect_seq_con_info (node & node, const std::string & name) -{ - auto composite = std::make_unique (name); - composite->add_component (collect_seq_con_info (node.alarm, "alarm")); - composite->add_component (collect_seq_con_info (node.work, "work")); - composite->add_component (collect_seq_con_info (node.gap_cache, "gap_cache")); - composite->add_component (collect_seq_con_info (node.ledger, "ledger")); - composite->add_component (collect_seq_con_info (node.active, "active")); - composite->add_component (collect_seq_con_info (node.bootstrap_initiator, "bootstrap_initiator")); - composite->add_component (collect_seq_con_info (node.bootstrap, "bootstrap")); - composite->add_component (node.network.tcp_channels.collect_seq_con_info ("tcp_channels")); - composite->add_component (node.network.udp_channels.collect_seq_con_info ("udp_channels")); - composite->add_component (node.network.syn_cookies.collect_seq_con_info ("syn_cookies")); - composite->add_component (collect_seq_con_info (node.observers, "observers")); - composite->add_component (collect_seq_con_info (node.wallets, "wallets")); - composite->add_component (collect_seq_con_info (node.vote_processor, "vote_processor")); - composite->add_component (collect_seq_con_info (node.rep_crawler, "rep_crawler")); - composite->add_component (collect_seq_con_info (node.block_processor, "block_processor")); - composite->add_component (collect_seq_con_info (node.block_arrival, "block_arrival")); - composite->add_component (collect_seq_con_info (node.online_reps, "online_reps")); - composite->add_component (collect_seq_con_info (node.votes_cache, "votes_cache")); - composite->add_component (collect_seq_con_info (node.block_uniquer, "block_uniquer")); - composite->add_component (collect_seq_con_info (node.vote_uniquer, "vote_uniquer")); - composite->add_component (collect_seq_con_info (node.confirmation_height_processor, "confirmation_height_processor")); - composite->add_component (collect_seq_con_info (node.pending_confirmation_height, "pending_confirmation_height")); - composite->add_component (collect_seq_con_info (node.worker, "worker")); - composite->add_component (collect_seq_con_info (node.distributed_work, "distributed_work")); + auto composite = std::make_unique (name); + composite->add_component (collect_container_info (node.alarm, "alarm")); + composite->add_component (collect_container_info (node.work, "work")); + composite->add_component (collect_container_info (node.gap_cache, "gap_cache")); + composite->add_component (collect_container_info (node.ledger, "ledger")); + composite->add_component (collect_container_info (node.active, "active")); + composite->add_component (collect_container_info (node.bootstrap_initiator, "bootstrap_initiator")); + composite->add_component (collect_container_info (node.bootstrap, "bootstrap")); + composite->add_component (collect_container_info (node.network, "network")); + if (node.telemetry) + { + composite->add_component (collect_container_info (*node.telemetry, "telemetry")); + } + composite->add_component (collect_container_info (node.observers, "observers")); + composite->add_component (collect_container_info (node.wallets, "wallets")); + composite->add_component (collect_container_info (node.vote_processor, "vote_processor")); + composite->add_component (collect_container_info (node.rep_crawler, "rep_crawler")); + composite->add_component (collect_container_info (node.block_processor, "block_processor")); + composite->add_component (collect_container_info (node.block_arrival, "block_arrival")); + composite->add_component (collect_container_info (node.online_reps, "online_reps")); + composite->add_component (collect_container_info (node.votes_cache, "votes_cache")); + composite->add_component (collect_container_info (node.block_uniquer, "block_uniquer")); + composite->add_component (collect_container_info (node.vote_uniquer, "vote_uniquer")); + composite->add_component (collect_container_info (node.confirmation_height_processor, "confirmation_height_processor")); + composite->add_component (collect_container_info (node.worker, "worker")); + composite->add_component (collect_container_info (node.distributed_work, "distributed_work")); + composite->add_component (collect_container_info (node.aggregator, "request_aggregator")); return composite; } -} void nano::node::process_active (std::shared_ptr incoming) { @@ -623,7 +595,7 @@ void nano::node::process_active (std::shared_ptr incoming) block_processor.add (incoming, nano::seconds_since_epoch ()); } -nano::process_return nano::node::process (nano::block const & block_a) +nano::process_return nano::node::process (nano::block & block_a) { auto transaction (store.tx_begin_write ({ tables::accounts, tables::cached_counts, tables::change_blocks, tables::frontiers, tables::open_blocks, tables::pending, tables::receive_blocks, tables::representation, tables::send_blocks, tables::state_blocks }, { tables::confirmation_height })); auto result (ledger.process (transaction, block_a)); @@ -639,12 +611,14 @@ nano::process_return nano::node::process_local (std::shared_ptr blo // Notify block processor to release write lock block_processor.wait_write (); // Process block - auto transaction (store.tx_begin_write ()); - return block_processor.process_one (transaction, info, work_watcher_a); + block_post_events events; + auto transaction (store.tx_begin_write ({ tables::accounts, tables::cached_counts, tables::change_blocks, tables::frontiers, tables::open_blocks, tables::pending, tables::receive_blocks, tables::representation, tables::send_blocks, tables::state_blocks }, { tables::confirmation_height })); + return block_processor.process_one (transaction, events, info, work_watcher_a, nano::block_origin::local); } void nano::node::start () { + long_inactivity_cleanup (); network.start (); add_initial_peers (); if (!flags.disable_legacy_bootstrap) @@ -666,9 +640,11 @@ void nano::node::start () ongoing_rep_calculation (); ongoing_peer_store (); ongoing_online_weight_calculation_queue (); - if (config.tcp_incoming_connections_max > 0) + bool tcp_enabled (false); + if (config.tcp_incoming_connections_max > 0 && !(flags.disable_bootstrap_listener && flags.disable_tcp_realtime)) { bootstrap.start (); + tcp_enabled = true; } if (!flags.disable_backup) { @@ -683,7 +659,8 @@ void nano::node::start () this_l->bootstrap_wallet (); }); } - if (config.external_address == boost::asio::ip::address_v6{}.any ()) + // Start port mapping if external address is not defined and TCP or UDP ports are enabled + if (config.external_address == boost::asio::ip::address_v6{}.any ().to_string () && (tcp_enabled || !flags.disable_udp)) { port_mapping.start (); } @@ -694,7 +671,6 @@ void nano::node::stop () if (!stopped.exchange (true)) { logger.always_log ("Node stopping"); - write_database_queue.stop (); // Cancels ongoing work generation tasks, which may be blocking other threads // No tasks may wait for work generation in I/O threads, or termination signal capturing will be unable to call node::stop() distributed_work.stop (); @@ -703,10 +679,16 @@ void nano::node::stop () { block_processor_thread.join (); } + aggregator.stop (); vote_processor.stop (); - confirmation_height_processor.stop (); active.stop (); + confirmation_height_processor.stop (); network.stop (); + if (telemetry) + { + telemetry->stop (); + telemetry = nullptr; + } if (websocket_server) { websocket_server->stop (); @@ -718,6 +700,11 @@ void nano::node::stop () wallets.stop (); stats.stop (); worker.stop (); + auto epoch_upgrade = epoch_upgrading.lock (); + if (epoch_upgrade->valid ()) + { + epoch_upgrade->wait (); + } // work pool is not stopped on purpose due to testing setup } } @@ -784,6 +771,31 @@ nano::uint128_t nano::node::minimum_principal_weight (nano::uint128_t const & on return online_stake / network_params.network.principal_weight_factor; } +void nano::node::long_inactivity_cleanup () +{ + bool perform_cleanup = false; + auto transaction (store.tx_begin_write ({ tables::online_weight, tables::peers })); + if (store.online_weight_count (transaction) > 0) + { + auto i (store.online_weight_begin (transaction)); + auto sample (store.online_weight_begin (transaction)); + auto n (store.online_weight_end ()); + while (++i != n) + { + ++sample; + } + debug_assert (sample != n); + auto const one_week_ago = (std::chrono::system_clock::now () - std::chrono::hours (7 * 24)).time_since_epoch ().count (); + perform_cleanup = sample->first < one_week_ago; + } + if (perform_cleanup) + { + store.online_weight_clear (transaction); + store.peer_clear (transaction); + logger.always_log ("Removed records of peers and online weight after a long period of inactivity"); + } +} + void nano::node::ongoing_rep_calculation () { auto now (std::chrono::steady_clock::now ()); @@ -900,16 +912,20 @@ void nano::node::bootstrap_wallet () } } } - bootstrap_initiator.bootstrap_wallet (accounts); + if (!accounts.empty ()) + { + bootstrap_initiator.bootstrap_wallet (accounts); + } } void nano::node::unchecked_cleanup () { + std::vector digests; std::deque cleaning_list; auto attempt (bootstrap_initiator.current_attempt ()); bool long_attempt (attempt != nullptr && std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt->attempt_start).count () > config.unchecked_cutoff_time.count ()); // Collect old unchecked keys - if (!flags.disable_unchecked_cleanup && ledger.block_count_cache >= ledger.bootstrap_weight_max_blocks && !long_attempt) + if (!flags.disable_unchecked_cleanup && ledger.cache.block_count >= ledger.bootstrap_weight_max_blocks && !long_attempt) { auto now (nano::seconds_since_epoch ()); auto transaction (store.tx_begin_read ()); @@ -920,6 +936,7 @@ void nano::node::unchecked_cleanup () nano::unchecked_info const & info (i->second); if ((now - info.modified) > static_cast (config.unchecked_cutoff_time.count ())) { + digests.push_back (network.publish_filter.hash (info.block)); cleaning_list.push_back (key); } } @@ -932,14 +949,21 @@ void nano::node::unchecked_cleanup () while (!cleaning_list.empty ()) { size_t deleted_count (0); - auto transaction (store.tx_begin_write ()); + auto transaction (store.tx_begin_write ({ tables::unchecked })); while (deleted_count++ < 2 * 1024 && !cleaning_list.empty ()) { auto key (cleaning_list.front ()); cleaning_list.pop_front (); - store.unchecked_del (transaction, key); + if (store.unchecked_exists (transaction, key)) + { + store.unchecked_del (transaction, key); + debug_assert (ledger.cache.unchecked_count > 0); + --ledger.cache.unchecked_count; + } } } + // Delete from the duplicate filter + network.publish_filter.clear (digests); } void nano::node::ongoing_unchecked_cleanup () @@ -955,7 +979,7 @@ void nano::node::ongoing_unchecked_cleanup () int nano::node::price (nano::uint128_t const & balance_a, int amount_a) { - assert (balance_a >= amount_a * nano::Gxrb_ratio); + debug_assert (balance_a >= amount_a * nano::Gxrb_ratio); auto balance_l (balance_a); double result (0.0); for (auto i (0); i < amount_a; ++i) @@ -969,6 +993,25 @@ int nano::node::price (nano::uint128_t const & balance_a, int amount_a) return static_cast (result * 100.0); } +uint64_t nano::node::default_difficulty (nano::work_version const version_a) const +{ + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) + { + case nano::work_version::work_1: + result = ledger.cache.epoch_2_started ? nano::work_threshold_base (version_a) : network_params.network.publish_thresholds.epoch_1; + break; + default: + debug_assert (false && "Invalid version specified to default_difficulty"); + } + return result; +} + +uint64_t nano::node::max_work_generate_difficulty (nano::work_version const version_a) const +{ + return nano::difficulty::from_multiplier (config.max_work_generate_multiplier, default_difficulty (version_a)); +} + bool nano::node::local_work_generation_enabled () const { return config.work_threads > 0 || work.opencl; @@ -984,14 +1027,9 @@ bool nano::node::work_generation_enabled (std::vector nano::node::work_generate_blocking (nano::block & block_a) -{ - return work_generate_blocking (block_a, network_params.network.publish_threshold); -} - boost::optional nano::node::work_generate_blocking (nano::block & block_a, uint64_t difficulty_a) { - auto opt_work_l (work_generate_blocking (block_a.root (), difficulty_a, block_a.account ())); + auto opt_work_l (work_generate_blocking (block_a.work_version (), block_a.root (), difficulty_a, block_a.account ())); if (opt_work_l.is_initialized ()) { block_a.block_work_set (*opt_work_l); @@ -999,38 +1037,45 @@ boost::optional nano::node::work_generate_blocking (nano::block & bloc return opt_work_l; } -void nano::node::work_generate (nano::root const & root_a, std::function)> callback_a, boost::optional const & account_a) -{ - work_generate (root_a, callback_a, network_params.network.publish_threshold, account_a); -} - -void nano::node::work_generate (nano::root const & root_a, std::function)> callback_a, uint64_t difficulty_a, boost::optional const & account_a, bool secondary_work_peers_a) +void nano::node::work_generate (nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::function)> callback_a, boost::optional const & account_a, bool secondary_work_peers_a) { auto const & peers_l (secondary_work_peers_a ? config.secondary_work_peers : config.work_peers); - if (distributed_work.make (root_a, peers_l, callback_a, difficulty_a, account_a)) + if (distributed_work.make (version_a, root_a, peers_l, difficulty_a, callback_a, account_a)) { // Error in creating the job (either stopped or work generation is not possible) callback_a (boost::none); } } -boost::optional nano::node::work_generate_blocking (nano::root const & root_a, boost::optional const & account_a) -{ - return work_generate_blocking (root_a, network_params.network.publish_threshold, account_a); -} - -boost::optional nano::node::work_generate_blocking (nano::root const & root_a, uint64_t difficulty_a, boost::optional const & account_a) +boost::optional nano::node::work_generate_blocking (nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, boost::optional const & account_a) { std::promise> promise; - // clang-format off - work_generate (root_a, [&promise](boost::optional opt_work_a) { + work_generate ( + version_a, root_a, difficulty_a, [&promise](boost::optional opt_work_a) { promise.set_value (opt_work_a); }, - difficulty_a, account_a); - // clang-format on + account_a); return promise.get_future ().get (); } +boost::optional nano::node::work_generate_blocking (nano::block & block_a) +{ + debug_assert (network_params.network.is_test_network ()); + return work_generate_blocking (block_a, default_difficulty (nano::work_version::work_1)); +} + +boost::optional nano::node::work_generate_blocking (nano::root const & root_a) +{ + debug_assert (network_params.network.is_test_network ()); + return work_generate_blocking (root_a, default_difficulty (nano::work_version::work_1)); +} + +boost::optional nano::node::work_generate_blocking (nano::root const & root_a, uint64_t difficulty_a) +{ + debug_assert (network_params.network.is_test_network ()); + return work_generate_blocking (nano::work_version::work_1, root_a, difficulty_a); +} + void nano::node::add_initial_peers () { auto transaction (store.tx_begin_read ()); @@ -1056,18 +1101,22 @@ void nano::node::add_initial_peers () void nano::node::block_confirm (std::shared_ptr block_a) { - active.start (block_a, false); - network.broadcast_confirm_req (block_a); - // Calculate votes for local representatives - if (config.enable_voting && active.active (*block_a)) + auto election = active.insert (block_a); + if (election.inserted) { - block_processor.generator.add (block_a->hash ()); + election.election->transition_active (); } } +bool nano::node::block_confirmed (nano::block_hash const & hash_a) +{ + auto transaction (store.tx_begin_read ()); + return store.block_exists (transaction, hash_a) && ledger.block_confirmed (transaction, hash_a); +} + bool nano::node::block_confirmed_or_being_confirmed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) { - return ledger.block_confirmed (transaction_a, hash_a) || pending_confirmation_height.is_processing_block (hash_a); + return confirmation_height_processor.is_processing_block (hash_a) || ledger.block_confirmed (transaction_a, hash_a); } nano::uint128_t nano::node::delta () const @@ -1136,7 +1185,7 @@ class confirmed_visitor : public nano::block_visitor if (!node.store.block_exists (transaction, hash)) { node.logger.try_log (boost::str (boost::format ("Confirmed block is missing: %1%") % hash.to_string ())); - assert (false && "Confirmed block is missing"); + debug_assert (false && "Confirmed block is missing"); } else { @@ -1176,18 +1225,18 @@ void nano::node::receive_confirmed (nano::transaction const & transaction_a, std block_a->visit (visitor); } -void nano::node::process_confirmed_data (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_hash const & hash_a, nano::block_sideband const & sideband_a, nano::account & account_a, nano::uint128_t & amount_a, bool & is_state_send_a, nano::account & pending_account_a) +void nano::node::process_confirmed_data (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_hash const & hash_a, nano::account & account_a, nano::uint128_t & amount_a, bool & is_state_send_a, nano::account & pending_account_a) { // Faster account calculation account_a = block_a->account (); if (account_a.is_zero ()) { - account_a = sideband_a.account; + account_a = block_a->sideband ().account; } // Faster amount calculation auto previous (block_a->previous ()); auto previous_balance (ledger.balance (transaction_a, previous)); - auto block_balance (store.block_balance_calculated (block_a, sideband_a)); + auto block_balance (store.block_balance_calculated (block_a)); if (hash_a != ledger.network_params.ledger.genesis_account) { amount_a = block_balance > previous_balance ? block_balance - previous_balance : previous_balance - block_balance; @@ -1210,29 +1259,30 @@ void nano::node::process_confirmed_data (nano::transaction const & transaction_a } } -void nano::node::process_confirmed (nano::election_status const & status_a, uint8_t iteration) +void nano::node::process_confirmed (nano::election_status const & status_a, uint64_t iteration_a) { - if (status_a.type == nano::election_status_type::active_confirmed_quorum) + auto block_a (status_a.winner); + auto hash (block_a->hash ()); + const auto num_iters = (config.block_processor_batch_max_time / network_params.node.process_confirmed_interval) * 4; + if (ledger.block_exists (block_a->type (), hash)) { - auto block_a (status_a.winner); - auto hash (block_a->hash ()); - auto transaction (store.tx_begin_read ()); - if (store.block_get (transaction, hash) != nullptr) - { - confirmation_height_processor.add (hash); - } - // Limit to 0.5 * 20 = 10 seconds (more than max block_processor::process_batch finish time) - else if (iteration < 20) - { - iteration++; - std::weak_ptr node_w (shared ()); - alarm.add (std::chrono::steady_clock::now () + network_params.node.process_confirmed_interval, [node_w, status_a, iteration]() { - if (auto node_l = node_w.lock ()) - { - node_l->process_confirmed (status_a, iteration); - } - }); - } + confirmation_height_processor.add (hash); + } + else if (iteration_a < num_iters) + { + iteration_a++; + std::weak_ptr node_w (shared ()); + alarm.add (std::chrono::steady_clock::now () + network_params.node.process_confirmed_interval, [node_w, status_a, iteration_a]() { + if (auto node_l = node_w.lock ()) + { + node_l->process_confirmed (status_a, iteration_a); + } + }); + } + else + { + // Do some cleanup due to this block never being processed by confirmation height processor + active.remove_election_winner_details (hash); } } @@ -1240,7 +1290,7 @@ bool nano::block_arrival::add (nano::block_hash const & hash_a) { nano::lock_guard lock (mutex); auto now (std::chrono::steady_clock::now ()); - auto inserted (arrival.insert (nano::block_arrival_info{ now, hash_a })); + auto inserted (arrival.get ().emplace_back (nano::block_arrival_info{ now, hash_a })); auto result (!inserted.second); return result; } @@ -1249,16 +1299,14 @@ bool nano::block_arrival::recent (nano::block_hash const & hash_a) { nano::lock_guard lock (mutex); auto now (std::chrono::steady_clock::now ()); - while (arrival.size () > arrival_size_min && arrival.begin ()->arrival + arrival_time_min < now) + while (arrival.size () > arrival_size_min && arrival.get ().front ().arrival + arrival_time_min < now) { - arrival.erase (arrival.begin ()); + arrival.get ().pop_front (); } - return arrival.get<1> ().find (hash_a) != arrival.get<1> ().end (); + return arrival.get ().find (hash_a) != arrival.get ().end (); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (block_arrival & block_arrival, const std::string & name) +std::unique_ptr nano::collect_container_info (block_arrival & block_arrival, const std::string & name) { size_t count = 0; { @@ -1267,94 +1315,362 @@ std::unique_ptr collect_seq_con_info (block_arrival & bl } auto sizeof_element = sizeof (decltype (block_arrival.arrival)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "arrival", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "arrival", count, sizeof_element })); return composite; } -} std::shared_ptr nano::node::shared () { return shared_from_this (); } -bool nano::node::validate_block_by_previous (nano::transaction const & transaction, std::shared_ptr block_a) +int nano::node::store_version () +{ + auto transaction (store.tx_begin_read ()); + return store.version_get (transaction); +} + +bool nano::node::init_error () const { - bool result (false); - nano::root account; - if (!block_a->previous ().is_zero ()) + return store.init_error () || wallets_store.init_error (); +} + +bool nano::node::epoch_upgrader (nano::private_key const & prv_a, nano::epoch epoch_a, uint64_t count_limit, uint64_t threads) +{ + bool error = stopped.load (); + if (!error) { - if (store.block_exists (transaction, block_a->previous ())) + auto epoch_upgrade = epoch_upgrading.lock (); + error = epoch_upgrade->valid () && epoch_upgrade->wait_for (std::chrono::seconds (0)) == std::future_status::timeout; + if (!error) { - account = ledger.account (transaction, block_a->previous ()); + *epoch_upgrade = std::async (std::launch::async, &nano::node::epoch_upgrader_impl, this, prv_a, epoch_a, count_limit, threads); + } + } + return error; +} + +void nano::node::epoch_upgrader_impl (nano::private_key const & prv_a, nano::epoch epoch_a, uint64_t count_limit, uint64_t threads) +{ + nano::thread_role::set (nano::thread_role::name::epoch_upgrader); + auto upgrader_process = [](nano::node & node_a, std::atomic & counter, std::shared_ptr epoch, uint64_t difficulty, nano::public_key const & signer_a, nano::root const & root_a, nano::account const & account_a) { + epoch->block_work_set (node_a.work_generate_blocking (nano::work_version::work_1, root_a, difficulty).value_or (0)); + bool valid_signature (!nano::validate_message (signer_a, epoch->hash (), epoch->block_signature ())); + bool valid_work (epoch->difficulty () >= difficulty); + nano::process_result result (nano::process_result::old); + if (valid_signature && valid_work) + { + result = node_a.process_local (epoch).code; + } + if (result == nano::process_result::progress) + { + ++counter; } else { - result = true; + bool fork (result == nano::process_result::fork); + node_a.logger.always_log (boost::str (boost::format ("Failed to upgrade account %1%. Valid signature: %2%. Valid work: %3%. Block processor fork: %4%") % account_a.to_account () % valid_signature % valid_work % fork)); } - } - else + }; + + uint64_t const upgrade_batch_size = 1000; + nano::block_builder builder; + auto link (ledger.epoch_link (epoch_a)); + nano::raw_key raw_key; + raw_key.data = prv_a; + auto signer (nano::pub_key (prv_a)); + debug_assert (signer == ledger.epoch_signer (link)); + + std::mutex upgrader_mutex; + nano::condition_variable upgrader_condition; + + class account_upgrade_item final { - account = block_a->root (); - } - if (!result && block_a->type () == nano::block_type::state) + public: + nano::account account{ 0 }; + uint64_t modified{ 0 }; + }; + class account_tag + { + }; + class modified_tag + { + }; + // clang-format off + boost::multi_index_container, + boost::multi_index::member, + std::greater>, + boost::multi_index::hashed_unique, + boost::multi_index::member>>> + accounts_list; + // clang-format on + + bool finished_upgrade (false); + + while (!finished_upgrade && !stopped) { - std::shared_ptr block_l (std::static_pointer_cast (block_a)); - nano::amount prev_balance (0); - if (!block_l->hashables.previous.is_zero ()) + bool finished_accounts (false); + uint64_t total_upgraded_accounts (0); + while (!finished_accounts && count_limit != 0 && !stopped) { - if (store.block_exists (transaction, block_l->hashables.previous)) { - prev_balance = ledger.balance (transaction, block_l->hashables.previous); + auto transaction (store.tx_begin_read ()); + // Collect accounts to upgrade + for (auto i (store.latest_begin (transaction)), n (store.latest_end ()); i != n && accounts_list.size () < count_limit; ++i) + { + nano::account const & account (i->first); + nano::account_info const & info (i->second); + if (info.epoch () < epoch_a) + { + release_assert (nano::epochs::is_sequential (info.epoch (), epoch_a)); + accounts_list.emplace (account_upgrade_item{ account, info.modified }); + } + } + } + + /* Upgrade accounts + Repeat until accounts with previous epoch exist in latest table */ + std::atomic upgraded_accounts (0); + uint64_t workers (0); + uint64_t attempts (0); + for (auto i (accounts_list.get ().begin ()), n (accounts_list.get ().end ()); i != n && attempts < upgrade_batch_size && attempts < count_limit && !stopped; ++i) + { + auto transaction (store.tx_begin_read ()); + nano::account_info info; + nano::account const & account (i->account); + if (!store.account_get (transaction, account, info) && info.epoch () < epoch_a) + { + ++attempts; + auto difficulty (nano::work_threshold (nano::work_version::work_1, nano::block_details (epoch_a, false, false, true))); + nano::root const & root (info.head); + std::shared_ptr epoch = builder.state () + .account (account) + .previous (info.head) + .representative (info.representative) + .balance (info.balance) + .link (link) + .sign (raw_key, signer) + .work (0) + .build (); + if (threads != 0) + { + { + nano::unique_lock lock (upgrader_mutex); + ++workers; + while (workers > threads) + { + upgrader_condition.wait (lock); + } + } + worker.push_task ([node_l = shared_from_this (), &upgrader_process, &upgrader_mutex, &upgrader_condition, &upgraded_accounts, &workers, epoch, difficulty, signer, root, account]() { + upgrader_process (*node_l, upgraded_accounts, epoch, difficulty, signer, root, account); + { + nano::lock_guard lock (upgrader_mutex); + --workers; + } + upgrader_condition.notify_all (); + }); + } + else + { + upgrader_process (*this, upgraded_accounts, epoch, difficulty, signer, root, account); + } + } + } + { + nano::unique_lock lock (upgrader_mutex); + while (workers > 0) + { + upgrader_condition.wait (lock); + } + } + total_upgraded_accounts += upgraded_accounts; + count_limit -= upgraded_accounts; + + if (!accounts_list.empty ()) + { + logger.always_log (boost::str (boost::format ("%1% accounts were upgraded to new epoch, %2% remain...") % total_upgraded_accounts % (accounts_list.size () - upgraded_accounts))); + accounts_list.clear (); } else { - result = true; + logger.always_log (boost::str (boost::format ("%1% total accounts were upgraded to new epoch") % total_upgraded_accounts)); + finished_accounts = true; } } - if (!result) + + // Pending blocks upgrade + bool finished_pending (false); + uint64_t total_upgraded_pending (0); + while (!finished_pending && count_limit != 0 && !stopped) { - if (block_l->hashables.balance == prev_balance && ledger.is_epoch_link (block_l->hashables.link)) + std::atomic upgraded_pending (0); + uint64_t workers (0); + uint64_t attempts (0); + auto transaction (store.tx_begin_read ()); + for (auto i (store.pending_begin (transaction, nano::pending_key (1, 0))), n (store.pending_end ()); i != n && attempts < upgrade_batch_size && attempts < count_limit && !stopped;) + { + bool to_next_account (false); + nano::pending_key const & key (i->first); + if (!store.account_exists (transaction, key.account)) + { + nano::pending_info const & info (i->second); + if (info.epoch < epoch_a) + { + ++attempts; + release_assert (nano::epochs::is_sequential (info.epoch, epoch_a)); + auto difficulty (nano::work_threshold (nano::work_version::work_1, nano::block_details (epoch_a, false, false, true))); + nano::root const & root (key.account); + nano::account const & account (key.account); + std::shared_ptr epoch = builder.state () + .account (key.account) + .previous (0) + .representative (0) + .balance (0) + .link (link) + .sign (raw_key, signer) + .work (0) + .build (); + if (threads != 0) + { + { + nano::unique_lock lock (upgrader_mutex); + ++workers; + while (workers > threads) + { + upgrader_condition.wait (lock); + } + } + worker.push_task ([node_l = shared_from_this (), &upgrader_process, &upgrader_mutex, &upgrader_condition, &upgraded_pending, &workers, epoch, difficulty, signer, root, account]() { + upgrader_process (*node_l, upgraded_pending, epoch, difficulty, signer, root, account); + { + nano::lock_guard lock (upgrader_mutex); + --workers; + } + upgrader_condition.notify_all (); + }); + } + else + { + upgrader_process (*this, upgraded_pending, epoch, difficulty, signer, root, account); + } + } + } + else + { + to_next_account = true; + } + if (to_next_account) + { + // Move to next account if pending account exists or was upgraded + if (key.account.number () == std::numeric_limits::max ()) + { + break; + } + else + { + i = store.pending_begin (transaction, nano::pending_key (key.account.number () + 1, 0)); + } + } + else + { + // Move to next pending item + ++i; + } + } + { + nano::unique_lock lock (upgrader_mutex); + while (workers > 0) + { + upgrader_condition.wait (lock); + } + } + + total_upgraded_pending += upgraded_pending; + count_limit -= upgraded_pending; + + // Repeat if some pending accounts were upgraded + if (upgraded_pending != 0) { - account = ledger.epoch_signer (block_l->link ()); + logger.always_log (boost::str (boost::format ("%1% unopened accounts with pending blocks were upgraded to new epoch...") % total_upgraded_pending)); + } + else + { + logger.always_log (boost::str (boost::format ("%1% total unopened accounts with pending blocks were upgraded to new epoch") % total_upgraded_pending)); + finished_pending = true; } } + + finished_upgrade = (total_upgraded_accounts == 0) && (total_upgraded_pending == 0); } - if (!result && (account.is_zero () || nano::validate_message (account, block_a->hash (), block_a->block_signature ()))) - { - result = true; - } - return result; -} -int nano::node::store_version () -{ - auto transaction (store.tx_begin_read ()); - return store.version_get (transaction); + logger.always_log ("Epoch upgrade is completed"); } -bool nano::node::init_error () const +std::pair nano::node::get_bootstrap_weights () const { - return store.init_error () || wallets_store.init_error (); + std::unordered_map weights; + const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta; + size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size; + nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size); + nano::uint128_union block_height; + uint64_t max_blocks = 0; + if (!nano::try_read (weight_stream, block_height)) + { + max_blocks = nano::narrow_cast (block_height.number ()); + while (true) + { + nano::account account; + if (nano::try_read (weight_stream, account.bytes)) + { + break; + } + nano::amount weight; + if (nano::try_read (weight_stream, weight.bytes)) + { + break; + } + weights[account] = weight.number (); + } + } + return { max_blocks, weights }; } -nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, uint16_t peering_port_a, nano::node_flags const & node_flags) : -path (path_a), +nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, nano::node_flags const & node_flags_a) : io_context (std::make_shared ()), alarm (*io_context), -work (1), -peering_port (peering_port_a) +work (1) { boost::system::error_code error_chmod; /* * @warning May throw a filesystem exception */ - boost::filesystem::create_directories (path); - nano::set_secure_perm_directory (path, error_chmod); - logging.max_size = std::numeric_limits::max (); - logging.init (path); - node = std::make_shared (*io_context, peering_port, path, alarm, logging, work, node_flags); + boost::filesystem::create_directories (path_a); + nano::set_secure_perm_directory (path_a, error_chmod); + nano::daemon_config daemon_config (path_a); + auto error = nano::read_node_config_toml (path_a, daemon_config, node_flags_a.config_overrides); + if (error) + { + std::cerr << "Error deserializing config file"; + if (!node_flags_a.config_overrides.empty ()) + { + std::cerr << " or --config option"; + } + std::cerr << "\n" + << error.get_message () << std::endl; + std::exit (1); + } + + auto & node_config = daemon_config.node; + node_config.peering_port = nano::get_available_port (); + node_config.logging.max_size = std::numeric_limits::max (); + node_config.logging.init (path_a); + + node = std::make_shared (*io_context, path_a, alarm, node_config, work, node_flags_a); node->active.stop (); } @@ -1368,12 +1684,17 @@ nano::node_flags const & nano::inactive_node_flag_defaults () static nano::node_flags node_flags; node_flags.inactive_node = true; node_flags.read_only = true; - node_flags.cache_representative_weights_from_frontiers = false; - node_flags.cache_cemented_count_from_frontiers = false; + node_flags.generate_cache.reps = false; + node_flags.generate_cache.cemented_count = false; + node_flags.generate_cache.unchecked_count = false; + node_flags.generate_cache.account_count = false; + node_flags.generate_cache.epoch_2 = false; + node_flags.disable_bootstrap_listener = true; + node_flags.disable_tcp_realtime = true; return node_flags; } -std::unique_ptr nano::make_store (nano::logger_mt & logger, boost::filesystem::path const & path, bool read_only, bool add_db_postfix, nano::rocksdb_config const & rocksdb_config, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, int lmdb_max_dbs, size_t batch_size, bool backup_before_upgrade, bool use_rocksdb_backend) +std::unique_ptr nano::make_store (nano::logger_mt & logger, boost::filesystem::path const & path, bool read_only, bool add_db_postfix, nano::rocksdb_config const & rocksdb_config, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, nano::lmdb_config const & lmdb_config_a, size_t batch_size, bool backup_before_upgrade, bool use_rocksdb_backend) { #if NANO_ROCKSDB auto make_rocksdb = [&logger, add_db_postfix, &path, &rocksdb_config, read_only]() { @@ -1404,5 +1725,5 @@ std::unique_ptr nano::make_store (nano::logger_mt & logger, b #endif } - return std::make_unique (logger, add_db_postfix ? path / "data.ldb" : path, txn_tracking_config_a, block_processor_batch_max_time_a, lmdb_max_dbs, batch_size, backup_before_upgrade); + return std::make_unique (logger, add_db_postfix ? path / "data.ldb" : path, txn_tracking_config_a, block_processor_batch_max_time_a, lmdb_config_a, batch_size, backup_before_upgrade); } diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 983394ab0d..f016eb9b13 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -1,22 +1,18 @@ #pragma once -#include #include -#include #include #include +#include #include #include #include -#include -#include -#include +#include #include #include -#include +#include #include #include -#include #include #include #include @@ -24,32 +20,36 @@ #include #include #include +#include #include +#include #include #include -#include #include #include +#include -#include #include #include #include -#include +#include #include +#include #include -#include #include -#include #include -#include +#include #include namespace nano { +namespace websocket +{ + class listener; +} class node; - +class telemetry; class work_pool; class block_arrival_info final { @@ -65,27 +65,30 @@ class block_arrival final // Return `true' to indicated an error if the block has already been inserted bool add (nano::block_hash const &); bool recent (nano::block_hash const &); - boost::multi_index_container< - nano::block_arrival_info, - boost::multi_index::indexed_by< - boost::multi_index::ordered_non_unique>, - boost::multi_index::hashed_unique>>> + // clang-format off + class tag_sequence {}; + class tag_hash {}; + boost::multi_index_container>, + boost::multi_index::hashed_unique, + boost::multi_index::member>>> arrival; + // clang-format on std::mutex mutex; static size_t constexpr arrival_size_min = 8 * 1024; static std::chrono::seconds constexpr arrival_time_min = std::chrono::seconds (300); }; -std::unique_ptr collect_seq_con_info (block_arrival & block_arrival, const std::string & name); +std::unique_ptr collect_container_info (block_arrival & block_arrival, const std::string & name); -std::unique_ptr collect_seq_con_info (rep_crawler & rep_crawler, const std::string & name); -std::unique_ptr collect_seq_con_info (block_processor & block_processor, const std::string & name); +std::unique_ptr collect_container_info (rep_crawler & rep_crawler, const std::string & name); class node final : public std::enable_shared_from_this { public: - node (boost::asio::io_context &, uint16_t, boost::filesystem::path const &, nano::alarm &, nano::logging const &, nano::work_pool &, nano::node_flags = nano::node_flags ()); - node (boost::asio::io_context &, boost::filesystem::path const &, nano::alarm &, nano::node_config const &, nano::work_pool &, nano::node_flags = nano::node_flags ()); + node (boost::asio::io_context &, uint16_t, boost::filesystem::path const &, nano::alarm &, nano::logging const &, nano::work_pool &, nano::node_flags = nano::node_flags (), unsigned seq = 0); + node (boost::asio::io_context &, boost::filesystem::path const &, nano::alarm &, nano::node_config const &, nano::work_pool &, nano::node_flags = nano::node_flags (), unsigned seq = 0); ~node (); template void background (T action_a) @@ -99,10 +102,10 @@ class node final : public std::enable_shared_from_this std::shared_ptr shared (); int store_version (); void receive_confirmed (nano::transaction const &, std::shared_ptr, nano::block_hash const &); - void process_confirmed_data (nano::transaction const &, std::shared_ptr, nano::block_hash const &, nano::block_sideband const &, nano::account &, nano::uint128_t &, bool &, nano::account &); - void process_confirmed (nano::election_status const &, uint8_t = 0); + void process_confirmed_data (nano::transaction const &, std::shared_ptr, nano::block_hash const &, nano::account &, nano::uint128_t &, bool &, nano::account &); + void process_confirmed (nano::election_status const &, uint64_t = 0); void process_active (std::shared_ptr); - nano::process_return process (nano::block const &); + nano::process_return process (nano::block &); nano::process_return process_local (std::shared_ptr, bool const = false); void keepalive_preconfigured (std::vector const &); nano::block_hash latest (nano::account const &); @@ -123,26 +126,28 @@ class node final : public std::enable_shared_from_this void bootstrap_wallet (); void unchecked_cleanup (); int price (nano::uint128_t const &, int); + // The default difficulty updates to base only when the first epoch_2 block is processed + uint64_t default_difficulty (nano::work_version const) const; + uint64_t max_work_generate_difficulty (nano::work_version const) const; bool local_work_generation_enabled () const; bool work_generation_enabled () const; bool work_generation_enabled (std::vector> const &) const; boost::optional work_generate_blocking (nano::block &, uint64_t); - boost::optional work_generate_blocking (nano::block &); - boost::optional work_generate_blocking (nano::root const &, uint64_t, boost::optional const & = boost::none); - boost::optional work_generate_blocking (nano::root const &, boost::optional const & = boost::none); - void work_generate (nano::root const &, std::function)>, uint64_t, boost::optional const & = boost::none, bool const = false); - void work_generate (nano::root const &, std::function)>, boost::optional const & = boost::none); + boost::optional work_generate_blocking (nano::work_version const, nano::root const &, uint64_t, boost::optional const & = boost::none); + void work_generate (nano::work_version const, nano::root const &, uint64_t, std::function)>, boost::optional const & = boost::none, bool const = false); void add_initial_peers (); void block_confirm (std::shared_ptr); + bool block_confirmed (nano::block_hash const &); bool block_confirmed_or_being_confirmed (nano::transaction const &, nano::block_hash const &); void process_fork (nano::transaction const &, std::shared_ptr); - bool validate_block_by_previous (nano::transaction const &, std::shared_ptr); void do_rpc_callback (boost::asio::ip::tcp::resolver::iterator i_a, std::string const &, uint16_t, std::shared_ptr, std::shared_ptr, std::shared_ptr); nano::uint128_t delta () const; void ongoing_online_weight_calculation (); void ongoing_online_weight_calculation_queue (); bool online () const; bool init_error () const; + bool epoch_upgrader (nano::private_key const &, nano::epoch, uint64_t, uint64_t); + std::pair get_bootstrap_weights () const; nano::worker worker; nano::write_database_queue write_database_queue; boost::asio::io_context & io_ctx; @@ -164,6 +169,7 @@ class node final : public std::enable_shared_from_this nano::ledger ledger; nano::signature_checker checker; nano::network network; + std::shared_ptr telemetry; nano::bootstrap_initiator bootstrap_initiator; nano::bootstrap_listener bootstrap; boost::filesystem::path application_path; @@ -173,16 +179,16 @@ class node final : public std::enable_shared_from_this nano::rep_crawler rep_crawler; unsigned warmed_up; nano::block_processor block_processor; - boost::thread block_processor_thread; + std::thread block_processor_thread; nano::block_arrival block_arrival; nano::online_reps online_reps; nano::votes_cache votes_cache; nano::keypair node_id; nano::block_uniquer block_uniquer; nano::vote_uniquer vote_uniquer; - nano::pending_confirmation_height pending_confirmation_height; // Used by both active and confirmation height processor - nano::active_transactions active; nano::confirmation_height_processor confirmation_height_processor; + nano::active_transactions active; + nano::request_aggregator aggregator; nano::payment_observer_processor payment_observer_processor; nano::wallets wallets; const std::chrono::steady_clock::time_point startup_time; @@ -191,23 +197,34 @@ class node final : public std::enable_shared_from_this std::atomic stopped{ false }; static double constexpr price_max = 16.0; static double constexpr free_cutoff = 1024.0; + // For tests only + unsigned node_seq; + // For tests only + boost::optional work_generate_blocking (nano::block &); + // For tests only + boost::optional work_generate_blocking (nano::root const &, uint64_t); + // For tests only + boost::optional work_generate_blocking (nano::root const &); + +private: + void long_inactivity_cleanup (); + void epoch_upgrader_impl (nano::private_key const &, nano::epoch, uint64_t, uint64_t); + nano::locked> epoch_upgrading; }; -std::unique_ptr collect_seq_con_info (node & node, const std::string & name); +std::unique_ptr collect_container_info (node & node, const std::string & name); nano::node_flags const & inactive_node_flag_defaults (); class inactive_node final { public: - inactive_node (boost::filesystem::path const & path = nano::working_path (), uint16_t = 24000, nano::node_flags const & = nano::inactive_node_flag_defaults ()); + inactive_node (boost::filesystem::path const & path_a, nano::node_flags const & node_flags_a = nano::inactive_node_flag_defaults ()); ~inactive_node (); - boost::filesystem::path path; std::shared_ptr io_context; nano::alarm alarm; - nano::logging logging; nano::work_pool work; - uint16_t peering_port; std::shared_ptr node; }; +std::unique_ptr default_inactive_node (boost::filesystem::path const &, boost::program_options::variables_map const &); } diff --git a/nano/node/node_observers.cpp b/nano/node/node_observers.cpp index e4d074b91d..09fdf582ac 100644 --- a/nano/node/node_observers.cpp +++ b/nano/node/node_observers.cpp @@ -1,15 +1,15 @@ #include -std::unique_ptr nano::collect_seq_con_info (nano::node_observers & node_observers, const std::string & name) +std::unique_ptr nano::collect_container_info (nano::node_observers & node_observers, const std::string & name) { - auto composite = std::make_unique (name); - composite->add_component (collect_seq_con_info (node_observers.blocks, "blocks")); - composite->add_component (collect_seq_con_info (node_observers.wallet, "wallet")); - composite->add_component (collect_seq_con_info (node_observers.vote, "vote")); - composite->add_component (collect_seq_con_info (node_observers.active_stopped, "active_stopped")); - composite->add_component (collect_seq_con_info (node_observers.account_balance, "account_balance")); - composite->add_component (collect_seq_con_info (node_observers.endpoint, "endpoint")); - composite->add_component (collect_seq_con_info (node_observers.disconnect, "disconnect")); - composite->add_component (collect_seq_con_info (node_observers.work_cancel, "work_cancel")); + auto composite = std::make_unique (name); + composite->add_component (collect_container_info (node_observers.blocks, "blocks")); + composite->add_component (collect_container_info (node_observers.wallet, "wallet")); + composite->add_component (collect_container_info (node_observers.vote, "vote")); + composite->add_component (collect_container_info (node_observers.active_stopped, "active_stopped")); + composite->add_component (collect_container_info (node_observers.account_balance, "account_balance")); + composite->add_component (collect_container_info (node_observers.endpoint, "endpoint")); + composite->add_component (collect_container_info (node_observers.disconnect, "disconnect")); + composite->add_component (collect_container_info (node_observers.work_cancel, "work_cancel")); return composite; } diff --git a/nano/node/node_observers.hpp b/nano/node/node_observers.hpp index 5666d8cba9..50f16d0545 100644 --- a/nano/node/node_observers.hpp +++ b/nano/node/node_observers.hpp @@ -1,28 +1,28 @@ #pragma once -#include #include #include #include #include -#include namespace nano { +class telemetry; class node_observers final { public: using blocks_t = nano::observer_set; blocks_t blocks; nano::observer_set wallet; - nano::observer_set, std::shared_ptr> vote; + nano::observer_set, std::shared_ptr, nano::vote_code> vote; nano::observer_set active_stopped; nano::observer_set account_balance; nano::observer_set> endpoint; nano::observer_set<> disconnect; nano::observer_set difficulty; nano::observer_set work_cancel; + nano::observer_set telemetry; }; -std::unique_ptr collect_seq_con_info (node_observers & node_observers, const std::string & name); +std::unique_ptr collect_container_info (node_observers & node_observers, const std::string & name); } diff --git a/nano/node/node_pow_server_config.cpp b/nano/node/node_pow_server_config.cpp index b98e3aa966..1258903645 100644 --- a/nano/node/node_pow_server_config.cpp +++ b/nano/node/node_pow_server_config.cpp @@ -1,12 +1,10 @@ -#include -#include #include #include nano::error nano::node_pow_server_config::serialize_toml (nano::tomlconfig & toml) const { - toml.put ("enable", enable, "Enable or disable starting Nano PoW Server as a child process.\ntype:bool"); - toml.put ("nano_pow_server_path", pow_server_path, "Path to the nano_pow_server executable.\ntype:string,path"); + toml.put ("enable", enable, "Value is currently not in use. Enable or disable starting Nano PoW Server as a child process.\ntype:bool"); + toml.put ("nano_pow_server_path", pow_server_path, "Value is currently not in use. Path to the nano_pow_server executable.\ntype:string,path"); return toml.get_error (); } diff --git a/nano/node/node_rpc_config.cpp b/nano/node/node_rpc_config.cpp index 75e7cbb572..cadea39bbc 100644 --- a/nano/node/node_rpc_config.cpp +++ b/nano/node/node_rpc_config.cpp @@ -1,7 +1,5 @@ -#include #include #include -#include #include #include diff --git a/nano/node/node_rpc_config.hpp b/nano/node/node_rpc_config.hpp index f175e1f0f6..f2f24961ab 100644 --- a/nano/node/node_rpc_config.hpp +++ b/nano/node/node_rpc_config.hpp @@ -2,10 +2,16 @@ #include -#include - #include +namespace boost +{ +namespace filesystem +{ + class path; +} +} + namespace nano { class tomlconfig; diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index cce5a840d4..f0ce4b411e 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -1,13 +1,14 @@ #include #include #include -#include #include #include #include -// NOTE: to reduce compile times, this include can be replaced by more narrow includes -// once nano::network is factored out of node.{c|h}pp -#include +#include + +#include + +#include namespace { @@ -25,7 +26,8 @@ node_config (0, nano::logging ()) nano::node_config::node_config (uint16_t peering_port_a, nano::logging const & logging_a) : peering_port (peering_port_a), -logging (logging_a) +logging (logging_a), +external_address (boost::asio::ip::address_v6{}.to_string ()) { // The default constructor passes 0 to indicate we should use the default port, // which is determined at node startup based on active network. @@ -33,7 +35,6 @@ logging (logging_a) { peering_port = network_params.network.default_node_port; } - max_work_generate_difficulty = nano::difficulty::from_multiplier (max_work_generate_multiplier, network_params.network.publish_threshold); switch (network_params.network.network ()) { case nano::nano_networks::nano_test_network: @@ -57,7 +58,7 @@ logging (logging_a) preconfigured_representatives.emplace_back ("3FE80B4BC842E82C1C18ABFEEC47EA989E63953BC82AC411F304D13833D52A56"); break; default: - assert (false); + debug_assert (false); break; } } @@ -73,11 +74,12 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const toml.put ("io_threads", io_threads, "Number of threads dedicated to I/O opeations. Defaults to the number of CPU threads, and at least 4.\ntype:uint64"); toml.put ("network_threads", network_threads, "Number of threads dedicated to processing network messages. Defaults to the number of CPU threads, and at least 4.\ntype:uint64"); toml.put ("work_threads", work_threads, "Number of threads dedicated to CPU generated work. Defaults to all available CPU threads.\ntype:uint64"); - toml.put ("signature_checker_threads", signature_checker_threads, "Number of additional threads dedicated to signature verification. Defaults to the number of CPU threads minus 1.\ntype:uint64"); + toml.put ("signature_checker_threads", signature_checker_threads, "Number of additional threads dedicated to signature verification. Defaults to number of CPU threads / 2.\ntype:uint64"); toml.put ("enable_voting", enable_voting, "Enable or disable voting. Enabling this option requires additional system resources, namely increased CPU, bandwidth and disk usage.\ntype:bool"); toml.put ("bootstrap_connections", bootstrap_connections, "Number of outbound bootstrap connections. Must be a power of 2. Defaults to 4.\nWarning: a larger amount of connections may use substantially more system memory.\ntype:uint64"); toml.put ("bootstrap_connections_max", bootstrap_connections_max, "Maximum number of inbound bootstrap connections. Defaults to 64.\nWarning: a larger amount of connections may use additional system memory.\ntype:uint64"); - toml.put ("lmdb_max_dbs", lmdb_max_dbs, "Maximum open lmdb databases. Increase default if more than 100 wallets is required.\nNote: external management is recommended when a large amounts of wallets are required (see https://docs.nano.org/integration-guides/key-management/).\ntype:uint64"); + toml.put ("bootstrap_initiator_threads", bootstrap_initiator_threads, "Number of threads dedicated to concurrent bootstrap attempts. Defaults to 2 (if the number of CPU threads is more than 1), otherwise 1.\nWarning: a larger amount of attempts may use additional system memory and disk IO.\ntype:uint64"); + toml.put ("lmdb_max_dbs", deprecated_lmdb_max_dbs, "DEPRECATED: use node.lmdb.max_databases instead.\nMaximum open lmdb databases. Increase default if more than 100 wallets is required.\nNote: external management is recommended when a large amounts of wallets are required (see https://docs.nano.org/integration-guides/key-management/).\ntype:uint64"); toml.put ("block_processor_batch_max_time", block_processor_batch_max_time.count (), "The maximum time the block processor can continously process blocks for.\ntype:milliseconds"); toml.put ("allow_local_peers", allow_local_peers, "Enable or disable local host peering.\ntype:bool"); toml.put ("vote_minimum", vote_minimum.to_string_dec (), "Local representatives do not vote if the delegated weight is under this threshold. Saves on system resources.\ntype:string,amount,raw"); @@ -86,18 +88,20 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const toml.put ("unchecked_cutoff_time", unchecked_cutoff_time.count (), "Number of seconds before deleting an unchecked entry.\nWarning: lower values (e.g., 3600 seconds, or 1 hour) may result in unsuccessful bootstraps, especially a bootstrap from scratch.\ntype:seconds"); toml.put ("tcp_io_timeout", tcp_io_timeout.count (), "Timeout for TCP connect-, read- and write operations.\nWarning: a low value (e.g., below 5 seconds) may result in TCP connections failing.\ntype:seconds"); toml.put ("pow_sleep_interval", pow_sleep_interval.count (), "Time to sleep between batch work generation attempts. Reduces max CPU usage at the expense of a longer generation time.\ntype:nanoseconds"); - toml.put ("external_address", external_address.to_string (), "The external address of this node (NAT). If not set, the node will request this information via UPnP.\ntype:string,ip"); + toml.put ("external_address", external_address, "The external address of this node (NAT). If not set, the node will request this information via UPnP.\ntype:string,ip"); toml.put ("external_port", external_port, "The external port number of this node (NAT). Only used if external_address is set.\ntype:uint16"); toml.put ("tcp_incoming_connections_max", tcp_incoming_connections_max, "Maximum number of incoming TCP connections.\ntype:uint64"); toml.put ("use_memory_pools", use_memory_pools, "If true, allocate memory from memory pools. Enabling this may improve performance. Memory is never released to the OS.\ntype:bool"); toml.put ("confirmation_history_size", confirmation_history_size, "Maximum confirmation history size. If tracking the rate of block confirmations, the websocket feature is recommended instead.\ntype:uint64"); toml.put ("active_elections_size", active_elections_size, "Number of active elections. Elections beyond this limit have limited survival time.\nWarning: modifying this value may result in a lower confirmation rate.\ntype:uint64,[250..]"); - toml.put ("bandwidth_limit", bandwidth_limit, "Outbound traffic limit in bytes/sec after which messages will be dropped.\nNote: changing to unlimited bandwidth is not recommended for limited connections.\ntype:uint64"); + toml.put ("bandwidth_limit", bandwidth_limit, "Outbound traffic limit in bytes/sec after which messages will be dropped.\nNote: changing to unlimited bandwidth (0) is not recommended for limited connections.\ntype:uint64"); + toml.put ("bandwidth_limit_burst_ratio", bandwidth_limit_burst_ratio, "Burst ratio for outbound traffic shaping.\ntype:double"); toml.put ("conf_height_processor_batch_min_time", conf_height_processor_batch_min_time.count (), "Minimum write batching time when there are blocks pending confirmation height.\ntype:milliseconds"); toml.put ("backup_before_upgrade", backup_before_upgrade, "Backup the ledger database before performing upgrades.\nWarning: uses more disk storage and increases startup time when upgrading.\ntype:bool"); toml.put ("work_watcher_period", work_watcher_period.count (), "Time between checks for confirmation and re-generating higher difficulty work if unconfirmed, for blocks in the work watcher.\ntype:seconds"); toml.put ("max_work_generate_multiplier", max_work_generate_multiplier, "Maximum allowed difficulty multiplier for work generation.\ntype:double,[1..]"); toml.put ("frontiers_confirmation", serialize_frontiers_confirmation (frontiers_confirmation), "Mode controlling frontier confirmation rate.\ntype:string,{auto,always,disabled}"); + toml.put ("max_queued_requests", max_queued_requests, "Limit for number of queued confirmation requests for one channel, after which new requests are dropped until the queue drops below this value.\ntype:uint32"); auto work_peers_l (toml.create_array ("work_peers", "A list of \"address:port\" entries to identify work peers.")); for (auto i (work_peers.begin ()), n (work_peers.end ()); i != n; ++i) @@ -105,7 +109,7 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const work_peers_l->push_back (boost::str (boost::format ("%1%:%2%") % i->first % i->second)); } - auto preconfigured_peers_l (toml.create_array ("preconfigured_peers", "A list of \"address:port\" entries to identify preconfigured peers.")); + auto preconfigured_peers_l (toml.create_array ("preconfigured_peers", "A list of \"address\" (hostname or ip address) entries to identify preconfigured peers.")); for (auto i (preconfigured_peers.begin ()), n (preconfigured_peers.end ()); i != n; ++i) { preconfigured_peers_l->push_back (*i); @@ -156,6 +160,10 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const rocksdb_config.serialize_toml (rocksdb_l); toml.put_child ("rocksdb", rocksdb_l); + nano::tomlconfig lmdb_l; + lmdb_config.serialize_toml (lmdb_l); + toml.put_child ("lmdb", lmdb_l); + return toml.get_error (); } @@ -298,11 +306,37 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) toml.get ("network_threads", network_threads); toml.get ("bootstrap_connections", bootstrap_connections); toml.get ("bootstrap_connections_max", bootstrap_connections_max); - toml.get ("lmdb_max_dbs", lmdb_max_dbs); + toml.get ("bootstrap_initiator_threads", bootstrap_initiator_threads); toml.get ("enable_voting", enable_voting); toml.get ("allow_local_peers", allow_local_peers); toml.get (signature_checker_threads_key, signature_checker_threads); - toml.get ("external_address", external_address); + + auto lmdb_max_dbs_default = deprecated_lmdb_max_dbs; + toml.get ("lmdb_max_dbs", deprecated_lmdb_max_dbs); + bool is_deprecated_lmdb_dbs_used = lmdb_max_dbs_default != deprecated_lmdb_max_dbs; + + // Note: using the deprecated setting will result in a fail-fast config error in the future + if (!network_params.network.is_test_network () && is_deprecated_lmdb_dbs_used) + { + std::cerr << "WARNING: The node.lmdb_max_dbs setting is deprecated and will be removed in a future version." << std::endl; + std::cerr << "Please use the node.lmdb.max_databases setting instead." << std::endl; + } + + if (toml.has_key ("lmdb")) + { + auto lmdb_config_l (toml.get_required_child ("lmdb")); + lmdb_config.deserialize_toml (lmdb_config_l, is_deprecated_lmdb_dbs_used); + + // Note that the lmdb config fails is both the deprecated and new setting are changed. + if (is_deprecated_lmdb_dbs_used) + { + lmdb_config.max_databases = deprecated_lmdb_max_dbs; + } + } + + boost::asio::ip::address_v6 external_address_l; + toml.get ("external_address", external_address_l); + external_address = external_address_l.to_string (); toml.get ("external_port", external_port); toml.get ("tcp_incoming_connections_max", tcp_incoming_connections_max); @@ -313,6 +347,7 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) toml.get ("confirmation_history_size", confirmation_history_size); toml.get ("active_elections_size", active_elections_size); toml.get ("bandwidth_limit", bandwidth_limit); + toml.get ("bandwidth_limit_burst_ratio", bandwidth_limit_burst_ratio); toml.get ("backup_before_upgrade", backup_before_upgrade); auto work_watcher_period_l = work_watcher_period.count (); @@ -325,7 +360,8 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) nano::network_constants network; toml.get ("max_work_generate_multiplier", max_work_generate_multiplier); - max_work_generate_difficulty = nano::difficulty::from_multiplier (max_work_generate_multiplier, network.publish_threshold); + + toml.get ("max_queued_requests", max_queued_requests); if (toml.has_key ("frontiers_confirmation")) { @@ -346,6 +382,7 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) } // Validate ranges + nano::network_params network_params; if (online_weight_quorum > 100) { toml.get_error ().set ("online_weight_quorum must be less than 100"); @@ -364,7 +401,7 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) } if (bandwidth_limit > std::numeric_limits::max ()) { - toml.get_error ().set ("bandwidth_limit unbounded = 0, default = 5242880, max = 18446744073709551615"); + toml.get_error ().set ("bandwidth_limit unbounded = 0, default = 10485760, max = 18446744073709551615"); } if (vote_generator_threshold < 1 || vote_generator_threshold > 11) { @@ -382,6 +419,10 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) { toml.get_error ().set ("frontiers_confirmation value is invalid (available: always, auto, disabled)"); } + if (block_processor_batch_max_time < network_params.node.process_confirmed_interval) + { + toml.get_error ().set ((boost::format ("block_processor_batch_max_time value must be equal or larger than %1%ms") % network_params.node.process_confirmed_interval.count ()).str ()); + } } catch (std::runtime_error const & ex) { @@ -435,7 +476,7 @@ nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const json.put ("callback_address", callback_address); json.put ("callback_port", callback_port); json.put ("callback_target", callback_target); - json.put ("lmdb_max_dbs", lmdb_max_dbs); + json.put ("lmdb_max_dbs", deprecated_lmdb_max_dbs); json.put ("block_processor_batch_max_time", block_processor_batch_max_time.count ()); json.put ("allow_local_peers", allow_local_peers); json.put ("vote_minimum", vote_minimum.to_string_dec ()); @@ -444,7 +485,7 @@ nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const json.put ("unchecked_cutoff_time", unchecked_cutoff_time.count ()); json.put ("tcp_io_timeout", tcp_io_timeout.count ()); json.put ("pow_sleep_interval", pow_sleep_interval.count ()); - json.put ("external_address", external_address.to_string ()); + json.put ("external_address", external_address); json.put ("external_port", external_port); json.put ("tcp_incoming_connections_max", tcp_incoming_connections_max); json.put ("use_memory_pools", use_memory_pools); @@ -574,7 +615,7 @@ bool nano::node_config::upgrade_json (unsigned version_a, nano::jsonconfig & jso json.put_child ("diagnostics", diagnostics_l); json.put ("tcp_io_timeout", tcp_io_timeout.count ()); json.put (pow_sleep_interval_key, pow_sleep_interval.count ()); - json.put ("external_address", external_address.to_string ()); + json.put ("external_address", external_address); json.put ("external_port", external_port); json.put ("tcp_incoming_connections_max", tcp_incoming_connections_max); json.put ("vote_generator_delay", vote_generator_delay.count ()); @@ -636,7 +677,7 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco if (!result) { auto address (entry.substr (0, port_position)); - this->work_peers.push_back (std::make_pair (address, port)); + this->work_peers.emplace_back (address, port); } } }); @@ -729,11 +770,13 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco json.get ("callback_address", callback_address); json.get ("callback_port", callback_port); json.get ("callback_target", callback_target); - json.get ("lmdb_max_dbs", lmdb_max_dbs); + json.get ("lmdb_max_dbs", deprecated_lmdb_max_dbs); json.get ("enable_voting", enable_voting); json.get ("allow_local_peers", allow_local_peers); json.get (signature_checker_threads_key, signature_checker_threads); - json.get ("external_address", external_address); + boost::asio::ip::address_v6 external_address_l; + json.get ("external_address", external_address_l); + external_address = external_address_l.to_string (); json.get ("external_port", external_port); json.get ("tcp_incoming_connections_max", tcp_incoming_connections_max); @@ -774,7 +817,7 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco } if (bandwidth_limit > std::numeric_limits::max ()) { - json.get_error ().set ("bandwidth_limit unbounded = 0, default = 5242880, max = 18446744073709551615"); + json.get_error ().set ("bandwidth_limit unbounded = 0, default = 10485760, max = 18446744073709551615"); } if (vote_generator_threshold < 1 || vote_generator_threshold > 11) { @@ -846,7 +889,7 @@ void nano::node_config::deserialize_address (std::string const & entry_a, std::v nano::account nano::node_config::random_representative () const { - assert (!preconfigured_representatives.empty ()); + debug_assert (!preconfigured_representatives.empty ()); size_t index (nano::random_pool::generate_word32 (0, static_cast (preconfigured_representatives.size () - 1))); auto result (preconfigured_representatives[index]); return result; diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index 139ae2b5db..e81fd2b8cb 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -4,10 +4,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include #include @@ -56,44 +57,50 @@ class node_config nano::amount online_weight_minimum{ 60000 * nano::Gxrb_ratio }; unsigned online_weight_quorum{ 50 }; unsigned password_fanout{ 1024 }; - unsigned io_threads{ std::max (4, boost::thread::hardware_concurrency ()) }; - unsigned network_threads{ std::max (4, boost::thread::hardware_concurrency ()) }; - unsigned work_threads{ std::max (4, boost::thread::hardware_concurrency ()) }; - unsigned signature_checker_threads{ (boost::thread::hardware_concurrency () != 0) ? boost::thread::hardware_concurrency () - 1 : 0 }; /* The calling thread does checks as well so remove it from the number of threads used */ + unsigned io_threads{ std::max (4, std::thread::hardware_concurrency ()) }; + unsigned network_threads{ std::max (4, std::thread::hardware_concurrency ()) }; + unsigned work_threads{ std::max (4, std::thread::hardware_concurrency ()) }; + /* Use half available threads on the system for signature checking. The calling thread does checks as well, so these are extra worker threads */ + unsigned signature_checker_threads{ std::thread::hardware_concurrency () / 2 }; bool enable_voting{ false }; unsigned bootstrap_connections{ 4 }; unsigned bootstrap_connections_max{ 64 }; + unsigned bootstrap_initiator_threads{ network_params.network.is_test_network () ? 1u : std::min (2, std::max (1, std::thread::hardware_concurrency ())) }; nano::websocket::config websocket_config; nano::diagnostics_config diagnostics_config; size_t confirmation_history_size{ 2048 }; std::string callback_address; uint16_t callback_port{ 0 }; std::string callback_target; - int lmdb_max_dbs{ 128 }; + int deprecated_lmdb_max_dbs{ 128 }; bool allow_local_peers{ !network_params.network.is_live_network () }; // disable by default for live network nano::stat_config stat_config; nano::ipc::ipc_config ipc_config; - boost::asio::ip::address_v6 external_address{ boost::asio::ip::address_v6{} }; + std::string external_address; uint16_t external_port{ 0 }; - std::chrono::milliseconds block_processor_batch_max_time{ std::chrono::milliseconds (5000) }; + std::chrono::milliseconds block_processor_batch_max_time{ network_params.network.is_test_network () ? std::chrono::milliseconds (500) : std::chrono::milliseconds (5000) }; std::chrono::seconds unchecked_cutoff_time{ std::chrono::seconds (4 * 60 * 60) }; // 4 hours /** Timeout for initiated async operations */ std::chrono::seconds tcp_io_timeout{ (network_params.network.is_test_network () && !is_sanitizer_build) ? std::chrono::seconds (5) : std::chrono::seconds (15) }; std::chrono::nanoseconds pow_sleep_interval{ 0 }; - size_t active_elections_size{ 10000 }; + size_t active_elections_size{ 50000 }; /** Default maximum incoming TCP connections, including realtime network & bootstrap */ unsigned tcp_incoming_connections_max{ 1024 }; bool use_memory_pools{ true }; static std::chrono::seconds constexpr keepalive_period = std::chrono::seconds (60); static std::chrono::seconds constexpr keepalive_cutoff = keepalive_period * 5; static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5); - size_t bandwidth_limit{ 5 * 1024 * 1024 }; // 5MB/s + /** Default outbound traffic shaping is 10MB/s */ + size_t bandwidth_limit{ 10 * 1024 * 1024 }; + /** By default, allow bursts of 15MB/s (not sustainable) */ + double bandwidth_limit_burst_ratio{ 3. }; std::chrono::milliseconds conf_height_processor_batch_min_time{ 50 }; bool backup_before_upgrade{ false }; std::chrono::seconds work_watcher_period{ std::chrono::seconds (5) }; double max_work_generate_multiplier{ 64. }; - uint64_t max_work_generate_difficulty{ nano::network_constants::publish_full_threshold }; + uint32_t max_queued_requests{ 512 }; nano::rocksdb_config rocksdb_config; + nano::lmdb_config lmdb_config; nano::frontiers_confirmation_mode frontiers_confirmation{ nano::frontiers_confirmation_mode::automatic }; std::string serialize_frontiers_confirmation (nano::frontiers_confirmation_mode) const; nano::frontiers_confirmation_mode deserialize_frontiers_confirmation (std::string const &); @@ -118,20 +125,28 @@ class node_flags final bool disable_bootstrap_bulk_pull_server{ false }; bool disable_bootstrap_bulk_push_client{ false }; bool disable_rep_crawler{ false }; + bool disable_request_loop{ false }; bool disable_tcp_realtime{ false }; - bool disable_udp{ false }; + bool disable_udp{ true }; bool disable_unchecked_cleanup{ false }; bool disable_unchecked_drop{ true }; + bool disable_providing_telemetry_metrics{ false }; + bool disable_ongoing_telemetry_requests{ false }; + bool disable_initial_telemetry_requests{ false }; + bool disable_block_processor_unchecked_deletion{ false }; + bool disable_block_processor_republishing{ false }; + bool allow_bootstrap_peers_duplicates{ false }; + bool disable_max_peers_per_ip{ false }; // For testing only bool fast_bootstrap{ false }; bool read_only{ false }; - /** Whether to read all frontiers and construct the representative weights */ - bool cache_representative_weights_from_frontiers{ true }; - /** Whether to read all frontiers and construct the total cemented count */ - bool cache_cemented_count_from_frontiers{ true }; + nano::confirmation_height_mode confirmation_height_processor_mode{ nano::confirmation_height_mode::automatic }; + nano::generate_cache generate_cache; bool inactive_node{ false }; size_t sideband_batch_size{ 512 }; size_t block_processor_batch_size{ 0 }; size_t block_processor_full_size{ 65536 }; size_t block_processor_verification_size{ 0 }; + size_t inactive_votes_cache_size{ 16 * 1024 }; + size_t vote_processor_capacity{ 144 * 1024 }; }; } diff --git a/nano/node/online_reps.cpp b/nano/node/online_reps.cpp index cc03cdeabe..92a0337092 100644 --- a/nano/node/online_reps.cpp +++ b/nano/node/online_reps.cpp @@ -1,22 +1,23 @@ -#include #include +#include +#include +#include -#include - -nano::online_reps::online_reps (nano::node & node_a, nano::uint128_t minimum_a) : -node (node_a), +nano::online_reps::online_reps (nano::ledger & ledger_a, nano::network_params & network_params_a, nano::uint128_t minimum_a) : +ledger (ledger_a), +network_params (network_params_a), minimum (minimum_a) { - if (!node.ledger.store.init_error ()) + if (!ledger.store.init_error ()) { - auto transaction (node.ledger.store.tx_begin_read ()); + auto transaction (ledger.store.tx_begin_read ()); online = trend (transaction); } } void nano::online_reps::observe (nano::account const & rep_a) { - if (node.ledger.weight (rep_a) > 0) + if (ledger.weight (rep_a) > 0) { nano::lock_guard lock (mutex); reps.insert (rep_a); @@ -25,13 +26,13 @@ void nano::online_reps::observe (nano::account const & rep_a) void nano::online_reps::sample () { - auto transaction (node.ledger.store.tx_begin_write ()); + auto transaction (ledger.store.tx_begin_write ({ tables::online_weight })); // Discard oldest entries - while (node.ledger.store.online_weight_count (transaction) >= node.network_params.node.max_weight_samples) + while (ledger.store.online_weight_count (transaction) >= network_params.node.max_weight_samples) { - auto oldest (node.ledger.store.online_weight_begin (transaction)); - assert (oldest != node.ledger.store.online_weight_end ()); - node.ledger.store.online_weight_del (transaction, oldest->first); + auto oldest (ledger.store.online_weight_begin (transaction)); + debug_assert (oldest != ledger.store.online_weight_end ()); + ledger.store.online_weight_del (transaction, oldest->first); } // Calculate current active rep weight nano::uint128_t current; @@ -42,9 +43,9 @@ void nano::online_reps::sample () } for (auto & i : reps_copy) { - current += node.ledger.weight (i); + current += ledger.weight (i); } - node.ledger.store.online_weight_put (transaction, std::chrono::system_clock::now ().time_since_epoch ().count (), current); + ledger.store.online_weight_put (transaction, std::chrono::system_clock::now ().time_since_epoch ().count (), current); auto trend_l (trend (transaction)); nano::lock_guard lock (mutex); online = trend_l; @@ -53,9 +54,9 @@ void nano::online_reps::sample () nano::uint128_t nano::online_reps::trend (nano::transaction & transaction_a) { std::vector items; - items.reserve (node.network_params.node.max_weight_samples + 1); + items.reserve (network_params.node.max_weight_samples + 1); items.push_back (minimum); - for (auto i (node.ledger.store.online_weight_begin (transaction_a)), n (node.ledger.store.online_weight_end ()); i != n; ++i) + for (auto i (ledger.store.online_weight_begin (transaction_a)), n (ledger.store.online_weight_end ()); i != n; ++i) { items.push_back (i->second.number ()); } @@ -76,26 +77,20 @@ std::vector nano::online_reps::list () { std::vector result; nano::lock_guard lock (mutex); - for (auto & i : reps) - { - result.push_back (i); - } + result.insert (result.end (), reps.begin (), reps.end ()); return result; } -namespace nano +std::unique_ptr nano::collect_container_info (online_reps & online_reps, const std::string & name) { -std::unique_ptr collect_seq_con_info (online_reps & online_reps, const std::string & name) -{ - size_t count = 0; + size_t count; { nano::lock_guard guard (online_reps.mutex); count = online_reps.reps.size (); } auto sizeof_element = sizeof (decltype (online_reps.reps)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "arrival", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "reps", count, sizeof_element })); return composite; } -} diff --git a/nano/node/online_reps.hpp b/nano/node/online_reps.hpp index 27488bbe33..216b8238e3 100644 --- a/nano/node/online_reps.hpp +++ b/nano/node/online_reps.hpp @@ -9,15 +9,15 @@ namespace nano { -class node; +class ledger; +class network_params; class transaction; /** Track online representatives and trend online weight */ class online_reps final { public: - online_reps (nano::node &, nano::uint128_t); - + online_reps (nano::ledger & ledger_a, nano::network_params & network_params_a, nano::uint128_t minimum_a); /** Add voting account \p rep_account to the set of online representatives */ void observe (nano::account const & rep_account); /** Called periodically to sample online weight */ @@ -30,13 +30,14 @@ class online_reps final private: nano::uint128_t trend (nano::transaction &); mutable std::mutex mutex; - nano::node & node; + nano::ledger & ledger; + nano::network_params & network_params; std::unordered_set reps; nano::uint128_t online; nano::uint128_t minimum; - friend std::unique_ptr collect_seq_con_info (online_reps & online_reps, const std::string & name); + friend std::unique_ptr collect_container_info (online_reps & online_reps, const std::string & name); }; -std::unique_ptr collect_seq_con_info (online_reps & online_reps, const std::string & name); +std::unique_ptr collect_container_info (online_reps & online_reps, const std::string & name); } diff --git a/nano/node/openclwork.cpp b/nano/node/openclwork.cpp index c06048b907..39c3d46667 100644 --- a/nano/node/openclwork.cpp +++ b/nano/node/openclwork.cpp @@ -1,13 +1,13 @@ #include -#include -#include +#include #include #include #include +#include + #include #include -#include #include #include @@ -687,22 +687,21 @@ nano::opencl_work::~opencl_work () } } -boost::optional nano::opencl_work::generate_work (nano::root const & root_a, uint64_t const difficulty_a) +boost::optional nano::opencl_work::generate_work (nano::work_version const version_a, nano::root const & root_a, uint64_t const difficulty_a) { std::atomic ticket_l{ 0 }; - return generate_work (root_a, difficulty_a, ticket_l); + return generate_work (version_a, root_a, difficulty_a, ticket_l); } -boost::optional nano::opencl_work::generate_work (nano::root const & root_a, uint64_t const difficulty_a, std::atomic & ticket_a) +boost::optional nano::opencl_work::generate_work (nano::work_version const version_a, nano::root const & root_a, uint64_t const difficulty_a, std::atomic & ticket_a) { nano::lock_guard lock (mutex); bool error (false); int ticket_l (ticket_a); uint64_t result (0); - uint64_t computed_difficulty (0); unsigned thread_count (config.threads); size_t work_size[] = { thread_count, 0, 0 }; - while ((nano::work_validate (root_a, result, &computed_difficulty) || computed_difficulty < difficulty_a) && !error && ticket_a == ticket_l) + while (nano::work_difficulty (version_a, root_a, result) < difficulty_a && !error && ticket_a == ticket_l) { result = rand.next (); cl_int write_error1 = clEnqueueWriteBuffer (queue, attempt_buffer, false, 0, sizeof (uint64_t), &result, 0, nullptr, nullptr); diff --git a/nano/node/openclwork.hpp b/nano/node/openclwork.hpp index 5b8b93d6a2..2b1de6040c 100644 --- a/nano/node/openclwork.hpp +++ b/nano/node/openclwork.hpp @@ -1,14 +1,12 @@ #pragma once -#include -#include +#include #include #include #include -#include -#include +#include #include #include @@ -43,8 +41,8 @@ class opencl_work public: opencl_work (bool &, nano::opencl_config const &, nano::opencl_environment &, nano::logger_mt &); ~opencl_work (); - boost::optional generate_work (nano::root const &, uint64_t const); - boost::optional generate_work (nano::root const &, uint64_t const, std::atomic &); + boost::optional generate_work (nano::work_version const, nano::root const &, uint64_t const); + boost::optional generate_work (nano::work_version const, nano::root const &, uint64_t const, std::atomic &); static std::unique_ptr create (bool, nano::opencl_config const &, nano::logger_mt &); nano::opencl_config const & config; std::mutex mutex; diff --git a/nano/node/payment_observer_processor.cpp b/nano/node/payment_observer_processor.cpp index d804196596..acb96f2f42 100644 --- a/nano/node/payment_observer_processor.cpp +++ b/nano/node/payment_observer_processor.cpp @@ -28,13 +28,13 @@ void nano::payment_observer_processor::observer_action (nano::account const & ac void nano::payment_observer_processor::add (nano::account const & account_a, std::shared_ptr payment_observer_a) { nano::lock_guard lock (mutex); - assert (payment_observers.find (account_a) == payment_observers.end ()); + debug_assert (payment_observers.find (account_a) == payment_observers.end ()); payment_observers[account_a] = payment_observer_a; } void nano::payment_observer_processor::erase (nano::account & account_a) { nano::lock_guard lock (mutex); - assert (payment_observers.find (account_a) != payment_observers.end ()); + debug_assert (payment_observers.find (account_a) != payment_observers.end ()); payment_observers.erase (account_a); } diff --git a/nano/node/peer_exclusion.cpp b/nano/node/peer_exclusion.cpp new file mode 100644 index 0000000000..2eba6927af --- /dev/null +++ b/nano/node/peer_exclusion.cpp @@ -0,0 +1,95 @@ +#include + +constexpr std::chrono::hours nano::peer_exclusion::exclude_time_hours; +constexpr std::chrono::hours nano::peer_exclusion::exclude_remove_hours; +constexpr size_t nano::peer_exclusion::size_max; +constexpr double nano::peer_exclusion::peers_percentage_limit; + +uint64_t nano::peer_exclusion::add (nano::tcp_endpoint const & endpoint_a, size_t const network_peers_count_a) +{ + uint64_t result (0); + nano::lock_guard guard (mutex); + // Clean old excluded peers + auto limited = limited_size (network_peers_count_a); + while (peers.size () > 1 && peers.size () > limited) + { + peers.get ().erase (peers.get ().begin ()); + } + debug_assert (peers.size () <= size_max); + auto & peers_by_endpoint (peers.get ()); + auto address = endpoint_a.address (); + auto existing (peers_by_endpoint.find (address)); + if (existing == peers_by_endpoint.end ()) + { + // Insert new endpoint + auto inserted (peers.emplace (peer_exclusion::item{ std::chrono::steady_clock::steady_clock::now () + exclude_time_hours, address, 1 })); + (void)inserted; + debug_assert (inserted.second); + result = 1; + } + else + { + // Update existing endpoint + peers_by_endpoint.modify (existing, [&result](peer_exclusion::item & item_a) { + ++item_a.score; + result = item_a.score; + if (item_a.score == peer_exclusion::score_limit) + { + item_a.exclude_until = std::chrono::steady_clock::now () + peer_exclusion::exclude_time_hours; + } + else if (item_a.score > peer_exclusion::score_limit) + { + item_a.exclude_until = std::chrono::steady_clock::now () + peer_exclusion::exclude_time_hours * item_a.score * 2; + } + }); + } + return result; +} + +bool nano::peer_exclusion::check (nano::tcp_endpoint const & endpoint_a) +{ + bool excluded (false); + nano::lock_guard guard (mutex); + auto & peers_by_endpoint (peers.get ()); + auto existing (peers_by_endpoint.find (endpoint_a.address ())); + if (existing != peers_by_endpoint.end () && existing->score >= score_limit) + { + if (existing->exclude_until > std::chrono::steady_clock::now ()) + { + excluded = true; + } + else if (existing->exclude_until + exclude_remove_hours * existing->score < std::chrono::steady_clock::now ()) + { + peers_by_endpoint.erase (existing); + } + } + return excluded; +} + +void nano::peer_exclusion::remove (nano::tcp_endpoint const & endpoint_a) +{ + nano::lock_guard guard (mutex); + peers.get ().erase (endpoint_a.address ()); +} + +size_t nano::peer_exclusion::limited_size (size_t const network_peers_count_a) const +{ + return std::min (size_max, network_peers_count_a * peers_percentage_limit); +} + +size_t nano::peer_exclusion::size () const +{ + nano::lock_guard guard (mutex); + return peers.size (); +} + +std::unique_ptr nano::collect_container_info (nano::peer_exclusion const & excluded_peers, const std::string & name) +{ + auto composite = std::make_unique (name); + + size_t excluded_peers_count = excluded_peers.size (); + auto sizeof_excluded_peers_element = sizeof (nano::peer_exclusion::ordered_endpoints::value_type); + composite->add_component (std::make_unique (container_info{ "peers", excluded_peers_count, sizeof_excluded_peers_element })); + + return composite; +} diff --git a/nano/node/peer_exclusion.hpp b/nano/node/peer_exclusion.hpp new file mode 100644 index 0000000000..88a4127ea1 --- /dev/null +++ b/nano/node/peer_exclusion.hpp @@ -0,0 +1,61 @@ +#include + +#include +#include +#include +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class peer_exclusion final +{ + class item final + { + public: + item () = delete; + std::chrono::steady_clock::time_point exclude_until; + decltype (std::declval ().address ()) address; + uint64_t score; + }; + + // clang-format off + class tag_endpoint {}; + class tag_exclusion {}; + // clang-format on + +public: + // clang-format off + using ordered_endpoints = boost::multi_index_container, + mi::member>, + mi::hashed_unique, + mi::member>>>; + // clang-format on + +private: + ordered_endpoints peers; + mutable std::mutex mutex; + +public: + constexpr static size_t size_max = 5000; + constexpr static double peers_percentage_limit = 0.5; + constexpr static uint64_t score_limit = 2; + constexpr static std::chrono::hours exclude_time_hours = std::chrono::hours (1); + constexpr static std::chrono::hours exclude_remove_hours = std::chrono::hours (24); + + uint64_t add (nano::tcp_endpoint const &, size_t const); + bool check (nano::tcp_endpoint const &); + void remove (nano::tcp_endpoint const &); + size_t limited_size (size_t const) const; + size_t size () const; + + friend class telemetry_remove_peer_different_genesis_Test; + friend class telemetry_remove_peer_different_genesis_udp_Test; + friend class telemetry_remove_peer_invalid_signature_Test; + friend class peer_exclusion_validate_Test; +}; +std::unique_ptr collect_container_info (peer_exclusion const & excluded_peers, const std::string & name); +} diff --git a/nano/node/portmapping.cpp b/nano/node/portmapping.cpp index a4d3ddac6c..34ab94489e 100644 --- a/nano/node/portmapping.cpp +++ b/nano/node/portmapping.cpp @@ -1,12 +1,15 @@ #include #include +#include +#include + #include #include nano::port_mapping::port_mapping (nano::node & node_a) : node (node_a), -protocols ({ { { "TCP", 0, boost::asio::ip::address_v4::any (), 0 }, { "UDP", 0, boost::asio::ip::address_v4::any (), 0 } } }) +protocols ({ { { "TCP", 0, boost::asio::ip::address_v4::any (), 0, true }, { "UDP", 0, boost::asio::ip::address_v4::any (), 0, !node_a.flags.disable_udp } } }) { } @@ -59,7 +62,7 @@ nano::endpoint nano::port_mapping::external_address () { nano::endpoint result_l (boost::asio::ip::address_v6{}, 0); nano::lock_guard guard_l (mutex); - for (auto & protocol : protocols) + for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; })) { if (protocol.external_port != 0) { @@ -78,7 +81,7 @@ void nano::port_mapping::refresh_mapping () auto config_port_l (get_config_port (node_port_l)); // We don't map the RPC port because, unless RPC authentication was added, this would almost always be a security risk - for (auto & protocol : protocols) + for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; })) { auto upnp_description = std::string ("Nano Node (") + network_params.network.get_current_network_as_string () + ")"; auto add_port_mapping_error_l (UPNP_AddPortMapping (upnp.urls.controlURL, upnp.data.first.servicetype, config_port_l.c_str (), node_port_l.c_str (), address.to_string ().c_str (), upnp_description.c_str (), protocol.name, nullptr, nullptr)); @@ -112,7 +115,7 @@ int nano::port_mapping::check_mapping () nano::lock_guard guard_l (mutex); auto node_port_l (std::to_string (node.network.endpoint ().port ())); auto config_port_l (get_config_port (node_port_l)); - for (auto & protocol : protocols) + for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; })) { std::array int_client_l; std::array int_port_l; @@ -186,7 +189,7 @@ void nano::port_mapping::stop () { on = false; nano::lock_guard guard_l (mutex); - for (auto & protocol : protocols) + for (auto & protocol : protocols | boost::adaptors::filtered ([](auto const & p) { return p.enabled; })) { if (protocol.external_port != 0) { diff --git a/nano/node/portmapping.hpp b/nano/node/portmapping.hpp index cd42788dbc..3828785bf5 100644 --- a/nano/node/portmapping.hpp +++ b/nano/node/portmapping.hpp @@ -1,8 +1,5 @@ #pragma once -#include -#include - #include #include @@ -20,6 +17,7 @@ class mapping_protocol int remaining; boost::asio::ip::address_v4 external_address; uint16_t external_port; + bool enabled; }; /** Collection of discovered UPnP devices and state*/ @@ -61,7 +59,7 @@ class port_mapping boost::asio::ip::address_v4 address; std::array protocols; uint64_t check_count{ 0 }; - bool on{ false }; + std::atomic on{ false }; std::mutex mutex; }; } diff --git a/nano/node/repcrawler.cpp b/nano/node/repcrawler.cpp index 0e34ebdad3..f4a5bed75e 100644 --- a/nano/node/repcrawler.cpp +++ b/nano/node/repcrawler.cpp @@ -1,6 +1,8 @@ #include #include +#include + nano::rep_crawler::rep_crawler (nano::node & node_a) : node (node_a) { @@ -12,27 +14,73 @@ node (node_a) } } -void nano::rep_crawler::add (nano::block_hash const & hash_a) -{ - nano::lock_guard lock (active_mutex); - active.insert (hash_a); -} - void nano::rep_crawler::remove (nano::block_hash const & hash_a) { nano::lock_guard lock (active_mutex); active.erase (hash_a); } -bool nano::rep_crawler::exists (nano::block_hash const & hash_a) +void nano::rep_crawler::start () { - nano::lock_guard lock (active_mutex); - return active.count (hash_a) != 0; + ongoing_crawl (); } -void nano::rep_crawler::start () +void nano::rep_crawler::validate () { - ongoing_crawl (); + decltype (responses) responses_l; + { + nano::lock_guard lock (active_mutex); + responses_l.swap (responses); + } + auto transaction (node.store.tx_begin_read ()); + auto minimum = node.minimum_principal_weight (); + for (auto const & i : responses_l) + { + auto & vote = i.second; + auto & channel = i.first; + debug_assert (channel != nullptr); + nano::uint128_t rep_weight = node.ledger.weight (vote->account); + if (rep_weight > minimum) + { + auto updated_or_inserted = false; + nano::unique_lock lock (probable_reps_mutex); + auto existing (probable_reps.find (vote->account)); + if (existing != probable_reps.end ()) + { + probable_reps.modify (existing, [rep_weight, &updated_or_inserted, &vote, &channel](nano::representative & info) { + info.last_response = std::chrono::steady_clock::now (); + + // Update if representative channel was changed + if (info.channel->get_endpoint () != channel->get_endpoint ()) + { + debug_assert (info.account == vote->account); + updated_or_inserted = true; + info.weight = rep_weight; + info.channel = channel; + } + }); + } + else + { + probable_reps.emplace (nano::representative (vote->account, rep_weight, channel)); + updated_or_inserted = true; + } + lock.unlock (); + if (updated_or_inserted) + { + node.logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel->to_string ())); + } + } + // This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them + // Only do this if the sequence number is significantly different to account for network reordering + // Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase + auto max_vote (node.store.vote_max (transaction, vote)); + if (max_vote->sequence > vote->sequence + 10000) + { + nano::confirm_ack confirm (max_vote); + channel->send (confirm); // this is non essential traffic as it will be resolicited if not received + } + } } void nano::rep_crawler::ongoing_crawl () @@ -41,6 +89,7 @@ void nano::rep_crawler::ongoing_crawl () auto total_weight_l (total_weight ()); cleanup_reps (); update_weights (); + validate (); query (get_crawl_targets (total_weight_l)); auto sufficient_weight (total_weight_l > node.config.online_weight_minimum.number ()); // If online weight drops below minimum, reach out to preconfigured peers @@ -49,9 +98,9 @@ void nano::rep_crawler::ongoing_crawl () node.keepalive_preconfigured (node.config.preconfigured_peers); } // Reduce crawl frequency when there's enough total peer weight - unsigned next_run_seconds = sufficient_weight ? 7 : 3; + unsigned next_run_ms = node.network_params.network.is_test_network () ? 100 : sufficient_weight ? 7000 : 3000; std::weak_ptr node_w (node.shared ()); - node.alarm.add (now + std::chrono::seconds (next_run_seconds), [node_w, this]() { + node.alarm.add (now + std::chrono::milliseconds (next_run_ms), [node_w, this]() { if (auto node_l = node_w.lock ()) { this->ongoing_crawl (); @@ -75,7 +124,7 @@ std::vector> nano::rep_crawler::get_cr required_peer_count += required_peer_count / 2; // The rest of the endpoints are picked randomly - auto random_peers (node.network.random_set (required_peer_count)); + auto random_peers (node.network.random_set (required_peer_count, 0, true)); // Include channels with ephemeral remote ports std::vector> result; result.insert (result.end (), random_peers.begin (), random_peers.end ()); return result; @@ -86,18 +135,22 @@ void nano::rep_crawler::query (std::vector block (node.store.block_random (transaction)); auto hash (block->hash ()); - // Don't send same block multiple times in tests - if (node.network_params.network.is_test_network ()) { - for (auto i (0); exists (hash) && i < 4; ++i) + nano::lock_guard lock (active_mutex); + // Don't send same block multiple times in tests + if (node.network_params.network.is_test_network ()) { - block = node.store.block_random (transaction); - hash = block->hash (); + for (auto i (0); active.count (hash) != 0 && i < 4; ++i) + { + block = node.store.block_random (transaction); + hash = block->hash (); + } } + active.insert (hash); } - add (hash); for (auto i (channels_a.begin ()), n (channels_a.end ()); i != n; ++i) { + debug_assert (*i != nullptr); on_rep_request (*i); node.network.send_confirm_req (*i, block); } @@ -112,39 +165,39 @@ void nano::rep_crawler::query (std::vector channel_a) +void nano::rep_crawler::query (std::shared_ptr const & channel_a) { std::vector> peers; - peers.push_back (channel_a); + peers.emplace_back (channel_a); query (peers); } -bool nano::rep_crawler::response (std::shared_ptr channel_a, nano::account const & rep_account_a, nano::amount const & weight_a) +bool nano::rep_crawler::is_pr (nano::transport::channel const & channel_a) const { - auto updated_or_inserted (false); nano::lock_guard lock (probable_reps_mutex); - auto existing (probable_reps.find (rep_account_a)); - if (existing != probable_reps.end ()) + auto existing = probable_reps.get ().find (channel_a); + bool result = false; + if (existing != probable_reps.get ().end ()) { - probable_reps.modify (existing, [weight_a, &updated_or_inserted, rep_account_a, channel_a](nano::representative & info) { - info.last_response = std::chrono::steady_clock::now (); - - // Update if representative channel was changed - if (info.channel->get_endpoint () != channel_a->get_endpoint ()) - { - assert (info.account == rep_account_a); - updated_or_inserted = true; - info.weight = weight_a; - info.channel = channel_a; - } - }); + result = existing->weight > node.minimum_principal_weight (); } - else + return result; +} + +bool nano::rep_crawler::response (std::shared_ptr & channel_a, std::shared_ptr & vote_a) +{ + bool error = true; + nano::lock_guard lock (active_mutex); + for (auto i = vote_a->begin (), n = vote_a->end (); i != n; ++i) { - probable_reps.insert (nano::representative (rep_account_a, weight_a, channel_a)); - updated_or_inserted = true; + if (active.count (*i) != 0) + { + responses.emplace_back (channel_a, vote_a); + error = false; + break; + } } - return updated_or_inserted; + return error; } nano::uint128_t nano::rep_crawler::total_weight () const @@ -169,16 +222,18 @@ nano::uint128_t nano::rep_crawler::total_weight () const void nano::rep_crawler::on_rep_request (std::shared_ptr channel_a) { nano::lock_guard lock (probable_reps_mutex); - - probably_rep_t::index::type & channel_ref_index = probable_reps.get (); - - // Find and update the timestamp on all reps available on the endpoint (a single host may have multiple reps) - auto itr_pair = probable_reps.get ().equal_range (*channel_a); - for (; itr_pair.first != itr_pair.second; itr_pair.first++) + if (channel_a->get_tcp_endpoint ().address () != boost::asio::ip::address_v6::any ()) { - channel_ref_index.modify (itr_pair.first, [](nano::representative & value_a) { - value_a.last_request = std::chrono::steady_clock::now (); - }); + probably_rep_t::index::type & channel_ref_index = probable_reps.get (); + + // Find and update the timestamp on all reps available on the endpoint (a single host may have multiple reps) + auto itr_pair = channel_ref_index.equal_range (*channel_a); + for (; itr_pair.first != itr_pair.second; itr_pair.first++) + { + channel_ref_index.modify (itr_pair.first, [](nano::representative & value_a) { + value_a.last_request = std::chrono::steady_clock::now (); + }); + } } } @@ -188,13 +243,23 @@ void nano::rep_crawler::cleanup_reps () { // Check known rep channels nano::lock_guard lock (probable_reps_mutex); - for (auto i (probable_reps.get ().begin ()), n (probable_reps.get ().end ()); i != n; ++i) + auto iterator (probable_reps.get ().begin ()); + while (iterator != probable_reps.get ().end ()) { - channels.push_back (i->channel); + if (iterator->channel->get_tcp_endpoint ().address () != boost::asio::ip::address_v6::any ()) + { + channels.push_back (iterator->channel); + ++iterator; + } + else + { + // Remove reps with closed channels + iterator = probable_reps.get ().erase (iterator); + } } } // Remove reps with inactive channels - for (auto i : channels) + for (auto const & i : channels) { bool equal (false); if (i->get_type () == nano::transport::transport_type::tcp) @@ -245,13 +310,14 @@ void nano::rep_crawler::update_weights () } } -std::vector nano::rep_crawler::representatives (size_t count_a) +std::vector nano::rep_crawler::representatives (size_t count_a, nano::uint128_t const weight_a, boost::optional const & opt_version_min_a) { + auto version_min (opt_version_min_a.value_or (node.network_params.protocol.protocol_version_min (node.ledger.cache.epoch_2_started))); std::vector result; nano::lock_guard lock (probable_reps_mutex); for (auto i (probable_reps.get ().begin ()), n (probable_reps.get ().end ()); i != n && result.size () < count_a; ++i) { - if (!i->weight.is_zero ()) + if (i->weight > weight_a && i->channel->get_network_version () >= version_min) { result.push_back (*i); } @@ -259,11 +325,16 @@ std::vector nano::rep_crawler::representatives (size_t cou return result; } +std::vector nano::rep_crawler::principal_representatives (size_t count_a, boost::optional const & opt_version_min_a) +{ + return representatives (count_a, node.minimum_principal_weight (), opt_version_min_a); +} + std::vector> nano::rep_crawler::representative_endpoints (size_t count_a) { std::vector> result; auto reps (representatives (count_a)); - for (auto rep : reps) + for (auto const & rep : reps) { result.push_back (rep.channel); } diff --git a/nano/node/repcrawler.hpp b/nano/node/repcrawler.hpp index b1fb9b3a3c..a18d2e4743 100644 --- a/nano/node/repcrawler.hpp +++ b/nano/node/repcrawler.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -10,10 +9,10 @@ #include #include #include +#include #include #include -#include #include namespace mi = boost::multi_index; @@ -32,6 +31,7 @@ class representative representative (nano::account account_a, nano::amount weight_a, std::shared_ptr channel_a) : account (account_a), weight (weight_a), channel (channel_a) { + debug_assert (channel != nullptr); } std::reference_wrapper channel_ref () const { @@ -52,9 +52,9 @@ class representative * Crawls the network for representatives. Queries are performed by requesting confirmation of a * random block and observing the corresponding vote. */ -class rep_crawler +class rep_crawler final { - friend std::unique_ptr collect_seq_con_info (rep_crawler & rep_crawler, const std::string & name); + friend std::unique_ptr collect_container_info (rep_crawler & rep_crawler, const std::string & name); // clang-format off class tag_account {}; @@ -80,33 +80,33 @@ class rep_crawler /** Start crawling */ void start (); - /** Add block hash to list of active rep queries */ - void add (nano::block_hash const &); - /** Remove block hash from list of active rep queries */ void remove (nano::block_hash const &); - /** Check if block hash is in the list of active rep queries */ - bool exists (nano::block_hash const &); - /** Attempt to determine if the peer manages one or more representative accounts */ void query (std::vector> const & channels_a); /** Attempt to determine if the peer manages one or more representative accounts */ - void query (std::shared_ptr channel_a); + void query (std::shared_ptr const & channel_a); + + /** Query if a peer manages a principle representative */ + bool is_pr (nano::transport::channel const &) const; /** * Called when a non-replay vote on a block previously sent by query() is received. This indiciates * with high probability that the endpoint is a representative node. - * @return True if the rep entry was updated with new information due to increase in weight. + * @return false if the vote corresponded to any active hash. */ - bool response (std::shared_ptr channel_a, nano::account const & rep_account_a, nano::amount const & weight_a); + bool response (std::shared_ptr &, std::shared_ptr &); /** Get total available weight from representatives */ nano::uint128_t total_weight () const; - /** Request a list of the top \p count_a known representatives in descending order of weight. */ - std::vector representatives (size_t count_a = std::numeric_limits::max ()); + /** Request a list of the top \p count_a known representatives in descending order of weight, with at least \p weight_a voting weight, and optionally with a minimum version \p opt_version_min_a */ + std::vector representatives (size_t count_a = std::numeric_limits::max (), nano::uint128_t const weight_a = 0, boost::optional const & opt_version_min_a = boost::none); + + /** Request a list of the top \p count_a known principal representatives in descending order of weight, optionally with a minimum version \p opt_version_min_a */ + std::vector principal_representatives (size_t count_a = std::numeric_limits::max (), boost::optional const & opt_version_min_a = boost::none); /** Request a list of the top \p count_a known representative endpoints. */ std::vector> representative_endpoints (size_t count_a); @@ -123,6 +123,9 @@ class rep_crawler /** We have solicted votes for these random blocks */ std::unordered_set active; + // Validate responses to see if they're reps + void validate (); + /** Called continuously to crawl for representatives */ void ongoing_crawl (); @@ -143,5 +146,10 @@ class rep_crawler /** Probable representatives */ probably_rep_t probable_reps; + + friend class active_transactions_confirm_active_Test; + friend class active_transactions_confirm_frontier_Test; + + std::deque, std::shared_ptr>> responses; }; } diff --git a/nano/node/request_aggregator.cpp b/nano/node/request_aggregator.cpp new file mode 100644 index 0000000000..050cd1c1e5 --- /dev/null +++ b/nano/node/request_aggregator.cpp @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +nano::request_aggregator::request_aggregator (nano::network_constants const & network_constants_a, nano::node_config const & config_a, nano::stat & stats_a, nano::votes_cache & cache_a, nano::ledger & ledger_a, nano::wallets & wallets_a, nano::active_transactions & active_a) : +max_delay (network_constants_a.is_test_network () ? 50 : 300), +small_delay (network_constants_a.is_test_network () ? 10 : 50), +max_channel_requests (config_a.max_queued_requests), +stats (stats_a), +votes_cache (cache_a), +ledger (ledger_a), +wallets (wallets_a), +active (active_a), +thread ([this]() { run (); }) +{ + nano::unique_lock lock (mutex); + condition.wait (lock, [& started = started] { return started; }); +} + +void nano::request_aggregator::add (std::shared_ptr & channel_a, std::vector> const & hashes_roots_a) +{ + debug_assert (wallets.reps ().voting > 0); + bool error = true; + auto const endpoint (nano::transport::map_endpoint_to_v6 (channel_a->get_endpoint ())); + nano::unique_lock lock (mutex); + // Protecting from ever-increasing memory usage when request are consumed slower than generated + // Reject request if the oldest request has not yet been processed after its deadline + a modest margin + if (requests.empty () || (requests.get ().begin ()->deadline + 2 * this->max_delay > std::chrono::steady_clock::now ())) + { + auto & requests_by_endpoint (requests.get ()); + auto existing (requests_by_endpoint.find (endpoint)); + if (existing == requests_by_endpoint.end ()) + { + existing = requests_by_endpoint.emplace (channel_a).first; + } + requests_by_endpoint.modify (existing, [&hashes_roots_a, &channel_a, &error, this](channel_pool & pool_a) { + // This extends the lifetime of the channel, which is acceptable up to max_delay + pool_a.channel = channel_a; + if (pool_a.hashes_roots.size () + hashes_roots_a.size () <= this->max_channel_requests) + { + error = false; + auto new_deadline (std::min (pool_a.start + this->max_delay, std::chrono::steady_clock::now () + this->small_delay)); + pool_a.deadline = new_deadline; + pool_a.hashes_roots.insert (pool_a.hashes_roots.begin (), hashes_roots_a.begin (), hashes_roots_a.end ()); + } + }); + if (requests.size () == 1) + { + lock.unlock (); + condition.notify_all (); + } + } + stats.inc (nano::stat::type::aggregator, !error ? nano::stat::detail::aggregator_accepted : nano::stat::detail::aggregator_dropped); +} + +void nano::request_aggregator::run () +{ + nano::thread_role::set (nano::thread_role::name::request_aggregator); + nano::unique_lock lock (mutex); + started = true; + lock.unlock (); + condition.notify_all (); + lock.lock (); + while (!stopped) + { + if (!requests.empty ()) + { + auto & requests_by_deadline (requests.get ()); + auto front (requests_by_deadline.begin ()); + if (front->deadline < std::chrono::steady_clock::now ()) + { + // Store the channel and requests for processing after erasing this pool + decltype (front->channel) channel{}; + decltype (front->hashes_roots) hashes_roots{}; + requests_by_deadline.modify (front, [&channel, &hashes_roots](channel_pool & pool) { + channel.swap (pool.channel); + hashes_roots.swap (pool.hashes_roots); + }); + requests_by_deadline.erase (front); + lock.unlock (); + erase_duplicates (hashes_roots); + auto transaction (ledger.store.tx_begin_read ()); + auto remaining = aggregate (transaction, hashes_roots, channel); + if (!remaining.empty ()) + { + // Generate votes for the remaining hashes + generate (transaction, remaining, channel); + } + lock.lock (); + } + else + { + auto deadline = front->deadline; + condition.wait_until (lock, deadline, [this, &deadline]() { return this->stopped || deadline < std::chrono::steady_clock::now (); }); + } + } + else + { + condition.wait_for (lock, small_delay, [this]() { return this->stopped || !this->requests.empty (); }); + } + } +} + +void nano::request_aggregator::stop () +{ + { + nano::lock_guard guard (mutex); + stopped = true; + } + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); + } +} + +std::size_t nano::request_aggregator::size () +{ + nano::unique_lock lock (mutex); + return requests.size (); +} + +bool nano::request_aggregator::empty () +{ + return size () == 0; +} + +void nano::request_aggregator::erase_duplicates (std::vector> & requests_a) const +{ + std::sort (requests_a.begin (), requests_a.end (), [](auto const & pair1, auto const & pair2) { + return pair1.first < pair2.first; + }); + requests_a.erase (std::unique (requests_a.begin (), requests_a.end (), [](auto const & pair1, auto const & pair2) { + return pair1.first == pair2.first; + }), + requests_a.end ()); +} + +std::vector nano::request_aggregator::aggregate (nano::transaction const & transaction_a, std::vector> const & requests_a, std::shared_ptr & channel_a) const +{ + size_t cached_hashes = 0; + std::vector to_generate; + std::vector> cached_votes; + for (auto const & hash_root : requests_a) + { + // 1. Votes in cache + auto find_votes (votes_cache.find (hash_root.first)); + if (!find_votes.empty ()) + { + ++cached_hashes; + cached_votes.insert (cached_votes.end (), find_votes.begin (), find_votes.end ()); + } + else + { + // 2. Election winner by hash + auto block = active.winner (hash_root.first); + + // 3. Ledger by hash + if (block == nullptr) + { + block = ledger.store.block_get (transaction_a, hash_root.first); + } + + // 4. Ledger by root + if (block == nullptr && !hash_root.second.is_zero ()) + { + // Search for block root + auto successor (ledger.store.block_successor (transaction_a, hash_root.second)); + // Search for account root + if (successor.is_zero ()) + { + nano::account_info info; + auto error (ledger.store.account_get (transaction_a, hash_root.second, info)); + if (!error) + { + successor = info.open_block; + } + } + if (!successor.is_zero ()) + { + auto successor_block = ledger.store.block_get (transaction_a, successor); + debug_assert (successor_block != nullptr); + // 5. Votes in cache for successor + auto find_successor_votes (votes_cache.find (successor)); + if (!find_successor_votes.empty ()) + { + cached_votes.insert (cached_votes.end (), find_successor_votes.begin (), find_successor_votes.end ()); + } + else + { + block = std::move (successor_block); + } + } + } + + if (block) + { + // Attempt to vote for this block + if (ledger.can_vote (transaction_a, *block)) + { + to_generate.push_back (block->hash ()); + } + else + { + stats.inc (nano::stat::type::requests, nano::stat::detail::requests_cannot_vote, stat::dir::in); + } + // Let the node know about the alternative block + if (block->hash () != hash_root.first) + { + nano::publish publish (block); + channel_a->send (publish); + } + } + else + { + stats.inc (nano::stat::type::requests, nano::stat::detail::requests_unknown, stat::dir::in); + } + } + } + // Unique votes + std::sort (cached_votes.begin (), cached_votes.end ()); + cached_votes.erase (std::unique (cached_votes.begin (), cached_votes.end ()), cached_votes.end ()); + for (auto const & vote : cached_votes) + { + nano::confirm_ack confirm (vote); + channel_a->send (confirm); + } + stats.add (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes, stat::dir::in, cached_hashes); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_cached_votes, stat::dir::in, cached_votes.size ()); + return to_generate; +} + +void nano::request_aggregator::generate (nano::transaction const & transaction_a, std::vector const & hashes_a, std::shared_ptr & channel_a) const +{ + size_t generated_l = 0; + auto i (hashes_a.begin ()); + auto n (hashes_a.end ()); + while (i != n) + { + std::vector hashes_l; + for (; i != n && hashes_l.size () < nano::network::confirm_ack_hashes_max; ++i) + { + hashes_l.push_back (*i); + } + wallets.foreach_representative ([this, &generated_l, &hashes_l, &channel_a, &transaction_a](nano::public_key const & pub_a, nano::raw_key const & prv_a) { + auto vote (this->ledger.store.vote_generate (transaction_a, pub_a, prv_a, hashes_l)); + ++generated_l; + nano::confirm_ack confirm (vote); + channel_a->send (confirm); + this->votes_cache.add (vote); + }); + } + stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes, stat::dir::in, hashes_a.size ()); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_votes, stat::dir::in, generated_l); +} + +std::unique_ptr nano::collect_container_info (nano::request_aggregator & aggregator, const std::string & name) +{ + auto pools_count = aggregator.size (); + auto sizeof_element = sizeof (decltype (aggregator.requests)::value_type); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "pools", pools_count, sizeof_element })); + return composite; +} diff --git a/nano/node/request_aggregator.hpp b/nano/node/request_aggregator.hpp new file mode 100644 index 0000000000..1185ea2f0b --- /dev/null +++ b/nano/node/request_aggregator.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class active_transactions; +class ledger; +class node_config; +class stat; +class votes_cache; +class wallets; +/** + * Pools together confirmation requests, separately for each endpoint. + * Requests are added from network messages, and aggregated to minimize bandwidth and vote generation. Example: + * * Two votes are cached, one for hashes {1,2,3} and another for hashes {4,5,6} + * * A request arrives for hashes {1,4,5}. Another request arrives soon afterwards for hashes {2,3,6} + * * The aggregator will reply with the two cached votes + * Votes are generated for uncached hashes. + */ +class request_aggregator final +{ + /** + * Holds a buffer of incoming requests from an endpoint. + * Extends the lifetime of the corresponding channel. The channel is updated on a new request arriving from the same endpoint, such that only the newest channel is held + */ + struct channel_pool final + { + channel_pool () = delete; + explicit channel_pool (std::shared_ptr & channel_a) : + channel (channel_a), + endpoint (nano::transport::map_endpoint_to_v6 (channel_a->get_endpoint ())) + { + } + std::vector> hashes_roots; + std::shared_ptr channel; + nano::endpoint endpoint; + std::chrono::steady_clock::time_point const start{ std::chrono::steady_clock::now () }; + std::chrono::steady_clock::time_point deadline; + }; + + // clang-format off + class tag_endpoint {}; + class tag_deadline {}; + // clang-format on + +public: + request_aggregator () = delete; + request_aggregator (nano::network_constants const &, nano::node_config const & config, nano::stat & stats_a, nano::votes_cache &, nano::ledger &, nano::wallets &, nano::active_transactions &); + + /** Add a new request by \p channel_a for hashes \p hashes_roots_a */ + void add (std::shared_ptr & channel_a, std::vector> const & hashes_roots_a); + void stop (); + /** Returns the number of currently queued request pools */ + size_t size (); + bool empty (); + + const std::chrono::milliseconds max_delay; + const std::chrono::milliseconds small_delay; + const size_t max_channel_requests; + +private: + void run (); + /** Remove duplicate requests **/ + void erase_duplicates (std::vector> &) const; + /** Aggregate \p requests_a and send cached votes to \p channel_a . Return the remaining hashes that need vote generation **/ + std::vector aggregate (nano::transaction const &, std::vector> const & requests_a, std::shared_ptr & channel_a) const; + /** Generate votes from \p hashes_a and send to \p channel_a **/ + void generate (nano::transaction const &, std::vector const & hashes_a, std::shared_ptr & channel_a) const; + + nano::stat & stats; + nano::votes_cache & votes_cache; + nano::ledger & ledger; + nano::wallets & wallets; + nano::active_transactions & active; + + // clang-format off + boost::multi_index_container, + mi::member>, + mi::ordered_non_unique, + mi::member>>> + requests; + // clang-format on + + bool stopped{ false }; + bool started{ false }; + nano::condition_variable condition; + std::mutex mutex; + std::thread thread; + + friend std::unique_ptr collect_container_info (request_aggregator &, const std::string &); +}; +std::unique_ptr collect_container_info (request_aggregator &, const std::string &); +} diff --git a/nano/node/rocksdb/rocksdb.cpp b/nano/node/rocksdb/rocksdb.cpp index 18457447c7..6eb2b5d22b 100644 --- a/nano/node/rocksdb/rocksdb.cpp +++ b/nano/node/rocksdb/rocksdb.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -126,7 +127,7 @@ nano::write_transaction nano::rocksdb_store::tx_begin_write (std::vector (db) }; } +std::string nano::rocksdb_store::vendor_get () const +{ + return boost::str (boost::format ("RocksDB %1%.%2%.%3%") % ROCKSDB_MAJOR % ROCKSDB_MINOR % ROCKSDB_PATCH); +} + rocksdb::ColumnFamilyHandle * nano::rocksdb_store::table_to_column_family (tables table_a) const { auto & handles_l = handles; @@ -143,7 +149,7 @@ rocksdb::ColumnFamilyHandle * nano::rocksdb_store::table_to_column_family (table auto iter = std::find_if (handles_l.begin (), handles_l.end (), [name](auto handle) { return (handle->GetName () == name); }); - assert (iter != handles_l.end ()); + debug_assert (iter != handles_l.end ()); return *iter; }; @@ -166,7 +172,7 @@ rocksdb::ColumnFamilyHandle * nano::rocksdb_store::table_to_column_family (table case tables::pending: return get_handle ("pending"); case tables::blocks_info: - assert (false); + debug_assert (false); case tables::representation: return get_handle ("representation"); case tables::unchecked: @@ -208,18 +214,14 @@ bool nano::rocksdb_store::exists (nano::transaction const & transaction_a, table int nano::rocksdb_store::del (nano::write_transaction const & transaction_a, tables table_a, nano::rocksdb_val const & key_a) { - assert (transaction_a.contains (table_a)); - if (!exists (transaction_a, table_a, key_a)) - { - return status_code_not_found (); - } - else + debug_assert (transaction_a.contains (table_a)); + // RocksDB does not report not_found status, it is a pre-condition that the key exists + debug_assert (exists (transaction_a, table_a, key_a)); + + // Removing an entry so counts may need adjusting + if (is_caching_counts (table_a)) { - // Adding a new entry so counts need adjusting (use RMW otherwise known as merge) - if (is_caching_counts (table_a)) - { - decrement (transaction_a, tables::cached_counts, rocksdb_val (rocksdb::Slice (table_to_column_family (table_a)->GetName ())), 1); - } + decrement (transaction_a, tables::cached_counts, rocksdb_val (rocksdb::Slice (table_to_column_family (table_a)->GetName ())), 1); } return tx (transaction_a)->Delete (table_to_column_family (table_a), key_a).code (); @@ -227,14 +229,14 @@ int nano::rocksdb_store::del (nano::write_transaction const & transaction_a, tab bool nano::rocksdb_store::block_info_get (nano::transaction const &, nano::block_hash const &, nano::block_info &) const { - // Should not be called - assert (false); + // Should not be called as the RocksDB backend does not use this table + debug_assert (false); return true; } void nano::rocksdb_store::version_put (nano::write_transaction const & transaction_a, int version_a) { - assert (transaction_a.contains (tables::meta)); + debug_assert (transaction_a.contains (tables::meta)); nano::uint256_union version_key (1); nano::uint256_union version_value (version_a); auto status (put (transaction_a, tables::meta, version_key, nano::rocksdb_val (version_value))); @@ -243,7 +245,7 @@ void nano::rocksdb_store::version_put (nano::write_transaction const & transacti rocksdb::Transaction * nano::rocksdb_store::tx (nano::transaction const & transaction_a) const { - assert (!is_read (transaction_a)); + debug_assert (!is_read (transaction_a)); return static_cast (transaction_a.get_handle ()); } @@ -276,8 +278,6 @@ bool nano::rocksdb_store::is_caching_counts (nano::tables table_a) const { switch (table_a) { - case tables::accounts: - case tables::unchecked: case tables::send_blocks: case tables::receive_blocks: case tables::open_blocks: @@ -318,7 +318,7 @@ int nano::rocksdb_store::decrement (nano::write_transaction const & transaction_ int nano::rocksdb_store::put (nano::write_transaction const & transaction_a, tables table_a, nano::rocksdb_val const & key_a, nano::rocksdb_val const & value_a) { - assert (transaction_a.contains (table_a)); + debug_assert (transaction_a.contains (table_a)); auto txn = tx (transaction_a); if (is_caching_counts (table_a)) @@ -366,7 +366,7 @@ uint64_t nano::rocksdb_store::count (nano::transaction const & transaction_a, ro size_t nano::rocksdb_store::count (nano::transaction const & transaction_a, tables table_a) const { size_t sum = 0; - // Some column families are small enough that they can just be iterated, rather than doing extra io caching counts + // Some column families are small enough (except unchecked) that they can just be iterated, rather than doing extra io caching counts if (table_a == tables::peers) { for (auto i (peers_begin (transaction_a)), n (peers_end ()); i != n; ++i) @@ -381,8 +381,26 @@ size_t nano::rocksdb_store::count (nano::transaction const & transaction_a, tabl ++sum; } } + // This should only be used during initialization as can be expensive during bootstrapping + else if (table_a == tables::unchecked) + { + for (auto i (unchecked_begin (transaction_a)), n (unchecked_end ()); i != n; ++i) + { + ++sum; + } + } + // This should only be used in tests + else if (table_a == tables::accounts) + { + debug_assert (network_constants ().is_test_network ()); + for (auto i (latest_begin (transaction_a)), n (latest_end ()); i != n; ++i) + { + ++sum; + } + } else { + debug_assert (is_caching_counts (table_a)); return count (transaction_a, table_to_column_family (table_a)); } @@ -391,7 +409,7 @@ size_t nano::rocksdb_store::count (nano::transaction const & transaction_a, tabl int nano::rocksdb_store::drop (nano::write_transaction const & transaction_a, tables table_a) { - assert (transaction_a.contains (table_a)); + debug_assert (transaction_a.contains (table_a)); auto col = table_to_column_family (table_a); int status = static_cast (rocksdb::Status::Code::kOk); @@ -432,7 +450,7 @@ int nano::rocksdb_store::clear (rocksdb::ColumnFamilyHandle * column_family) // Need to add it back as we just want to clear the contents auto handle_it = std::find (handles.begin (), handles.end (), column_family); - assert (handle_it != handles.cend ()); + debug_assert (handle_it != handles.cend ()); status = db->CreateColumnFamily (get_cf_options (), name, &column_family); release_assert (status.ok ()); *handle_it = column_family; @@ -601,7 +619,14 @@ bool nano::rocksdb_store::copy_db (boost::filesystem::path const & destination_p return false; } +void nano::rocksdb_store::rebuild_db (nano::write_transaction const & transaction_a) +{ + release_assert (false && "Not available for RocksDB"); +} + bool nano::rocksdb_store::init_error () const { return error; -} \ No newline at end of file +} +// Explicitly instantiate +template class nano::block_store_partial; diff --git a/nano/node/rocksdb/rocksdb.hpp b/nano/node/rocksdb/rocksdb.hpp index 648826a247..3f69adef3f 100644 --- a/nano/node/rocksdb/rocksdb.hpp +++ b/nano/node/rocksdb/rocksdb.hpp @@ -19,6 +19,7 @@ namespace nano { class logging_mt; class rocksdb_config; + /** * rocksdb implementation of the block store */ @@ -30,6 +31,8 @@ class rocksdb_store : public block_store_partial nano::write_transaction tx_begin_write (std::vector const & tables_requiring_lock = {}, std::vector const & tables_no_lock = {}) override; nano::read_transaction tx_begin_read () override; + std::string vendor_get () const override; + bool block_info_get (nano::transaction const &, nano::block_hash const &, nano::block_info &) const override; size_t count (nano::transaction const & transaction_a, tables table_a) const override; void version_put (nano::write_transaction const &, int) override; @@ -52,6 +55,7 @@ class rocksdb_store : public block_store_partial } bool copy_db (boost::filesystem::path const & destination) override; + void rebuild_db (nano::write_transaction const & transaction_a) override; template nano::store_iterator make_iterator (nano::transaction const & transaction_a, tables table_a) const @@ -100,4 +104,6 @@ class rocksdb_store : public block_store_partial rocksdb::BlockBasedTableOptions get_table_options () const; nano::rocksdb_config rocksdb_config; }; + +extern template class block_store_partial; } diff --git a/nano/node/rocksdb/rocksdb_iterator.hpp b/nano/node/rocksdb/rocksdb_iterator.hpp index 0110066a77..ff10f02170 100644 --- a/nano/node/rocksdb/rocksdb_iterator.hpp +++ b/nano/node/rocksdb/rocksdb_iterator.hpp @@ -18,7 +18,7 @@ inline bool is_read (nano::transaction const & transaction_a) inline rocksdb::ReadOptions const & snapshot_options (nano::transaction const & transaction_a) { - assert (is_read (transaction_a)); + debug_assert (is_read (transaction_a)); return *static_cast (transaction_a.get_handle ()); } } @@ -136,9 +136,9 @@ class rocksdb_iterator : public store_iterator_impl } auto result (std::memcmp (current.first.data (), other_a->current.first.data (), current.first.size ()) == 0); - assert (!result || (current.first.size () == other_a->current.first.size ())); - assert (!result || (current.second.data () == other_a->current.second.data ())); - assert (!result || (current.second.size () == other_a->current.second.size ())); + debug_assert (!result || (current.first.size () == other_a->current.first.size ())); + debug_assert (!result || (current.second.data () == other_a->current.second.data ())); + debug_assert (!result || (current.second.size () == other_a->current.second.size ())); return result; } @@ -172,7 +172,7 @@ class rocksdb_iterator : public store_iterator_impl { current.first = nano::rocksdb_val{}; current.second = nano::rocksdb_val{}; - assert (is_end_sentinal ()); + debug_assert (is_end_sentinal ()); } nano::rocksdb_iterator & operator= (nano::rocksdb_iterator && other_a) { diff --git a/nano/node/signatures.cpp b/nano/node/signatures.cpp index 4adb784547..69d347c31b 100644 --- a/nano/node/signatures.cpp +++ b/nano/node/signatures.cpp @@ -1,4 +1,7 @@ +#include +#include #include +#include #include nano::signature_checker::signature_checker (unsigned num_threads) : @@ -19,16 +22,13 @@ nano::signature_checker::~signature_checker () void nano::signature_checker::verify (nano::signature_check_set & check_a) { + // Don't process anything else if we have stopped + if (stopped) { - // Don't process anything else if we have stopped - nano::lock_guard guard (mutex); - if (stopped) - { - return; - } + return; } - if (check_a.size < multithreaded_cutoff || single_threaded) + if (check_a.size <= batch_size || single_threaded) { // Not dealing with many so just use the calling thread for checking signatures auto result = verify_batch (check_a, 0, check_a.size); @@ -50,9 +50,16 @@ void nano::signature_checker::verify (nano::signature_check_set & check_a) auto num_full_batches_thread = (num_base_batches_each * num_threads); if (num_full_overflow_batches > 0) { - size_calling_thread += batch_size; - auto remaining = num_full_overflow_batches - 1; - num_full_batches_thread += remaining; + if (overflow_size == 0) + { + // Give the calling thread priority over any batches when there is no excess remainder. + size_calling_thread += batch_size; + num_full_batches_thread += num_full_overflow_batches - 1; + } + else + { + num_full_batches_thread += num_full_overflow_batches; + } } release_assert (check_a.size == (num_full_batches_thread * batch_size + size_calling_thread)); @@ -73,32 +80,26 @@ void nano::signature_checker::verify (nano::signature_check_set & check_a) void nano::signature_checker::stop () { - nano::lock_guard guard (mutex); - if (!stopped) + if (!stopped.exchange (true)) { - stopped = true; thread_pool.join (); } } void nano::signature_checker::flush () { - nano::lock_guard guard (mutex); while (!stopped && tasks_remaining != 0) ; } bool nano::signature_checker::verify_batch (const nano::signature_check_set & check_a, size_t start_index, size_t size) { - /* Returns false if there are at least 1 invalid signature */ - auto code (nano::validate_message_batch (check_a.messages + start_index, check_a.message_lengths + start_index, check_a.pub_keys + start_index, check_a.signatures + start_index, size, check_a.verifications + start_index)); - (void)code; - + nano::validate_message_batch (check_a.messages + start_index, check_a.message_lengths + start_index, check_a.pub_keys + start_index, check_a.signatures + start_index, size, check_a.verifications + start_index); return std::all_of (check_a.verifications + start_index, check_a.verifications + start_index + size, [](int verification) { return verification == 0 || verification == 1; }); } /* This operates on a number of signatures of size (num_batches * batch_size) from the beginning of the check_a pointers. - * Caller should check the value of the promise which indicateswhen the work has been completed. + * Caller should check the value of the promise which indicates when the work has been completed. */ void nano::signature_checker::verify_async (nano::signature_check_set & check_a, size_t num_batches, std::promise & promise) { @@ -126,10 +127,6 @@ void nano::signature_checker::verify_async (nano::signature_check_set & check_a, // Set the names of all the threads in the thread pool for easier identification void nano::signature_checker::set_thread_names (unsigned num_threads) { - auto ready = false; - auto pending = num_threads; - nano::condition_variable cv; - std::vector> promises (num_threads); std::vector> futures; futures.reserve (num_threads); @@ -139,25 +136,10 @@ void nano::signature_checker::set_thread_names (unsigned num_threads) for (auto i = 0u; i < num_threads; ++i) { - // clang-format off - boost::asio::post (thread_pool, [&cv, &ready, &pending, &mutex = mutex, &promise = promises[i]]() { - nano::unique_lock lk (mutex); + boost::asio::post (thread_pool, [& promise = promises[i]]() { nano::thread_role::set (nano::thread_role::name::signature_checking); - if (--pending == 0) - { - // All threads have been reached - ready = true; - lk.unlock (); - cv.notify_all (); - } - else - { - // We need to wait until the other threads are finished - cv.wait (lk, [&ready]() { return ready; }); - } promise.set_value (); }); - // clang-format on } // Wait until all threads have finished @@ -165,5 +147,4 @@ void nano::signature_checker::set_thread_names (unsigned num_threads) { future.wait (); } - assert (pending == 0); } diff --git a/nano/node/signatures.hpp b/nano/node/signatures.hpp index caaa783d2e..b1d65d7a38 100644 --- a/nano/node/signatures.hpp +++ b/nano/node/signatures.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -35,6 +35,8 @@ class signature_checker final void stop (); void flush (); + static size_t constexpr batch_size = 256; + private: struct Task final { @@ -55,12 +57,8 @@ class signature_checker final void set_thread_names (unsigned num_threads); boost::asio::thread_pool thread_pool; std::atomic tasks_remaining{ 0 }; - /** minimum signature_check_set size eligible to be multithreaded */ - static constexpr size_t multithreaded_cutoff = 513; - static constexpr size_t batch_size = 256; const bool single_threaded; unsigned num_threads; - std::mutex mutex; - bool stopped{ false }; + std::atomic stopped{ false }; }; } diff --git a/nano/node/socket.cpp b/nano/node/socket.cpp index 62b0ec608b..2dd958c1d9 100644 --- a/nano/node/socket.cpp +++ b/nano/node/socket.cpp @@ -1,6 +1,11 @@ +#include +#include +#include #include #include +#include + #include nano::socket::socket (std::shared_ptr node_a, boost::optional io_timeout_a, nano::socket::concurrency concurrency_a) : @@ -39,43 +44,63 @@ void nano::socket::async_connect (nano::tcp_endpoint const & endpoint_a, std::fu void nano::socket::async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) { - assert (size_a <= buffer_a->size ()); - auto this_l (shared_from_this ()); - if (!closed) + if (size_a <= buffer_a->size ()) { - start_timer (); - boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, size_a, this_l]() { - boost::asio::async_read (this_l->tcp_socket, boost::asio::buffer (buffer_a->data (), size_a), - boost::asio::bind_executor (this_l->strand, - [this_l, buffer_a, callback_a](boost::system::error_code const & ec, size_t size_a) { - if (auto node = this_l->node.lock ()) - { - node->stats.add (nano::stat::type::traffic_tcp, nano::stat::dir::in, size_a); - this_l->stop_timer (); - callback_a (ec, size_a); - } + auto this_l (shared_from_this ()); + if (!closed) + { + start_timer (); + boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, size_a, this_l]() { + boost::asio::async_read (this_l->tcp_socket, boost::asio::buffer (buffer_a->data (), size_a), + boost::asio::bind_executor (this_l->strand, + [this_l, buffer_a, callback_a](boost::system::error_code const & ec, size_t size_a) { + if (auto node = this_l->node.lock ()) + { + node->stats.add (nano::stat::type::traffic_tcp, nano::stat::dir::in, size_a); + this_l->stop_timer (); + callback_a (ec, size_a); + } + })); })); - })); + } + } + else + { + debug_assert (false && "nano::socket::async_read called with incorrect buffer size"); + boost::system::error_code ec_buffer = boost::system::errc::make_error_code (boost::system::errc::no_buffer_space); + callback_a (ec_buffer, 0); } } -void nano::socket::async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a) +void nano::socket::async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a, nano::buffer_drop_policy drop_policy_a) { auto this_l (shared_from_this ()); if (!closed) { if (writer_concurrency == nano::socket::concurrency::multi_writer) { - boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, this_l]() { + boost::asio::post (strand, boost::asio::bind_executor (strand, [buffer_a, callback_a, this_l, drop_policy_a]() { bool write_in_progress = !this_l->send_queue.empty (); auto queue_size = this_l->send_queue.size (); - if (queue_size < this_l->queue_size_max) + if (queue_size < this_l->queue_size_max || (drop_policy_a == nano::buffer_drop_policy::no_socket_drop && queue_size < (this_l->queue_size_max * 2))) { this_l->send_queue.emplace_back (nano::socket::queue_item{ buffer_a, callback_a }); } else if (auto node_l = this_l->node.lock ()) { - node_l->stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out); + if (drop_policy_a == nano::buffer_drop_policy::no_socket_drop) + { + node_l->stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out); + } + else + { + node_l->stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out); + } + + if (callback_a) + { + callback_a (boost::system::errc::make_error_code (boost::system::errc::no_buffer_space), 0); + } } if (!write_in_progress) { @@ -253,6 +278,11 @@ void nano::socket::set_writer_concurrency (concurrency writer_concurrency_a) writer_concurrency = writer_concurrency_a; } +size_t nano::socket::get_max_write_queue_size () const +{ + return queue_size_max; +} + nano::server_socket::server_socket (std::shared_ptr node_a, boost::asio::ip::tcp::endpoint local_a, size_t max_connections_a, nano::socket::concurrency concurrency_a) : socket (node_a, std::chrono::seconds::max (), concurrency_a), acceptor (node_a->io_ctx), local (local_a), deferred_accept_timer (node_a->io_ctx), max_inbound_connections (max_connections_a), concurrency_new_connections (concurrency_a) { @@ -360,6 +390,6 @@ void nano::server_socket::on_connection (std::function +#include +#include #include -#include #include #include #include #include -#include #include namespace nano { +/** Policy to affect at which stage a buffer can be dropped */ +enum class buffer_drop_policy +{ + /** Can be dropped by bandwidth limiter (default) */ + limiter, + /** Should not be dropped by bandwidth limiter */ + no_limiter_drop, + /** Should not be dropped by bandwidth limiter or socket write queue limiter */ + no_socket_drop +}; + class node; class server_socket; @@ -45,7 +55,7 @@ class socket : public std::enable_shared_from_this virtual ~socket (); void async_connect (boost::asio::ip::tcp::endpoint const &, std::function); void async_read (std::shared_ptr>, size_t, std::function); - void async_write (nano::shared_const_buffer const &, std::function = nullptr); + void async_write (nano::shared_const_buffer const &, std::function = nullptr, nano::buffer_drop_policy = nano::buffer_drop_policy::limiter); void close (); boost::asio::ip::tcp::endpoint remote_endpoint () const; @@ -56,6 +66,8 @@ class socket : public std::enable_shared_from_this void start_timer (std::chrono::seconds deadline_a); /** Change write concurrent */ void set_writer_concurrency (concurrency writer_concurrency_a); + /** Returns the maximum number of buffers in the write queue */ + size_t get_max_write_queue_size () const; protected: /** Holds the buffer and callback for queued writes */ diff --git a/nano/node/state_block_signature_verification.cpp b/nano/node/state_block_signature_verification.cpp new file mode 100644 index 0000000000..9dc6700221 --- /dev/null +++ b/nano/node/state_block_signature_verification.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +nano::state_block_signature_verification::state_block_signature_verification (nano::signature_checker & signature_checker, nano::epochs & epochs, nano::node_config & node_config, nano::logger_mt & logger, uint64_t state_block_signature_verification_size) : +signature_checker (signature_checker), +epochs (epochs), +node_config (node_config), +logger (logger), +thread ([this, state_block_signature_verification_size]() { + nano::thread_role::set (nano::thread_role::name::state_block_signature_verification); + this->run (state_block_signature_verification_size); +}) +{ +} + +nano::state_block_signature_verification::~state_block_signature_verification () +{ + stop (); +} + +void nano::state_block_signature_verification::stop () +{ + { + nano::lock_guard guard (mutex); + stopped = true; + } + + if (thread.joinable ()) + { + condition.notify_one (); + thread.join (); + } +} + +void nano::state_block_signature_verification::run (uint64_t state_block_signature_verification_size) +{ + nano::unique_lock lk (mutex); + while (!stopped) + { + if (!state_blocks.empty ()) + { + size_t const max_verification_batch (state_block_signature_verification_size != 0 ? state_block_signature_verification_size : nano::signature_checker::batch_size * (node_config.signature_checker_threads + 1)); + active = true; + while (!state_blocks.empty () && !stopped) + { + auto items = setup_items (max_verification_batch); + lk.unlock (); + verify_state_blocks (items); + lk.lock (); + } + active = false; + lk.unlock (); + transition_inactive_callback (); + lk.lock (); + } + else + { + condition.wait (lk); + } + } +} + +bool nano::state_block_signature_verification::is_active () +{ + nano::lock_guard guard (mutex); + return active; +} + +void nano::state_block_signature_verification::add (nano::unchecked_info const & info_a) +{ + { + nano::lock_guard guard (mutex); + state_blocks.push_back (info_a); + } + condition.notify_one (); +} + +size_t nano::state_block_signature_verification::size () +{ + nano::lock_guard guard (mutex); + return state_blocks.size (); +} + +std::deque nano::state_block_signature_verification::setup_items (size_t max_count) +{ + std::deque items; + if (state_blocks.size () <= max_count) + { + items.swap (state_blocks); + } + else + { + for (auto i (0); i < max_count; ++i) + { + items.push_back (state_blocks.front ()); + state_blocks.pop_front (); + } + debug_assert (!state_blocks.empty ()); + } + return items; +} + +void nano::state_block_signature_verification::verify_state_blocks (std::deque & items) +{ + if (!items.empty ()) + { + nano::timer<> timer_l; + timer_l.start (); + auto size (items.size ()); + std::vector hashes; + hashes.reserve (size); + std::vector messages; + messages.reserve (size); + std::vector lengths; + lengths.reserve (size); + std::vector accounts; + accounts.reserve (size); + std::vector pub_keys; + pub_keys.reserve (size); + std::vector blocks_signatures; + blocks_signatures.reserve (size); + std::vector signatures; + signatures.reserve (size); + std::vector verifications; + verifications.resize (size, 0); + for (auto & item : items) + { + hashes.push_back (item.block->hash ()); + messages.push_back (hashes.back ().bytes.data ()); + lengths.push_back (sizeof (decltype (hashes)::value_type)); + nano::account account (item.block->account ()); + if (!item.block->link ().is_zero () && epochs.is_epoch_link (item.block->link ())) + { + account = epochs.signer (epochs.epoch (item.block->link ())); + } + else if (!item.account.is_zero ()) + { + account = item.account; + } + accounts.push_back (account); + pub_keys.push_back (accounts.back ().bytes.data ()); + blocks_signatures.push_back (item.block->block_signature ()); + signatures.push_back (blocks_signatures.back ().bytes.data ()); + } + nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () }; + signature_checker.verify (check); + if (node_config.logging.timing_logging () && timer_l.stop () > std::chrono::milliseconds (10)) + { + logger.try_log (boost::str (boost::format ("Batch verified %1% state blocks in %2% %3%") % size % timer_l.value ().count () % timer_l.unit ())); + } + blocks_verified_callback (items, verifications, hashes, blocks_signatures); + } +} + +std::unique_ptr nano::collect_container_info (state_block_signature_verification & state_block_signature_verification, const std::string & name) +{ + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "state_blocks", state_block_signature_verification.size (), sizeof (nano::unchecked_info) })); + return composite; +} diff --git a/nano/node/state_block_signature_verification.hpp b/nano/node/state_block_signature_verification.hpp new file mode 100644 index 0000000000..993026dade --- /dev/null +++ b/nano/node/state_block_signature_verification.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace nano +{ +class epochs; +class logger_mt; +class node_config; +class signature_checker; + +class state_block_signature_verification +{ +public: + state_block_signature_verification (nano::signature_checker &, nano::epochs &, nano::node_config &, nano::logger_mt &, uint64_t); + ~state_block_signature_verification (); + void add (nano::unchecked_info const & info_a); + size_t size (); + void stop (); + bool is_active (); + + std::function &, std::vector const &, std::vector const &, std::vector const &)> blocks_verified_callback; + std::function transition_inactive_callback; + +private: + nano::signature_checker & signature_checker; + nano::epochs & epochs; + nano::node_config & node_config; + nano::logger_mt & logger; + + std::mutex mutex; + bool stopped{ false }; + bool active{ false }; + std::deque state_blocks; + nano::condition_variable condition; + std::thread thread; + + void run (uint64_t block_processor_verification_size); + std::deque setup_items (size_t); + void verify_state_blocks (std::deque &); +}; + +std::unique_ptr collect_container_info (state_block_signature_verification & state_block_signature_verification, const std::string & name); +} diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp new file mode 100644 index 0000000000..541ec31ab9 --- /dev/null +++ b/nano/node/telemetry.cpp @@ -0,0 +1,653 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +nano::telemetry::telemetry (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a, nano::observer_set & observers_a, nano::stat & stats_a, nano::network_params & network_params_a, bool disable_ongoing_requests_a) : +network (network_a), +alarm (alarm_a), +worker (worker_a), +observers (observers_a), +stats (stats_a), +network_params (network_params_a), +disable_ongoing_requests (disable_ongoing_requests_a) +{ +} + +void nano::telemetry::start () +{ + // Cannot be done in the constructor as a shared_from_this () call is made in ongoing_req_all_peers + if (!disable_ongoing_requests) + { + ongoing_req_all_peers (std::chrono::milliseconds (0)); + } +} + +void nano::telemetry::stop () +{ + stopped = true; +} + +void nano::telemetry::set (nano::telemetry_ack const & message_a, nano::transport::channel const & channel_a) +{ + if (!stopped) + { + nano::unique_lock lk (mutex); + nano::endpoint endpoint = channel_a.get_endpoint (); + auto it = recent_or_initial_request_telemetry_data.find (endpoint); + if (it == recent_or_initial_request_telemetry_data.cend () || !it->undergoing_request) + { + // Not requesting telemetry data from this peer so ignore it + stats.inc (nano::stat::type::telemetry, nano::stat::detail::unsolicited_telemetry_ack); + return; + } + + recent_or_initial_request_telemetry_data.modify (it, [&message_a](nano::telemetry_info & telemetry_info_a) { + telemetry_info_a.data = message_a.data; + }); + + // This can also remove the peer + auto error = verify_message (message_a, channel_a); + + if (!error) + { + // Received telemetry data from a peer which hasn't disabled providing telemetry metrics and there's no errors with the data + lk.unlock (); + observers.notify (message_a.data, endpoint); + lk.lock (); + } + channel_processed (endpoint, error); + } +} + +bool nano::telemetry::verify_message (nano::telemetry_ack const & message_a, nano::transport::channel const & channel_a) +{ + if (message_a.is_empty_payload ()) + { + return true; + } + + auto remove_channel = false; + // We want to ensure that the node_id of the channel matches that in the message before attempting to + // use the data to remove any peers. + auto node_id_mismatch = (channel_a.get_node_id () != message_a.data.node_id); + if (!node_id_mismatch) + { + // The data could be correctly signed but for a different node id + remove_channel = message_a.data.validate_signature (message_a.size ()); + if (!remove_channel) + { + // Check for different genesis blocks + remove_channel = (message_a.data.genesis_block != network_params.ledger.genesis_hash); + if (remove_channel) + { + stats.inc (nano::stat::type::telemetry, nano::stat::detail::different_genesis_hash); + } + } + else + { + stats.inc (nano::stat::type::telemetry, nano::stat::detail::invalid_signature); + } + } + else + { + stats.inc (nano::stat::type::telemetry, nano::stat::detail::node_id_mismatch); + } + + if (remove_channel) + { + // Add to peer exclusion list + network.excluded_peers.add (channel_a.get_tcp_endpoint (), network.size ()); + + // Disconnect from peer with incorrect telemetry data + network.erase (channel_a); + } + + return remove_channel || node_id_mismatch; +} + +std::chrono::milliseconds nano::telemetry::cache_plus_buffer_cutoff_time () const +{ + // This include the waiting time for the response as well as a buffer (1 second) waiting for the alarm operation to be scheduled and completed + return cache_cutoff + response_time_cutoff + 1s; +} + +bool nano::telemetry::within_cache_plus_buffer_cutoff (telemetry_info const & telemetry_info) const +{ + auto is_within = (telemetry_info.last_response + cache_plus_buffer_cutoff_time ()) >= std::chrono::steady_clock::now (); + return !telemetry_info.awaiting_first_response () && is_within; +} + +bool nano::telemetry::within_cache_cutoff (telemetry_info const & telemetry_info) const +{ + auto is_within = (telemetry_info.last_response + cache_cutoff) >= std::chrono::steady_clock::now (); + return !telemetry_info.awaiting_first_response () && is_within; +} + +void nano::telemetry::ongoing_req_all_peers (std::chrono::milliseconds next_request_interval) +{ + alarm.add (std::chrono::steady_clock::now () + next_request_interval, [this_w = std::weak_ptr (shared_from_this ())]() { + if (auto this_l = this_w.lock ()) + { + // Check if there are any peers which are in the peers list which haven't been request, or any which are below or equal to the cache cutoff time + if (!this_l->stopped) + { + class tag_channel + { + }; + + struct channel_wrapper + { + std::shared_ptr channel; + channel_wrapper (std::shared_ptr const & channel_a) : + channel (channel_a) + { + } + nano::endpoint endpoint () const + { + return channel->get_endpoint (); + } + }; + + // clang-format off + namespace mi = boost::multi_index; + boost::multi_index_container, + mi::const_mem_fun>, + mi::hashed_unique, + mi::member, &channel_wrapper::channel>>>> peers; + // clang-format on + + { + // Copy peers to the multi index container so can get better asymptotic complexity in future operations + auto temp_peers = this_l->network.list (std::numeric_limits::max (), this_l->network_params.protocol.telemetry_protocol_version_min); + peers.insert (temp_peers.begin (), temp_peers.end ()); + } + + { + // Cleanup any stale saved telemetry data for non-existent peers + nano::lock_guard guard (this_l->mutex); + for (auto it = this_l->recent_or_initial_request_telemetry_data.begin (); it != this_l->recent_or_initial_request_telemetry_data.end ();) + { + if (!it->undergoing_request && !this_l->within_cache_cutoff (*it) && peers.count (it->endpoint) == 0) + { + it = this_l->recent_or_initial_request_telemetry_data.erase (it); + } + else + { + ++it; + } + } + + // Remove from peers list if it exists and is within the cache cutoff + for (auto peers_it = peers.begin (); peers_it != peers.end ();) + { + auto it = this_l->recent_or_initial_request_telemetry_data.find (peers_it->endpoint ()); + if (it != this_l->recent_or_initial_request_telemetry_data.cend () && this_l->within_cache_cutoff (*it)) + { + peers_it = peers.erase (peers_it); + } + else + { + ++peers_it; + } + } + } + + // Request data from new peers, or ones which are out of date + for (auto const & peer : boost::make_iterator_range (peers)) + { + this_l->get_metrics_single_peer_async (peer.channel, [](auto const &) { + // Intentionally empty, just using to refresh the cache + }); + } + + // Schedule the next request; Use the default request time unless a telemetry request cache expires sooner + nano::lock_guard guard (this_l->mutex); + long long next_round = std::chrono::duration_cast (this_l->cache_cutoff + this_l->response_time_cutoff).count (); + if (!this_l->recent_or_initial_request_telemetry_data.empty ()) + { + auto range = boost::make_iterator_range (this_l->recent_or_initial_request_telemetry_data.get ()); + for (auto telemetry_info : range) + { + if (!telemetry_info.undergoing_request && peers.count (telemetry_info.endpoint) == 0) + { + auto const last_response = telemetry_info.last_response; + auto now = std::chrono::steady_clock::now (); + if (now > last_response + this_l->cache_cutoff) + { + next_round = std::min (next_round, std::chrono::duration_cast (now - (last_response + this_l->cache_cutoff)).count ()); + } + // We are iterating in sorted order from last_updated, so can break once we have found the first valid one. + break; + } + } + } + + this_l->ongoing_req_all_peers (std::chrono::milliseconds (next_round)); + } + } + }); +} + +std::unordered_map nano::telemetry::get_metrics () +{ + std::unordered_map telemetry_data; + + nano::lock_guard guard (mutex); + auto range = boost::make_iterator_range (recent_or_initial_request_telemetry_data); + // clang-format off + nano::transform_if (range.begin (), range.end (), std::inserter (telemetry_data, telemetry_data.end ()), + [this](auto const & telemetry_info) { return this->within_cache_plus_buffer_cutoff (telemetry_info); }, + [](auto const & telemetry_info) { return std::pair{ telemetry_info.endpoint, telemetry_info.data }; }); + // clang-format on + + return telemetry_data; +} + +void nano::telemetry::get_metrics_single_peer_async (std::shared_ptr const & channel_a, std::function const & callback_a) +{ + auto invoke_callback_with_error = [&callback_a, &worker = this->worker, channel_a]() { + nano::endpoint endpoint; + if (channel_a) + { + endpoint = channel_a->get_endpoint (); + } + worker.push_task ([callback_a, endpoint]() { + auto const error = true; + callback_a ({ nano::telemetry_data{}, endpoint, error }); + }); + }; + + if (!stopped) + { + if (channel_a && (channel_a->get_network_version () >= network_params.protocol.telemetry_protocol_version_min)) + { + auto add_callback_async = [& worker = this->worker, &callback_a](telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a) { + telemetry_data_response telemetry_data_response_l{ telemetry_data_a, endpoint_a, false }; + worker.push_task ([telemetry_data_response_l, callback_a]() { + callback_a (telemetry_data_response_l); + }); + }; + + // Check if this is within the cache + nano::lock_guard guard (mutex); + auto it = recent_or_initial_request_telemetry_data.find (channel_a->get_endpoint ()); + if (it != recent_or_initial_request_telemetry_data.cend () && within_cache_cutoff (*it)) + { + add_callback_async (it->data, it->endpoint); + } + else + { + if (it != recent_or_initial_request_telemetry_data.cend () && it->undergoing_request) + { + // A request is currently undergoing, add the callback + debug_assert (callbacks.count (it->endpoint) > 0); + callbacks[it->endpoint].push_back (callback_a); + } + else + { + if (it == recent_or_initial_request_telemetry_data.cend ()) + { + // Insert dummy values, it's important not to use "last_response" time here without first checking that awaiting_first_response () returns false. + recent_or_initial_request_telemetry_data.emplace (channel_a->get_endpoint (), nano::telemetry_data (), std::chrono::steady_clock::now (), true); + it = recent_or_initial_request_telemetry_data.find (channel_a->get_endpoint ()); + } + else + { + recent_or_initial_request_telemetry_data.modify (it, [](nano::telemetry_info & telemetry_info_a) { + telemetry_info_a.undergoing_request = true; + }); + } + callbacks[it->endpoint].push_back (callback_a); + fire_request_message (channel_a); + } + } + } + else + { + invoke_callback_with_error (); + } + } + else + { + invoke_callback_with_error (); + } +} + +nano::telemetry_data_response nano::telemetry::get_metrics_single_peer (std::shared_ptr const & channel_a) +{ + std::promise promise; + get_metrics_single_peer_async (channel_a, [&promise](telemetry_data_response const & single_metric_data_a) { + promise.set_value (single_metric_data_a); + }); + + return promise.get_future ().get (); +} + +void nano::telemetry::fire_request_message (std::shared_ptr const & channel_a) +{ + // Fire off a telemetry request to all passed in channels + debug_assert (channel_a->get_network_version () >= network_params.protocol.telemetry_protocol_version_min); + + uint64_t round_l; + { + auto it = recent_or_initial_request_telemetry_data.find (channel_a->get_endpoint ()); + recent_or_initial_request_telemetry_data.modify (it, [](nano::telemetry_info & telemetry_info_a) { + ++telemetry_info_a.round; + }); + round_l = it->round; + } + + std::weak_ptr this_w (shared_from_this ()); + nano::telemetry_req message; + // clang-format off + channel_a->send (message, [this_w, endpoint = channel_a->get_endpoint (), round_l](boost::system::error_code const & ec, size_t size_a) { + if (auto this_l = this_w.lock ()) + { + if (ec) + { + // Error sending the telemetry_req message + this_l->stats.inc (nano::stat::type::telemetry, nano::stat::detail::failed_send_telemetry_req); + nano::lock_guard guard (this_l->mutex); + this_l->channel_processed (endpoint, true); + } + else + { + // If no response is seen after a certain period of time remove it + this_l->alarm.add (std::chrono::steady_clock::now () + this_l->response_time_cutoff, [round_l, this_w, endpoint]() { + if (auto this_l = this_w.lock ()) + { + nano::lock_guard guard (this_l->mutex); + auto it = this_l->recent_or_initial_request_telemetry_data.find (endpoint); + if (it != this_l->recent_or_initial_request_telemetry_data.cend () && it->undergoing_request && round_l == it->round) + { + this_l->stats.inc (nano::stat::type::telemetry, nano::stat::detail::no_response_received); + this_l->channel_processed (endpoint, true); + } + } + }); + } + } + }, + nano::buffer_drop_policy::no_socket_drop); + // clang-format on +} + +void nano::telemetry::channel_processed (nano::endpoint const & endpoint_a, bool error_a) +{ + auto it = recent_or_initial_request_telemetry_data.find (endpoint_a); + if (it != recent_or_initial_request_telemetry_data.end ()) + { + if (!error_a) + { + recent_or_initial_request_telemetry_data.modify (it, [](nano::telemetry_info & telemetry_info_a) { + telemetry_info_a.last_response = std::chrono::steady_clock::now (); + telemetry_info_a.undergoing_request = false; + }); + } + else + { + recent_or_initial_request_telemetry_data.erase (endpoint_a); + } + flush_callbacks_async (endpoint_a, error_a); + } +} + +void nano::telemetry::flush_callbacks_async (nano::endpoint const & endpoint_a, bool error_a) +{ + // Post to worker so that it's truly async and not on the calling thread (same problem as std::async otherwise) + worker.push_task ([endpoint_a, error_a, this_w = std::weak_ptr (shared_from_this ())]() { + if (auto this_l = this_w.lock ()) + { + nano::unique_lock lk (this_l->mutex); + while (!this_l->callbacks[endpoint_a].empty ()) + { + lk.unlock (); + this_l->invoke_callbacks (endpoint_a, error_a); + lk.lock (); + } + } + }); +} + +void nano::telemetry::invoke_callbacks (nano::endpoint const & endpoint_a, bool error_a) +{ + std::vector> callbacks_l; + telemetry_data_response response_data{ nano::telemetry_data (), endpoint_a, error_a }; + { + // Copy data so that it can be used outside of holding the lock + nano::lock_guard guard (mutex); + + callbacks_l = callbacks[endpoint_a]; + auto it = recent_or_initial_request_telemetry_data.find (endpoint_a); + if (it != recent_or_initial_request_telemetry_data.end ()) + { + response_data.telemetry_data = it->data; + } + callbacks.erase (endpoint_a); + } + + // Need to account for nodes which disable telemetry data in responses + for (auto & callback : callbacks_l) + { + callback (response_data); + } +} + +size_t nano::telemetry::telemetry_data_size () +{ + nano::lock_guard guard (mutex); + return recent_or_initial_request_telemetry_data.size (); +} + +nano::telemetry_info::telemetry_info (nano::endpoint const & endpoint_a, nano::telemetry_data const & data_a, std::chrono::steady_clock::time_point last_response_a, bool undergoing_request_a) : +endpoint (endpoint_a), +data (data_a), +last_response (last_response_a), +undergoing_request (undergoing_request_a) +{ +} + +bool nano::telemetry_info::awaiting_first_response () const +{ + return data == nano::telemetry_data (); +} + +std::unique_ptr nano::collect_container_info (telemetry & telemetry, const std::string & name) +{ + auto composite = std::make_unique (name); + size_t callbacks_count; + { + nano::lock_guard guard (telemetry.mutex); + std::unordered_map>> callbacks; + callbacks_count = std::accumulate (callbacks.begin (), callbacks.end (), static_cast (0), [](auto total, auto const & callback_a) { + return total += callback_a.second.size (); + }); + } + + composite->add_component (std::make_unique (container_info{ "recent_or_initial_request_telemetry_data", telemetry.telemetry_data_size (), sizeof (decltype (telemetry.recent_or_initial_request_telemetry_data)::value_type) })); + composite->add_component (std::make_unique (container_info{ "callbacks", callbacks_count, sizeof (decltype (telemetry.callbacks)::value_type::second_type) })); + + return composite; +} + +nano::telemetry_data nano::consolidate_telemetry_data (std::vector const & telemetry_datas) +{ + if (telemetry_datas.empty ()) + { + return {}; + } + else if (telemetry_datas.size () == 1) + { + // Only 1 element in the collection, so just return it. + return telemetry_datas.front (); + } + + std::unordered_map protocol_versions; + std::unordered_map vendor_versions; + std::unordered_map bandwidth_caps; + std::unordered_map genesis_blocks; + + // Use a trimmed average which excludes the upper and lower 10% of the results + std::multiset account_counts; + std::multiset block_counts; + std::multiset cemented_counts; + std::multiset peer_counts; + std::multiset unchecked_counts; + std::multiset uptimes; + std::multiset bandwidths; + std::multiset timestamps; + std::multiset active_difficulties; + + for (auto const & telemetry_data : telemetry_datas) + { + account_counts.insert (telemetry_data.account_count); + block_counts.insert (telemetry_data.block_count); + cemented_counts.insert (telemetry_data.cemented_count); + + std::ostringstream ss; + ss << telemetry_data.major_version << "." << telemetry_data.minor_version << "." << telemetry_data.patch_version << "." << telemetry_data.pre_release_version << "." << telemetry_data.maker; + ++vendor_versions[ss.str ()]; + timestamps.insert (std::chrono::duration_cast (telemetry_data.timestamp.time_since_epoch ()).count ()); + ++protocol_versions[telemetry_data.protocol_version]; + peer_counts.insert (telemetry_data.peer_count); + unchecked_counts.insert (telemetry_data.unchecked_count); + uptimes.insert (telemetry_data.uptime); + // 0 has a special meaning (unlimited), don't include it in the average as it will be heavily skewed + if (telemetry_data.bandwidth_cap != 0) + { + bandwidths.insert (telemetry_data.bandwidth_cap); + } + + ++bandwidth_caps[telemetry_data.bandwidth_cap]; + ++genesis_blocks[telemetry_data.genesis_block]; + active_difficulties.insert (telemetry_data.active_difficulty); + } + + // Remove 10% of the results from the lower and upper bounds to catch any outliers. Need at least 10 responses before any are removed. + auto num_either_side_to_remove = telemetry_datas.size () / 10; + + auto strip_outliers_and_sum = [num_either_side_to_remove](auto & counts) { + counts.erase (counts.begin (), std::next (counts.begin (), num_either_side_to_remove)); + counts.erase (std::next (counts.rbegin (), num_either_side_to_remove).base (), counts.end ()); + return std::accumulate (counts.begin (), counts.end (), nano::uint128_t (0), [](nano::uint128_t total, auto count) { + return total += count; + }); + }; + + auto account_sum = strip_outliers_and_sum (account_counts); + auto block_sum = strip_outliers_and_sum (block_counts); + auto cemented_sum = strip_outliers_and_sum (cemented_counts); + auto peer_sum = strip_outliers_and_sum (peer_counts); + auto unchecked_sum = strip_outliers_and_sum (unchecked_counts); + auto uptime_sum = strip_outliers_and_sum (uptimes); + auto bandwidth_sum = strip_outliers_and_sum (bandwidths); + auto active_difficulty_sum = strip_outliers_and_sum (active_difficulties); + + nano::telemetry_data consolidated_data; + auto size = telemetry_datas.size () - num_either_side_to_remove * 2; + consolidated_data.account_count = boost::numeric_cast (account_sum / size); + consolidated_data.block_count = boost::numeric_cast (block_sum / size); + consolidated_data.cemented_count = boost::numeric_cast (cemented_sum / size); + consolidated_data.peer_count = boost::numeric_cast (peer_sum / size); + consolidated_data.uptime = boost::numeric_cast (uptime_sum / size); + consolidated_data.unchecked_count = boost::numeric_cast (unchecked_sum / size); + consolidated_data.active_difficulty = boost::numeric_cast (active_difficulty_sum / size); + + if (!timestamps.empty ()) + { + auto timestamp_sum = strip_outliers_and_sum (timestamps); + consolidated_data.timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (boost::numeric_cast (timestamp_sum / timestamps.size ()))); + } + + auto set_mode_or_average = [](auto const & collection, auto & var, auto const & sum, size_t size) { + auto max = std::max_element (collection.begin (), collection.end (), [](auto const & lhs, auto const & rhs) { + return lhs.second < rhs.second; + }); + if (max->second > 1) + { + var = max->first; + } + else + { + var = (sum / size).template convert_to> (); + } + }; + + auto set_mode = [](auto const & collection, auto & var, size_t size) { + auto max = std::max_element (collection.begin (), collection.end (), [](auto const & lhs, auto const & rhs) { + return lhs.second < rhs.second; + }); + if (max->second > 1) + { + var = max->first; + } + else + { + // Just pick the first one + var = collection.begin ()->first; + } + }; + + // Use the mode of protocol version and vendor version. Also use it for bandwidth cap if there is 2 or more of the same cap. + set_mode_or_average (bandwidth_caps, consolidated_data.bandwidth_cap, bandwidth_sum, size); + set_mode (protocol_versions, consolidated_data.protocol_version, size); + set_mode (genesis_blocks, consolidated_data.genesis_block, size); + + // Vendor version, needs to be parsed out of the string + std::string version; + set_mode (vendor_versions, version, size); + + // May only have major version, but check for optional parameters as well, only output if all are used + std::vector version_fragments; + boost::split (version_fragments, version, boost::is_any_of (".")); + debug_assert (version_fragments.size () == 5); + consolidated_data.major_version = boost::lexical_cast (version_fragments.front ()); + consolidated_data.minor_version = boost::lexical_cast (version_fragments[1]); + consolidated_data.patch_version = boost::lexical_cast (version_fragments[2]); + consolidated_data.pre_release_version = boost::lexical_cast (version_fragments[3]); + consolidated_data.maker = boost::lexical_cast (version_fragments[4]); + + return consolidated_data; +} + +nano::telemetry_data nano::local_telemetry_data (nano::ledger_cache const & ledger_cache_a, nano::network & network_a, uint64_t bandwidth_limit_a, nano::network_params const & network_params_a, std::chrono::steady_clock::time_point statup_time_a, uint64_t active_difficulty_a, nano::keypair const & node_id_a) +{ + nano::telemetry_data telemetry_data; + telemetry_data.node_id = node_id_a.pub; + telemetry_data.block_count = ledger_cache_a.block_count; + telemetry_data.cemented_count = ledger_cache_a.cemented_count; + telemetry_data.bandwidth_cap = bandwidth_limit_a; + telemetry_data.protocol_version = network_params_a.protocol.protocol_version; + telemetry_data.uptime = std::chrono::duration_cast (std::chrono::steady_clock::now () - statup_time_a).count (); + telemetry_data.unchecked_count = ledger_cache_a.unchecked_count; + telemetry_data.genesis_block = network_params_a.ledger.genesis_hash; + telemetry_data.peer_count = nano::narrow_cast (network_a.size ()); + telemetry_data.account_count = ledger_cache_a.account_count; + telemetry_data.major_version = nano::get_major_node_version (); + telemetry_data.minor_version = nano::get_minor_node_version (); + telemetry_data.patch_version = nano::get_patch_node_version (); + telemetry_data.pre_release_version = nano::get_pre_release_node_version (); + telemetry_data.maker = 0; // 0 Indicates it originated from the NF + telemetry_data.timestamp = std::chrono::system_clock::now (); + telemetry_data.active_difficulty = active_difficulty_a; + // Make sure this is the final operation! + telemetry_data.sign (node_id_a); + return telemetry_data; +} diff --git a/nano/node/telemetry.hpp b/nano/node/telemetry.hpp new file mode 100644 index 0000000000..06cf07c836 --- /dev/null +++ b/nano/node/telemetry.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class network; +class alarm; +class worker; +class stat; +namespace transport +{ + class channel; +} + +/* + * Holds a response from a telemetry request + */ +class telemetry_data_response +{ +public: + nano::telemetry_data telemetry_data; + nano::endpoint endpoint; + bool error{ true }; +}; + +class telemetry_info final +{ +public: + telemetry_info () = default; + telemetry_info (nano::endpoint const & endpoint, nano::telemetry_data const & data, std::chrono::steady_clock::time_point last_response, bool undergoing_request); + bool awaiting_first_response () const; + + nano::endpoint endpoint; + nano::telemetry_data data; + std::chrono::steady_clock::time_point last_response; + bool undergoing_request{ false }; + uint64_t round{ 0 }; +}; + +/* + * This class requests node telemetry metrics from peers and invokes any callbacks which have been aggregated. + * All calls to get_metrics return cached data, it does not do any requests, these are periodically done in + * ongoing_req_all_peers. This can be disabled with the disable_ongoing_telemetry_requests node flag. + * Calls to get_metrics_single_peer_async will wait until a response is made if it is not within the cache + * cut off. + */ +class telemetry : public std::enable_shared_from_this +{ +public: + telemetry (nano::network &, nano::alarm &, nano::worker &, nano::observer_set &, nano::stat &, nano::network_params &, bool); + void start (); + void stop (); + + /* + * Received telemetry metrics from this peer + */ + void set (nano::telemetry_ack const &, nano::transport::channel const &); + + /* + * This returns what ever is in the cache + */ + std::unordered_map get_metrics (); + + /* + * This makes a telemetry request to the specific channel. + * Error is set for: no response received, no payload received, invalid signature or unsound metrics in message (e.g different genesis block) + */ + void get_metrics_single_peer_async (std::shared_ptr const &, std::function const &); + + /* + * A blocking version of get_metrics_single_peer_async + */ + telemetry_data_response get_metrics_single_peer (std::shared_ptr const &); + + /* + * Return the number of node metrics collected + */ + size_t telemetry_data_size (); + + /* + * Returns the time for the cache, response and a small buffer for alarm operations to be scheduled and completed + */ + std::chrono::milliseconds cache_plus_buffer_cutoff_time () const; + +private: + class tag_endpoint + { + }; + class tag_last_updated + { + }; + + nano::network & network; + nano::alarm & alarm; + nano::worker & worker; + nano::observer_set & observers; + nano::stat & stats; + /* Important that this is a reference to the node network_params for tests which want to modify genesis block */ + nano::network_params & network_params; + bool disable_ongoing_requests; + + std::atomic stopped{ false }; + + std::mutex mutex; + // clang-format off + // This holds the last telemetry data received from peers or can be a placeholder awaiting the first response (check with awaiting_first_response ()) + boost::multi_index_container, + mi::member>, + mi::ordered_non_unique, + mi::member>>> recent_or_initial_request_telemetry_data; + // clang-format on + + // Anything older than this requires requesting metrics from other nodes. + std::chrono::seconds const cache_cutoff{ nano::telemetry_cache_cutoffs::network_to_time (network_params.network) }; + + // The maximum time spent waiting for a response to a telemetry request + std::chrono::seconds const response_time_cutoff{ network_params.network.is_test_network () ? (is_sanitizer_build || nano::running_within_valgrind () ? 6 : 3) : 10 }; + + std::unordered_map>> callbacks; + + void ongoing_req_all_peers (std::chrono::milliseconds); + + void fire_request_message (std::shared_ptr const &); + void channel_processed (nano::endpoint const &, bool); + void flush_callbacks_async (nano::endpoint const &, bool); + void invoke_callbacks (nano::endpoint const &, bool); + + bool within_cache_cutoff (nano::telemetry_info const &) const; + bool within_cache_plus_buffer_cutoff (telemetry_info const &) const; + bool verify_message (nano::telemetry_ack const &, nano::transport::channel const &); + friend std::unique_ptr collect_container_info (telemetry &, const std::string &); + friend class telemetry_remove_peer_invalid_signature_Test; +}; + +std::unique_ptr collect_container_info (telemetry & telemetry, const std::string & name); + +nano::telemetry_data consolidate_telemetry_data (std::vector const & telemetry_data); +nano::telemetry_data local_telemetry_data (nano::ledger_cache const &, nano::network &, uint64_t, nano::network_params const &, std::chrono::steady_clock::time_point, uint64_t, nano::keypair const &); +} diff --git a/nano/node/testing.cpp b/nano/node/testing.cpp index e4c1fe0152..461abec1d7 100644 --- a/nano/node/testing.cpp +++ b/nano/node/testing.cpp @@ -1,3 +1,4 @@ +#define IGNORE_GTEST_INCL #include #include #include @@ -5,7 +6,6 @@ #include #include -#include #include @@ -22,17 +22,23 @@ std::string nano::error_system_messages::message (int ev) const return "Invalid error code"; } +std::shared_ptr nano::system::add_node (nano::node_flags node_flags_a, nano::transport::transport_type type_a) +{ + return add_node (nano::node_config (nano::get_available_port (), logging), node_flags_a, type_a); +} + /** Returns the node added. */ std::shared_ptr nano::system::add_node (nano::node_config const & node_config_a, nano::node_flags node_flags_a, nano::transport::transport_type type_a) { - auto node (std::make_shared (io_ctx, nano::unique_path (), alarm, node_config_a, work, node_flags_a)); - assert (!node->init_error ()); + auto node (std::make_shared (io_ctx, nano::unique_path (), alarm, node_config_a, work, node_flags_a, node_sequence++)); + debug_assert (!node->init_error ()); node->start (); node->wallets.create (nano::random_wallet_id ()); nodes.reserve (nodes.size () + 1); nodes.push_back (node); if (nodes.size () > 1) { + debug_assert (nodes.size () - 1 <= node->network_params.node.max_peers_per_ip || node->flags.disable_max_peers_per_ip); // Check that we don't start more nodes than limit for single IP address auto begin = nodes.end () - 2; for (auto i (begin), j (begin + 1), n (nodes.end ()); j != n; ++i, ++j) { @@ -78,7 +84,7 @@ std::shared_ptr nano::system::add_node (nano::node_config const & no { poll (); ++iterations1; - assert (iterations1 < 10000); + debug_assert (iterations1 < 10000); } } else @@ -88,7 +94,7 @@ std::shared_ptr nano::system::add_node (nano::node_config const & no { poll (); ++iterations1; - assert (iterations1 < 10000); + debug_assert (iterations1 < 10000); } } @@ -105,14 +111,14 @@ nano::system::system () logging.init (nano::unique_path ()); } -nano::system::system (uint16_t port_a, uint16_t count_a, nano::transport::transport_type type_a) : +nano::system::system (uint16_t count_a, nano::transport::transport_type type_a, nano::node_flags flags_a) : system () { nodes.reserve (count_a); for (uint16_t i (0); i < count_a; ++i) { - nano::node_config config (port_a + i, logging); - add_node (config, nano::node_flags (), type_a); + nano::node_config config (nano::get_available_port (), logging); + add_node (config, flags_a, type_a); } } @@ -139,10 +145,10 @@ nano::system::~system () std::shared_ptr nano::system::wallet (size_t index_a) { - assert (nodes.size () > index_a); + debug_assert (nodes.size () > index_a); auto size (nodes[index_a]->wallets.items.size ()); (void)size; - assert (size == 1); + debug_assert (size == 1); return nodes[index_a]->wallets.items.begin ()->second; } @@ -150,12 +156,68 @@ nano::account nano::system::account (nano::transaction const & transaction_a, si { auto wallet_l (wallet (index_a)); auto keys (wallet_l->store.begin (transaction_a)); - assert (keys != wallet_l->store.end ()); + debug_assert (keys != wallet_l->store.end ()); auto result (keys->first); - assert (++keys == wallet_l->store.end ()); + debug_assert (++keys == wallet_l->store.end ()); return nano::account (result); } +uint64_t nano::system::work_generate_limited (nano::block_hash const & root_a, uint64_t min_a, uint64_t max_a) +{ + debug_assert (min_a > 0); + uint64_t result = 0; + do + { + result = *work.generate (root_a, min_a); + } while (nano::work_difficulty (nano::work_version::work_1, root_a, result) >= max_a); + return result; +} + +std::unique_ptr nano::upgrade_epoch (nano::work_pool & pool_a, nano::ledger & ledger_a, nano::epoch epoch_a) +{ + auto transaction (ledger_a.store.tx_begin_write ()); + auto account = nano::test_genesis_key.pub; + auto latest = ledger_a.latest (transaction, account); + auto balance = ledger_a.account_balance (transaction, account); + + nano::state_block_builder builder; + std::error_code ec; + auto epoch = builder + .account (nano::test_genesis_key.pub) + .previous (latest) + .balance (balance) + .link (ledger_a.epoch_link (epoch_a)) + .representative (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*pool_a.generate (latest, nano::work_threshold (nano::work_version::work_1, nano::block_details (epoch_a, false, false, true)))) + .build (ec); + + bool error{ true }; + if (!ec && epoch) + { + error = ledger_a.process (transaction, *epoch).code != nano::process_result::progress; + } + + return !error ? std::move (epoch) : nullptr; +} + +void nano::blocks_confirm (nano::node & node_a, std::vector> const & blocks_a) +{ + // Finish processing all blocks + node_a.block_processor.flush (); + for (auto const & block : blocks_a) + { + // A sideband is required to start an election + debug_assert (block->has_sideband ()); + node_a.block_confirm (block); + } +} + +std::unique_ptr nano::system::upgrade_genesis_epoch (nano::node & node_a, nano::epoch const epoch_a) +{ + return upgrade_epoch (work, node_a.ledger, epoch_a); +} + void nano::system::deadline_set (std::chrono::duration const & delta_a) { deadline = std::chrono::steady_clock::now () + delta_a * deadline_scaling_factor; @@ -174,6 +236,17 @@ std::error_code nano::system::poll (std::chrono::nanoseconds const & wait_time) return ec; } +std::error_code nano::system::poll_until_true (std::chrono::nanoseconds deadline_a, std::function predicate_a) +{ + std::error_code ec; + deadline_set (deadline_a); + while (!ec && !predicate_a ()) + { + ec = poll (); + } + return ec; +} + namespace { class traffic_generator : public std::enable_shared_from_this @@ -215,8 +288,8 @@ void nano::system::generate_usage_traffic (uint32_t count_a, uint32_t wait_a) void nano::system::generate_usage_traffic (uint32_t count_a, uint32_t wait_a, size_t index_a) { - assert (nodes.size () > index_a); - assert (count_a > 0); + debug_assert (nodes.size () > index_a); + debug_assert (count_a > 0); auto generate (std::make_shared (count_a, wait_a, nodes[index_a], *this)); generate->run (); } @@ -224,7 +297,7 @@ void nano::system::generate_usage_traffic (uint32_t count_a, uint32_t wait_a, si void nano::system::generate_rollback (nano::node & node_a, std::vector & accounts_a) { auto transaction (node_a.store.tx_begin_write ()); - assert (std::numeric_limits::max () > accounts_a.size ()); + debug_assert (std::numeric_limits::max () > accounts_a.size ()); auto index (random_pool::generate_word32 (0, static_cast (accounts_a.size () - 1))); auto account (accounts_a[index]); nano::account_info info; @@ -240,10 +313,10 @@ void nano::system::generate_rollback (nano::node & node_a, std::vector> rollback_list; auto error = node_a.ledger.rollback (transaction, hash, rollback_list); (void)error; - assert (!error); + debug_assert (!error); for (auto & i : rollback_list) { - node_a.wallets.watcher->remove (i); + node_a.wallets.watcher->remove (*i); node_a.active.erase (*i); } } @@ -302,7 +375,7 @@ void nano::system::generate_activity (nano::node & node_a, std::vector & accounts_a) { - assert (std::numeric_limits::max () > accounts_a.size ()); + debug_assert (std::numeric_limits::max () > accounts_a.size ()); auto index (random_pool::generate_word32 (0, static_cast (accounts_a.size () - 1))); auto result (accounts_a[index]); return result; @@ -330,7 +403,7 @@ void nano::system::generate_send_existing (nano::node & node_a, std::vectorfirst); source = get_random_account (accounts_a); amount = get_random_amount (transaction, node_a, source); @@ -339,7 +412,7 @@ void nano::system::generate_send_existing (nano::node & node_a, std::vectorsend_sync (source, destination, amount)); (void)hash; - assert (!hash.is_zero ()); + debug_assert (!hash.is_zero ()); } } @@ -351,7 +424,7 @@ void nano::system::generate_change_known (nano::node & node_a, std::vectorchange_sync (source, destination)); (void)change_error; - assert (!change_error); + debug_assert (!change_error); } } @@ -364,13 +437,13 @@ void nano::system::generate_change_unknown (nano::node & node_a, std::vectorchange_sync (source, destination)); (void)change_error; - assert (!change_error); + debug_assert (!change_error); } } void nano::system::generate_send_new (nano::node & node_a, std::vector & accounts_a) { - assert (node_a.wallets.items.size () == 1); + debug_assert (node_a.wallets.items.size () == 1); nano::uint128_t amount; nano::account source; { @@ -384,7 +457,7 @@ void nano::system::generate_send_new (nano::node & node_a, std::vectorsend_sync (source, pub, amount)); (void)hash; - assert (!hash.is_zero ()); + debug_assert (!hash.is_zero ()); } } diff --git a/nano/node/testing.hpp b/nano/node/testing.hpp index ec22c4d138..b981f0336f 100644 --- a/nano/node/testing.hpp +++ b/nano/node/testing.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -18,7 +17,7 @@ class system final { public: system (); - system (uint16_t, uint16_t, nano::transport::transport_type = nano::transport::transport_type::tcp); + system (uint16_t, nano::transport::transport_type = nano::transport::transport_type::tcp, nano::node_flags = nano::node_flags ()); ~system (); void generate_activity (nano::node &, std::vector &); void generate_mass_activity (uint32_t, nano::node &); @@ -32,23 +31,31 @@ class system final void generate_receive (nano::node &); void generate_send_new (nano::node &, std::vector &); void generate_send_existing (nano::node &, std::vector &); + std::unique_ptr upgrade_genesis_epoch (nano::node &, nano::epoch const); std::shared_ptr wallet (size_t); nano::account account (nano::transaction const &, size_t); + /** Generate work with difficulty between \p min_difficulty_a (inclusive) and \p max_difficulty_a (exclusive) */ + uint64_t work_generate_limited (nano::block_hash const & root_a, uint64_t min_difficulty_a, uint64_t max_difficulty_a); /** * Polls, sleep if there's no work to be done (default 50ms), then check the deadline * @returns 0 or nano::deadline_expired */ std::error_code poll (const std::chrono::nanoseconds & sleep_time = std::chrono::milliseconds (50)); + std::error_code poll_until_true (std::chrono::nanoseconds deadline, std::function); void stop (); void deadline_set (const std::chrono::duration & delta); + std::shared_ptr add_node (nano::node_flags = nano::node_flags (), nano::transport::transport_type = nano::transport::transport_type::tcp); std::shared_ptr add_node (nano::node_config const &, nano::node_flags = nano::node_flags (), nano::transport::transport_type = nano::transport::transport_type::tcp); boost::asio::io_context io_ctx; nano::alarm alarm{ io_ctx }; std::vector> nodes; nano::logging logging; - nano::work_pool work{ 1 }; + nano::work_pool work{ std::max (std::thread::hardware_concurrency (), 1u) }; std::chrono::time_point> deadline{ std::chrono::steady_clock::time_point::max () }; double deadline_scaling_factor{ 1.0 }; + unsigned node_sequence{ 0 }; }; +std::unique_ptr upgrade_epoch (nano::work_pool &, nano::ledger &, nano::epoch); +void blocks_confirm (nano::node &, std::vector> const &); } REGISTER_ERROR_CODES (nano, error_system); diff --git a/nano/node/transport/tcp.cpp b/nano/node/transport/tcp.cpp index 4951e227a7..3d2fe64210 100644 --- a/nano/node/transport/tcp.cpp +++ b/nano/node/transport/tcp.cpp @@ -2,6 +2,8 @@ #include #include +#include + nano::transport::channel_tcp::channel_tcp (nano::node & node_a, std::weak_ptr socket_a) : channel (node_a), socket (socket_a) @@ -14,7 +16,7 @@ nano::transport::channel_tcp::~channel_tcp () // Close socket. Exception: socket is used by bootstrap_server if (auto socket_l = socket.lock ()) { - if (!server) + if (!temporary) { socket_l->close (); } @@ -48,11 +50,11 @@ bool nano::transport::channel_tcp::operator== (nano::transport::channel const & return result; } -void nano::transport::channel_tcp::send_buffer (nano::shared_const_buffer const & buffer_a, nano::stat::detail detail_a, std::function const & callback_a) +void nano::transport::channel_tcp::send_buffer (nano::shared_const_buffer const & buffer_a, nano::stat::detail detail_a, std::function const & callback_a, nano::buffer_drop_policy drop_policy_a) { if (auto socket_l = socket.lock ()) { - socket_l->async_write (buffer_a, tcp_callback (detail_a, socket_l->remote_endpoint (), callback_a)); + socket_l->async_write (buffer_a, tcp_callback (detail_a, socket_l->remote_endpoint (), callback_a), drop_policy_a); } } @@ -63,9 +65,7 @@ std::function nano::transport:: std::function nano::transport::channel_tcp::tcp_callback (nano::stat::detail detail_a, nano::tcp_endpoint const & endpoint_a, std::function const & callback_a) const { - // clang-format off - return [endpoint_a, node = std::weak_ptr (node.shared ()), callback_a ](boost::system::error_code const & ec, size_t size_a) - { + return [endpoint_a, node = std::weak_ptr (node.shared ()), callback_a](boost::system::error_code const & ec, size_t size_a) { if (auto node_l = node.lock ()) { if (!ec) @@ -82,7 +82,6 @@ std::function nano::transport:: } } }; - // clang-format on } std::string nano::transport::channel_tcp::to_string () const @@ -102,7 +101,7 @@ node (node_a) bool nano::transport::tcp_channels::insert (std::shared_ptr channel_a, std::shared_ptr socket_a, std::shared_ptr bootstrap_server_a) { auto endpoint (channel_a->get_tcp_endpoint ()); - assert (endpoint.address ().is_v6 ()); + debug_assert (endpoint.address ().is_v6 ()); auto udp_endpoint (nano::transport::map_tcp_to_endpoint (endpoint)); bool error (true); if (!node.network.not_a_peer (udp_endpoint, node.config.allow_local_peers) && !stopped) @@ -112,11 +111,12 @@ bool nano::transport::tcp_channels::insert (std::shared_ptr ().end ()) { auto node_id (channel_a->get_node_id ()); - if (!channel_a->server) + if (!channel_a->temporary) { channels.get ().erase (node_id); } - channels.get ().insert ({ channel_a, socket_a, bootstrap_server_a }); + channels.get ().emplace (channel_a, socket_a, bootstrap_server_a); + attempts.get ().erase (endpoint); error = false; lock.unlock (); node.network.channel_observer (channel_a); @@ -153,7 +153,7 @@ std::shared_ptr nano::transport::tcp_channels::fin return result; } -std::unordered_set> nano::transport::tcp_channels::random_set (size_t count_a) const +std::unordered_set> nano::transport::tcp_channels::random_set (size_t count_a, uint8_t min_version, bool include_temporary_channels_a) const { std::unordered_set> result; result.reserve (count_a); @@ -168,7 +168,12 @@ std::unordered_set> nano::transport::t for (auto i (0); i < random_cutoff && result.size () < count_a; ++i) { auto index (nano::random_pool::generate_word32 (0, static_cast (peers_size - 1))); - result.insert (channels.get ()[index].channel); + + auto channel = channels.get ()[index].channel; + if (channel->get_network_version () >= min_version && (include_temporary_channels_a || !channel->temporary)) + { + result.insert (channel); + } } } return result; @@ -177,15 +182,15 @@ std::unordered_set> nano::transport::t void nano::transport::tcp_channels::random_fill (std::array & target_a) const { auto peers (random_set (target_a.size ())); - assert (peers.size () <= target_a.size ()); + debug_assert (peers.size () <= target_a.size ()); auto endpoint (nano::endpoint (boost::asio::ip::address_v6{}, 0)); - assert (endpoint.address ().is_v6 ()); + debug_assert (endpoint.address ().is_v6 ()); std::fill (target_a.begin (), target_a.end (), endpoint); auto j (target_a.begin ()); for (auto i (peers.begin ()), n (peers.end ()); i != n; ++i, ++j) { - assert ((*i)->get_endpoint ().address ().is_v6 ()); - assert (j < target_a.end ()); + debug_assert ((*i)->get_endpoint ().address ().is_v6 ()); + debug_assert (j < target_a.end ()); *j = (*i)->get_endpoint (); } } @@ -254,9 +259,21 @@ nano::tcp_endpoint nano::transport::tcp_channels::bootstrap_peer (uint8_t connec return result; } +void nano::transport::tcp_channels::process_messages () +{ + while (!stopped) + { + auto item (node.network.tcp_message_manager.get_message ()); + if (item.message != nullptr) + { + process_message (*item.message, item.endpoint, item.node_id, item.socket, item.type); + } + } +} + void nano::transport::tcp_channels::process_message (nano::message const & message_a, nano::tcp_endpoint const & endpoint_a, nano::account const & node_id_a, std::shared_ptr socket_a, nano::bootstrap_server_type type_a) { - if (!stopped) + if (!stopped && message_a.header.version_using >= protocol_constants ().protocol_version_min (node.ledger.cache.epoch_2_started)) { auto channel (node.network.find_channel (nano::transport::map_tcp_to_endpoint (endpoint_a))); if (channel) @@ -270,52 +287,39 @@ void nano::transport::tcp_channels::process_message (nano::message const & messa { node.network.process_message (message_a, channel); } - else if (!node_id_a.is_zero ()) + else if (!node.network.excluded_peers.check (endpoint_a)) { - // Add temporary channel - socket_a->set_writer_concurrency (nano::socket::concurrency::multi_writer); - auto temporary_channel (std::make_shared (node, socket_a)); - assert (endpoint_a == temporary_channel->get_tcp_endpoint ()); - temporary_channel->set_node_id (node_id_a); - temporary_channel->set_network_version (message_a.header.version_using); - temporary_channel->set_last_packet_received (std::chrono::steady_clock::now ()); - temporary_channel->set_last_packet_sent (std::chrono::steady_clock::now ()); - temporary_channel->server = true; - assert (type_a == nano::bootstrap_server_type::realtime || type_a == nano::bootstrap_server_type::realtime_response_server); - // Don't insert temporary channels for response_server - if (type_a == nano::bootstrap_server_type::realtime) + if (!node_id_a.is_zero ()) { - insert (temporary_channel, socket_a, nullptr); + // Add temporary channel + socket_a->set_writer_concurrency (nano::socket::concurrency::multi_writer); + auto temporary_channel (std::make_shared (node, socket_a)); + debug_assert (endpoint_a == temporary_channel->get_tcp_endpoint ()); + temporary_channel->set_node_id (node_id_a); + temporary_channel->set_network_version (message_a.header.version_using); + temporary_channel->set_last_packet_received (std::chrono::steady_clock::now ()); + temporary_channel->set_last_packet_sent (std::chrono::steady_clock::now ()); + temporary_channel->temporary = true; + debug_assert (type_a == nano::bootstrap_server_type::realtime || type_a == nano::bootstrap_server_type::realtime_response_server); + // Don't insert temporary channels for response_server + if (type_a == nano::bootstrap_server_type::realtime) + { + insert (temporary_channel, socket_a, nullptr); + } + node.network.process_message (message_a, temporary_channel); + } + else + { + // Initial node_id_handshake request without node ID + debug_assert (message_a.header.type == nano::message_type::node_id_handshake); + debug_assert (type_a == nano::bootstrap_server_type::undefined); + node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } - node.network.process_message (message_a, temporary_channel); - } - else - { - // Initial node_id_handshake request without node ID - assert (message_a.header.type == nano::message_type::node_id_handshake); - assert (type_a == nano::bootstrap_server_type::undefined); - node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } } } } -void nano::transport::tcp_channels::process_keepalive (nano::keepalive const & message_a, nano::tcp_endpoint const & endpoint_a) -{ - if (!max_ip_connections (endpoint_a)) - { - // Check for special node port data - auto peer0 (message_a.peers[0]); - if (peer0.address () == boost::asio::ip::address_v6{} && peer0.port () != 0) - { - nano::endpoint new_endpoint (endpoint_a.address (), peer0.port ()); - node.network.merge_peer (new_endpoint); - } - auto udp_channel (std::make_shared (node.network.udp_channels, nano::transport::map_tcp_to_endpoint (endpoint_a), node.network_params.protocol.protocol_version)); - node.network.process_message (message_a, udp_channel); - } -} - void nano::transport::tcp_channels::start () { ongoing_keepalive (); @@ -344,8 +348,16 @@ void nano::transport::tcp_channels::stop () bool nano::transport::tcp_channels::max_ip_connections (nano::tcp_endpoint const & endpoint_a) { - nano::unique_lock lock (mutex); - bool result (channels.get ().count (endpoint_a.address ()) >= nano::transport::max_peers_per_ip); + bool result (false); + if (!node.flags.disable_max_peers_per_ip) + { + nano::unique_lock lock (mutex); + result = channels.get ().count (endpoint_a.address ()) >= node.network_params.node.max_peers_per_ip; + if (!result) + { + result = attempts.get ().count (endpoint_a.address ()) >= node.network_params.node.max_peers_per_ip; + } + } return result; } @@ -353,24 +365,23 @@ bool nano::transport::tcp_channels::reachout (nano::endpoint const & endpoint_a) { auto tcp_endpoint (nano::transport::map_endpoint_to_tcp (endpoint_a)); // Don't overload single IP - bool error = max_ip_connections (tcp_endpoint); - if (!error) + bool error = node.network.excluded_peers.check (tcp_endpoint) || max_ip_connections (tcp_endpoint); + if (!error && !node.flags.disable_tcp_realtime) { // Don't keepalive to nodes that already sent us something error |= find_channel (tcp_endpoint) != nullptr; nano::lock_guard lock (mutex); - auto existing (attempts.find (tcp_endpoint)); - error |= existing != attempts.end (); - attempts.insert ({ tcp_endpoint, std::chrono::steady_clock::now () }); + auto inserted (attempts.emplace (tcp_endpoint)); + error |= !inserted.second; } return error; } -std::unique_ptr nano::transport::tcp_channels::collect_seq_con_info (std::string const & name) +std::unique_ptr nano::transport::tcp_channels::collect_container_info (std::string const & name) { - size_t channels_count = 0; - size_t attemps_count = 0; - size_t node_id_handshake_sockets_count = 0; + size_t channels_count; + size_t attemps_count; + size_t node_id_handshake_sockets_count; { nano::lock_guard guard (mutex); channels_count = channels.size (); @@ -378,10 +389,10 @@ std::unique_ptr nano::transport::tcp_channels::col node_id_handshake_sockets_count = node_id_handshake_sockets.size (); } - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "channels", channels_count, sizeof (decltype (channels)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "attempts", attemps_count, sizeof (decltype (attempts)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "node_id_handshake_sockets", node_id_handshake_sockets_count, sizeof (decltype (node_id_handshake_sockets)::value_type) })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "channels", channels_count, sizeof (decltype (channels)::value_type) })); + composite->add_component (std::make_unique (container_info{ "attempts", attemps_count, sizeof (decltype (attempts)::value_type) })); + composite->add_component (std::make_unique (container_info{ "node_id_handshake_sockets", node_id_handshake_sockets_count, sizeof (decltype (node_id_handshake_sockets)::value_type) })); return composite; } @@ -392,8 +403,12 @@ void nano::transport::tcp_channels::purge (std::chrono::steady_clock::time_point auto disconnect_cutoff (channels.get ().lower_bound (cutoff_a)); channels.get ().erase (channels.get ().begin (), disconnect_cutoff); // Remove keepalive attempt tracking for attempts older than cutoff - auto attempts_cutoff (attempts.get<1> ().lower_bound (cutoff_a)); - attempts.get<1> ().erase (attempts.get<1> ().begin (), attempts_cutoff); + auto attempts_cutoff (attempts.get ().lower_bound (cutoff_a)); + attempts.get ().erase (attempts.get ().begin (), attempts_cutoff); + + // Check if any tcp channels belonging to old protocol versions which may still be alive due to async operations + auto lower_bound = channels.get ().lower_bound (node.network_params.protocol.protocol_version_min (node.ledger.cache.epoch_2_started)); + channels.get ().erase (channels.get ().begin (), lower_bound); // Cleanup any sockets which may still be existing from failed node id handshakes node_id_handshake_sockets.erase (std::remove_if (node_id_handshake_sockets.begin (), node_id_handshake_sockets.end (), [this](auto socket) { @@ -426,8 +441,8 @@ void nano::transport::tcp_channels::ongoing_keepalive () size_t random_count (std::min (static_cast (6), static_cast (std::ceil (std::sqrt (node.network.udp_channels.size ()))))); for (auto i (0); i <= random_count; ++i) { - auto tcp_endpoint (node.network.udp_channels.bootstrap_peer (node.network_params.protocol.tcp_realtime_protocol_version_min)); - if (tcp_endpoint != invalid_endpoint && find_channel (tcp_endpoint) == nullptr) + auto tcp_endpoint (node.network.udp_channels.bootstrap_peer (node.network_params.protocol.protocol_version_min (node.ledger.cache.epoch_2_started))); + if (tcp_endpoint != invalid_endpoint && find_channel (tcp_endpoint) == nullptr && !node.network.excluded_peers.check (tcp_endpoint)) { start_tcp (nano::transport::map_tcp_to_endpoint (tcp_endpoint)); } @@ -445,13 +460,24 @@ void nano::transport::tcp_channels::ongoing_keepalive () }); } -void nano::transport::tcp_channels::list (std::deque> & deque_a) +void nano::transport::tcp_channels::list_below_version (std::vector> & channels_a, uint8_t cutoff_version_a) { nano::lock_guard lock (mutex); - for (auto i (channels.begin ()), j (channels.end ()); i != j; ++i) - { - deque_a.push_back (i->channel); - } + // clang-format off + nano::transform_if (channels.get ().begin (), channels.get ().end (), std::back_inserter (channels_a), + [cutoff_version_a](auto & channel_a) { return channel_a.channel->get_network_version () < cutoff_version_a; }, + [](const auto & channel) { return channel.channel; }); + // clang-format on +} + +void nano::transport::tcp_channels::list (std::deque> & deque_a, uint8_t minimum_version_a, bool include_temporary_channels_a) +{ + nano::lock_guard lock (mutex); + // clang-format off + nano::transform_if (channels.get ().begin (), channels.get ().end (), std::back_inserter (deque_a), + [include_temporary_channels_a, minimum_version_a](auto & channel_a) { return channel_a.channel->get_network_version () >= minimum_version_a && (include_temporary_channels_a || !channel_a.channel->temporary); }, + [](const auto & channel) { return channel.channel; }); + // clang-format on } void nano::transport::tcp_channels::modify (std::shared_ptr channel_a, std::function)> modify_callback_a) @@ -520,7 +546,7 @@ void nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint_a // TCP node ID handshake auto cookie (node_l->network.syn_cookies.assign (endpoint_a)); nano::node_id_handshake message (cookie, boost::none); - auto bytes = message.to_shared_const_buffer (); + auto bytes = message.to_shared_const_buffer (node_l->ledger.cache.epoch_2_started); if (node_l->config.logging.network_node_id_handshake_logging ()) { node_l->logger.try_log (boost::str (boost::format ("Node ID handshake request sent with node ID %1% to %2%: query %3%") % node_l->node_id.pub.to_node_id () % endpoint_a % (*cookie).to_string ())); @@ -540,6 +566,7 @@ void nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint_a if (auto socket_l = channel->socket.lock ()) { node_l->network.tcp_channels.remove_node_id_handshake_socket (socket_l); + socket_l->close (); } if (node_l->config.logging.network_node_id_handshake_logging ()) { @@ -563,21 +590,26 @@ void nano::transport::tcp_channels::start_tcp_receive_node_id (std::shared_ptr node_w (node.shared ()); if (auto socket_l = channel_a->socket.lock ()) { - // clang-format off - auto cleanup_and_udp_fallback = [socket_w = channel_a->socket, node_w](nano::endpoint const & endpoint_a, std::function)> const & callback_a) { + auto cleanup_node_id_handshake_socket = [socket_w = channel_a->socket, node_w](nano::endpoint const & endpoint_a, std::function)> const & callback_a) { if (auto node_l = node_w.lock ()) { - node_l->network.tcp_channels.udp_fallback (endpoint_a, callback_a); - if (auto socket_l = socket_w.lock ()) { node_l->network.tcp_channels.remove_node_id_handshake_socket (socket_l); + socket_l->close (); } } }; - // clang-format on - socket_l->async_read (receive_buffer_a, 8 + sizeof (nano::account) + sizeof (nano::account) + sizeof (nano::signature), [node_w, channel_a, endpoint_a, receive_buffer_a, callback_a, cleanup_and_udp_fallback](boost::system::error_code const & ec, size_t size_a) { + auto cleanup_and_udp_fallback = [socket_w = channel_a->socket, node_w, cleanup_node_id_handshake_socket](nano::endpoint const & endpoint_a, std::function)> const & callback_a) { + if (auto node_l = node_w.lock ()) + { + node_l->network.tcp_channels.udp_fallback (endpoint_a, callback_a); + cleanup_node_id_handshake_socket (endpoint_a, callback_a); + } + }; + + socket_l->async_read (receive_buffer_a, 8 + sizeof (nano::account) + sizeof (nano::account) + sizeof (nano::signature), [node_w, channel_a, endpoint_a, receive_buffer_a, callback_a, cleanup_and_udp_fallback, cleanup_node_id_handshake_socket](boost::system::error_code const & ec, size_t size_a) { if (auto node_l = node_w.lock ()) { if (!ec && channel_a) @@ -586,72 +618,91 @@ void nano::transport::tcp_channels::start_tcp_receive_node_id (std::shared_ptrdata (), size_a); nano::message_header header (error, stream); - if (!error && header.type == nano::message_type::node_id_handshake && header.version_using >= node_l->network_params.protocol.protocol_version_min) + if (!error && header.type == nano::message_type::node_id_handshake) { - nano::node_id_handshake message (error, stream, header); - if (!error && message.response && message.query) + if (header.version_using >= node_l->network_params.protocol.protocol_version_min (node_l->ledger.cache.epoch_2_started)) { - channel_a->set_network_version (header.version_using); - auto node_id (message.response->first); - bool process (!node_l->network.syn_cookies.validate (endpoint_a, node_id, message.response->second) && node_id != node_l->node_id.pub); - if (process) + nano::node_id_handshake message (error, stream, header); + if (!error && message.response && message.query) { - /* If node ID is known, don't establish new connection - Exception: temporary channels from bootstrap_server */ - auto existing_channel (node_l->network.tcp_channels.find_node_id (node_id)); - if (existing_channel) + channel_a->set_network_version (header.version_using); + auto node_id (message.response->first); + bool process (!node_l->network.syn_cookies.validate (endpoint_a, node_id, message.response->second) && node_id != node_l->node_id.pub); + if (process) { - process = existing_channel->server; + /* If node ID is known, don't establish new connection + Exception: temporary channels from bootstrap_server */ + auto existing_channel (node_l->network.tcp_channels.find_node_id (node_id)); + if (existing_channel) + { + process = existing_channel->temporary; + } } - } - if (process) - { - channel_a->set_node_id (node_id); - channel_a->set_last_packet_received (std::chrono::steady_clock::now ()); - boost::optional> response (std::make_pair (node_l->node_id.pub, nano::sign_message (node_l->node_id.prv, node_l->node_id.pub, *message.query))); - nano::node_id_handshake response_message (boost::none, response); - auto bytes = response_message.to_shared_const_buffer (); - if (node_l->config.logging.network_node_id_handshake_logging ()) + if (process) { - node_l->logger.try_log (boost::str (boost::format ("Node ID handshake response sent with node ID %1% to %2%: query %3%") % node_l->node_id.pub.to_node_id () % endpoint_a % (*message.query).to_string ())); - } - channel_a->send_buffer (bytes, nano::stat::detail::node_id_handshake, [node_w, channel_a, endpoint_a, callback_a, cleanup_and_udp_fallback](boost::system::error_code const & ec, size_t size_a) { - if (auto node_l = node_w.lock ()) + channel_a->set_node_id (node_id); + channel_a->set_last_packet_received (std::chrono::steady_clock::now ()); + boost::optional> response (std::make_pair (node_l->node_id.pub, nano::sign_message (node_l->node_id.prv, node_l->node_id.pub, *message.query))); + nano::node_id_handshake response_message (boost::none, response); + auto bytes = response_message.to_shared_const_buffer (node_l->ledger.cache.epoch_2_started); + if (node_l->config.logging.network_node_id_handshake_logging ()) { - if (!ec && channel_a) + node_l->logger.try_log (boost::str (boost::format ("Node ID handshake response sent with node ID %1% to %2%: query %3%") % node_l->node_id.pub.to_node_id () % endpoint_a % (*message.query).to_string ())); + } + channel_a->send_buffer (bytes, nano::stat::detail::node_id_handshake, [node_w, channel_a, endpoint_a, callback_a, cleanup_and_udp_fallback](boost::system::error_code const & ec, size_t size_a) { + if (auto node_l = node_w.lock ()) { - // Insert new node ID connection - if (auto socket_l = channel_a->socket.lock ()) + if (!ec && channel_a) { - channel_a->set_last_packet_sent (std::chrono::steady_clock::now ()); - auto response_server = std::make_shared (socket_l, node_l); - node_l->network.tcp_channels.insert (channel_a, socket_l, response_server); - if (callback_a) + // Insert new node ID connection + if (auto socket_l = channel_a->socket.lock ()) { - callback_a (channel_a); + channel_a->set_last_packet_sent (std::chrono::steady_clock::now ()); + auto response_server = std::make_shared (socket_l, node_l); + node_l->network.tcp_channels.insert (channel_a, socket_l, response_server); + if (callback_a) + { + callback_a (channel_a); + } + // Listen for possible responses + response_server->type = nano::bootstrap_server_type::realtime_response_server; + response_server->remote_node_id = channel_a->get_node_id (); + response_server->receive (); + node_l->network.tcp_channels.remove_node_id_handshake_socket (socket_l); + + if (!node_l->flags.disable_initial_telemetry_requests) + { + node_l->telemetry->get_metrics_single_peer_async (channel_a, [](nano::telemetry_data_response /* unused */) { + // Intentionally empty, starts the telemetry request cycle to more quickly disconnect from invalid peers + }); + } } - // Listen for possible responses - response_server->type = nano::bootstrap_server_type::realtime_response_server; - response_server->remote_node_id = channel_a->get_node_id (); - response_server->receive (); - node_l->network.tcp_channels.remove_node_id_handshake_socket (socket_l); } - } - else - { - if (node_l->config.logging.network_node_id_handshake_logging ()) + else { - node_l->logger.try_log (boost::str (boost::format ("Error sending node_id_handshake to %1%: %2%") % endpoint_a % ec.message ())); + if (node_l->config.logging.network_node_id_handshake_logging ()) + { + node_l->logger.try_log (boost::str (boost::format ("Error sending node_id_handshake to %1%: %2%") % endpoint_a % ec.message ())); + } + cleanup_and_udp_fallback (endpoint_a, callback_a); } - cleanup_and_udp_fallback (endpoint_a, callback_a); } - } - }); + }); + } + } + else + { + cleanup_and_udp_fallback (endpoint_a, callback_a); } } else { - cleanup_and_udp_fallback (endpoint_a, callback_a); + // Version of channel is not high enough, just abort. Don't fallback to udp, instead cleanup attempt + cleanup_node_id_handshake_socket (endpoint_a, callback_a); + { + nano::lock_guard lock (node_l->network.tcp_channels.mutex); + node_l->network.tcp_channels.attempts.get ().erase (nano::transport::map_endpoint_to_tcp (endpoint_a)); + } } } else @@ -674,6 +725,10 @@ void nano::transport::tcp_channels::start_tcp_receive_node_id (std::shared_ptr)> const & callback_a) { + { + nano::lock_guard lock (mutex); + attempts.get ().erase (nano::transport::map_endpoint_to_tcp (endpoint_a)); + } if (callback_a && !node.flags.disable_udp) { auto channel_udp (node.network.udp_channels.create (endpoint_a)); diff --git a/nano/node/transport/tcp.hpp b/nano/node/transport/tcp.hpp index f207744352..5279a04937 100644 --- a/nano/node/transport/tcp.hpp +++ b/nano/node/transport/tcp.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -11,10 +10,23 @@ #include #include +#include + +namespace mi = boost::multi_index; + namespace nano { class bootstrap_server; enum class bootstrap_server_type; +class tcp_message_item final +{ +public: + std::shared_ptr message; + nano::tcp_endpoint endpoint; + nano::account node_id; + std::shared_ptr socket; + nano::bootstrap_server_type type; +}; namespace transport { class tcp_channels; @@ -27,7 +39,7 @@ namespace transport ~channel_tcp (); size_t hash_code () const override; bool operator== (nano::transport::channel const &) const override; - void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr) override; + void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr, nano::buffer_drop_policy = nano::buffer_drop_policy::limiter) override; std::function callback (nano::stat::detail, std::function const & = nullptr) const override; std::function tcp_callback (nano::stat::detail, nano::tcp_endpoint const &, std::function const & = nullptr) const; std::string to_string () const override; @@ -37,7 +49,9 @@ namespace transport } std::weak_ptr socket; std::weak_ptr response_server; - bool server{ false }; + /* Mark for temporary channels. Usually remote ports of these channels are ephemeral and received from incoming connections to server. + If remote part has open listening port, temporary channel will be replaced with direct connection to listening port soon. But if other side is behing NAT or firewall this connection can be pemanent. */ + std::atomic temporary{ false }; nano::endpoint get_endpoint () const override { @@ -73,6 +87,7 @@ namespace transport class tcp_channels final { friend class nano::transport::channel_tcp; + friend class telemetry_simultaneous_requests_Test; public: tcp_channels (nano::node &); @@ -81,7 +96,7 @@ namespace transport size_t size () const; std::shared_ptr find_channel (nano::tcp_endpoint const &) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + std::unordered_set> random_set (size_t, uint8_t = 0, bool = false) const; bool store_all (bool = true); std::shared_ptr find_node_id (nano::account const &); // Get the next peer for attempting a tcp connection @@ -89,15 +104,16 @@ namespace transport void receive (); void start (); void stop (); + void process_messages (); void process_message (nano::message const &, nano::tcp_endpoint const &, nano::account const &, std::shared_ptr, nano::bootstrap_server_type); - void process_keepalive (nano::keepalive const &, nano::tcp_endpoint const &); bool max_ip_connections (nano::tcp_endpoint const &); // Should we reach out to this endpoint with a keepalive message bool reachout (nano::endpoint const &); - std::unique_ptr collect_seq_con_info (std::string const &); + std::unique_ptr collect_container_info (std::string const &); void purge (std::chrono::steady_clock::time_point const &); void ongoing_keepalive (); - void list (std::deque> &); + void list_below_version (std::vector> &, uint8_t); + void list (std::deque> &, uint8_t = 0, bool = true); void modify (std::shared_ptr, std::function)>); void update (nano::tcp_endpoint const &); // Connection start @@ -125,15 +141,26 @@ namespace transport class last_bootstrap_attempt_tag { }; + class last_attempt_tag + { + }; class node_id_tag { }; + class version_tag + { + }; + class channel_tcp_wrapper final { public: std::shared_ptr channel; std::shared_ptr socket; std::shared_ptr response_server; + channel_tcp_wrapper (std::shared_ptr const & channel_a, std::shared_ptr const & socket_a, std::shared_ptr const & server_a) : + channel (channel_a), socket (socket_a), response_server (server_a) + { + } nano::tcp_endpoint endpoint () const { return channel->get_tcp_endpoint (); @@ -153,33 +180,55 @@ namespace transport nano::account node_id () const { auto node_id (channel->get_node_id ()); - assert (!node_id.is_zero ()); + debug_assert (!node_id.is_zero ()); return node_id; } + uint8_t network_version () const + { + return channel->get_network_version (); + } }; class tcp_endpoint_attempt final { public: nano::tcp_endpoint endpoint; - std::chrono::steady_clock::time_point last_attempt; + boost::asio::ip::address address; + std::chrono::steady_clock::time_point last_attempt{ std::chrono::steady_clock::now () }; + + explicit tcp_endpoint_attempt (nano::tcp_endpoint const & endpoint_a) : + endpoint (endpoint_a), + address (endpoint_a.address ()) + { + } }; mutable std::mutex mutex; - boost::multi_index_container< - channel_tcp_wrapper, - boost::multi_index::indexed_by< - boost::multi_index::random_access>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::hashed_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::hashed_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>>> + // clang-format off + boost::multi_index_container>, + mi::ordered_non_unique, + mi::const_mem_fun>, + mi::hashed_unique, + mi::const_mem_fun>, + mi::hashed_non_unique, + mi::const_mem_fun>, + mi::ordered_non_unique, + mi::const_mem_fun>, + mi::ordered_non_unique, + mi::const_mem_fun>, + mi::hashed_non_unique, + mi::const_mem_fun>>> channels; - boost::multi_index_container< - tcp_endpoint_attempt, - boost::multi_index::indexed_by< - boost::multi_index::hashed_unique>, - boost::multi_index::ordered_non_unique>>> + boost::multi_index_container, + mi::member>, + mi::hashed_non_unique, + mi::member>, + mi::ordered_non_unique, + mi::member>>> attempts; + // clang-format on // This owns the sockets until the node_id_handshake has been completed. Needed to prevent self referencing callbacks, they are periodically removed if any are dangling. std::vector> node_id_handshake_sockets; std::atomic stopped{ false }; diff --git a/nano/node/transport/transport.cpp b/nano/node/transport/transport.cpp index 20ecc7d999..3b5c7e2b48 100644 --- a/nano/node/transport/transport.cpp +++ b/nano/node/transport/transport.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include namespace @@ -45,6 +47,14 @@ class callback_visitor : public nano::message_visitor { result = nano::stat::detail::node_id_handshake; } + void telemetry_req (nano::telemetry_req const & message_a) override + { + result = nano::stat::detail::telemetry_req; + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + result = nano::stat::detail::telemetry_ack; + } nano::stat::detail result; }; } @@ -70,25 +80,31 @@ nano::tcp_endpoint nano::transport::map_endpoint_to_tcp (nano::endpoint const & } nano::transport::channel::channel (nano::node & node_a) : -limiter (node_a.config.bandwidth_limit), node (node_a) { set_network_version (node_a.network_params.protocol.protocol_version); } -void nano::transport::channel::send (nano::message const & message_a, std::function const & callback_a, bool const is_droppable_a) +void nano::transport::channel::send (nano::message const & message_a, std::function const & callback_a, nano::buffer_drop_policy drop_policy_a) { callback_visitor visitor; message_a.visit (visitor); - auto buffer (message_a.to_shared_const_buffer ()); + auto buffer (message_a.to_shared_const_buffer (node.ledger.cache.epoch_2_started)); auto detail (visitor.result); - if (!is_droppable_a || !limiter.should_drop (buffer.size ())) + auto is_droppable_by_limiter = drop_policy_a == nano::buffer_drop_policy::limiter; + auto should_drop (node.network.limiter.should_drop (buffer.size ())); + if (!is_droppable_by_limiter || !should_drop) { - send_buffer (buffer, detail, callback_a); + send_buffer (buffer, detail, callback_a, drop_policy_a); node.stats.inc (nano::stat::type::message, detail, nano::stat::dir::out); } else { + if (callback_a) + { + callback_a (boost::system::errc::make_error_code (boost::system::errc::not_supported), 0); + } + node.stats.inc (nano::stat::type::drop, detail, nano::stat::dir::out); if (node.config.logging.network_packet_logging ()) { @@ -108,7 +124,7 @@ boost::asio::ip::address_v6 mapped_from_v4_bytes (unsigned long address_a) bool nano::transport::reserved_address (nano::endpoint const & endpoint_a, bool allow_local_peers) { - assert (endpoint_a.address ().is_v6 ()); + debug_assert (endpoint_a.address ().is_v6 ()); auto bytes (endpoint_a.address ().to_v6 ()); auto result (false); static auto const rfc1700_min (mapped_from_v4_bytes (0x00000000ul)); @@ -131,14 +147,14 @@ bool nano::transport::reserved_address (nano::endpoint const & endpoint_a, bool static auto const ipv4_multicast_max (mapped_from_v4_bytes (0xeffffffful)); static auto const rfc6890_min (mapped_from_v4_bytes (0xf0000000ul)); static auto const rfc6890_max (mapped_from_v4_bytes (0xfffffffful)); - static auto const rfc6666_min (boost::asio::ip::address_v6::from_string ("100::")); - static auto const rfc6666_max (boost::asio::ip::address_v6::from_string ("100::ffff:ffff:ffff:ffff")); - static auto const rfc3849_min (boost::asio::ip::address_v6::from_string ("2001:db8::")); - static auto const rfc3849_max (boost::asio::ip::address_v6::from_string ("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff")); - static auto const rfc4193_min (boost::asio::ip::address_v6::from_string ("fc00::")); - static auto const rfc4193_max (boost::asio::ip::address_v6::from_string ("fd00:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); - static auto const ipv6_multicast_min (boost::asio::ip::address_v6::from_string ("ff00::")); - static auto const ipv6_multicast_max (boost::asio::ip::address_v6::from_string ("ff00:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + static auto const rfc6666_min (boost::asio::ip::make_address_v6 ("100::")); + static auto const rfc6666_max (boost::asio::ip::make_address_v6 ("100::ffff:ffff:ffff:ffff")); + static auto const rfc3849_min (boost::asio::ip::make_address_v6 ("2001:db8::")); + static auto const rfc3849_max (boost::asio::ip::make_address_v6 ("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff")); + static auto const rfc4193_min (boost::asio::ip::make_address_v6 ("fc00::")); + static auto const rfc4193_max (boost::asio::ip::make_address_v6 ("fd00:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + static auto const ipv6_multicast_min (boost::asio::ip::make_address_v6 ("ff00::")); + static auto const ipv6_multicast_max (boost::asio::ip::make_address_v6 ("ff00:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); if (endpoint_a.port () == 0) { result = true; @@ -207,43 +223,12 @@ bool nano::transport::reserved_address (nano::endpoint const & endpoint_a, bool using namespace std::chrono_literals; -nano::bandwidth_limiter::bandwidth_limiter (const size_t limit_a) : -next_trend (std::chrono::steady_clock::now () + 50ms), -limit (limit_a), -rate (0), -trended_rate (0) -{ -} - -bool nano::bandwidth_limiter::should_drop (const size_t & message_size) +nano::bandwidth_limiter::bandwidth_limiter (const double limit_burst_ratio_a, const size_t limit_a) : +bucket (limit_a * limit_burst_ratio_a, limit_a) { - bool result (false); - if (limit == 0) //never drop if limit is 0 - { - return result; - } - nano::lock_guard lock (mutex); - - if (message_size > limit / rate_buffer.size () || rate + message_size > limit) - { - result = true; - } - else - { - rate = rate + message_size; - } - if (next_trend < std::chrono::steady_clock::now ()) - { - next_trend = std::chrono::steady_clock::now () + 50ms; - rate_buffer.push_back (rate); - trended_rate = std::accumulate (rate_buffer.begin (), rate_buffer.end (), size_t{ 0 }) / rate_buffer.size (); - rate = 0; - } - return result; } -size_t nano::bandwidth_limiter::get_rate () +bool nano::bandwidth_limiter::should_drop (const size_t & message_size_a) { - nano::lock_guard lock (mutex); - return trended_rate; + return !bucket.try_consume (message_size_a); } diff --git a/nano/node/transport/transport.hpp b/nano/node/transport/transport.hpp index 398b60a4b2..32c7378111 100644 --- a/nano/node/transport/transport.hpp +++ b/nano/node/transport/transport.hpp @@ -1,34 +1,24 @@ #pragma once +#include +#include #include #include #include -#include - namespace nano { class bandwidth_limiter final { public: - // initialize with rate 0 = unbounded - bandwidth_limiter (const size_t); + // initialize with limit 0 = unbounded + bandwidth_limiter (const double, const size_t); bool should_drop (const size_t &); - size_t get_rate (); private: - //last time rate was adjusted - std::chrono::steady_clock::time_point next_trend; - //trend rate over 20 poll periods - boost::circular_buffer rate_buffer{ 20, 0 }; - //limit bandwidth to - const size_t limit; - //rate, increment if message_size + rate < rate - size_t rate; - //trended rate to even out spikes in traffic - size_t trended_rate; - std::mutex mutex; + nano::rate::token_bucket bucket; }; + namespace transport { class message; @@ -37,8 +27,6 @@ namespace transport nano::tcp_endpoint map_endpoint_to_tcp (nano::endpoint const &); // Unassigned, reserved, self bool reserved_address (nano::endpoint const &, bool = false); - // Maximum number of peers per IP - static size_t constexpr max_peers_per_ip = 10; static std::chrono::seconds constexpr syn_cookie_cutoff = std::chrono::seconds (5); enum class transport_type : uint8_t { @@ -53,8 +41,8 @@ namespace transport virtual ~channel () = default; virtual size_t hash_code () const = 0; virtual bool operator== (nano::transport::channel const &) const = 0; - void send (nano::message const &, std::function const & = nullptr, bool const = true); - virtual void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr) = 0; + void send (nano::message const &, std::function const & = nullptr, nano::buffer_drop_policy = nano::buffer_drop_policy::limiter); + virtual void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr, nano::buffer_drop_policy = nano::buffer_drop_policy::limiter) = 0; virtual std::function callback (nano::stat::detail, std::function const & = nullptr) const = 0; virtual std::string to_string () const = 0; virtual nano::endpoint get_endpoint () const = 0; @@ -133,7 +121,6 @@ namespace transport } mutable std::mutex channel_mutex; - nano::bandwidth_limiter limiter; private: std::chrono::steady_clock::time_point last_bootstrap_attempt{ std::chrono::steady_clock::time_point () }; diff --git a/nano/node/transport/udp.cpp b/nano/node/transport/udp.cpp index f6712bb87b..89d40dbcd5 100644 --- a/nano/node/transport/udp.cpp +++ b/nano/node/transport/udp.cpp @@ -1,15 +1,19 @@ +#include +#include #include #include #include #include +#include + nano::transport::channel_udp::channel_udp (nano::transport::udp_channels & channels_a, nano::endpoint const & endpoint_a, uint8_t protocol_version_a) : channel (channels_a.node), endpoint (endpoint_a), channels (channels_a) { set_network_version (protocol_version_a); - assert (endpoint_a.address ().is_v6 ()); + debug_assert (endpoint_a.address ().is_v6 ()); } size_t nano::transport::channel_udp::hash_code () const @@ -29,7 +33,7 @@ bool nano::transport::channel_udp::operator== (nano::transport::channel const & return result; } -void nano::transport::channel_udp::send_buffer (nano::shared_const_buffer const & buffer_a, nano::stat::detail detail_a, std::function const & callback_a) +void nano::transport::channel_udp::send_buffer (nano::shared_const_buffer const & buffer_a, nano::stat::detail detail_a, std::function const & callback_a, nano::buffer_drop_policy drop_policy_a) { set_last_packet_sent (std::chrono::steady_clock::now ()); channels.send (buffer_a, endpoint, callback (detail_a, callback_a)); @@ -37,9 +41,7 @@ void nano::transport::channel_udp::send_buffer (nano::shared_const_buffer const std::function nano::transport::channel_udp::callback (nano::stat::detail detail_a, std::function const & callback_a) const { - // clang-format off - return [node = std::weak_ptr (channels.node.shared ()), callback_a ](boost::system::error_code const & ec, size_t size_a) - { + return [node = std::weak_ptr (channels.node.shared ()), callback_a](boost::system::error_code const & ec, size_t size_a) { if (auto node_l = node.lock ()) { if (ec == boost::system::errc::host_unreachable) @@ -57,7 +59,6 @@ std::function nano::transport:: } } }; - // clang-format on } std::string nano::transport::channel_udp::to_string () const @@ -67,31 +68,41 @@ std::string nano::transport::channel_udp::to_string () const nano::transport::udp_channels::udp_channels (nano::node & node_a, uint16_t port_a) : node (node_a), -strand (node_a.io_ctx.get_executor ()), -socket (node_a.io_ctx, nano::endpoint (boost::asio::ip::address_v6::any (), port_a)) +strand (node_a.io_ctx.get_executor ()) { - boost::system::error_code ec; - auto port (socket.local_endpoint (ec).port ()); - if (ec) + if (!node.flags.disable_udp) + { + socket = std::make_unique (node_a.io_ctx, nano::endpoint (boost::asio::ip::address_v6::any (), port_a)); + boost::system::error_code ec; + auto port (socket->local_endpoint (ec).port ()); + if (ec) + { + node.logger.try_log ("Unable to retrieve port: ", ec.message ()); + } + local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), port); + } + else { - node.logger.try_log ("Unable to retrieve port: ", ec.message ()); + local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), 0); + stopped = true; } - - local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), port); } void nano::transport::udp_channels::send (nano::shared_const_buffer const & buffer_a, nano::endpoint endpoint_a, std::function const & callback_a) { boost::asio::post (strand, [this, buffer_a, endpoint_a, callback_a]() { - this->socket.async_send_to (buffer_a, endpoint_a, - boost::asio::bind_executor (strand, callback_a)); + if (!this->stopped) + { + this->socket->async_send_to (buffer_a, endpoint_a, + boost::asio::bind_executor (strand, callback_a)); + } }); } std::shared_ptr nano::transport::udp_channels::insert (nano::endpoint const & endpoint_a, unsigned network_version_a) { - assert (endpoint_a.address ().is_v6 ()); + debug_assert (endpoint_a.address ().is_v6 ()); std::shared_ptr result; if (!node.network.not_a_peer (endpoint_a, node.config.allow_local_peers) && (node.network_params.network.is_test_network () || !max_ip_connections (endpoint_a))) { @@ -104,7 +115,8 @@ std::shared_ptr nano::transport::udp_channels::ins else { result = std::make_shared (*this, endpoint_a, network_version_a); - channels.get ().insert ({ result }); + channels.get ().insert (result); + attempts.get ().erase (endpoint_a); lock.unlock (); node.network.channel_observer (result); } @@ -136,7 +148,7 @@ std::shared_ptr nano::transport::udp_channels::cha return result; } -std::unordered_set> nano::transport::udp_channels::random_set (size_t count_a) const +std::unordered_set> nano::transport::udp_channels::random_set (size_t count_a, uint8_t min_version) const { std::unordered_set> result; result.reserve (count_a); @@ -151,7 +163,11 @@ std::unordered_set> nano::transport::u for (auto i (0); i < random_cutoff && result.size () < count_a; ++i) { auto index (nano::random_pool::generate_word32 (0, static_cast (peers_size - 1))); - result.insert (channels.get ()[index].channel); + auto channel = channels.get ()[index].channel; + if (channel->get_network_version () >= min_version) + { + result.insert (channel); + } } } return result; @@ -160,15 +176,15 @@ std::unordered_set> nano::transport::u void nano::transport::udp_channels::random_fill (std::array & target_a) const { auto peers (random_set (target_a.size ())); - assert (peers.size () <= target_a.size ()); + debug_assert (peers.size () <= target_a.size ()); auto endpoint (nano::endpoint (boost::asio::ip::address_v6{}, 0)); - assert (endpoint.address ().is_v6 ()); + debug_assert (endpoint.address ().is_v6 ()); std::fill (target_a.begin (), target_a.end (), endpoint); auto j (target_a.begin ()); for (auto i (peers.begin ()), n (peers.end ()); i != n; ++i, ++j) { - assert ((*i)->get_endpoint ().address ().is_v6 ()); - assert (j < target_a.end ()); + debug_assert ((*i)->get_endpoint ().address ().is_v6 ()); + debug_assert (j < target_a.end ()); *j = (*i)->get_endpoint (); } } @@ -260,43 +276,48 @@ nano::tcp_endpoint nano::transport::udp_channels::bootstrap_peer (uint8_t connec void nano::transport::udp_channels::receive () { - if (node.config.logging.network_packet_logging ()) + if (!stopped) { - node.logger.try_log ("Receiving packet"); - } - - auto data (node.network.buffer_container.allocate ()); - - socket.async_receive_from (boost::asio::buffer (data->buffer, nano::network::buffer_size), data->endpoint, - boost::asio::bind_executor (strand, - [this, data](boost::system::error_code const & error, std::size_t size_a) { - if (!error && !stopped) + release_assert (socket != nullptr); + if (node.config.logging.network_packet_logging ()) { - data->size = size_a; - this->node.network.buffer_container.enqueue (data); - this->receive (); + node.logger.try_log ("Receiving packet"); } - else - { - this->node.network.buffer_container.release (data); - if (error) + + auto data (node.network.buffer_container.allocate ()); + + socket->async_receive_from (boost::asio::buffer (data->buffer, nano::network::buffer_size), data->endpoint, + boost::asio::bind_executor (strand, + [this, data](boost::system::error_code const & error, std::size_t size_a) { + if (!error && !this->stopped) { - if (this->node.config.logging.network_logging ()) - { - this->node.logger.try_log (boost::str (boost::format ("UDP Receive error: %1%") % error.message ())); - } + data->size = size_a; + this->node.network.buffer_container.enqueue (data); + this->receive (); } - if (!stopped) + else { - this->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this]() { this->receive (); }); + this->node.network.buffer_container.release (data); + if (error) + { + if (this->node.config.logging.network_logging ()) + { + this->node.logger.try_log (boost::str (boost::format ("UDP Receive error: %1%") % error.message ())); + } + } + if (!this->stopped) + { + this->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this]() { this->receive (); }); + } } - } - })); + })); + } } void nano::transport::udp_channels::start () { - for (size_t i = 0; i < node.config.io_threads; ++i) + debug_assert (!node.flags.disable_udp); + for (size_t i = 0; i < node.config.io_threads && !stopped; ++i) { boost::asio::post (strand, [this]() { receive (); @@ -308,31 +329,33 @@ void nano::transport::udp_channels::start () void nano::transport::udp_channels::stop () { // Stop and invalidate local endpoint - stopped = true; - nano::lock_guard lock (mutex); - local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), 0); - - // On test-net, close directly to avoid address-reuse issues. On livenet, close - // through the strand as multiple IO threads may access the socket. - // clang-format off - if (node.network_params.network.is_test_network ()) - { - this->close_socket (); - } - else + if (!stopped.exchange (true)) { - boost::asio::dispatch (strand, [this] { + nano::lock_guard lock (mutex); + local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), 0); + + // On test-net, close directly to avoid address-reuse issues. On livenet, close + // through the strand as multiple IO threads may access the socket. + if (node.network_params.network.is_test_network ()) + { this->close_socket (); - }); + } + else + { + boost::asio::dispatch (strand, [this] { + this->close_socket (); + }); + } } - // clang-format on } void nano::transport::udp_channels::close_socket () { - boost::system::error_code ignored; - this->socket.close (ignored); - this->local_endpoint = nano::endpoint (boost::asio::ip::address_v6::loopback (), 0); + if (this->socket != nullptr) + { + boost::system::error_code ignored; + this->socket->close (ignored); + } } nano::endpoint nano::transport::udp_channels::get_local_endpoint () const @@ -396,19 +419,45 @@ class udp_message_visitor : public nano::message_visitor } void bulk_pull (nano::bulk_pull const &) override { - assert (false); + debug_assert (false); } void bulk_pull_account (nano::bulk_pull_account const &) override { - assert (false); + debug_assert (false); } void bulk_push (nano::bulk_push const &) override { - assert (false); + debug_assert (false); } void frontier_req (nano::frontier_req const &) override { - assert (false); + debug_assert (false); + } + void telemetry_req (nano::telemetry_req const & message_a) override + { + auto find_channel (node.network.udp_channels.channel (endpoint)); + if (find_channel) + { + auto is_very_first_message = find_channel->get_last_telemetry_req () == std::chrono::steady_clock::time_point{}; + auto cache_exceeded = std::chrono::steady_clock::now () >= find_channel->get_last_telemetry_req () + nano::telemetry_cache_cutoffs::network_to_time (node.network_params.network); + if (is_very_first_message || cache_exceeded) + { + node.network.udp_channels.modify (find_channel, [](std::shared_ptr channel_a) { + channel_a->set_last_telemetry_req (std::chrono::steady_clock::now ()); + }); + message (message_a); + } + else + { + node.network.udp_channels.modify (find_channel, [](std::shared_ptr channel_a) { + channel_a->set_last_packet_received (std::chrono::steady_clock::now ()); + }); + } + } + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + message (message_a); } void node_id_handshake (nano::node_id_handshake const & message_a) override { @@ -480,7 +529,7 @@ class udp_message_visitor : public nano::message_visitor void nano::transport::udp_channels::receive_action (nano::message_buffer * data_a) { auto allowed_sender (true); - if (data_a->endpoint == local_endpoint) + if (data_a->endpoint == get_local_endpoint ()) { allowed_sender = false; } @@ -495,9 +544,17 @@ void nano::transport::udp_channels::receive_action (nano::message_buffer * data_ if (allowed_sender) { udp_message_visitor visitor (node, data_a->endpoint); - nano::message_parser parser (node.block_uniquer, node.vote_uniquer, visitor, node.work); + nano::message_parser parser (node.network.publish_filter, node.block_uniquer, node.vote_uniquer, visitor, node.work, node.ledger.cache.epoch_2_started); parser.deserialize_buffer (data_a->buffer, data_a->size); - if (parser.status != nano::message_parser::parse_status::success) + if (parser.status == nano::message_parser::parse_status::success) + { + node.stats.add (nano::stat::type::traffic_udp, nano::stat::dir::in, data_a->size); + } + else if (parser.status == nano::message_parser::parse_status::duplicate_publish_message) + { + node.stats.inc (nano::stat::type::filter, nano::stat::detail::duplicate_publish); + } + else { node.stats.inc (nano::stat::type::error); @@ -534,18 +591,21 @@ void nano::transport::udp_channels::receive_action (nano::message_buffer * data_ case nano::message_parser::parse_status::invalid_node_id_handshake_message: node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_node_id_handshake_message); break; + case nano::message_parser::parse_status::invalid_telemetry_req_message: + node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_telemetry_req_message); + break; + case nano::message_parser::parse_status::invalid_telemetry_ack_message: + node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_telemetry_ack_message); + break; case nano::message_parser::parse_status::outdated_version: node.stats.inc (nano::stat::type::udp, nano::stat::detail::outdated_version); break; + case nano::message_parser::parse_status::duplicate_publish_message: case nano::message_parser::parse_status::success: /* Already checked, unreachable */ break; } } - else - { - node.stats.add (nano::stat::type::traffic_udp, nano::stat::dir::in, data_a->size); - } } else { @@ -579,8 +639,12 @@ std::shared_ptr nano::transport::udp_channels::create bool nano::transport::udp_channels::max_ip_connections (nano::endpoint const & endpoint_a) { - nano::unique_lock lock (mutex); - bool result (channels.get ().count (endpoint_a.address ()) >= nano::transport::max_peers_per_ip); + bool result (false); + if (!node.flags.disable_max_peers_per_ip) + { + nano::unique_lock lock (mutex); + result = channels.get ().count (endpoint_a.address ()) >= node.network_params.node.max_peers_per_ip; + } return result; } @@ -588,32 +652,31 @@ bool nano::transport::udp_channels::reachout (nano::endpoint const & endpoint_a) { // Don't overload single IP bool error = max_ip_connections (endpoint_a); - if (!error) + if (!error && !node.flags.disable_udp) { auto endpoint_l (nano::transport::map_endpoint_to_v6 (endpoint_a)); // Don't keepalive to nodes that already sent us something error |= channel (endpoint_l) != nullptr; nano::lock_guard lock (mutex); - auto existing (attempts.find (endpoint_l)); - error |= existing != attempts.end (); - attempts.insert ({ endpoint_l, std::chrono::steady_clock::now () }); + auto inserted (attempts.emplace (endpoint_l)); + error |= !inserted.second; } return error; } -std::unique_ptr nano::transport::udp_channels::collect_seq_con_info (std::string const & name) +std::unique_ptr nano::transport::udp_channels::collect_container_info (std::string const & name) { - size_t channels_count = 0; - size_t attemps_count = 0; + size_t channels_count; + size_t attemps_count; { nano::lock_guard guard (mutex); channels_count = channels.size (); attemps_count = attempts.size (); } - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "channels", channels_count, sizeof (decltype (channels)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "attempts", attemps_count, sizeof (decltype (attempts)::value_type) })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "channels", channels_count, sizeof (decltype (channels)::value_type) })); + composite->add_component (std::make_unique (container_info{ "attempts", attemps_count, sizeof (decltype (attempts)::value_type) })); return composite; } @@ -624,8 +687,8 @@ void nano::transport::udp_channels::purge (std::chrono::steady_clock::time_point auto disconnect_cutoff (channels.get ().lower_bound (cutoff_a)); channels.get ().erase (channels.get ().begin (), disconnect_cutoff); // Remove keepalive attempt tracking for attempts older than cutoff - auto attempts_cutoff (attempts.get<1> ().lower_bound (cutoff_a)); - attempts.get<1> ().erase (attempts.get<1> ().begin (), attempts_cutoff); + auto attempts_cutoff (attempts.get ().lower_bound (cutoff_a)); + attempts.get ().erase (attempts.get ().begin (), attempts_cutoff); } void nano::transport::udp_channels::ongoing_keepalive () @@ -653,13 +716,24 @@ void nano::transport::udp_channels::ongoing_keepalive () }); } -void nano::transport::udp_channels::list (std::deque> & deque_a) +void nano::transport::udp_channels::list_below_version (std::vector> & channels_a, uint8_t cutoff_version_a) { nano::lock_guard lock (mutex); - for (auto i (channels.begin ()), j (channels.end ()); i != j; ++i) - { - deque_a.push_back (i->channel); - } + // clang-format off + nano::transform_if (channels.get ().begin (), channels.get ().end (), std::back_inserter (channels_a), + [cutoff_version_a](auto & channel_a) { return channel_a.channel->get_network_version () < cutoff_version_a; }, + [](const auto & channel) { return channel.channel; }); + // clang-format on +} + +void nano::transport::udp_channels::list (std::deque> & deque_a, uint8_t minimum_version_a) +{ + nano::lock_guard lock (mutex); + // clang-format off + nano::transform_if (channels.get ().begin (), channels.get ().end (), std::back_inserter (deque_a), + [minimum_version_a](auto & channel_a) { return channel_a.channel->get_network_version () >= minimum_version_a; }, + [](const auto & channel) { return channel.channel; }); + // clang-format on } void nano::transport::udp_channels::modify (std::shared_ptr channel_a, std::function)> modify_callback_a) diff --git a/nano/node/transport/udp.hpp b/nano/node/transport/udp.hpp index 1f528efaed..19d629ad65 100644 --- a/nano/node/transport/udp.hpp +++ b/nano/node/transport/udp.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -12,6 +11,9 @@ #include #include +#include + +namespace mi = boost::multi_index; namespace nano { @@ -27,7 +29,7 @@ namespace transport channel_udp (nano::transport::udp_channels &, nano::endpoint const &, uint8_t protocol_version); size_t hash_code () const override; bool operator== (nano::transport::channel const &) const override; - void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr) override; + void send_buffer (nano::shared_const_buffer const &, nano::stat::detail, std::function const & = nullptr, nano::buffer_drop_policy = nano::buffer_drop_policy::limiter) override; std::function callback (nano::stat::detail, std::function const & = nullptr) const override; std::string to_string () const override; bool operator== (nano::transport::channel_udp const & other_a) const @@ -52,9 +54,22 @@ namespace transport return nano::transport::transport_type::udp; } + std::chrono::steady_clock::time_point get_last_telemetry_req () + { + nano::lock_guard lk (channel_mutex); + return last_telemetry_req; + } + + void set_last_telemetry_req (std::chrono::steady_clock::time_point const time_a) + { + nano::lock_guard lk (channel_mutex); + last_telemetry_req = time_a; + } + private: nano::endpoint endpoint; nano::transport::udp_channels & channels; + std::chrono::steady_clock::time_point last_telemetry_req{ std::chrono::steady_clock::time_point () }; }; class udp_channels final { @@ -67,7 +82,7 @@ namespace transport size_t size () const; std::shared_ptr channel (nano::endpoint const &) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + std::unordered_set> random_set (size_t, uint8_t = 0) const; bool store_all (bool = true); std::shared_ptr find_node_id (nano::account const &); void clean_node_id (nano::account const &); @@ -85,10 +100,11 @@ namespace transport bool max_ip_connections (nano::endpoint const &); // Should we reach out to this endpoint with a keepalive message bool reachout (nano::endpoint const &); - std::unique_ptr collect_seq_con_info (std::string const &); + std::unique_ptr collect_container_info (std::string const &); void purge (std::chrono::steady_clock::time_point const &); void ongoing_keepalive (); - void list (std::deque> &); + void list_below_version (std::vector> &, uint8_t); + void list (std::deque> &, uint8_t = 0); void modify (std::shared_ptr, std::function)>); nano::node & node; @@ -109,6 +125,9 @@ namespace transport class last_bootstrap_attempt_tag { }; + class last_attempt_tag + { + }; class node_id_tag { }; @@ -116,6 +135,10 @@ namespace transport { public: std::shared_ptr channel; + channel_udp_wrapper (std::shared_ptr const & channel_a) : + channel (channel_a) + { + } nano::endpoint endpoint () const { return channel->get_endpoint (); @@ -128,6 +151,10 @@ namespace transport { return channel->get_last_bootstrap_attempt (); } + std::chrono::steady_clock::time_point last_telemetry_req () const + { + return channel->get_last_telemetry_req (); + } boost::asio::ip::address ip_address () const { return endpoint ().address (); @@ -141,27 +168,41 @@ namespace transport { public: nano::endpoint endpoint; - std::chrono::steady_clock::time_point last_attempt; + std::chrono::steady_clock::time_point last_attempt{ std::chrono::steady_clock::now () }; + + explicit endpoint_attempt (nano::endpoint const & endpoint_a) : + endpoint (endpoint_a) + { + } }; mutable std::mutex mutex; + // clang-format off boost::multi_index_container< channel_udp_wrapper, - boost::multi_index::indexed_by< - boost::multi_index::random_access>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::hashed_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::hashed_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>, - boost::multi_index::ordered_non_unique, boost::multi_index::const_mem_fun>>> + mi::indexed_by< + mi::random_access>, + mi::ordered_non_unique, + mi::const_mem_fun>, + mi::hashed_unique, + mi::const_mem_fun>, + mi::hashed_non_unique, + mi::const_mem_fun>, + mi::ordered_non_unique, + mi::const_mem_fun>, + mi::hashed_non_unique, + mi::const_mem_fun>>> channels; boost::multi_index_container< endpoint_attempt, - boost::multi_index::indexed_by< - boost::multi_index::hashed_unique>, - boost::multi_index::ordered_non_unique>>> + mi::indexed_by< + mi::hashed_unique, + mi::member>, + mi::ordered_non_unique, + mi::member>>> attempts; + // clang-format on boost::asio::strand strand; - boost::asio::ip::udp::socket socket; + std::unique_ptr socket; nano::endpoint local_endpoint; std::atomic stopped{ false }; }; diff --git a/nano/node/vote_processor.cpp b/nano/node/vote_processor.cpp index 7b90d3e737..8b8b38d500 100644 --- a/nano/node/vote_processor.cpp +++ b/nano/node/vote_processor.cpp @@ -1,12 +1,34 @@ +#include +#include +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include -nano::vote_processor::vote_processor (nano::node & node_a) : -node (node_a), +#include + +nano::vote_processor::vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::node_flags & flags_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a) : +checker (checker_a), +active (active_a), +observers (observers_a), +stats (stats_a), +config (config_a), +logger (logger_a), +online_reps (online_reps_a), +ledger (ledger_a), +network_params (network_params_a), +max_votes (flags_a.vote_processor_capacity), started (false), stopped (false), -active (false), +is_active (false), thread ([this]() { nano::thread_role::set (nano::thread_role::name::vote_processing); process_loop (); @@ -32,11 +54,11 @@ void nano::vote_processor::process_loop () { if (!votes.empty ()) { - std::deque, std::shared_ptr>> votes_l; + decltype (votes) votes_l; votes_l.swap (votes); log_this_iteration = false; - if (node.config.logging.network_logging () && votes_l.size () > 50) + if (config.logging.network_logging () && votes_l.size () > 50) { /* * Only log the timing information for this iteration if @@ -45,28 +67,11 @@ void nano::vote_processor::process_loop () log_this_iteration = true; elapsed.restart (); } - active = true; + is_active = true; lock.unlock (); verify_votes (votes_l); - { - nano::unique_lock active_single_lock (node.active.mutex); - auto transaction (node.store.tx_begin_read ()); - uint64_t count (1); - for (auto & i : votes_l) - { - vote_blocking (transaction, i.first, i.second, true); - // Free active_transactions mutex each 100 processed votes - if (count % 100 == 0) - { - active_single_lock.unlock (); - transaction.refresh (); - active_single_lock.lock (); - } - count++; - } - } lock.lock (); - active = false; + is_active = false; lock.unlock (); condition.notify_all (); @@ -74,7 +79,7 @@ void nano::vote_processor::process_loop () if (log_this_iteration && elapsed.stop () > std::chrono::milliseconds (100)) { - node.logger.try_log (boost::str (boost::format ("Processed %1% votes in %2% milliseconds (rate of %3% votes per second)") % votes_l.size () % elapsed.value ().count () % ((votes_l.size () * 1000ULL) / elapsed.value ().count ()))); + logger.try_log (boost::str (boost::format ("Processed %1% votes in %2% milliseconds (rate of %3% votes per second)") % votes_l.size () % elapsed.value ().count () % ((votes_l.size () * 1000ULL) / elapsed.value ().count ()))); } } else @@ -84,59 +89,48 @@ void nano::vote_processor::process_loop () } } -void nano::vote_processor::vote (std::shared_ptr vote_a, std::shared_ptr channel_a) +bool nano::vote_processor::vote (std::shared_ptr vote_a, std::shared_ptr channel_a) { + bool process (false); nano::unique_lock lock (mutex); if (!stopped) { - bool process (false); - /* Random early delection levels - Always process votes for test network (process = true) - Stop processing with max 144 * 1024 votes */ - if (!node.network_params.network.is_test_network ()) + // Level 0 (< 0.1%) + if (votes.size () < 6.0 / 9.0 * max_votes) { - // Level 0 (< 0.1%) - if (votes.size () < 96 * 1024) - { - process = true; - } - // Level 1 (0.1-1%) - else if (votes.size () < 112 * 1024) - { - process = (representatives_1.find (vote_a->account) != representatives_1.end ()); - } - // Level 2 (1-5%) - else if (votes.size () < 128 * 1024) - { - process = (representatives_2.find (vote_a->account) != representatives_2.end ()); - } - // Level 3 (> 5%) - else if (votes.size () < 144 * 1024) - { - process = (representatives_3.find (vote_a->account) != representatives_3.end ()); - } + process = true; } - else + // Level 1 (0.1-1%) + else if (votes.size () < 7.0 / 9.0 * max_votes) { - // Process for test network - process = true; + process = (representatives_1.find (vote_a->account) != representatives_1.end ()); + } + // Level 2 (1-5%) + else if (votes.size () < 8.0 / 9.0 * max_votes) + { + process = (representatives_2.find (vote_a->account) != representatives_2.end ()); + } + // Level 3 (> 5%) + else if (votes.size () < max_votes) + { + process = (representatives_3.find (vote_a->account) != representatives_3.end ()); } if (process) { - votes.push_back (std::make_pair (vote_a, channel_a)); - + votes.emplace_back (vote_a, channel_a); lock.unlock (); condition.notify_all (); - lock.lock (); + // Lock no longer required } else { - node.stats.inc (nano::stat::type::vote, nano::stat::detail::vote_overflow); + stats.inc (nano::stat::type::vote, nano::stat::detail::vote_overflow); } } + return !process; } -void nano::vote_processor::verify_votes (std::deque, std::shared_ptr>> & votes_a) +void nano::vote_processor::verify_votes (decltype (votes) const & votes_a) { auto size (votes_a.size ()); std::vector messages; @@ -150,7 +144,7 @@ void nano::vote_processor::verify_votes (std::deque verifications; verifications.resize (size); - for (auto & vote : votes_a) + for (auto const & vote : votes_a) { hashes.push_back (vote.first->hash ()); messages.push_back (hashes.back ().bytes.data ()); @@ -158,72 +152,50 @@ void nano::vote_processor::verify_votes (std::dequesignature.bytes.data ()); } nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () }; - node.checker.verify (check); - std::remove_reference_t result; + checker.verify (check); auto i (0); - for (auto & vote : votes_a) + for (auto const & vote : votes_a) { - assert (verifications[i] == 1 || verifications[i] == 0); + debug_assert (verifications[i] == 1 || verifications[i] == 0); if (verifications[i] == 1) { - result.push_back (vote); + vote_blocking (vote.first, vote.second, true); } ++i; } - votes_a.swap (result); } -// node.active.mutex lock required -nano::vote_code nano::vote_processor::vote_blocking (nano::transaction const & transaction_a, std::shared_ptr vote_a, std::shared_ptr channel_a, bool validated) +nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr vote_a, std::shared_ptr channel_a, bool validated) { - assert (!node.active.mutex.try_lock ()); auto result (nano::vote_code::invalid); if (validated || !vote_a->validate ()) { - auto max_vote (node.store.vote_max (transaction_a, vote_a)); - result = nano::vote_code::replay; - if (!node.active.vote (vote_a, true)) - { - result = nano::vote_code::vote; - } - switch (result) - { - case nano::vote_code::vote: - node.observers.vote.notify (vote_a, channel_a); - case nano::vote_code::replay: - // This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them - // Only do this if the sequence number is significantly different to account for network reordering - // Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase - if (max_vote->sequence > vote_a->sequence + 10000) - { - nano::confirm_ack confirm (max_vote); - channel_a->send (confirm); // this is non essential traffic as it will be resolicited if not received - } - break; - case nano::vote_code::invalid: - assert (false); - break; - } + result = active.vote (vote_a); + observers.vote.notify (vote_a, channel_a, result); } std::string status; switch (result) { case nano::vote_code::invalid: status = "Invalid"; - node.stats.inc (nano::stat::type::vote, nano::stat::detail::vote_invalid); + stats.inc (nano::stat::type::vote, nano::stat::detail::vote_invalid); break; case nano::vote_code::replay: status = "Replay"; - node.stats.inc (nano::stat::type::vote, nano::stat::detail::vote_replay); + stats.inc (nano::stat::type::vote, nano::stat::detail::vote_replay); break; case nano::vote_code::vote: status = "Vote"; - node.stats.inc (nano::stat::type::vote, nano::stat::detail::vote_valid); + stats.inc (nano::stat::type::vote, nano::stat::detail::vote_valid); + break; + case nano::vote_code::indeterminate: + status = "Indeterminate"; + stats.inc (nano::stat::type::vote, nano::stat::detail::vote_indeterminate); break; } - if (node.config.logging.vote_logging ()) + if (config.logging.vote_logging ()) { - node.logger.try_log (boost::str (boost::format ("Vote from: %1% sequence: %2% block(s): %3%status: %4%") % vote_a->account.to_account () % std::to_string (vote_a->sequence) % vote_a->hashes_string () % status)); + logger.try_log (boost::str (boost::format ("Vote from: %1% sequence: %2% block(s): %3%status: %4%") % vote_a->account.to_account () % std::to_string (vote_a->sequence) % vote_a->hashes_string () % status)); } return result; } @@ -244,12 +216,24 @@ void nano::vote_processor::stop () void nano::vote_processor::flush () { nano::unique_lock lock (mutex); - while (active || !votes.empty ()) + while (is_active || !votes.empty ()) { condition.wait (lock); } } +size_t nano::vote_processor::size () +{ + nano::lock_guard guard (mutex); + return votes.size (); +} + +bool nano::vote_processor::empty () +{ + nano::lock_guard guard (mutex); + return votes.empty (); +} + void nano::vote_processor::calculate_weights () { nano::unique_lock lock (mutex); @@ -258,12 +242,12 @@ void nano::vote_processor::calculate_weights () representatives_1.clear (); representatives_2.clear (); representatives_3.clear (); - auto supply (node.online_reps.online_stake ()); - auto rep_amounts = node.ledger.rep_weights.get_rep_amounts (); + auto supply (online_reps.online_stake ()); + auto rep_amounts = ledger.cache.rep_weights.get_rep_amounts (); for (auto const & rep_amount : rep_amounts) { nano::account const & representative (rep_amount.first); - auto weight (node.ledger.weight (representative)); + auto weight (ledger.weight (representative)); if (weight > supply / 1000) // 0.1% or above (level 1) { representatives_1.insert (representative); @@ -280,14 +264,12 @@ void nano::vote_processor::calculate_weights () } } -namespace nano +std::unique_ptr nano::collect_container_info (vote_processor & vote_processor, const std::string & name) { -std::unique_ptr collect_seq_con_info (vote_processor & vote_processor, const std::string & name) -{ - size_t votes_count = 0; - size_t representatives_1_count = 0; - size_t representatives_2_count = 0; - size_t representatives_3_count = 0; + size_t votes_count; + size_t representatives_1_count; + size_t representatives_2_count; + size_t representatives_3_count; { nano::lock_guard guard (vote_processor.mutex); @@ -297,11 +279,10 @@ std::unique_ptr collect_seq_con_info (vote_processor & v representatives_3_count = vote_processor.representatives_3.size (); } - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "votes", votes_count, sizeof (decltype (vote_processor.votes)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "representatives_1", representatives_1_count, sizeof (decltype (vote_processor.representatives_1)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "representatives_2", representatives_2_count, sizeof (decltype (vote_processor.representatives_2)::value_type) })); - composite->add_component (std::make_unique (seq_con_info{ "representatives_3", representatives_3_count, sizeof (decltype (vote_processor.representatives_3)::value_type) })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "votes", votes_count, sizeof (decltype (vote_processor.votes)::value_type) })); + composite->add_component (std::make_unique (container_info{ "representatives_1", representatives_1_count, sizeof (decltype (vote_processor.representatives_1)::value_type) })); + composite->add_component (std::make_unique (container_info{ "representatives_2", representatives_2_count, sizeof (decltype (vote_processor.representatives_2)::value_type) })); + composite->add_component (std::make_unique (container_info{ "representatives_3", representatives_3_count, sizeof (decltype (vote_processor.representatives_3)::value_type) })); return composite; } -} diff --git a/nano/node/vote_processor.hpp b/nano/node/vote_processor.hpp index 49eb53fb06..c23eaf1fa3 100644 --- a/nano/node/vote_processor.hpp +++ b/nano/node/vote_processor.hpp @@ -4,16 +4,25 @@ #include #include -#include - #include #include #include +#include #include namespace nano { -class node; +class signature_checker; +class active_transactions; +class block_store; +class node_observers; +class stats; +class node_config; +class logger_mt; +class online_reps; +class ledger; +class network_params; + class transaction; namespace transport { @@ -23,18 +32,33 @@ namespace transport class vote_processor final { public: - explicit vote_processor (nano::node &); - void vote (std::shared_ptr, std::shared_ptr); + explicit vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::node_flags & flags_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a); + /** Returns false if the vote was processed */ + bool vote (std::shared_ptr, std::shared_ptr); /** Note: node.active.mutex lock is required */ - nano::vote_code vote_blocking (nano::transaction const &, std::shared_ptr, std::shared_ptr, bool = false); - void verify_votes (std::deque, std::shared_ptr>> &); + nano::vote_code vote_blocking (std::shared_ptr, std::shared_ptr, bool = false); + void verify_votes (std::deque, std::shared_ptr>> const &); void flush (); + size_t size (); + bool empty (); void calculate_weights (); - nano::node & node; void stop (); private: void process_loop (); + + nano::signature_checker & checker; + nano::active_transactions & active; + nano::node_observers & observers; + nano::stat & stats; + nano::node_config & config; + nano::logger_mt & logger; + nano::online_reps & online_reps; + nano::ledger & ledger; + nano::network_params & network_params; + + size_t max_votes; + std::deque, std::shared_ptr>> votes; /** Representatives levels for random early detection */ std::unordered_set representatives_1; @@ -44,11 +68,12 @@ class vote_processor final std::mutex mutex; bool started; bool stopped; - bool active; - boost::thread thread; + bool is_active; + std::thread thread; - friend std::unique_ptr collect_seq_con_info (vote_processor & vote_processor, const std::string & name); + friend std::unique_ptr collect_container_info (vote_processor & vote_processor, const std::string & name); + friend class vote_processor_weights_Test; }; -std::unique_ptr collect_seq_con_info (vote_processor & vote_processor, const std::string & name); +std::unique_ptr collect_container_info (vote_processor & vote_processor, const std::string & name); } diff --git a/nano/node/voting.cpp b/nano/node/voting.cpp index f892db9659..34dfc7c9ad 100644 --- a/nano/node/voting.cpp +++ b/nano/node/voting.cpp @@ -1,10 +1,25 @@ -#include +#include "transport/udp.hpp" + +#include +#include +#include +#include #include +#include +#include +#include + +#include #include -nano::vote_generator::vote_generator (nano::node & node_a) : -node (node_a), +nano::vote_generator::vote_generator (nano::node_config const & config_a, nano::ledger & ledger_a, nano::wallets & wallets_a, nano::vote_processor & vote_processor_a, nano::votes_cache & votes_cache_a, nano::network & network_a) : +config (config_a), +ledger (ledger_a), +wallets (wallets_a), +vote_processor (vote_processor_a), +votes_cache (votes_cache_a), +network (network_a), thread ([this]() { run (); }) { nano::unique_lock lock (mutex); @@ -13,12 +28,17 @@ thread ([this]() { run (); }) void nano::vote_generator::add (nano::block_hash const & hash_a) { + auto transaction (ledger.store.tx_begin_read ()); nano::unique_lock lock (mutex); - hashes.push_back (hash_a); - if (hashes.size () >= 12) + auto block (ledger.store.block_get (transaction, hash_a)); + if (block != nullptr && ledger.can_vote (transaction, *block)) { - lock.unlock (); - condition.notify_all (); + hashes.push_back (hash_a); + if (hashes.size () >= nano::network::confirm_ack_hashes_max) + { + lock.unlock (); + condition.notify_all (); + } } } @@ -39,19 +59,21 @@ void nano::vote_generator::stop () void nano::vote_generator::send (nano::unique_lock & lock_a) { std::vector hashes_l; - hashes_l.reserve (12); - while (!hashes.empty () && hashes_l.size () < 12) + hashes_l.reserve (nano::network::confirm_ack_hashes_max); + while (!hashes.empty () && hashes_l.size () < nano::network::confirm_ack_hashes_max) { hashes_l.push_back (hashes.front ()); hashes.pop_front (); } lock_a.unlock (); { - auto transaction (node.store.tx_begin_read ()); - node.wallets.foreach_representative ([this, &hashes_l, &transaction](nano::public_key const & pub_a, nano::raw_key const & prv_a) { - auto vote (this->node.store.vote_generate (transaction, pub_a, prv_a, hashes_l)); - this->node.vote_processor.vote (vote, std::make_shared (this->node.network.udp_channels, this->node.network.endpoint (), this->node.network_params.protocol.protocol_version)); - this->node.votes_cache.add (vote); + auto transaction (ledger.store.tx_begin_read ()); + wallets.foreach_representative ([this, &hashes_l, &transaction](nano::public_key const & pub_a, nano::raw_key const & prv_a) { + auto vote (this->ledger.store.vote_generate (transaction, pub_a, prv_a, hashes_l)); + this->votes_cache.add (vote); + this->network.flood_vote_pr (vote); + this->network.flood_vote (vote, 2.0f); + this->vote_processor.vote (vote, std::make_shared (this->network.udp_channels, this->network.endpoint (), this->network_params.protocol.protocol_version)); }); } lock_a.lock (); @@ -67,16 +89,16 @@ void nano::vote_generator::run () lock.lock (); while (!stopped) { - if (hashes.size () >= 12) + if (hashes.size () >= nano::network::confirm_ack_hashes_max) { send (lock); } else { - condition.wait_for (lock, node.config.vote_generator_delay, [this]() { return this->hashes.size () >= 12; }); - if (hashes.size () >= node.config.vote_generator_threshold && hashes.size () < 12) + condition.wait_for (lock, config.vote_generator_delay, [this]() { return this->hashes.size () >= nano::network::confirm_ack_hashes_max; }); + if (hashes.size () >= config.vote_generator_threshold && hashes.size () < nano::network::confirm_ack_hashes_max) { - condition.wait_for (lock, node.config.vote_generator_delay, [this]() { return this->hashes.size () >= 12; }); + condition.wait_for (lock, config.vote_generator_delay, [this]() { return this->hashes.size () >= nano::network::confirm_ack_hashes_max; }); } if (!hashes.empty ()) { @@ -86,29 +108,60 @@ void nano::vote_generator::run () } } +nano::vote_generator_session::vote_generator_session (nano::vote_generator & vote_generator_a) : +generator (vote_generator_a) +{ +} + +void nano::vote_generator_session::add (nano::block_hash const & hash_a) +{ + debug_assert (nano::thread_role::get () == nano::thread_role::name::request_loop); + hashes.push_back (hash_a); +} + +void nano::vote_generator_session::flush () +{ + debug_assert (nano::thread_role::get () == nano::thread_role::name::request_loop); + for (auto const & i : hashes) + { + generator.add (i); + } +} + +nano::votes_cache::votes_cache (nano::wallets & wallets_a) : +wallets (wallets_a) +{ +} + void nano::votes_cache::add (std::shared_ptr const & vote_a) { + auto voting (wallets.reps ().voting); + if (voting == 0) + { + return; + } nano::lock_guard lock (cache_mutex); + auto const max_cache_size (network_params.voting.max_cache / std::max (voting, static_cast (1))); for (auto & block : vote_a->blocks) { auto hash (boost::get (block)); - auto existing (cache.get<1> ().find (hash)); - if (existing == cache.get<1> ().end ()) + auto existing (cache.get ().find (hash)); + if (existing == cache.get ().end ()) { // Clean old votes - if (cache.size () >= network_params.voting.max_cache) + if (cache.size () >= max_cache_size) { - cache.erase (cache.begin ()); + cache.get ().pop_front (); } // Insert new votes (new hash) - auto inserted (cache.insert (nano::cached_votes{ std::chrono::steady_clock::now (), hash, std::vector> (1, vote_a) })); + auto inserted (cache.get ().emplace_back (nano::cached_votes{ hash, std::vector> (1, vote_a) })); (void)inserted; - assert (inserted.second); + debug_assert (inserted.second); } else { // Insert new votes (old hash) - cache.get<1> ().modify (existing, [vote_a](nano::cached_votes & cache_a) { + cache.get ().modify (existing, [vote_a](nano::cached_votes & cache_a) { // Replace old vote for same representative & hash bool replaced (false); for (auto i (cache_a.votes.begin ()), n (cache_a.votes.end ()); i != n && !replaced; ++i) @@ -133,8 +186,8 @@ std::vector> nano::votes_cache::find (nano::block_ha { std::vector> result; nano::lock_guard lock (cache_mutex); - auto existing (cache.get<1> ().find (hash_a)); - if (existing != cache.get<1> ().end ()) + auto existing (cache.get ().find (hash_a)); + if (existing != cache.get ().end ()) { result = existing->votes; } @@ -144,12 +197,10 @@ std::vector> nano::votes_cache::find (nano::block_ha void nano::votes_cache::remove (nano::block_hash const & hash_a) { nano::lock_guard lock (cache_mutex); - cache.get<1> ().erase (hash_a); + cache.get ().erase (hash_a); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (vote_generator & vote_generator, const std::string & name) +std::unique_ptr nano::collect_container_info (vote_generator & vote_generator, const std::string & name) { size_t hashes_count = 0; @@ -158,23 +209,22 @@ std::unique_ptr collect_seq_con_info (vote_generator & v hashes_count = vote_generator.hashes.size (); } auto sizeof_element = sizeof (decltype (vote_generator.hashes)::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "state_blocks", hashes_count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "state_blocks", hashes_count, sizeof_element })); return composite; } -std::unique_ptr collect_seq_con_info (votes_cache & votes_cache, const std::string & name) +std::unique_ptr nano::collect_container_info (votes_cache & votes_cache, const std::string & name) { - size_t cache_count = 0; + size_t cache_count; { nano::lock_guard guard (votes_cache.cache_mutex); cache_count = votes_cache.cache.size (); } auto sizeof_element = sizeof (decltype (votes_cache.cache)::value_type); - auto composite = std::make_unique (name); + auto composite = std::make_unique (name); /* This does not currently loop over each element inside the cache to get the sizes of the votes inside cached_votes */ - composite->add_component (std::make_unique (seq_con_info{ "cache", cache_count, sizeof_element })); + composite->add_component (std::make_unique (container_info{ "cache", cache_count, sizeof_element })); return composite; } -} diff --git a/nano/node/voting.hpp b/nano/node/voting.hpp index b98acba616..2846f3c4c6 100644 --- a/nano/node/voting.hpp +++ b/nano/node/voting.hpp @@ -1,72 +1,101 @@ #pragma once -#include +#include #include #include +#include #include #include #include #include -#include +#include #include -#include #include #include #include +#include namespace nano { -class node; +class ledger; +class network; +class node_config; +class vote_processor; +class votes_cache; +class wallets; + class vote_generator final { public: - vote_generator (nano::node &); + vote_generator (nano::node_config const & config_a, nano::ledger &, nano::wallets & wallets_a, nano::vote_processor & vote_processor_a, nano::votes_cache & votes_cache_a, nano::network & network_a); void add (nano::block_hash const &); void stop (); private: void run (); void send (nano::unique_lock &); - nano::node & node; + nano::node_config const & config; + nano::ledger & ledger; + nano::wallets & wallets; + nano::vote_processor & vote_processor; + nano::votes_cache & votes_cache; + nano::network & network; std::mutex mutex; nano::condition_variable condition; std::deque hashes; nano::network_params network_params; bool stopped{ false }; bool started{ false }; - boost::thread thread; + std::thread thread; + + friend std::unique_ptr collect_container_info (vote_generator & vote_generator, const std::string & name); +}; + +class vote_generator_session final +{ +public: + vote_generator_session (vote_generator & vote_generator_a); + void add (nano::block_hash const &); + void flush (); - friend std::unique_ptr collect_seq_con_info (vote_generator & vote_generator, const std::string & name); +private: + nano::vote_generator & generator; + std::vector hashes; }; -std::unique_ptr collect_seq_con_info (vote_generator & vote_generator, const std::string & name); +std::unique_ptr collect_container_info (vote_generator & vote_generator, const std::string & name); class cached_votes final { public: - std::chrono::steady_clock::time_point time; nano::block_hash hash; std::vector> votes; }; class votes_cache final { public: + votes_cache (nano::wallets & wallets_a); void add (std::shared_ptr const &); std::vector> find (nano::block_hash const &); void remove (nano::block_hash const &); private: std::mutex cache_mutex; - boost::multi_index_container< - nano::cached_votes, + // clang-format off + class tag_sequence {}; + class tag_hash {}; + boost::multi_index_container>, - boost::multi_index::hashed_unique>>> + boost::multi_index::sequenced>, + boost::multi_index::hashed_unique, + boost::multi_index::member>>> cache; + // clang-format on nano::network_params network_params; - friend std::unique_ptr collect_seq_con_info (votes_cache & votes_cache, const std::string & name); + nano::wallets & wallets; + friend std::unique_ptr collect_container_info (votes_cache & votes_cache, const std::string & name); }; -std::unique_ptr collect_seq_con_info (votes_cache & votes_cache, const std::string & name); +std::unique_ptr collect_container_info (votes_cache & votes_cache, const std::string & name); } diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 781c3bc93d..6799edd693 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1,12 +1,15 @@ #include +#include #include +#include #include #include #include -#include #include +#include #include +#include #include @@ -85,7 +88,7 @@ nano::public_key nano::wallet_store::deterministic_insert (nano::transaction con nano::private_key nano::wallet_store::deterministic_key (nano::transaction const & transaction_a, uint32_t index_a) { - assert (valid_password (transaction_a)); + debug_assert (valid_password (transaction_a)); nano::raw_key seed_l; seed (seed_l, transaction_a); return nano::deterministic_key (seed_l, index_a); @@ -163,7 +166,7 @@ bool nano::wallet_store::attempt_password (nano::transaction const & transaction case version_4: break; default: - assert (false); + debug_assert (false); } } return result; @@ -204,10 +207,10 @@ void nano::wallet_store::derive_key (nano::raw_key & prv_a, nano::transaction co nano::fan::fan (nano::uint256_union const & key, size_t count_a) { - std::unique_ptr first (new nano::uint256_union (key)); + auto first (std::make_unique (key)); for (auto i (1); i < count_a; ++i) { - std::unique_ptr entry (new nano::uint256_union); + auto entry (std::make_unique ()); nano::random_pool::generate_block (entry->bytes.data (), entry->bytes.size ()); *first ^= *entry; values.push_back (std::move (entry)); @@ -223,7 +226,7 @@ void nano::fan::value (nano::raw_key & prv_a) void nano::fan::value_get (nano::raw_key & prv_a) { - assert (!mutex.try_lock ()); + debug_assert (!mutex.try_lock ()); prv_a.data.clear (); for (auto & i : values) { @@ -268,7 +271,7 @@ kdf (kdf_a) if (!init_a) { MDB_val junk; - assert (mdb_get (tx (transaction_a), handle, nano::mdb_val (version_special), &junk) == MDB_NOTFOUND); + debug_assert (mdb_get (tx (transaction_a), handle, nano::mdb_val (version_special), &junk) == MDB_NOTFOUND); boost::property_tree::ptree wallet_l; std::stringstream istream (json_a); try @@ -375,9 +378,11 @@ std::vector nano::wallet_store::accounts (nano::transaction const void nano::wallet_store::initialize (nano::transaction const & transaction_a, bool & init_a, std::string const & path_a) { - assert (strlen (path_a.c_str ()) == path_a.size ()); + debug_assert (strlen (path_a.c_str ()) == path_a.size ()); auto error (0); - error |= mdb_dbi_open (tx (transaction_a), path_a.c_str (), MDB_CREATE, &handle); + MDB_dbi handle_l; + error |= mdb_dbi_open (tx (transaction_a), path_a.c_str (), MDB_CREATE, &handle_l); + handle = handle_l; init_a = error != 0; } @@ -399,7 +404,7 @@ nano::account nano::wallet_store::representative (nano::transaction const & tran nano::public_key nano::wallet_store::insert_adhoc (nano::transaction const & transaction_a, nano::raw_key const & prv) { - assert (valid_password (transaction_a)); + debug_assert (valid_password (transaction_a)); nano::public_key pub (nano::pub_key (prv.as_private_key ())); nano::raw_key password_l; wallet_key (password_l, transaction_a); @@ -423,7 +428,7 @@ void nano::wallet_store::erase (nano::transaction const & transaction_a, nano::a { auto status (mdb_del (tx (transaction_a), handle, nano::mdb_val (pub), nullptr)); (void)status; - assert (status == 0); + debug_assert (status == 0); } nano::wallet_value nano::wallet_store::entry_get_raw (nano::transaction const & transaction_a, nano::account const & pub_a) @@ -447,7 +452,7 @@ void nano::wallet_store::entry_put_raw (nano::transaction const & transaction_a, { auto status (mdb_put (tx (transaction_a), handle, nano::mdb_val (pub_a), nano::mdb_val (sizeof (entry_a), const_cast (&entry_a)), 0)); (void)status; - assert (status == 0); + debug_assert (status == 0); } nano::key_type nano::wallet_store::key_type (nano::wallet_value const & value_a) @@ -566,8 +571,8 @@ void nano::wallet_store::write_backup (nano::transaction const & transaction_a, bool nano::wallet_store::move (nano::transaction const & transaction_a, nano::wallet_store & other_a, std::vector const & keys) { - assert (valid_password (transaction_a)); - assert (other_a.valid_password (transaction_a)); + debug_assert (valid_password (transaction_a)); + debug_assert (other_a.valid_password (transaction_a)); auto result (false); for (auto i (keys.begin ()), n (keys.end ()); i != n; ++i) { @@ -585,8 +590,8 @@ bool nano::wallet_store::move (nano::transaction const & transaction_a, nano::wa bool nano::wallet_store::import (nano::transaction const & transaction_a, nano::wallet_store & other_a) { - assert (valid_password (transaction_a)); - assert (other_a.valid_password (transaction_a)); + debug_assert (valid_password (transaction_a)); + debug_assert (other_a.valid_password (transaction_a)); auto result (false); for (auto i (other_a.begin (transaction_a)), n (end ()); i != n; ++i) { @@ -627,7 +632,7 @@ bool nano::wallet_store::work_get (nano::transaction const & transaction_a, nano void nano::wallet_store::work_put (nano::transaction const & transaction_a, nano::public_key const & pub_a, uint64_t work_a) { auto entry (entry_get_raw (transaction_a, pub_a)); - assert (!entry.key.is_zero ()); + debug_assert (!entry.key.is_zero ()); entry.work = work_a; entry_put_raw (transaction_a, pub_a, entry); } @@ -648,7 +653,7 @@ void nano::wallet_store::version_put (nano::transaction const & transaction_a, u void nano::wallet_store::upgrade_v1_v2 (nano::transaction const & transaction_a) { - assert (version (transaction_a) == 1); + debug_assert (version (transaction_a) == 1); nano::raw_key zero_password; nano::wallet_value value (entry_get_raw (transaction_a, nano::wallet_store::wallet_key_special)); nano::raw_key kdf; @@ -691,7 +696,7 @@ void nano::wallet_store::upgrade_v1_v2 (nano::transaction const & transaction_a) void nano::wallet_store::upgrade_v2_v3 (nano::transaction const & transaction_a) { - assert (version (transaction_a) == 2); + debug_assert (version (transaction_a) == 2); nano::raw_key seed; random_pool::generate_block (seed.data.bytes.data (), seed.data.bytes.size ()); seed_set (transaction_a, seed); @@ -701,9 +706,9 @@ void nano::wallet_store::upgrade_v2_v3 (nano::transaction const & transaction_a) void nano::wallet_store::upgrade_v3_v4 (nano::transaction const & transaction_a) { - assert (version (transaction_a) == 3); + debug_assert (version (transaction_a) == 3); version_put (transaction_a, 4); - assert (valid_password (transaction_a)); + debug_assert (valid_password (transaction_a)); nano::raw_key seed; nano::wallet_value value (entry_get_raw (transaction_a, nano::wallet_store::seed_special)); nano::raw_key password_l; @@ -736,7 +741,7 @@ void nano::wallet_store::upgrade_v3_v4 (nano::transaction const & transaction_a) case nano::key_type::deterministic: break; default: - assert (false); + debug_assert (false); } } } @@ -747,7 +752,7 @@ void nano::kdf::phs (nano::raw_key & result_a, std::string const & password_a, n static nano::network_params network_params; nano::lock_guard lock (mutex); auto success (argon2_hash (1, network_params.kdf_work, 1, password_a.data (), password_a.size (), salt_a.bytes.data (), salt_a.bytes.size (), result_a.data.bytes.data (), result_a.data.bytes.size (), NULL, 0, Argon2_d, 0x10)); - assert (success == 0); + debug_assert (success == 0); (void)success; } @@ -895,7 +900,7 @@ bool nano::wallet::import (std::string const & json_a, std::string const & passw auto transaction (wallets.tx_begin_write ()); nano::uint256_union id; random_pool::generate_block (id.bytes.data (), id.bytes.size ()); - temp.reset (new nano::wallet_store (error, wallets.node.wallets.kdf, transaction, 0, 1, id.to_string (), json_a)); + temp = std::make_unique (error, wallets.node.wallets.kdf, transaction, 0, 1, id.to_string (), json_a); } if (!error) { @@ -921,7 +926,7 @@ void nano::wallet_store::destroy (nano::transaction const & transaction_a) { auto status (mdb_drop (tx (transaction_a), handle, 1)); (void)status; - assert (status == 0); + debug_assert (status == 0); handle = 0; } @@ -930,6 +935,8 @@ std::shared_ptr nano::wallet::receive_action (nano::block const & s nano::account account; auto hash (send_a.hash ()); std::shared_ptr block; + nano::block_details details; + details.is_receive = true; if (wallets.node.config.receive_minimum.number () <= amount_a.number ()) { auto block_transaction (wallets.node.ledger.store.tx_begin_read ()); @@ -951,11 +958,13 @@ std::shared_ptr nano::wallet::receive_action (nano::block const & s auto new_account (wallets.node.ledger.store.account_get (block_transaction, account, info)); if (!new_account) { - block.reset (new nano::state_block (account, info.head, info.representative, info.balance.number () + pending_info.amount.number (), hash, prv, account, work_a)); + block = std::make_shared (account, info.head, info.representative, info.balance.number () + pending_info.amount.number (), hash, prv, account, work_a); + details.epoch = std::max (info.epoch (), send_a.sideband ().details.epoch); } else { - block.reset (new nano::state_block (account, 0, representative_a, pending_info.amount, reinterpret_cast (hash), prv, account, work_a)); + block = std::make_shared (account, 0, representative_a, pending_info.amount, reinterpret_cast (hash), prv, account, work_a); + details.epoch = send_a.sideband ().details.epoch; } } else @@ -980,7 +989,7 @@ std::shared_ptr nano::wallet::receive_action (nano::block const & s } if (block != nullptr) { - if (action_complete (block, account, generate_work_a)) + if (action_complete (block, account, generate_work_a, details)) { // Return null block after work generation or ledger process error block = nullptr; @@ -992,6 +1001,7 @@ std::shared_ptr nano::wallet::receive_action (nano::block const & s std::shared_ptr nano::wallet::change_action (nano::account const & source_a, nano::account const & representative_a, uint64_t work_a, bool generate_work_a) { std::shared_ptr block; + nano::block_details details; { auto transaction (wallets.tx_begin_read ()); auto block_transaction (wallets.node.store.tx_begin_read ()); @@ -1003,22 +1013,23 @@ std::shared_ptr nano::wallet::change_action (nano::account const & nano::account_info info; auto error1 (wallets.node.ledger.store.account_get (block_transaction, source_a, info)); (void)error1; - assert (!error1); + debug_assert (!error1); nano::raw_key prv; auto error2 (store.fetch (transaction, source_a, prv)); (void)error2; - assert (!error2); + debug_assert (!error2); if (work_a == 0) { store.work_get (transaction, source_a, work_a); } - block.reset (new nano::state_block (source_a, info.head, representative_a, info.balance, 0, prv, source_a, work_a)); + block = std::make_shared (source_a, info.head, representative_a, info.balance, 0, prv, source_a, work_a); + details.epoch = info.epoch (); } } } if (block != nullptr) { - if (action_complete (block, source_a, generate_work_a)) + if (action_complete (block, source_a, generate_work_a, details)) { // Return null block after work generation or ledger process error block = nullptr; @@ -1035,12 +1046,13 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so id_mdb_val = nano::mdb_val (id_a->size (), const_cast (id_a->data ())); } - // clang-format off - auto prepare_send = [&id_mdb_val, &wallets = this->wallets, &store = this->store, &source_a, &amount_a, &work_a, &account_a] (const auto & transaction) { + auto prepare_send = [&id_mdb_val, &wallets = this->wallets, &store = this->store, &source_a, &amount_a, &work_a, &account_a](const auto & transaction) { auto block_transaction (wallets.node.store.tx_begin_read ()); auto error (false); auto cached_block (false); std::shared_ptr block; + nano::block_details details; + details.is_send = true; if (id_mdb_val) { nano::mdb_val result; @@ -1052,7 +1064,7 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so if (block != nullptr) { cached_block = true; - wallets.node.network.flood_block (block, false); + wallets.node.network.flood_block (block, nano::buffer_drop_policy::no_limiter_drop); } } else if (status != MDB_NOTFOUND) @@ -1073,16 +1085,17 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so nano::account_info info; auto error1 (wallets.node.ledger.store.account_get (block_transaction, source_a, info)); (void)error1; - assert (!error1); + debug_assert (!error1); nano::raw_key prv; auto error2 (store.fetch (transaction, source_a, prv)); (void)error2; - assert (!error2); + debug_assert (!error2); if (work_a == 0) { store.work_get (transaction, source_a, work_a); } - block.reset (new nano::state_block (source_a, info.head, info.representative, balance - amount_a, account_a, prv, source_a, work_a)); + block = std::make_shared (source_a, info.head, info.representative, balance - amount_a, account_a, prv, source_a, work_a); + details.epoch = info.epoch (); if (id_mdb_val && block != nullptr) { auto status (mdb_put (wallets.env.tx (transaction), wallets.node.wallets.send_action_ids, *id_mdb_val, nano::mdb_val (block->hash ()), 0)); @@ -1096,11 +1109,10 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so } } } - return std::make_tuple (block, error, cached_block); + return std::make_tuple (block, error, cached_block, details); }; - // clang-format on - std::tuple, bool, bool> result; + std::tuple, bool, bool, nano::block_details> result; { if (id_mdb_val) { @@ -1115,11 +1127,12 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so std::shared_ptr block; bool error; bool cached_block; - std::tie (block, error, cached_block) = result; + nano::block_details details; + std::tie (block, error, cached_block, details) = result; if (!error && block != nullptr && !cached_block) { - if (action_complete (block, source_a, generate_work_a)) + if (action_complete (block, source_a, generate_work_a, details)) { // Return null block after work generation or ledger process error block = nullptr; @@ -1128,20 +1141,25 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so return block; } -bool nano::wallet::action_complete (std::shared_ptr const & block_a, nano::account const & account_a, bool const generate_work_a) +bool nano::wallet::action_complete (std::shared_ptr const & block_a, nano::account const & account_a, bool const generate_work_a, nano::block_details const & details_a) { bool error{ false }; + // Unschedule any work caching for this account + wallets.delayed_work->erase (account_a); if (block_a != nullptr) { - if (nano::work_validate (*block_a)) + auto required_difficulty{ nano::work_threshold (block_a->work_version (), details_a) }; + if (block_a->difficulty () < required_difficulty) { wallets.node.logger.try_log (boost::str (boost::format ("Cached or provided work for block %1% account %2% is invalid, regenerating") % block_a->hash ().to_string () % account_a.to_account ())); - error = !wallets.node.work_generate_blocking (*block_a, wallets.node.active.limited_active_difficulty ()).is_initialized (); + debug_assert (required_difficulty <= wallets.node.max_work_generate_difficulty (block_a->work_version ())); + auto target_difficulty = std::max (required_difficulty, wallets.node.active.limited_active_difficulty (block_a->work_version (), required_difficulty)); + error = !wallets.node.work_generate_blocking (*block_a, target_difficulty).is_initialized (); } if (!error) { - wallets.watcher->add (block_a); - error = wallets.node.process_local (block_a).code != nano::process_result::progress; + error = wallets.node.process_local (block_a, true).code != nano::process_result::progress; + debug_assert (error || block_a->sideband ().details == details_a); } if (!error && generate_work_a) { @@ -1155,12 +1173,11 @@ bool nano::wallet::change_sync (nano::account const & source_a, nano::account co { std::promise result; std::future future = result.get_future (); - // clang-format off - change_async (source_a, representative_a, [&result](std::shared_ptr block_a) { + change_async ( + source_a, representative_a, [&result](std::shared_ptr block_a) { result.set_value (block_a == nullptr); }, true); - // clang-format on return future.get (); } @@ -1177,12 +1194,11 @@ bool nano::wallet::receive_sync (std::shared_ptr block_a, nano::acc { std::promise result; std::future future = result.get_future (); - // clang-format off - receive_async (block_a, representative_a, amount_a, [&result](std::shared_ptr block_a) { + receive_async ( + block_a, representative_a, amount_a, [&result](std::shared_ptr block_a) { result.set_value (block_a == nullptr); }, true); - // clang-format on return future.get (); } @@ -1199,12 +1215,11 @@ nano::block_hash nano::wallet::send_sync (nano::account const & source_a, nano:: { std::promise result; std::future future = result.get_future (); - // clang-format off - send_async (source_a, account_a, amount_a, [&result](std::shared_ptr block_a) { + send_async ( + source_a, account_a, amount_a, [&result](std::shared_ptr block_a) { result.set_value (block_a->hash ()); }, true); - // clang-format on return future.get (); } @@ -1220,8 +1235,8 @@ void nano::wallet::send_async (nano::account const & source_a, nano::account con // Update work for account if latest root is root_a void nano::wallet::work_update (nano::transaction const & transaction_a, nano::account const & account_a, nano::root const & root_a, uint64_t work_a) { - assert (!nano::work_validate (root_a, work_a)); - assert (store.exists (transaction_a, account_a)); + debug_assert (!nano::work_validate_entry (nano::work_version::work_1, root_a, work_a)); + debug_assert (store.exists (transaction_a, account_a)); auto block_transaction (wallets.node.store.tx_begin_read ()); auto latest (wallets.node.ledger.latest_root (block_transaction, account_a)); if (latest == root_a) @@ -1236,8 +1251,21 @@ void nano::wallet::work_update (nano::transaction const & transaction_a, nano::a void nano::wallet::work_ensure (nano::account const & account_a, nano::root const & root_a) { - wallets.node.wallets.queue_wallet_action (nano::wallets::generate_priority, shared_from_this (), [account_a, root_a](nano::wallet & wallet_a) { - wallet_a.work_cache_blocking (account_a, root_a); + using namespace std::chrono_literals; + std::chrono::seconds const precache_delay = wallets.node.network_params.network.is_test_network () ? 1s : 10s; + + wallets.delayed_work->operator[] (account_a) = root_a; + + wallets.node.alarm.add (std::chrono::steady_clock::now () + precache_delay, [this_l = shared_from_this (), account_a, root_a] { + auto delayed_work = this_l->wallets.delayed_work.lock (); + auto existing (delayed_work->find (account_a)); + if (existing != delayed_work->end () && existing->second == root_a) + { + delayed_work->erase (existing); + this_l->wallets.queue_wallet_action (nano::wallets::generate_priority, this_l, [account_a, root_a](nano::wallet & wallet_a) { + wallet_a.work_cache_blocking (account_a, root_a); + }); + } }); } @@ -1255,7 +1283,7 @@ bool nano::wallet::search_pending () // Don't search pending for watch-only accounts if (!nano::wallet_value (i->second).key.is_zero ()) { - for (auto j (wallets.node.store.pending_begin (block_transaction, nano::pending_key (account, 0))); nano::pending_key (j->first).account == account; ++j) + for (auto j (wallets.node.store.pending_begin (block_transaction, nano::pending_key (account, 0))), k (wallets.node.store.pending_end ()); j != k && nano::pending_key (j->first).account == account; ++j) { nano::pending_key key (j->first); auto hash (key.hash); @@ -1276,8 +1304,11 @@ bool nano::wallet::search_pending () } else { - // Request confirmation for unconfirmed block - wallets.node.block_confirm (block); + if (!wallets.node.confirmation_height_processor.is_processing_block (hash)) + { + // Request confirmation for block which is not being processed yet + wallets.node.block_confirm (block); + } } } } @@ -1320,7 +1351,7 @@ uint32_t nano::wallet::deterministic_check (nano::transaction const & transactio else { // Check if there are pending blocks for account - for (auto ii (wallets.node.store.pending_begin (block_transaction, nano::pending_key (pair.pub, 0))); nano::pending_key (ii->first).account == pair.pub; ++ii) + for (auto ii (wallets.node.store.pending_begin (block_transaction, nano::pending_key (pair.pub, 0))), nn (wallets.node.store.pending_end ()); ii != nn && nano::pending_key (ii->first).account == pair.pub; ++ii) { index = i; n = i + 64 + (i / 64); @@ -1367,7 +1398,8 @@ void nano::wallet::work_cache_blocking (nano::account const & account_a, nano::r { if (wallets.node.work_generation_enabled ()) { - auto opt_work_l (wallets.node.work_generate_blocking (root_a, account_a)); + auto difficulty (wallets.node.default_difficulty (nano::work_version::work_1)); + auto opt_work_l (wallets.node.work_generate_blocking (nano::work_version::work_1, root_a, difficulty, account_a)); if (opt_work_l.is_initialized ()) { auto transaction_l (wallets.tx_begin_write ()); @@ -1378,7 +1410,7 @@ void nano::wallet::work_cache_blocking (nano::account const & account_a, nano::r } else if (!wallets.node.stopped) { - wallets.node.logger.try_log (boost::str (boost::format ("Could not precache work for root %1 due to work generation failure") % root_a.to_string ())); + wallets.node.logger.try_log (boost::str (boost::format ("Could not precache work for root %1% due to work generation failure") % root_a.to_string ())); } } } @@ -1388,7 +1420,7 @@ node (node_a), stopped (false) { node.observers.blocks.add ([this](nano::election_status const & status_a, nano::account const & account_a, nano::amount const & amount_a, bool is_state_send_a) { - this->remove (status_a.winner); + this->remove (*status_a.winner); }); } @@ -1428,84 +1460,53 @@ void nano::work_watcher::watching (nano::qualified_root const & root_a, std::sha std::weak_ptr watcher_w (shared_from_this ()); node.alarm.add (std::chrono::steady_clock::now () + node.config.work_watcher_period, [block_a, root_a, watcher_w]() { auto watcher_l = watcher_w.lock (); - if (watcher_l && !watcher_l->stopped && block_a != nullptr) + if (watcher_l && !watcher_l->stopped && watcher_l->is_watched (root_a)) { - nano::unique_lock lock (watcher_l->mutex); - if (watcher_l->watched.find (root_a) != watcher_l->watched.end ()) // not yet confirmed or cancelled + auto active_difficulty (watcher_l->node.active.limited_active_difficulty (*block_a)); + /* + * Work watcher should still watch blocks even without work generation, although no rework is done + * Functionality may be added in the future that does not require updating work + */ + if (active_difficulty > block_a->difficulty () && watcher_l->node.work_generation_enabled ()) { - lock.unlock (); - uint64_t difficulty (0); - auto root_l (block_a->root ()); - nano::work_validate (root_l, block_a->block_work (), &difficulty); - auto active_difficulty (watcher_l->node.active.limited_active_difficulty ()); - /* - * Work watcher should still watch blocks even without work generation, although no rework is done - * Functionality may be added in the future that does not require updating work - */ - if (active_difficulty > difficulty && watcher_l->node.work_generation_enabled ()) - { - watcher_l->node.work_generate ( - root_l, [watcher_l, block_a, root_a](boost::optional work_a) { - if (block_a != nullptr && watcher_l != nullptr && !watcher_l->stopped) + watcher_l->node.work_generate ( + block_a->work_version (), block_a->root (), active_difficulty, [watcher_l, block_a, root_a](boost::optional work_a) { + if (watcher_l->is_watched (root_a)) + { + if (work_a.is_initialized ()) { - bool updated_l{ false }; - if (work_a.is_initialized ()) + debug_assert (nano::work_difficulty (block_a->work_version (), block_a->root (), *work_a) > block_a->difficulty ()); + nano::state_block_builder builder; + std::error_code ec; + std::shared_ptr block (builder.from (*block_a).work (*work_a).build (ec)); + if (!ec) { - nano::state_block_builder builder; - std::error_code ec; - std::shared_ptr block (builder.from (*block_a).work (*work_a).build (ec)); - - if (!ec) - { - { - auto hash (block_a->hash ()); - nano::lock_guard active_guard (watcher_l->node.active.mutex); - auto existing (watcher_l->node.active.roots.find (root_a)); - if (existing != watcher_l->node.active.roots.end ()) - { - auto election (existing->election); - if (election->status.winner->hash () == hash) - { - election->status.winner = block; - } - auto current (election->blocks.find (hash)); - assert (current != election->blocks.end ()); - current->second = block; - } - } - watcher_l->node.network.flood_block (block, false); - watcher_l->node.active.update_difficulty (block); - watcher_l->update (root_a, block); - updated_l = true; - watcher_l->watching (root_a, block); - } - } - if (!updated_l) - { - watcher_l->watching (root_a, block_a); + watcher_l->node.network.flood_block_initial (block); + watcher_l->node.active.update_difficulty (*block); + watcher_l->update (root_a, block); } } - }, - active_difficulty, block_a->account ()); - } - else - { - watcher_l->watching (root_a, block_a); - } + watcher_l->watching (root_a, block_a); + } + }, + block_a->account ()); + } + else + { + watcher_l->watching (root_a, block_a); } } }); } -void nano::work_watcher::remove (std::shared_ptr block_a) +void nano::work_watcher::remove (nano::block const & block_a) { - auto root_l (block_a->qualified_root ()); nano::lock_guard lock (mutex); - auto existing (watched.find (root_l)); - if (existing != watched.end () && existing->second->hash () == block_a->hash ()) + auto existing (watched.find (block_a.qualified_root ())); + if (existing != watched.end ()) { watched.erase (existing); - node.observers.work_cancel.notify (block_a->root ()); + node.observers.work_cancel.notify (block_a.root ()); } } @@ -1567,7 +1568,7 @@ thread ([this]() { auto status (mdb_dbi_open (env.tx (transaction), nullptr, MDB_CREATE, &handle)); split_if_needed (transaction, node.store); status |= mdb_dbi_open (env.tx (transaction), "send_action_ids", MDB_CREATE, &send_action_ids); - assert (status == 0); + debug_assert (status == 0); std::string beginning (nano::uint256_union (0).to_string ()); std::string end ((nano::uint256_union (nano::uint256_t (0) - nano::uint256_t (1))).to_string ()); nano::store_iterator, nano::no_value> i (std::make_unique, nano::no_value>> (transaction, handle, nano::mdb_val (beginning.size (), const_cast (beginning.c_str ())))); @@ -1577,8 +1578,8 @@ thread ([this]() { nano::wallet_id id; std::string text (i->first.data (), i->first.size ()); auto error (id.decode_hex (text)); - assert (!error); - assert (items.find (id) == items.end ()); + debug_assert (!error); + debug_assert (items.find (id) == items.end ()); auto wallet (std::make_shared (error, transaction, *this, text)); if (!error) { @@ -1642,7 +1643,7 @@ std::shared_ptr nano::wallets::open (nano::wallet_id const & id_a) std::shared_ptr nano::wallets::create (nano::wallet_id const & id_a) { nano::lock_guard lock (mutex); - assert (items.find (id_a) == items.end ()); + debug_assert (items.find (id_a) == items.end ()); std::shared_ptr result; bool error; { @@ -1687,7 +1688,7 @@ void nano::wallets::destroy (nano::wallet_id const & id_a) // action_mutex should be after transactions to prevent deadlocks in deterministic_insert () & insert_adhoc () nano::lock_guard action_lock (action_mutex); auto existing (items.find (id_a)); - assert (existing != items.end ()); + debug_assert (existing != items.end ()); auto wallet (existing->second); items.erase (existing); wallet->store.destroy (transaction); @@ -1707,7 +1708,7 @@ void nano::wallets::reload () nano::wallet_id id; std::string text (i->first.data (), i->first.size ()); auto error (id.decode_hex (text)); - assert (!error); + debug_assert (!error); // New wallet if (items.find (id) == items.end ()) { @@ -1731,7 +1732,7 @@ void nano::wallets::reload () } for (auto & i : deleted_items) { - assert (items.find (i) == items.end ()); + debug_assert (items.find (i) == items.end ()); items.erase (i); } } @@ -1740,7 +1741,7 @@ void nano::wallets::queue_wallet_action (nano::uint128_t const & amount_a, std:: { { nano::lock_guard action_lock (action_mutex); - actions.insert (std::make_pair (amount_a, std::make_pair (wallet_a, std::move (action_a)))); + actions.emplace (amount_a, std::make_pair (wallet_a, std::move (action_a))); } condition.notify_all (); } @@ -1749,41 +1750,51 @@ void nano::wallets::foreach_representative (std::function lock (mutex); - auto transaction_l (tx_begin_read ()); - for (auto i (items.begin ()), n (items.end ()); i != n; ++i) + std::vector> action_accounts_l; { - auto & wallet (*i->second); - nano::lock_guard store_lock (wallet.store.mutex); - nano::lock_guard representatives_lock (wallet.representatives_mutex); - for (auto ii (wallet.representatives.begin ()), nn (wallet.representatives.end ()); ii != nn; ++ii) + auto transaction_l (tx_begin_read ()); + nano::lock_guard lock (mutex); + for (auto i (items.begin ()), n (items.end ()); i != n; ++i) { - nano::account account (*ii); - if (wallet.store.exists (transaction_l, account)) + auto & wallet (*i->second); + nano::lock_guard store_lock (wallet.store.mutex); + decltype (wallet.representatives) representatives_l; { - if (!node.ledger.weight (account).is_zero ()) + nano::lock_guard representatives_lock (wallet.representatives_mutex); + representatives_l = wallet.representatives; + } + for (auto const & account : representatives_l) + { + if (wallet.store.exists (transaction_l, account)) { - if (wallet.store.valid_password (transaction_l)) - { - nano::raw_key prv; - auto error (wallet.store.fetch (transaction_l, account, prv)); - (void)error; - assert (!error); - action_a (account, prv); - } - else + if (!node.ledger.weight (account).is_zero ()) { - static auto last_log = std::chrono::steady_clock::time_point (); - if (last_log < std::chrono::steady_clock::now () - std::chrono::seconds (60)) + if (wallet.store.valid_password (transaction_l)) { - last_log = std::chrono::steady_clock::now (); - node.logger.always_log (boost::str (boost::format ("Representative locked inside wallet %1%") % i->first.to_string ())); + nano::raw_key prv; + auto error (wallet.store.fetch (transaction_l, account, prv)); + (void)error; + debug_assert (!error); + action_accounts_l.emplace_back (account, prv); + } + else + { + static auto last_log = std::chrono::steady_clock::time_point (); + if (last_log < std::chrono::steady_clock::now () - std::chrono::seconds (60)) + { + last_log = std::chrono::steady_clock::now (); + node.logger.always_log (boost::str (boost::format ("Representative locked inside wallet %1%") % i->first.to_string ())); + } } } } } } } + for (auto const & representative : action_accounts_l) + { + action_a (representative.first, representative.second); + } } } @@ -1827,20 +1838,32 @@ void nano::wallets::clear_send_ids (nano::transaction const & transaction_a) { auto status (mdb_drop (env.tx (transaction_a), send_action_ids, 0)); (void)status; - assert (status == 0); + debug_assert (status == 0); } -bool nano::wallets::check_rep (nano::account const & account_a, nano::uint128_t const & half_principal_weight_a) +nano::wallet_representatives nano::wallets::reps () const +{ + nano::lock_guard counts_guard (reps_cache_mutex); + return representatives; +} + +bool nano::wallets::check_rep (nano::account const & account_a, nano::uint128_t const & half_principal_weight_a, const bool acquire_lock_a) { bool result (false); auto weight (node.ledger.weight (account_a)); if (weight >= node.config.vote_minimum.number ()) { + nano::unique_lock lock; + if (acquire_lock_a) + { + lock = nano::unique_lock (reps_cache_mutex); + } result = true; - ++reps_count; + representatives.accounts.insert (account_a); + ++representatives.voting; if (weight >= half_principal_weight_a) { - ++half_principal_reps_count; + ++representatives.half_principal; } } return result; @@ -1848,9 +1871,9 @@ bool nano::wallets::check_rep (nano::account const & account_a, nano::uint128_t void nano::wallets::compute_reps () { - nano::lock_guard lock (mutex); - reps_count = 0; - half_principal_reps_count = 0; + nano::lock_guard guard (mutex); + nano::lock_guard counts_guard (reps_cache_mutex); + representatives.clear (); auto half_principal_weight (node.minimum_principal_weight () / 2); auto transaction (tx_begin_read ()); for (auto i (items.begin ()), n (items.end ()); i != n; ++i) @@ -1860,12 +1883,12 @@ void nano::wallets::compute_reps () for (auto ii (wallet.store.begin (transaction)), nn (wallet.store.end ()); ii != nn; ++ii) { auto account (ii->first); - if (check_rep (account, half_principal_weight)) + if (check_rep (account, half_principal_weight, false)) { representatives_l.insert (account); } } - nano::lock_guard representatives_lock (wallet.representatives_mutex); + nano::lock_guard representatives_guard (wallet.representatives_mutex); wallet.representatives.swap (representatives_l); } } @@ -1890,11 +1913,9 @@ void nano::wallets::split_if_needed (nano::transaction & transaction_destination std::string beginning (nano::uint256_union (0).to_string ()); std::string end ((nano::uint256_union (nano::uint256_t (0) - nano::uint256_t (1))).to_string ()); - // clang-format off - auto get_store_it = [&handle = handle](nano::transaction const & transaction_source, std::string const & hash) { + auto get_store_it = [& handle = handle](nano::transaction const & transaction_source, std::string const & hash) { return nano::store_iterator, nano::no_value> (std::make_unique, nano::no_value>> (transaction_source, handle, nano::mdb_val (hash.size (), const_cast (hash.c_str ())))); }; - // clang-format on // First do a read pass to check if there are any wallets that need extracting (to save holding a write lock and potentially being blocked) auto wallets_need_splitting (false); @@ -1918,8 +1939,8 @@ void nano::wallets::split_if_needed (nano::transaction & transaction_destination std::string text (i->first.data (), i->first.size ()); auto error1 (id.decode_hex (text)); (void)error1; - assert (!error1); - assert (strlen (text.c_str ()) == text.size ()); + debug_assert (!error1); + debug_assert (strlen (text.c_str ()) == text.size ()); move_table (text, tx_source, tx_destination); } } @@ -1932,15 +1953,15 @@ void nano::wallets::move_table (std::string const & name_a, MDB_txn * tx_source, MDB_dbi handle_source; auto error2 (mdb_dbi_open (tx_source, name_a.c_str (), MDB_CREATE, &handle_source)); (void)error2; - assert (!error2); + debug_assert (!error2); MDB_dbi handle_destination; auto error3 (mdb_dbi_open (tx_destination, name_a.c_str (), MDB_CREATE, &handle_destination)); (void)error3; - assert (!error3); + debug_assert (!error3); MDB_cursor * cursor; auto error4 (mdb_cursor_open (tx_source, handle_source, &cursor)); (void)error4; - assert (!error4); + debug_assert (!error4); MDB_val val_key; MDB_val val_value; auto cursor_status (mdb_cursor_get (cursor, &val_key, &val_value, MDB_FIRST)); @@ -1948,12 +1969,12 @@ void nano::wallets::move_table (std::string const & name_a, MDB_txn * tx_source, { auto error5 (mdb_put (tx_destination, handle_destination, &val_key, &val_value, 0)); (void)error5; - assert (!error5); + debug_assert (!error5); cursor_status = mdb_cursor_get (cursor, &val_key, &val_value, MDB_NEXT); } auto error6 (mdb_drop (tx_source, handle_source, 1)); (void)error6; - assert (!error6); + debug_assert (!error6); } nano::uint128_t const nano::wallets::generate_priority = std::numeric_limits::max (); @@ -1997,8 +2018,8 @@ nano::store_iterator nano::wallet_store::end { return nano::store_iterator (nullptr); } -nano::mdb_wallets_store::mdb_wallets_store (boost::filesystem::path const & path_a, int lmdb_max_dbs) : -environment (error, path_a, lmdb_max_dbs, false, 1ULL * 1024 * 1024 * 1024) +nano::mdb_wallets_store::mdb_wallets_store (boost::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a) : +environment (error, path_a, nano::mdb_env::options::make ().set_config (lmdb_config_a).override_config_sync (nano::lmdb_config::sync_strategy::always).override_config_map_size (1ULL * 1024 * 1024 * 1024)) { } @@ -2012,25 +2033,22 @@ MDB_txn * nano::wallet_store::tx (nano::transaction const & transaction_a) const return static_cast (transaction_a.get_handle ()); } -namespace nano +std::unique_ptr nano::collect_container_info (wallets & wallets, const std::string & name) { -std::unique_ptr collect_seq_con_info (wallets & wallets, const std::string & name) -{ - size_t items_count = 0; - size_t actions_count = 0; + size_t items_count; + size_t actions_count; { nano::lock_guard guard (wallets.mutex); items_count = wallets.items.size (); actions_count = wallets.actions.size (); } - auto composite = std::make_unique (name); auto sizeof_item_element = sizeof (decltype (wallets.items)::value_type); auto sizeof_actions_element = sizeof (decltype (wallets.actions)::value_type); auto sizeof_watcher_element = sizeof (decltype (wallets.watcher->watched)::value_type); - composite->add_component (std::make_unique (seq_con_info{ "items", items_count, sizeof_item_element })); - composite->add_component (std::make_unique (seq_con_info{ "actions", actions_count, sizeof_actions_element })); - composite->add_component (std::make_unique (seq_con_info{ "work_watcher", wallets.watcher->size (), sizeof_watcher_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "items", items_count, sizeof_item_element })); + composite->add_component (std::make_unique (container_info{ "actions", actions_count, sizeof_actions_element })); + composite->add_component (std::make_unique (container_info{ "work_watcher", wallets.watcher->size (), sizeof_watcher_element })); return composite; } -} diff --git a/nano/node/wallet.hpp b/nano/node/wallet.hpp index 7581f14547..b59201f70b 100644 --- a/nano/node/wallet.hpp +++ b/nano/node/wallet.hpp @@ -1,17 +1,18 @@ #pragma once -#include +#include +#include +#include #include #include #include #include #include -#include - +#include #include +#include #include - namespace nano { class node; @@ -111,7 +112,7 @@ class wallet_store final static size_t const seed_iv_index; static int const special_count; nano::kdf & kdf; - MDB_dbi handle{ 0 }; + std::atomic handle{ 0 }; std::recursive_mutex mutex; private: @@ -124,7 +125,7 @@ class wallet final : public std::enable_shared_from_this std::shared_ptr change_action (nano::account const &, nano::account const &, uint64_t = 0, bool = true); std::shared_ptr receive_action (nano::block const &, nano::account const &, nano::uint128_union const &, uint64_t = 0, bool = true); std::shared_ptr send_action (nano::account const &, nano::account const &, nano::uint128_t const &, uint64_t = 0, bool = true, boost::optional = {}); - bool action_complete (std::shared_ptr const &, nano::account const &, bool const); + bool action_complete (std::shared_ptr const &, nano::account const &, bool const, nano::block_details const &); wallet (bool &, nano::transaction &, nano::wallets &, std::string const &); wallet (bool &, nano::transaction &, nano::wallets &, std::string const &, std::string const &); void enter_initial_password (); @@ -146,6 +147,7 @@ class wallet final : public std::enable_shared_from_this void send_async (nano::account const &, nano::account const &, nano::uint128_t const &, std::function)> const &, uint64_t = 0, bool = true, boost::optional = {}); void work_cache_blocking (nano::account const &, nano::root const &); void work_update (nano::transaction const &, nano::account const &, nano::root const &, uint64_t); + // Schedule work generation after a few seconds void work_ensure (nano::account const &, nano::root const &); bool search_pending (); void init_free_accounts (nano::transaction const &); @@ -172,7 +174,7 @@ class work_watcher final : public std::enable_shared_from_this); void update (nano::qualified_root const &, std::shared_ptr); void watching (nano::qualified_root const &, std::shared_ptr); - void remove (std::shared_ptr); + void remove (nano::block const &); bool is_watched (nano::qualified_root const &); size_t size (); std::mutex mutex; @@ -180,6 +182,29 @@ class work_watcher final : public std::enable_shared_from_this> watched; std::atomic stopped; }; + +class wallet_representatives +{ +public: + uint64_t voting{ 0 }; // Number of representatives with at least the configured minimum voting weight + uint64_t half_principal{ 0 }; // Number of representatives with at least 50% of principal representative requirements + std::unordered_set accounts; // Representatives with at least the configured minimum voting weight + bool have_half_rep () const + { + return half_principal > 0; + } + bool exists (nano::account const & rep_a) const + { + return accounts.count (rep_a) > 0; + } + void clear () + { + voting = 0; + half_principal = 0; + accounts.clear (); + } +}; + /** * The wallets set is all the wallets a node controls. * A node may contain multiple wallets independently encrypted and operated. @@ -198,10 +223,11 @@ class wallets final void do_wallet_actions (); void queue_wallet_action (nano::uint128_t const &, std::shared_ptr, std::function const &); void foreach_representative (std::function const &); - bool exists (nano::transaction const &, nano::public_key const &); + bool exists (nano::transaction const &, nano::account const &); void stop (); void clear_send_ids (nano::transaction const &); - bool check_rep (nano::account const &, nano::uint128_t const &); + nano::wallet_representatives reps () const; + bool check_rep (nano::account const &, nano::uint128_t const &, const bool = true); void compute_reps (); void ongoing_compute_reps (); void split_if_needed (nano::transaction &, nano::block_store &); @@ -210,6 +236,7 @@ class wallets final std::function observer; std::unordered_map> items; std::multimap, std::function>, std::greater> actions; + nano::locked> delayed_work; std::mutex mutex; std::mutex action_mutex; nano::condition_variable condition; @@ -220,20 +247,21 @@ class wallets final nano::mdb_env & env; std::atomic stopped; std::shared_ptr watcher; - boost::thread thread; + std::thread thread; static nano::uint128_t const generate_priority; static nano::uint128_t const high_priority; - std::atomic reps_count{ 0 }; - std::atomic half_principal_reps_count{ 0 }; // Representatives with at least 50% of principal representative requirements - /** Start read-write transaction */ nano::write_transaction tx_begin_write (); /** Start read-only transaction */ nano::read_transaction tx_begin_read (); + +private: + mutable std::mutex reps_cache_mutex; + nano::wallet_representatives representatives; }; -std::unique_ptr collect_seq_con_info (wallets & wallets, const std::string & name); +std::unique_ptr collect_container_info (wallets & wallets, const std::string & name); class wallets_store { @@ -244,7 +272,7 @@ class wallets_store class mdb_wallets_store final : public wallets_store { public: - mdb_wallets_store (boost::filesystem::path const &, int lmdb_max_dbs = 128); + mdb_wallets_store (boost::filesystem::path const &, nano::lmdb_config const & lmdb_config_a = nano::lmdb_config{}); nano::mdb_env environment; bool init_error () const override; bool error{ false }; diff --git a/nano/node/websocket.cpp b/nano/node/websocket.cpp index bfa774942b..ab0f59ec7e 100644 --- a/nano/node/websocket.cpp +++ b/nano/node/websocket.cpp @@ -1,4 +1,10 @@ -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -7,13 +13,14 @@ #include #include -nano::websocket::confirmation_options::confirmation_options (nano::node & node_a) : -node (node_a) +nano::websocket::confirmation_options::confirmation_options (nano::wallets & wallets_a) : +wallets (wallets_a) { } -nano::websocket::confirmation_options::confirmation_options (boost::property_tree::ptree const & options_a, nano::node & node_a) : -node (node_a) +nano::websocket::confirmation_options::confirmation_options (boost::property_tree::ptree const & options_a, nano::wallets & wallets_a, nano::logger_mt & logger_a) : +wallets (wallets_a), +logger (logger_a) { // Non-account filtering options include_block = options_a.get ("include_block", true); @@ -52,7 +59,7 @@ node (node_a) if (!include_block) { - node.logger.always_log ("Websocket: Filtering option \"all_local_accounts\" requires that \"include_block\" is set to true to be effective"); + logger_a.always_log ("Websocket: Filtering option \"all_local_accounts\" requires that \"include_block\" is set to true to be effective"); } } auto accounts_l (options_a.get_child_optional ("accounts")); @@ -69,20 +76,16 @@ node (node_a) } else { - node.logger.always_log ("Websocket: invalid account provided for filtering blocks: ", account_l.second.data ()); + logger_a.always_log ("Websocket: invalid account provided for filtering blocks: ", account_l.second.data ()); } } if (!include_block) { - node.logger.always_log ("Websocket: Filtering option \"accounts\" requires that \"include_block\" is set to true to be effective"); + logger_a.always_log ("Websocket: Filtering option \"accounts\" requires that \"include_block\" is set to true to be effective"); } } - // Warn the user if the options resulted in an empty filter - if (has_account_filtering_options && !all_local_accounts && accounts.empty ()) - { - node.logger.always_log ("Websocket: provided options resulted in an empty block confirmation filter"); - } + check_filter_empty (); } bool nano::websocket::confirmation_options::should_filter (nano::websocket::message const & message_a) const @@ -110,14 +113,14 @@ bool nano::websocket::confirmation_options::should_filter (nano::websocket::mess auto source_text_l (message_a.contents.get ("message.account")); if (all_local_accounts) { - auto transaction_l (node.wallets.tx_begin_read ()); + auto transaction_l (wallets.tx_begin_read ()); nano::account source_l (0), destination_l (0); auto decode_source_ok_l (!source_l.decode_account (source_text_l)); auto decode_destination_ok_l (!destination_l.decode_account (destination_opt_l.get ())); (void)decode_source_ok_l; (void)decode_destination_ok_l; - assert (decode_source_ok_l && decode_destination_ok_l); - if (node.wallets.exists (transaction_l, source_l) || node.wallets.exists (transaction_l, destination_l)) + debug_assert (decode_source_ok_l && decode_destination_ok_l); + if (wallets.exists (transaction_l, source_l) || wallets.exists (transaction_l, destination_l)) { should_filter_account = false; } @@ -131,9 +134,64 @@ bool nano::websocket::confirmation_options::should_filter (nano::websocket::mess return should_filter_conf_type_l || should_filter_account; } -nano::websocket::vote_options::vote_options (boost::property_tree::ptree const & options_a, nano::node & node_a) : -node (node_a) +bool nano::websocket::confirmation_options::update (boost::property_tree::ptree const & options_a) { + auto update_accounts = [this](boost::property_tree::ptree const & accounts_text_a, bool insert_a) { + this->has_account_filtering_options = true; + for (auto const & account_l : accounts_text_a) + { + nano::account result_l (0); + if (!result_l.decode_account (account_l.second.data ())) + { + // Re-encode to keep old prefix support + auto encoded_l (result_l.to_account ()); + if (insert_a) + { + this->accounts.insert (encoded_l); + } + else + { + this->accounts.erase (encoded_l); + } + } + else if (this->logger.is_initialized ()) + { + this->logger->always_log ("Websocket: invalid account provided for filtering blocks: ", account_l.second.data ()); + } + } + }; + + // Adding accounts as filter exceptions + auto accounts_add_l (options_a.get_child_optional ("accounts_add")); + if (accounts_add_l) + { + update_accounts (*accounts_add_l, true); + } + + // Removing accounts as filter exceptions + auto accounts_del_l (options_a.get_child_optional ("accounts_del")); + if (accounts_del_l) + { + update_accounts (*accounts_del_l, false); + } + + check_filter_empty (); + return false; +} + +void nano::websocket::confirmation_options::check_filter_empty () const +{ + // Warn the user if the options resulted in an empty filter + if (logger.is_initialized () && has_account_filtering_options && !all_local_accounts && accounts.empty ()) + { + logger->always_log ("Websocket: provided options resulted in an empty block confirmation filter"); + } +} + +nano::websocket::vote_options::vote_options (boost::property_tree::ptree const & options_a, nano::logger_mt & logger_a) +{ + include_replays = options_a.get ("include_replays", false); + include_indeterminate = options_a.get ("include_indeterminate", false); auto representatives_l (options_a.get_child_optional ("representatives")); if (representatives_l) { @@ -147,24 +205,28 @@ node (node_a) } else { - node.logger.always_log ("Websocket: invalid account given to filter votes: ", representative_l.second.data ()); + logger_a.always_log ("Websocket: invalid account given to filter votes: ", representative_l.second.data ()); } } - } - // Warn the user if the options resulted in an empty filter - if (representatives.empty ()) - { - node.logger.always_log ("Websocket: provided options resulted in an empty vote filter"); + // Warn the user if the option will be ignored + if (representatives.empty ()) + { + logger_a.always_log ("Websocket: account filter for votes is empty, no messages will be filtered"); + } } } bool nano::websocket::vote_options::should_filter (nano::websocket::message const & message_a) const { - bool should_filter_l (true); - auto representative_text_l (message_a.contents.get ("message.account")); - if (representatives.find (representative_text_l) != representatives.end ()) + auto type (message_a.contents.get ("message.type")); + bool should_filter_l = (!include_replays && type == "replay") || (!include_indeterminate && type == "indeterminate"); + if (!should_filter_l && !representatives.empty ()) { - should_filter_l = false; + auto representative_text_l (message_a.contents.get ("message.account")); + if (representatives.find (representative_text_l) == representatives.end ()) + { + should_filter_l = true; + } } return should_filter_l; } @@ -173,7 +235,7 @@ nano::websocket::session::session (nano::websocket::listener & listener_a, socke ws_listener (listener_a), ws (std::move (socket_a)), strand (ws.get_executor ()) { ws.text (true); - ws_listener.get_node ().logger.try_log ("Websocket: session started"); + ws_listener.get_logger ().try_log ("Websocket: session started"); } nano::websocket::session::~session () @@ -198,17 +260,16 @@ void nano::websocket::session::handshake () } else { - this_l->ws_listener.get_node ().logger.always_log ("Websocket: handshake failed: ", ec.message ()); + this_l->ws_listener.get_logger ().always_log ("Websocket: handshake failed: ", ec.message ()); } }); } void nano::websocket::session::close () { - ws_listener.get_node ().logger.try_log ("Websocket: session closing"); + ws_listener.get_logger ().try_log ("Websocket: session closing"); auto this_l (shared_from_this ()); - // clang-format off boost::asio::dispatch (strand, [this_l]() { boost::beast::websocket::close_reason reason; @@ -217,12 +278,10 @@ void nano::websocket::session::close () boost::system::error_code ec_ignore; this_l->ws.close (reason, ec_ignore); }); - // clang-format on } void nano::websocket::session::write (nano::websocket::message message_a) { - // clang-format off nano::unique_lock lk (subscriptions_mutex); auto subscription (subscriptions.find (message_a.topic)); if (message_a.topic == nano::websocket::topic::ack || (subscription != subscriptions.end () && !subscription->second->should_filter (message_a))) @@ -239,7 +298,6 @@ void nano::websocket::session::write (nano::websocket::message message_a) } }); } - // clang-format on } void nano::websocket::session::write_queued_messages () @@ -247,7 +305,6 @@ void nano::websocket::session::write_queued_messages () auto msg (send_queue.front ().to_string ()); auto this_l (shared_from_this ()); - // clang-format off ws.async_write (nano::shared_const_buffer (msg), boost::asio::bind_executor (strand, [this_l](boost::system::error_code ec, std::size_t bytes_transferred) { @@ -260,14 +317,12 @@ void nano::websocket::session::write_queued_messages () } } })); - // clang-format on } void nano::websocket::session::read () { auto this_l (shared_from_this ()); - // clang-format off boost::asio::post (strand, [this_l]() { this_l->ws.async_read (this_l->read_buffer, boost::asio::bind_executor (this_l->strand, @@ -290,16 +345,15 @@ void nano::websocket::session::read () } catch (boost::property_tree::json_parser::json_parser_error const & ex) { - this_l->ws_listener.get_node ().logger.try_log ("Websocket: json parsing failed: ", ex.what ()); + this_l->ws_listener.get_logger ().try_log ("Websocket: json parsing failed: ", ex.what ()); } } else if (ec != boost::asio::error::eof) { - this_l->ws_listener.get_node ().logger.try_log ("Websocket: read failed: ", ec.message ()); + this_l->ws_listener.get_logger ().try_log ("Websocket: read failed: ", ec.message ()); } })); }); - // clang-format on } namespace @@ -331,6 +385,18 @@ nano::websocket::topic to_topic (std::string const & topic_a) { topic = nano::websocket::topic::work; } + else if (topic_a == "bootstrap") + { + topic = nano::websocket::topic::bootstrap; + } + else if (topic_a == "telemetry") + { + topic = nano::websocket::topic::telemetry; + } + else if (topic_a == "new_unconfirmed_block") + { + topic = nano::websocket::topic::new_unconfirmed_block; + } return topic; } @@ -362,6 +428,19 @@ std::string from_topic (nano::websocket::topic topic_a) { topic = "work"; } + else if (topic_a == nano::websocket::topic::bootstrap) + { + topic = "bootstrap"; + } + else if (topic_a == nano::websocket::topic::telemetry) + { + topic = "telemetry"; + } + else if (topic_a == nano::websocket::topic::new_unconfirmed_block) + { + topic = "new_unconfirmed_block"; + } + return topic; } } @@ -394,11 +473,11 @@ void nano::websocket::session::handle_message (boost::property_tree::ptree const std::unique_ptr options_l{ nullptr }; if (options_text_l && topic_l == nano::websocket::topic::confirmation) { - options_l = std::make_unique (options_text_l.get (), ws_listener.get_node ()); + options_l = std::make_unique (options_text_l.get (), ws_listener.get_wallets (), ws_listener.get_logger ()); } else if (options_text_l && topic_l == nano::websocket::topic::vote) { - options_l = std::make_unique (options_text_l.get (), ws_listener.get_node ()); + options_l = std::make_unique (options_text_l.get (), ws_listener.get_logger ()); } else { @@ -408,22 +487,35 @@ void nano::websocket::session::handle_message (boost::property_tree::ptree const if (existing != subscriptions.end ()) { existing->second = std::move (options_l); - ws_listener.get_node ().logger.always_log ("Websocket: updated subscription to topic: ", from_topic (topic_l)); + ws_listener.get_logger ().always_log ("Websocket: updated subscription to topic: ", from_topic (topic_l)); } else { - subscriptions.insert (std::make_pair (topic_l, std::move (options_l))); - ws_listener.get_node ().logger.always_log ("Websocket: new subscription to topic: ", from_topic (topic_l)); + subscriptions.emplace (topic_l, std::move (options_l)); + ws_listener.get_logger ().always_log ("Websocket: new subscription to topic: ", from_topic (topic_l)); ws_listener.increase_subscriber_count (topic_l); } action_succeeded = true; } + else if (action == "update") + { + nano::lock_guard lk (subscriptions_mutex); + auto existing (subscriptions.find (topic_l)); + if (existing != subscriptions.end ()) + { + auto options_text_l (message_a.get_child_optional ("options")); + if (options_text_l.is_initialized () && !existing->second->update (*options_text_l)) + { + action_succeeded = true; + } + } + } else if (action == "unsubscribe" && topic_l != nano::websocket::topic::invalid) { nano::lock_guard lk (subscriptions_mutex); if (subscriptions.erase (topic_l)) { - ws_listener.get_node ().logger.always_log ("Websocket: removed subscription to topic: ", from_topic (topic_l)); + ws_listener.get_logger ().always_log ("Websocket: removed subscription to topic: ", from_topic (topic_l)); ws_listener.decrease_subscriber_count (topic_l); } action_succeeded = true; @@ -457,10 +549,11 @@ void nano::websocket::listener::stop () sessions.clear (); } -nano::websocket::listener::listener (nano::node & node_a, boost::asio::ip::tcp::endpoint endpoint_a) : -node (node_a), -acceptor (node_a.io_ctx), -socket (node_a.io_ctx) +nano::websocket::listener::listener (nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a) : +logger (logger_a), +wallets (wallets_a), +acceptor (io_ctx_a), +socket (io_ctx_a) { try { @@ -471,7 +564,7 @@ socket (node_a.io_ctx) } catch (std::exception const & ex) { - node.logger.always_log ("Websocket: listen failed: ", ex.what ()); + logger.always_log ("Websocket: listen failed: ", ex.what ()); } } @@ -496,7 +589,7 @@ void nano::websocket::listener::on_accept (boost::system::error_code ec) { if (ec) { - node.logger.always_log ("Websocket: accept failed: ", ec.message ()); + logger.always_log ("Websocket: accept failed: ", ec.message ()); } else { @@ -531,7 +624,7 @@ void nano::websocket::listener::broadcast_confirmation (std::shared_ptrsubscriptions.find (nano::websocket::topic::confirmation)); if (subscription != session_ptr->subscriptions.end ()) { - nano::websocket::confirmation_options default_options (node); + nano::websocket::confirmation_options default_options (wallets); auto conf_options (dynamic_cast (subscription->second.get ())); if (conf_options == nullptr) { @@ -549,7 +642,7 @@ void nano::websocket::listener::broadcast_confirmation (std::shared_ptrwrite (include_block ? msg_with_block.get () : msg_without_block.get ()); @@ -629,7 +722,9 @@ nano::websocket::message nano::websocket::message_builder::block_confirmed (std: election_node_l.add ("duration", election_status_a.election_duration.count ()); election_node_l.add ("time", election_status_a.election_end.count ()); election_node_l.add ("tally", election_status_a.tally.to_string_dec ()); - election_node_l.add ("request_count", election_status_a.confirmation_request_count); + election_node_l.add ("blocks", std::to_string (election_status_a.block_count)); + election_node_l.add ("voters", std::to_string (election_status_a.voter_count)); + election_node_l.add ("request_count", std::to_string (election_status_a.confirmation_request_count)); message_node_l.add_child ("election_info", election_node_l); } @@ -649,7 +744,7 @@ nano::websocket::message nano::websocket::message_builder::block_confirmed (std: return message_l; } -nano::websocket::message nano::websocket::message_builder::vote_received (std::shared_ptr vote_a) +nano::websocket::message nano::websocket::message_builder::vote_received (std::shared_ptr vote_a, nano::vote_code code_a) { nano::websocket::message message_l (nano::websocket::topic::vote); set_common_fields (message_l); @@ -657,6 +752,25 @@ nano::websocket::message nano::websocket::message_builder::vote_received (std::s // Vote information boost::property_tree::ptree vote_node_l; vote_a->serialize_json (vote_node_l); + + // Vote processing information + std::string vote_type = "invalid"; + switch (code_a) + { + case nano::vote_code::vote: + vote_type = "vote"; + break; + case nano::vote_code::replay: + vote_type = "replay"; + break; + case nano::vote_code::indeterminate: + vote_type = "indeterminate"; + break; + case nano::vote_code::invalid: + debug_assert (false); + break; + } + vote_node_l.put ("type", vote_type); message_l.contents.add_child ("message", vote_node_l); return message_l; } @@ -677,7 +791,7 @@ nano::websocket::message nano::websocket::message_builder::difficulty_changed (u return message_l; } -nano::websocket::message nano::websocket::message_builder::work_generation (nano::block_hash const & root_a, uint64_t work_a, uint64_t difficulty_a, uint64_t publish_threshold_a, std::chrono::milliseconds const & duration_a, std::string const & peer_a, std::vector const & bad_peers_a, bool completed_a, bool cancelled_a) +nano::websocket::message nano::websocket::message_builder::work_generation (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t work_a, uint64_t difficulty_a, uint64_t publish_threshold_a, std::chrono::milliseconds const & duration_a, std::string const & peer_a, std::vector const & bad_peers_a, bool completed_a, bool cancelled_a) { nano::websocket::message message_l (nano::websocket::topic::work); set_common_fields (message_l); @@ -689,6 +803,7 @@ nano::websocket::message nano::websocket::message_builder::work_generation (nano work_l.put ("duration", duration_a.count ()); boost::property_tree::ptree request_l; + request_l.put ("version", nano::to_string (version_a)); request_l.put ("hash", root_a.to_string ()); request_l.put ("difficulty", nano::to_string_hex (difficulty_a)); auto request_multiplier_l (nano::difficulty::to_multiplier (difficulty_a, publish_threshold_a)); @@ -700,8 +815,7 @@ nano::websocket::message nano::websocket::message_builder::work_generation (nano boost::property_tree::ptree result_l; result_l.put ("source", peer_a); result_l.put ("work", nano::to_string_hex (work_a)); - uint64_t result_difficulty_l; - nano::work_validate (root_a, work_a, &result_difficulty_l); + auto result_difficulty_l (nano::work_difficulty (version_a, root_a, work_a)); result_l.put ("difficulty", nano::to_string_hex (result_difficulty_l)); auto result_multiplier_l (nano::difficulty::to_multiplier (result_difficulty_l, publish_threshold_a)); result_l.put ("multiplier", nano::to_string (result_multiplier_l)); @@ -721,14 +835,75 @@ nano::websocket::message nano::websocket::message_builder::work_generation (nano return message_l; } -nano::websocket::message nano::websocket::message_builder::work_cancelled (nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a) +nano::websocket::message nano::websocket::message_builder::work_cancelled (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a) +{ + return work_generation (version_a, root_a, 0, difficulty_a, publish_threshold_a, duration_a, "", bad_peers_a, false, true); +} + +nano::websocket::message nano::websocket::message_builder::work_failed (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a) +{ + return work_generation (version_a, root_a, 0, difficulty_a, publish_threshold_a, duration_a, "", bad_peers_a, false, false); +} + +nano::websocket::message nano::websocket::message_builder::bootstrap_started (std::string const & id_a, std::string const & mode_a) +{ + nano::websocket::message message_l (nano::websocket::topic::bootstrap); + set_common_fields (message_l); + + // Bootstrap information + boost::property_tree::ptree bootstrap_l; + bootstrap_l.put ("reason", "started"); + bootstrap_l.put ("id", id_a); + bootstrap_l.put ("mode", mode_a); + + message_l.contents.add_child ("message", bootstrap_l); + return message_l; +} + +nano::websocket::message nano::websocket::message_builder::bootstrap_exited (std::string const & id_a, std::string const & mode_a, std::chrono::steady_clock::time_point const start_time_a, uint64_t const total_blocks_a) +{ + nano::websocket::message message_l (nano::websocket::topic::bootstrap); + set_common_fields (message_l); + + // Bootstrap information + boost::property_tree::ptree bootstrap_l; + bootstrap_l.put ("reason", "exited"); + bootstrap_l.put ("id", id_a); + bootstrap_l.put ("mode", mode_a); + bootstrap_l.put ("total_blocks", total_blocks_a); + bootstrap_l.put ("duration", std::chrono::duration_cast (std::chrono::steady_clock::now () - start_time_a).count ()); + + message_l.contents.add_child ("message", bootstrap_l); + return message_l; +} + +nano::websocket::message nano::websocket::message_builder::telemetry_received (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a) { - return work_generation (root_a, 0, difficulty_a, publish_threshold_a, duration_a, "", bad_peers_a, false, true); + nano::websocket::message message_l (nano::websocket::topic::telemetry); + set_common_fields (message_l); + + // Telemetry information + nano::jsonconfig telemetry_l; + telemetry_data_a.serialize_json (telemetry_l, false); + telemetry_l.put ("address", endpoint_a.address ()); + telemetry_l.put ("port", endpoint_a.port ()); + + message_l.contents.add_child ("message", telemetry_l.get_tree ()); + return message_l; } -nano::websocket::message nano::websocket::message_builder::work_failed (nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a) +nano::websocket::message nano::websocket::message_builder::new_block_arrived (nano::block const & block_a) { - return work_generation (root_a, 0, difficulty_a, publish_threshold_a, duration_a, "", bad_peers_a, false, false); + nano::websocket::message message_l (nano::websocket::topic::new_unconfirmed_block); + set_common_fields (message_l); + + boost::property_tree::ptree block_l; + block_a.serialize_json (block_l); + auto subtype (nano::state_subtype (block_a.sideband ().details)); + block_l.put ("subtype", subtype); + + message_l.contents.add_child ("message", block_l); + return message_l; } void nano::websocket::message_builder::set_common_fields (nano::websocket::message & message_a) diff --git a/nano/node/websocket.hpp b/nano/node/websocket.hpp index ea2627849b..f76518fa47 100644 --- a/nano/node/websocket.hpp +++ b/nano/node/websocket.hpp @@ -1,21 +1,20 @@ #pragma once -#include -#include +#include +#include +#include #include #include +#include +#include +#include #include -#include -#include #include -#include -#include #include #include #include -#include #include #include #include @@ -31,12 +30,17 @@ using socket_type = boost::asio::basic_stream_socket block_a, nano::account const & account_a, nano::amount const & amount_a, std::string subtype, bool include_block, nano::election_status const & election_status_a, nano::websocket::confirmation_options const & options_a); message stopped_election (nano::block_hash const & hash_a); - message vote_received (std::shared_ptr vote_a); + message vote_received (std::shared_ptr vote_a, nano::vote_code code_a); message difficulty_changed (uint64_t publish_threshold_a, uint64_t difficulty_active_a); - message work_generation (nano::block_hash const & root_a, uint64_t const work_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::string const & peer_a, std::vector const & bad_peers_a, bool const completed_a = true, bool const cancelled_a = false); - message work_cancelled (nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a); - message work_failed (nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a); + message work_generation (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t const work_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::string const & peer_a, std::vector const & bad_peers_a, bool const completed_a = true, bool const cancelled_a = false); + message work_cancelled (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a); + message work_failed (nano::work_version const version_a, nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector const & bad_peers_a); + message bootstrap_started (std::string const & id_a, std::string const & mode_a); + message bootstrap_exited (std::string const & id_a, std::string const & mode_a, std::chrono::steady_clock::time_point const start_time_a, uint64_t const total_blocks_a); + message telemetry_received (nano::telemetry_data const &, nano::endpoint const &); + message new_block_arrived (nano::block const & block_a); private: /** Set the common fields for messages: timestamp and topic. */ @@ -98,6 +112,9 @@ namespace websocket class options { public: + virtual ~options () = default; + + protected: /** * Checks if a message should be filtered for default options (no options given). * @param message_a the message to be checked @@ -107,7 +124,16 @@ namespace websocket { return false; } - virtual ~options () = default; + /** + * Update options, if available for a given topic + * @return false on success + */ + virtual bool update (boost::property_tree::ptree const & options_a) + { + return true; + } + + friend class session; }; /** @@ -123,8 +149,8 @@ namespace websocket class confirmation_options final : public options { public: - confirmation_options (nano::node & node_a); - confirmation_options (boost::property_tree::ptree const & options_a, nano::node & node_a); + confirmation_options (nano::wallets & wallets_a); + confirmation_options (boost::property_tree::ptree const & options_a, nano::wallets & wallets_a, nano::logger_mt & logger_a); /** * Checks if a message should be filtered for given block confirmation options. @@ -133,6 +159,15 @@ namespace websocket */ bool should_filter (message const & message_a) const override; + /** + * Update some existing options + * Filtering options: + * - "accounts_add" (array of std::strings) - additional accounts for which blocks should not be filtered + * - "accounts_del" (array of std::strings) - accounts for which blocks should be filtered + * @return false + */ + bool update (boost::property_tree::ptree const & options_a) override; + /** Returns whether or not block contents should be included */ bool get_include_block () const { @@ -152,7 +187,10 @@ namespace websocket static constexpr const uint8_t type_all = type_all_active | type_inactive; private: - nano::node & node; + void check_filter_empty () const; + + nano::wallets & wallets; + boost::optional logger; bool include_election_info{ false }; bool include_block{ true }; bool has_account_filtering_options{ false }; @@ -169,8 +207,7 @@ namespace websocket class vote_options final : public options { public: - vote_options (); - vote_options (boost::property_tree::ptree const & options_a, nano::node & node_a); + vote_options (boost::property_tree::ptree const & options_a, nano::logger_mt & logger_a); /** * Checks if a message should be filtered for given vote received options. @@ -180,8 +217,9 @@ namespace websocket bool should_filter (message const & message_a) const override; private: - nano::node & node; std::unordered_set representatives; + bool include_replays{ false }; + bool include_indeterminate{ false }; }; /** A websocket session managing its own lifetime */ @@ -243,7 +281,7 @@ namespace websocket class listener final : public std::enable_shared_from_this { public: - listener (nano::node & node_a, boost::asio::ip::tcp::endpoint endpoint_a); + listener (nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a); /** Start accepting connections */ void run (); @@ -259,9 +297,14 @@ namespace websocket /** Broadcast \p message to all session subscribing to the message topic. */ void broadcast (nano::websocket::message message_a); - nano::node & get_node () const + nano::logger_mt & get_logger () const + { + return logger; + } + + nano::wallets & get_wallets () const { - return node; + return wallets; } /** @@ -287,7 +330,8 @@ namespace websocket /** Removes from subscription count of a specific topic*/ void decrease_subscriber_count (nano::websocket::topic const & topic_a); - nano::node & node; + nano::logger_mt & logger; + nano::wallets & wallets; boost::asio::ip::tcp::acceptor acceptor; socket_type socket; std::mutex sessions_mutex; diff --git a/nano/node/websocketconfig.cpp b/nano/node/websocketconfig.cpp index 7ce933a5a1..6cd91574bd 100644 --- a/nano/node/websocketconfig.cpp +++ b/nano/node/websocketconfig.cpp @@ -1,16 +1,18 @@ +#include #include #include #include nano::websocket::config::config () : -port (network_constants.default_websocket_port) +port (network_constants.default_websocket_port), +address (boost::asio::ip::address_v6::loopback ().to_string ()) { } nano::error nano::websocket::config::serialize_toml (nano::tomlconfig & toml) const { toml.put ("enable", enabled, "Enable or disable WebSocket server.\ntype:bool"); - toml.put ("address", address.to_string (), "WebSocket server bind address.\ntype:string,ip"); + toml.put ("address", address, "WebSocket server bind address.\ntype:string,ip"); toml.put ("port", port, "WebSocket server listening port.\ntype:uint16"); return toml.get_error (); } @@ -18,7 +20,9 @@ nano::error nano::websocket::config::serialize_toml (nano::tomlconfig & toml) co nano::error nano::websocket::config::deserialize_toml (nano::tomlconfig & toml) { toml.get ("enable", enabled); - toml.get ("address", address); + boost::asio::ip::address_v6 address_l; + toml.get_optional ("address", address_l, boost::asio::ip::address_v6::loopback ()); + address = address_l.to_string (); toml.get ("port", port); return toml.get_error (); } @@ -26,7 +30,7 @@ nano::error nano::websocket::config::deserialize_toml (nano::tomlconfig & toml) nano::error nano::websocket::config::serialize_json (nano::jsonconfig & json) const { json.put ("enable", enabled); - json.put ("address", address.to_string ()); + json.put ("address", address); json.put ("port", port); return json.get_error (); } @@ -34,7 +38,9 @@ nano::error nano::websocket::config::serialize_json (nano::jsonconfig & json) co nano::error nano::websocket::config::deserialize_json (nano::jsonconfig & json) { json.get ("enable", enabled); - json.get_required ("address", address); + boost::asio::ip::address_v6 address_l; + json.get_required ("address", address_l, boost::asio::ip::address_v6::loopback ()); + address = address_l.to_string (); json.get ("port", port); return json.get_error (); } diff --git a/nano/node/websocketconfig.hpp b/nano/node/websocketconfig.hpp index de994b10e7..f8b9eeef7b 100644 --- a/nano/node/websocketconfig.hpp +++ b/nano/node/websocketconfig.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -22,7 +21,7 @@ namespace websocket nano::network_constants network_constants; bool enabled{ false }; uint16_t port; - boost::asio::ip::address_v6 address{ boost::asio::ip::address_v6::loopback () }; + std::string address; }; } } diff --git a/nano/node/write_database_queue.cpp b/nano/node/write_database_queue.cpp index 599a1cc673..3c652c8b0c 100644 --- a/nano/node/write_database_queue.cpp +++ b/nano/node/write_database_queue.cpp @@ -3,25 +3,60 @@ #include -nano::write_guard::write_guard (nano::condition_variable & cv_a, std::function guard_finish_callback_a) : -cv (cv_a), +nano::write_guard::write_guard (std::function guard_finish_callback_a) : guard_finish_callback (guard_finish_callback_a) { } +nano::write_guard::write_guard (nano::write_guard && write_guard_a) noexcept : +guard_finish_callback (std::move (write_guard_a.guard_finish_callback)), +owns (write_guard_a.owns) +{ + write_guard_a.owns = false; + write_guard_a.guard_finish_callback = nullptr; +} + +nano::write_guard & nano::write_guard::operator= (nano::write_guard && write_guard_a) noexcept +{ + owns = write_guard_a.owns; + guard_finish_callback = std::move (write_guard_a.guard_finish_callback); + + write_guard_a.owns = false; + write_guard_a.guard_finish_callback = nullptr; + return *this; +} + nano::write_guard::~write_guard () { - guard_finish_callback (); - cv.notify_all (); + if (owns) + { + guard_finish_callback (); + } +} + +bool nano::write_guard::is_owned () const +{ + return owns; +} + +void nano::write_guard::release () +{ + debug_assert (owns); + if (owns) + { + guard_finish_callback (); + } + owns = false; } nano::write_database_queue::write_database_queue () : -// clang-format off -guard_finish_callback ([&queue = queue, &mutex = mutex]() { - nano::lock_guard guard (mutex); - queue.pop_front (); +guard_finish_callback ([& queue = queue, &mutex = mutex, &cv = cv]() { + { + nano::lock_guard guard (mutex); + queue.pop_front (); + } + cv.notify_all (); }) -// clang-format on { } @@ -35,12 +70,12 @@ nano::write_guard nano::write_database_queue::wait (nano::writer writer) queue.push_back (writer); } - while (!stopped && queue.front () != writer) + while (queue.front () != writer) { cv.wait (lk); } - return write_guard (cv, guard_finish_callback); + return write_guard (guard_finish_callback); } bool nano::write_database_queue::contains (nano::writer writer) @@ -74,11 +109,5 @@ bool nano::write_database_queue::process (nano::writer writer) nano::write_guard nano::write_database_queue::pop () { - return write_guard (cv, guard_finish_callback); -} - -void nano::write_database_queue::stop () -{ - stopped = true; - cv.notify_all (); + return write_guard (guard_finish_callback); } diff --git a/nano/node/write_database_queue.hpp b/nano/node/write_database_queue.hpp index 59c9c9cf62..ab3954923d 100644 --- a/nano/node/write_database_queue.hpp +++ b/nano/node/write_database_queue.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include + #include #include #include @@ -19,12 +20,18 @@ enum class writer class write_guard final { public: - write_guard (nano::condition_variable & cv_a, std::function guard_finish_callback_a); + write_guard (std::function guard_finish_callback_a); + void release (); ~write_guard (); + write_guard (write_guard const &) = delete; + write_guard & operator= (write_guard const &) = delete; + write_guard (write_guard &&) noexcept; + write_guard & operator= (write_guard &&) noexcept; + bool is_owned () const; private: - nano::condition_variable & cv; std::function guard_finish_callback; + bool owns{ true }; }; class write_database_queue final @@ -43,14 +50,10 @@ class write_database_queue final /** Doesn't actually pop anything until the returned write_guard is out of scope */ write_guard pop (); - /** This will release anything which is being blocked by the wait function */ - void stop (); - private: std::deque queue; std::mutex mutex; nano::condition_variable cv; std::function guard_finish_callback; - std::atomic stopped{ false }; }; } diff --git a/nano/qt/qt.cpp b/nano/qt/qt.cpp index ef4f05fb8b..dcf10eda87 100644 --- a/nano/qt/qt.cpp +++ b/nano/qt/qt.cpp @@ -43,7 +43,7 @@ void show_button_success (QPushButton & button) bool nano_qt::eventloop_processor::event (QEvent * event_a) { - assert (dynamic_cast (event_a) != nullptr); + debug_assert (dynamic_cast (event_a) != nullptr); static_cast (event_a)->action (); return true; } @@ -163,7 +163,7 @@ wallet (wallet_a) { auto error (this->wallet.account.decode_account (model->item (selection[0].row (), 1)->text ().toStdString ())); (void)error; - assert (!error); + debug_assert (!error); this->wallet.refresh (); } }); @@ -599,7 +599,7 @@ void nano_qt::history::refresh () { QList items; auto block (ledger.store.block_get (transaction, hash)); - assert (block != nullptr); + debug_assert (block != nullptr); block->visit (visitor); items.push_back (new QStandardItem (QString (visitor.type.c_str ()))); items.push_back (new QStandardItem (QString (visitor.account.to_account ().c_str ()))); @@ -868,7 +868,7 @@ wallet (wallet_a) void nano_qt::status::erase (nano_qt::status_types status_a) { - assert (status_a != nano_qt::status_types::nominal); + debug_assert (status_a != nano_qt::status_types::nominal); auto erased (active.erase (status_a)); (void)erased; set_text (); @@ -876,7 +876,7 @@ void nano_qt::status::erase (nano_qt::status_types status_a) void nano_qt::status::insert (nano_qt::status_types status_a) { - assert (status_a != nano_qt::status_types::nominal); + debug_assert (status_a != nano_qt::status_types::nominal); active.insert (status_a); set_text (); } @@ -889,15 +889,14 @@ void nano_qt::status::set_text () std::string nano_qt::status::text () { - assert (!active.empty ()); + debug_assert (!active.empty ()); std::string result; size_t unchecked (0); std::string count_string; { - auto transaction (wallet.wallet_m->wallets.node.store.tx_begin_read ()); - auto size (wallet.wallet_m->wallets.node.store.block_count (transaction)); - unchecked = wallet.wallet_m->wallets.node.store.unchecked_count (transaction); - count_string = std::to_string (size.sum ()); + auto size (wallet.wallet_m->wallets.node.ledger.cache.block_count.load ()); + unchecked = wallet.wallet_m->wallets.node.ledger.cache.unchecked_count; + count_string = std::to_string (size); } switch (*active.begin ()) @@ -924,7 +923,7 @@ std::string nano_qt::status::text () result = "Status: Running"; break; default: - assert (false); + debug_assert (false); break; } @@ -940,7 +939,7 @@ std::string nano_qt::status::text () std::string nano_qt::status::color () { - assert (!active.empty ()); + debug_assert (!active.empty ()); std::string result; switch (*active.begin ()) { @@ -966,7 +965,7 @@ std::string nano_qt::status::color () result = "color: black"; break; default: - assert (false); + debug_assert (false); break; } return result; @@ -1388,7 +1387,7 @@ void nano_qt::wallet::refresh () { { auto transaction (wallet_m->wallets.tx_begin_read ()); - assert (wallet_m->store.exists (transaction, account)); + debug_assert (wallet_m->store.exists (transaction, account)); } self.account_text->setText (QString (account.to_account ().c_str ())); needs_balance_refresh = true; @@ -1604,7 +1603,7 @@ wallet (wallet_a) } }); QObject::connect (back, &QPushButton::released, [this]() { - assert (this->wallet.main_stack->currentWidget () == window); + debug_assert (this->wallet.main_stack->currentWidget () == window); this->wallet.pop_main_stack (); }); QObject::connect (lock_toggle, &QPushButton::released, [this]() { @@ -1844,7 +1843,7 @@ wallet (wallet_a) }); auto selected_ratio_id (QSettings ().value (saved_ratio_key, ratio_group->id (mnano_unit)).toInt ()); auto selected_ratio_button = ratio_group->button (selected_ratio_id); - assert (selected_ratio_button != nullptr); + debug_assert (selected_ratio_button != nullptr); if (selected_ratio_button) { @@ -2003,7 +2002,15 @@ wallet (wallet_a) { show_label_ok (*status); this->status->setText (""); - this->wallet.node.process_active (std::move (block_l)); + if (!nano::work_validate_entry (*block_l)) + { + this->wallet.node.process_active (std::move (block_l)); + } + else + { + show_label_error (*status); + this->status->setText ("Invalid work"); + } } else { @@ -2121,7 +2128,7 @@ wallet (wallet_a) create_open (); break; default: - assert (false); + debug_assert (false); break; } }); @@ -2228,7 +2235,7 @@ void nano_qt::block_creation::create_send () nano::account_info info; auto error (wallet.node.store.account_get (block_transaction, account_l, info)); (void)error; - assert (!error); + debug_assert (!error); nano::state_block send (account_l, info.head, info.representative, balance - amount_l.number (), destination_l, key, account_l, 0); if (wallet.node.work_generate_blocking (send).is_initialized ()) { diff --git a/nano/qt_system/entry.cpp b/nano/qt_system/entry.cpp index 83f42f96f5..59b0c74e23 100644 --- a/nano/qt_system/entry.cpp +++ b/nano/qt_system/entry.cpp @@ -1,9 +1,12 @@ #include #include +#include #include #include #include +#include + #include int main (int argc, char ** argv) @@ -15,8 +18,8 @@ int main (int argc, char ** argv) QCoreApplication::setOrganizationDomain ("nano.org"); QCoreApplication::setApplicationName ("Nano Wallet"); nano_qt::eventloop_processor processor; - static uint16_t count (16); - nano::system system (24000, count); + const uint16_t count (16); + nano::system system (count); nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); std::unique_ptr client_tabs (new QTabWidget); std::vector> guis; @@ -25,7 +28,7 @@ int main (int argc, char ** argv) auto wallet (system.nodes[i]->wallets.create (nano::random_wallet_id ())); nano::keypair key; wallet->insert_adhoc (key.prv); - guis.push_back (std::unique_ptr (new nano_qt::wallet (application, processor, *system.nodes[i], wallet, key.pub))); + guis.push_back (std::make_unique (application, processor, *system.nodes[i], wallet, key.pub)); client_tabs->addTab (guis.back ()->client_window, boost::str (boost::format ("Wallet %1%") % i).c_str ()); } client_tabs->show (); @@ -40,7 +43,7 @@ int main (int argc, char ** argv) catch (...) { result = -1; - assert (false); + debug_assert (false); } runner.join (); return result; diff --git a/nano/qt_test/qt.cpp b/nano/qt_test/qt.cpp index 742760d022..4fb197049c 100644 --- a/nano/qt_test/qt.cpp +++ b/nano/qt_test/qt.cpp @@ -17,7 +17,7 @@ extern QApplication * test_application; TEST (wallet, construction) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto wallet_l (system.nodes[0]->wallets.create (nano::random_wallet_id ())); auto key (wallet_l->deterministic_insert ()); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], wallet_l, key)); @@ -32,7 +32,7 @@ TEST (wallet, construction) TEST (wallet, status) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto wallet_l (system.nodes[0]->wallets.create (nano::random_wallet_id ())); nano::keypair key; wallet_l->insert_adhoc (key.prv); @@ -63,7 +63,7 @@ TEST (wallet, status) TEST (wallet, startup_balance) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto wallet_l (system.nodes[0]->wallets.create (nano::random_wallet_id ())); nano::keypair key; wallet_l->insert_adhoc (key.prv); @@ -77,7 +77,7 @@ TEST (wallet, startup_balance) TEST (wallet, select_account) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto wallet_l (system.nodes[0]->wallets.create (nano::random_wallet_id ())); nano::public_key key1 (wallet_l->deterministic_insert ()); nano::public_key key2 (wallet_l->deterministic_insert ()); @@ -109,7 +109,7 @@ TEST (wallet, select_account) TEST (wallet, main) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto wallet_l (system.nodes[0]->wallets.create (nano::random_wallet_id ())); nano::keypair key; wallet_l->insert_adhoc (key.prv); @@ -140,7 +140,7 @@ TEST (wallet, main) TEST (wallet, password_change) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); nano::account account; system.wallet (0)->insert_adhoc (nano::keypair ().prv); { @@ -176,7 +176,7 @@ TEST (wallet, password_change) TEST (client, password_nochange) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); nano::account account; system.wallet (0)->insert_adhoc (nano::keypair ().prv); { @@ -220,7 +220,7 @@ TEST (client, password_nochange) TEST (wallet, enter_password) { nano_qt::eventloop_processor processor; - nano::system system (24000, 2); + nano::system system (2); nano::account account; system.wallet (0)->insert_adhoc (nano::keypair ().prv); { @@ -257,7 +257,7 @@ TEST (wallet, enter_password) TEST (wallet, send) { nano_qt::eventloop_processor processor; - nano::system system (24000, 2); + nano::system system (2); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::public_key key1 (system.wallet (1)->insert_adhoc (nano::keypair ().prv)); auto account (nano::test_genesis_key.pub); @@ -290,7 +290,7 @@ TEST (wallet, send) TEST (wallet, send_locked) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key1; { @@ -315,7 +315,7 @@ TEST (wallet, send_locked) TEST (wallet, process_block) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); nano::account account; nano::block_hash latest (system.nodes[0]->latest (nano::genesis_account)); system.wallet (0)->insert_adhoc (nano::keypair ().prv); @@ -360,7 +360,7 @@ TEST (wallet, create_send) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); auto account (nano::test_genesis_key.pub); @@ -390,7 +390,7 @@ TEST (wallet, create_open_receive) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 100); nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); @@ -420,7 +420,7 @@ TEST (wallet, create_open_receive) ASSERT_EQ (nano::process_result::old, system.nodes[0]->process (open).code); wallet->block_creation.block->clear (); wallet->block_creation.source->clear (); - QTest::mouseClick (wallet->block_creation.receive, Qt::LeftButton); + wallet->block_creation.receive->click (); QTest::keyClicks (wallet->block_creation.source, latest2.to_string ().c_str ()); QTest::mouseClick (wallet->block_creation.create, Qt::LeftButton); std::string json2 (wallet->block_creation.block->toPlainText ().toStdString ()); @@ -439,7 +439,7 @@ TEST (wallet, create_change) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto account (nano::test_genesis_key.pub); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), account)); @@ -447,10 +447,10 @@ TEST (wallet, create_change) wallet->client_window->show (); QTest::mouseClick (wallet->show_advanced, Qt::LeftButton); QTest::mouseClick (wallet->advanced.create_block, Qt::LeftButton); - QTest::mouseClick (wallet->block_creation.change, Qt::LeftButton); + wallet->block_creation.change->click (); QTest::keyClicks (wallet->block_creation.account, nano::test_genesis_key.pub.to_account ().c_str ()); QTest::keyClicks (wallet->block_creation.representative, key.pub.to_account ().c_str ()); - QTest::mouseClick (wallet->block_creation.create, Qt::LeftButton); + wallet->block_creation.create->click (); std::string json (wallet->block_creation.block->toPlainText ().toStdString ()); ASSERT_FALSE (json.empty ()); boost::property_tree::ptree tree1; @@ -467,7 +467,7 @@ TEST (history, short_text) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (key.prv); nano::account account; { @@ -481,7 +481,7 @@ TEST (history, short_text) nano::ledger ledger (store, system.nodes[0]->stats); { auto transaction (store.tx_begin_write ()); - store.initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store.initialize (transaction, genesis, ledger.cache); nano::keypair key; auto latest (ledger.latest (transaction, nano::test_genesis_key.pub)); nano::send_block send (latest, nano::test_genesis_key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); @@ -500,7 +500,7 @@ TEST (wallet, startup_work) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (key.prv); nano::account account; { @@ -532,7 +532,7 @@ TEST (wallet, block_viewer) { nano_qt::eventloop_processor processor; nano::keypair key; - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (key.prv); nano::account account; { @@ -556,7 +556,7 @@ TEST (wallet, block_viewer) TEST (wallet, import) { nano_qt::eventloop_processor processor; - nano::system system (24000, 2); + nano::system system (2); std::string json; nano::keypair key1; nano::keypair key2; @@ -590,7 +590,7 @@ TEST (wallet, import) TEST (wallet, republish) { nano_qt::eventloop_processor processor; - nano::system system (24000, 2); + nano::system system (2); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; nano::block_hash hash; @@ -621,7 +621,7 @@ TEST (wallet, republish) TEST (wallet, ignore_empty_adhoc) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); nano::keypair key1; system.wallet (0)->insert_adhoc (key1.prv); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), key1.pub)); @@ -648,7 +648,7 @@ TEST (wallet, ignore_empty_adhoc) TEST (wallet, change_seed) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto key1 (system.wallet (0)->deterministic_insert ()); system.wallet (0)->deterministic_insert (); nano::raw_key seed3; @@ -701,7 +701,7 @@ TEST (wallet, change_seed) TEST (wallet, seed_work_generation) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto key1 (system.wallet (0)->deterministic_insert ()); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), key1)); wallet->start (); @@ -727,13 +727,13 @@ TEST (wallet, seed_work_generation) ASSERT_NO_ERROR (ec); } auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_FALSE (nano::work_validate (system.nodes[0]->ledger.latest_root (transaction, pub), work)); + ASSERT_GE (nano::work_difficulty (nano::work_version::work_1, system.nodes[0]->ledger.latest_root (transaction, pub), work), system.nodes[0]->default_difficulty (nano::work_version::work_1)); } TEST (wallet, backup_seed) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto key1 (system.wallet (0)->deterministic_insert ()); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), key1)); wallet->start (); @@ -751,7 +751,7 @@ TEST (wallet, backup_seed) TEST (wallet, import_locked) { nano_qt::eventloop_processor processor; - nano::system system (24000, 1); + nano::system system (1); auto key1 (system.wallet (0)->deterministic_insert ()); { auto transaction (system.wallet (0)->wallets.tx_begin_write ()); @@ -789,8 +789,8 @@ TEST (wallet, import_locked) TEST (wallet, DISABLED_synchronizing) { nano_qt::eventloop_processor processor; - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); + nano::system system0 (1); + nano::system system1 (1); auto key1 (system0.wallet (0)->deterministic_insert ()); auto wallet (std::make_shared (*test_application, processor, *system0.nodes[0], system0.wallet (0), key1)); wallet->start (); diff --git a/nano/rpc/rpc.cpp b/nano/rpc/rpc.cpp index 42e82278cf..5cfdebc349 100644 --- a/nano/rpc/rpc.cpp +++ b/nano/rpc/rpc.cpp @@ -1,9 +1,12 @@ +#include #include #include #include #include +#include + #ifdef NANO_SECURE_RPC #include #endif @@ -28,7 +31,7 @@ nano::rpc::~rpc () void nano::rpc::start () { - auto endpoint (boost::asio::ip::tcp::endpoint (config.address, config.port)); + auto endpoint (boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address_v6 (config.address), config.port)); if (!endpoint.address ().is_loopback () && config.enable_control) { auto warning = boost::str (boost::format ("WARNING: control-level RPCs are enabled on non-local address %1%, potentially allowing wallet access outside local computer") % endpoint.address ().to_string ()); diff --git a/nano/rpc/rpc.hpp b/nano/rpc/rpc.hpp index 7949dee31f..838c642e52 100644 --- a/nano/rpc/rpc.hpp +++ b/nano/rpc/rpc.hpp @@ -1,10 +1,18 @@ #pragma once -#include +#include #include #include #include +namespace boost +{ +namespace asio +{ + class io_context; +} +} + namespace nano { class rpc_handler_interface; diff --git a/nano/rpc/rpc_connection.cpp b/nano/rpc/rpc_connection.cpp index 751033ab2a..b5b9e5b3f8 100644 --- a/nano/rpc/rpc_connection.cpp +++ b/nano/rpc/rpc_connection.cpp @@ -1,12 +1,17 @@ -#include +#include #include #include #include #include +#include #include #include +#include #include +#ifdef NANO_SECURE_RPC +#include +#endif #include nano::rpc_connection::rpc_connection (nano::rpc_config const & rpc_config, boost::asio::io_context & io_ctx, nano::logger_mt & logger, nano::rpc_handler_interface & rpc_handler_interface) : @@ -22,7 +27,7 @@ rpc_handler_interface (rpc_handler_interface) void nano::rpc_connection::parse_connection () { - read (); + read (socket); } void nano::rpc_connection::prepare_head (unsigned version, boost::beast::http::status status) @@ -47,16 +52,23 @@ void nano::rpc_connection::write_result (std::string body, unsigned version, boo } else { - assert (false && "RPC already responded and should only respond once"); + debug_assert (false && "RPC already responded and should only respond once"); } } -void nano::rpc_connection::read () +void nano::rpc_connection::write_completion_handler (std::shared_ptr rpc_connection) +{ + // Intentional no-op +} + +template +void nano::rpc_connection::read (STREAM_TYPE & stream) { auto this_l (shared_from_this ()); auto header_parser (std::make_shared> ()); header_parser->body_limit (rpc_config.max_request_size); - boost::beast::http::async_read_header (socket, buffer, *header_parser, boost::asio::bind_executor (strand, [this_l, header_parser](boost::system::error_code const & ec, size_t bytes_transferred) { + + boost::beast::http::async_read_header (stream, buffer, *header_parser, boost::asio::bind_executor (strand, [this_l, &stream, header_parser](boost::system::error_code const & ec, size_t bytes_transferred) { if (!ec) { if (boost::iequals (header_parser->get ()[boost::beast::http::field::expect], "100-continue")) @@ -65,73 +77,90 @@ void nano::rpc_connection::read () continue_response->version (11); continue_response->result (boost::beast::http::status::continue_); continue_response->set (boost::beast::http::field::server, "nano"); - boost::beast::http::async_write (this_l->socket, *continue_response, boost::asio::bind_executor (this_l->strand, [this_l, continue_response](boost::system::error_code const & ec, size_t bytes_transferred) {})); + boost::beast::http::async_write (stream, *continue_response, boost::asio::bind_executor (this_l->strand, [this_l, continue_response](boost::system::error_code const & ec, size_t bytes_transferred) {})); } - this_l->parse_request (header_parser); + this_l->parse_request (stream, header_parser); } else { this_l->logger.always_log ("RPC header error: ", ec.message ()); // Respond with the reason for the invalid header - auto response_handler ([this_l](std::string const & tree_a) { + auto response_handler ([this_l, &stream](std::string const & tree_a) { this_l->write_result (tree_a, 11); - boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { this_l->write_completion_handler (this_l); })); }); - json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); + nano::json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); } })); } -void nano::rpc_connection::parse_request (std::shared_ptr> header_parser) +template +void nano::rpc_connection::parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser) { auto this_l (shared_from_this ()); + auto header_field_credentials_l (header_parser->get ()["nano-api-key"]); + auto header_corr_id_l (header_parser->get ()["nano-correlation-id"]); auto body_parser (std::make_shared> (std::move (*header_parser))); - boost::beast::http::async_read (socket, buffer, *body_parser, boost::asio::bind_executor (strand, [this_l, body_parser](boost::system::error_code const & ec, size_t bytes_transferred) { + auto path_l (body_parser->get ().target ().to_string ()); + boost::beast::http::async_read (stream, buffer, *body_parser, boost::asio::bind_executor (strand, [this_l, body_parser, header_field_credentials_l, header_corr_id_l, path_l, &stream](boost::system::error_code const & ec, size_t bytes_transferred) { if (!ec) { - this_l->io_ctx.post ([this_l, body_parser]() { + this_l->io_ctx.post ([this_l, body_parser, header_field_credentials_l, header_corr_id_l, path_l, &stream]() { auto & req (body_parser->get ()); auto start (std::chrono::steady_clock::now ()); auto version (req.version ()); std::stringstream ss; ss << std::hex << std::showbase << reinterpret_cast (this_l.get ()); auto request_id = ss.str (); - auto response_handler ([this_l, version, start, request_id](std::string const & tree_a) { + auto response_handler ([this_l, version, start, request_id, &stream](std::string const & tree_a) { auto body = tree_a; this_l->write_result (body, version); - boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { this_l->write_completion_handler (this_l); })); std::stringstream ss; - ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; - this_l->logger.always_log (ss.str ().c_str ()); + if (this_l->rpc_config.rpc_logging.log_rpc) + { + ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; + this_l->logger.always_log (ss.str ().c_str ()); + } }); + + std::string api_path_l = "/api/v2"; + int rpc_version_l = boost::starts_with (path_l, api_path_l) ? 2 : 1; + auto method = req.method (); switch (method) { case boost::beast::http::verb::post: { auto handler (std::make_shared (this_l->rpc_config, req.body (), request_id, response_handler, this_l->rpc_handler_interface, this_l->logger)); - handler->process_request (); + nano::rpc_handler_request_params request_params; + request_params.rpc_version = rpc_version_l; + request_params.credentials = header_field_credentials_l.to_string (); + request_params.correlation_id = header_corr_id_l.to_string (); + request_params.path = boost::algorithm::erase_first_copy (path_l, api_path_l); + request_params.path = boost::algorithm::erase_first_copy (request_params.path, "/"); + handler->process_request (request_params); break; } case boost::beast::http::verb::options: { this_l->prepare_head (version); this_l->res.prepare_payload (); - boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { this_l->write_completion_handler (this_l); })); break; } default: { - json_error_response (response_handler, "Can only POST requests"); + nano::json_error_response (response_handler, "Can only POST requests"); break; } } @@ -144,7 +173,9 @@ void nano::rpc_connection::parse_request (std::shared_ptr rpc_connection) -{ - // Intentional no-op -} +template void nano::rpc_connection::read (socket_type &); +template void nano::rpc_connection::parse_request (socket_type &, std::shared_ptr>); +#ifdef NANO_SECURE_RPC +template void nano::rpc_connection::read (boost::asio::ssl::stream &); +template void nano::rpc_connection::parse_request (boost::asio::ssl::stream &, std::shared_ptr>); +#endif diff --git a/nano/rpc/rpc_connection.hpp b/nano/rpc/rpc_connection.hpp index ee0287fb9c..7f49203c3b 100644 --- a/nano/rpc/rpc_connection.hpp +++ b/nano/rpc/rpc_connection.hpp @@ -1,9 +1,11 @@ #pragma once -#include +#include +#include +#include +#include -#include -#include +#include #include @@ -29,9 +31,6 @@ class rpc_connection : public std::enable_shared_from_this virtual void write_completion_handler (std::shared_ptr rpc_connection); void prepare_head (unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); void write_result (std::string body, unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); - void parse_request (std::shared_ptr> header_parser); - - void read (); socket_type socket; boost::beast::flat_buffer buffer; @@ -42,5 +41,12 @@ class rpc_connection : public std::enable_shared_from_this nano::logger_mt & logger; nano::rpc_config const & rpc_config; nano::rpc_handler_interface & rpc_handler_interface; + +protected: + template + void read (STREAM_TYPE & stream); + + template + void parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser); }; } diff --git a/nano/rpc/rpc_connection_secure.cpp b/nano/rpc/rpc_connection_secure.cpp index cb1998f88c..d69efde4a9 100644 --- a/nano/rpc/rpc_connection_secure.cpp +++ b/nano/rpc/rpc_connection_secure.cpp @@ -14,9 +14,9 @@ void nano::rpc_connection_secure::parse_connection () // Perform the SSL handshake auto this_l = std::static_pointer_cast (shared_from_this ()); stream.async_handshake (boost::asio::ssl::stream_base::server, - [this_l](auto & ec) { + boost::asio::bind_executor (this_l->strand, [this_l](auto & ec) { this_l->handle_handshake (ec); - }); + })); } void nano::rpc_connection_secure::on_shutdown (const boost::system::error_code & error) @@ -29,7 +29,7 @@ void nano::rpc_connection_secure::handle_handshake (const boost::system::error_c { if (!error) { - read (); + read (stream); } else { diff --git a/nano/rpc/rpc_handler.cpp b/nano/rpc/rpc_handler.cpp index 2ad08b9b3f..e372826d6e 100644 --- a/nano/rpc/rpc_handler.cpp +++ b/nano/rpc/rpc_handler.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -27,7 +26,7 @@ logger (logger) { } -void nano::rpc_handler::process_request () +void nano::rpc_handler::process_request (nano::rpc_handler_request_params const & request_params) { try { @@ -51,55 +50,73 @@ void nano::rpc_handler::process_request () } else { - boost::property_tree::ptree request; + if (request_params.rpc_version == 1) { - std::stringstream ss; - ss << body; - boost::property_tree::read_json (ss, request); - } + boost::property_tree::ptree request; + { + std::stringstream ss; + ss << body; + boost::property_tree::read_json (ss, request); + } - auto action = request.get ("action"); - // Creating same string via stringstream as using it directly is generating a TSAN warning - std::stringstream ss; - ss << request_id; - logger.always_log (ss.str (), " ", filter_request (request)); + auto action = request.get ("action"); + if (rpc_config.rpc_logging.log_rpc) + { + // Creating same string via stringstream as using it directly is generating a TSAN warning + std::stringstream ss; + ss << request_id; + logger.always_log (ss.str (), " ", filter_request (request)); + } - // Check if this is a RPC command which requires RPC enabled control - std::error_code rpc_control_disabled_ec = nano::error_rpc::rpc_control_disabled; + // Check if this is a RPC command which requires RPC enabled control + std::error_code rpc_control_disabled_ec = nano::error_rpc::rpc_control_disabled; - bool error = false; - auto found = rpc_control_impl_set.find (action); - if (found != rpc_control_impl_set.cend () && !rpc_config.enable_control) - { - json_error_response (response, rpc_control_disabled_ec.message ()); - error = true; - } - else - { - // Special case with stats, type -> objects - if (action == "stats" && !rpc_config.enable_control) + bool error = false; + auto found = rpc_control_impl_set.find (action); + if (found != rpc_control_impl_set.cend () && !rpc_config.enable_control) { - if (request.get ("type") == "objects") - { - json_error_response (response, rpc_control_disabled_ec.message ()); - error = true; - } + json_error_response (response, rpc_control_disabled_ec.message ()); + error = true; } - else if (action == "process") + else { - auto force = request.get_optional ("force"); - auto watch_work = request.get_optional ("watch_work"); - if (((force.is_initialized () && *force) || (watch_work.is_initialized () && !*watch_work)) && !rpc_config.enable_control) + // Special case with stats, type -> objects + if (action == "stats" && !rpc_config.enable_control) + { + if (request.get ("type") == "objects") + { + json_error_response (response, rpc_control_disabled_ec.message ()); + error = true; + } + } + else if (action == "process") { - json_error_response (response, rpc_control_disabled_ec.message ()); - error = true; + auto force = request.get_optional ("force").value_or (false); + auto watch_work = request.get_optional ("watch_work").value_or (true); + if ((force || watch_work) && !rpc_config.enable_control) + { + json_error_response (response, rpc_control_disabled_ec.message ()); + error = true; + } } } - } - if (!error) + if (!error) + { + rpc_handler_interface.process_request (action, body, this->response); + } + } + else if (request_params.rpc_version == 2) + { + rpc_handler_interface.process_request_v2 (request_params, body, [response = response](std::shared_ptr body) { + std::string body_l = *body; + response (body_l); + }); + } + else { - rpc_handler_interface.process_request (action, body, this->response); + debug_assert (false); + json_error_response (response, "Invalid RPC version"); } } } diff --git a/nano/rpc/rpc_handler.hpp b/nano/rpc/rpc_handler.hpp index 3ef400b31d..bc75b6e15f 100644 --- a/nano/rpc/rpc_handler.hpp +++ b/nano/rpc/rpc_handler.hpp @@ -4,19 +4,19 @@ #include #include -#include namespace nano { class rpc_config; class rpc_handler_interface; class logger_mt; +class rpc_handler_request_params; class rpc_handler : public std::enable_shared_from_this { public: rpc_handler (nano::rpc_config const & rpc_config, std::string const & body_a, std::string const & request_id_a, std::function const & response_a, nano::rpc_handler_interface & rpc_handler_interface_a, nano::logger_mt & logger); - void process_request (); + void process_request (nano::rpc_handler_request_params const & request_params); private: std::string body; diff --git a/nano/rpc/rpc_request_processor.cpp b/nano/rpc/rpc_request_processor.cpp index f2de12cbd1..c92f59f7de 100644 --- a/nano/rpc/rpc_request_processor.cpp +++ b/nano/rpc/rpc_request_processor.cpp @@ -1,9 +1,12 @@ #include #include +#include #include +#include + nano::rpc_request_processor::rpc_request_processor (boost::asio::io_context & io_ctx, nano::rpc_config & rpc_config) : -ipc_address (rpc_config.rpc_process.ipc_address.to_string ()), +ipc_address (rpc_config.rpc_process.ipc_address), ipc_port (rpc_config.rpc_process.ipc_port), thread ([this]() { nano::thread_role::set (nano::thread_role::name::rpc_request_processor); @@ -16,13 +19,11 @@ thread ([this]() { { connections.push_back (std::make_shared (nano::ipc::ipc_client (io_ctx), false)); auto connection = this->connections.back (); - // clang-format off - connection->client.async_connect (ipc_address, ipc_port, [ connection, &connections_mutex = this->connections_mutex ](nano::error err) { + connection->client.async_connect (ipc_address, ipc_port, [connection, &connections_mutex = this->connections_mutex](nano::error err) { // Even if there is an error this needs to be set so that another attempt can be made to connect with the ipc connection nano::lock_guard lk (connections_mutex); connection->is_available = true; }); - // clang-format on } } @@ -145,7 +146,8 @@ void nano::rpc_request_processor::run () auto connection = *it; connection->is_available = false; // Make sure no one else can take it conditions_lk.unlock (); - auto req (nano::ipc::prepare_request (nano::ipc::payload_encoding::json_legacy, rpc_request->body)); + auto encoding (rpc_request->rpc_api_version == 1 ? nano::ipc::payload_encoding::json_v1 : nano::ipc::payload_encoding::flatbuffers_json); + auto req (nano::ipc::prepare_request (encoding, rpc_request->body)); auto res (std::make_shared> ()); // Have we tried to connect yet? diff --git a/nano/rpc/rpc_request_processor.hpp b/nano/rpc/rpc_request_processor.hpp index 42760d636c..3981b246a2 100644 --- a/nano/rpc/rpc_request_processor.hpp +++ b/nano/rpc/rpc_request_processor.hpp @@ -1,15 +1,11 @@ #pragma once -#include #include #include #include -#include #include -#include -#include -#include +#include namespace nano { @@ -31,6 +27,17 @@ struct rpc_request { } + rpc_request (int rpc_api_version_a, const std::string & body_a, std::function response_a) : + rpc_api_version (rpc_api_version_a), body (body_a), response (response_a) + { + } + + rpc_request (int rpc_api_version_a, const std::string & action_a, const std::string & body_a, std::function response_a) : + rpc_api_version (rpc_api_version_a), action (action_a), body (body_a), response (response_a) + { + } + + int rpc_api_version{ 1 }; std::string action; std::string body; std::function response; @@ -75,6 +82,15 @@ class ipc_rpc_processor final : public nano::rpc_handler_interface rpc_request_processor.add (std::make_shared (action_a, body_a, response_a)); } + void process_request_v2 (rpc_handler_request_params const & params_a, std::string const & body_a, std::function)> response_a) override + { + std::string body_l = params_a.json_envelope (body_a); + rpc_request_processor.add (std::make_shared (2 /* rpc version */, body_l, [response_a](std::string const & resp) { + auto resp_l (std::make_shared (resp)); + response_a (resp_l); + })); + } + void stop () override { rpc_request_processor.stop (); diff --git a/nano/rpc/rpc_secure.cpp b/nano/rpc/rpc_secure.cpp index df13ea4508..2b81165b0f 100644 --- a/nano/rpc/rpc_secure.cpp +++ b/nano/rpc/rpc_secure.cpp @@ -4,6 +4,8 @@ #include #include +#include + bool nano::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx) { X509_STORE_CTX * cts = ctx.native_handle (); @@ -61,38 +63,47 @@ bool nano::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl void nano::rpc_secure::load_certs (boost::asio::ssl::context & context_a) { - // This is called if the key is password protected - context_a.set_password_callback ( - [this](std::size_t, - boost::asio::ssl::context_base::password_purpose) { - return config.secure.server_key_passphrase; - }); + try + { + // This is called if the key is password protected + context_a.set_password_callback ( + [this](std::size_t, + boost::asio::ssl::context_base::password_purpose) { + return config.secure.server_key_passphrase; + }); - // The following two options disables the session cache and enables stateless session resumption. - // This is necessary because of the way the RPC server abruptly terminate connections. - SSL_CTX_set_session_cache_mode (context_a.native_handle (), SSL_SESS_CACHE_OFF); - SSL_CTX_set_options (context_a.native_handle (), SSL_OP_NO_TICKET); + // The following two options disables the session cache and enables stateless session resumption. + // This is necessary because of the way the RPC server abruptly terminate connections. + SSL_CTX_set_session_cache_mode (context_a.native_handle (), SSL_SESS_CACHE_OFF); + SSL_CTX_set_options (context_a.native_handle (), SSL_OP_NO_TICKET); - context_a.set_options ( - boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); + context_a.set_options ( + boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); - context_a.use_certificate_chain_file (config.secure.server_cert_path); - context_a.use_private_key_file (config.secure.server_key_path, boost::asio::ssl::context::pem); - context_a.use_tmp_dh_file (config.secure.server_dh_path); + context_a.use_certificate_chain_file (config.secure.server_cert_path); + context_a.use_private_key_file (config.secure.server_key_path, boost::asio::ssl::context::pem); + context_a.use_tmp_dh_file (config.secure.server_dh_path); - // Verify client certificates? - if (config.secure.client_certs_path.size () > 0) + // Verify client certificates? + if (!config.secure.client_certs_path.empty ()) + { + context_a.set_verify_mode (boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_peer); + context_a.add_verify_path (config.secure.client_certs_path); + context_a.set_verify_callback ([this](auto preverified, auto & ctx) { + return this->on_verify_certificate (preverified, ctx); + }); + } + } + catch (boost::system::system_error const & err) { - context_a.set_verify_mode (boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_peer); - context_a.add_verify_path (config.secure.client_certs_path); - context_a.set_verify_callback ([this](auto preverified, auto & ctx) { - return this->on_verify_certificate (preverified, ctx); - }); + auto error (boost::str (boost::format ("Could not load certificate information: %1%. Make sure the paths in the secure rpc configuration are correct.") % err.what ())); + std::cerr << error << std::endl; + logger.always_log (error); } } -nano::rpc_secure::rpc_secure (boost::asio::io_service & service_a, nano::rpc_config const & config_a, nano::rpc_handler_interface & rpc_handler_interface_a) : -rpc (service_a, config_a, rpc_handler_interface_a), +nano::rpc_secure::rpc_secure (boost::asio::io_context & context_a, nano::rpc_config const & config_a, nano::rpc_handler_interface & rpc_handler_interface_a) : +rpc (context_a, config_a, rpc_handler_interface_a), ssl_context (boost::asio::ssl::context::tlsv12_server) { load_certs (ssl_context); @@ -102,7 +113,7 @@ void nano::rpc_secure::accept () { auto connection (std::make_shared (config, io_ctx, logger, rpc_handler_interface, this->ssl_context)); acceptor.async_accept (connection->socket, boost::asio::bind_executor (connection->strand, [this, connection](boost::system::error_code const & ec) { - if (acceptor.is_open ()) + if (ec != boost::asio::error::operation_aborted && acceptor.is_open ()) { accept (); } diff --git a/nano/rpc/rpc_secure.hpp b/nano/rpc/rpc_secure.hpp index 336827f532..2601614504 100644 --- a/nano/rpc/rpc_secure.hpp +++ b/nano/rpc/rpc_secure.hpp @@ -2,7 +2,14 @@ #include #include -#include + +namespace boost +{ +namespace asio +{ + class io_context; +} +} namespace nano { @@ -12,7 +19,7 @@ namespace nano class rpc_secure : public rpc { public: - rpc_secure (boost::asio::io_service & service_a, nano::rpc_config const & config_a, nano::rpc_handler_interface & rpc_handler_interface_a); + rpc_secure (boost::asio::io_context & context_a, nano::rpc_config const & config_a, nano::rpc_handler_interface & rpc_handler_interface_a); /** Starts accepting connections */ void accept () override; diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 709c330842..fa2c92c4cd 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -1,8 +1,9 @@ +#include +#include #include -#include #include -#include -#include +#include +#include #include #include #include @@ -11,7 +12,8 @@ #include -#include +#include +#include #include @@ -92,24 +94,27 @@ class test_response std::atomic status{ 0 }; }; -void enable_ipc_transport_tcp (nano::ipc::ipc_config_tcp_socket & transport_tcp, uint16_t ipc_port) +std::shared_ptr add_ipc_enabled_node (nano::system & system, nano::node_config & node_config) { - transport_tcp.enabled = true; - transport_tcp.port = ipc_port; + node_config.ipc_config.transport_tcp.enabled = true; + node_config.ipc_config.transport_tcp.port = nano::get_available_port (); + return system.add_node (node_config); } -void enable_ipc_transport_tcp (nano::ipc::ipc_config_tcp_socket & transport_tcp) +std::shared_ptr add_ipc_enabled_node (nano::system & system) { - static nano::network_constants network_constants; - enable_ipc_transport_tcp (transport_tcp, network_constants.default_ipc_port); + nano::node_config node_config (nano::get_available_port (), system.logging); + return add_ipc_enabled_node (system, node_config); } void reset_confirmation_height (nano::block_store & store, nano::account const & account) { auto transaction = store.tx_begin_write (); - uint64_t confirmation_height; - store.confirmation_height_get (transaction, account, confirmation_height); - store.confirmation_height_clear (transaction, account, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + if (!store.confirmation_height_get (transaction, account, confirmation_height_info)) + { + store.confirmation_height_clear (transaction, account, confirmation_height_info.height); + } } void check_block_response_count (nano::system & system, nano::rpc & rpc, boost::property_tree::ptree & request, uint64_t size_count) @@ -149,15 +154,38 @@ class scoped_io_thread_name_change }; } +TEST (rpc, wrapped_task) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + nano::node_rpc_config node_rpc_config; + std::atomic response (false); + auto response_handler_l ([&response](std::string const & response_a) { + std::stringstream istream (response_a); + boost::property_tree::ptree json_l; + ASSERT_NO_THROW (boost::property_tree::read_json (istream, json_l)); + ASSERT_EQ (1, json_l.count ("error")); + ASSERT_EQ ("Unable to parse JSON", json_l.get ("error")); + response = true; + }); + auto handler_l (std::make_shared (node, node_rpc_config, "", response_handler_l)); + auto task (handler_l->create_worker_task ([](std::shared_ptr) { + // Exception should get caught + throw std::runtime_error (""); + })); + system.nodes[0]->worker.push_task (task); + ASSERT_TIMELY (5s, response == true); +} + TEST (rpc, account_balance) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -179,13 +207,13 @@ TEST (rpc, account_balance) TEST (rpc, account_block_count) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -205,19 +233,19 @@ TEST (rpc, account_block_count) TEST (rpc, account_create) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "account_create"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response0 (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response0.status == 0) @@ -229,7 +257,7 @@ TEST (rpc, account_create) nano::account account0; ASSERT_FALSE (account0.decode_account (account_text0)); ASSERT_TRUE (system.wallet (0)->exists (account0)); - uint64_t max_index (std::numeric_limits::max ()); + constexpr uint64_t max_index (std::numeric_limits::max ()); request.put ("index", max_index); test_response response1 (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -256,16 +284,16 @@ TEST (rpc, account_create) TEST (rpc, account_weight) { nano::keypair key; - nano::system system (24000, 1); - nano::block_hash latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + nano::block_hash latest (node1.latest (nano::test_genesis_key.pub)); nano::change_block block (latest, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); ASSERT_EQ (nano::process_result::progress, node1.process (block).code); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -285,14 +313,14 @@ TEST (rpc, account_weight) TEST (rpc, wallet_contains) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -315,13 +343,13 @@ TEST (rpc, wallet_contains) TEST (rpc, wallet_doesnt_contain) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -344,14 +372,14 @@ TEST (rpc, wallet_doesnt_contain) TEST (rpc, validate_account_number) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -370,14 +398,14 @@ TEST (rpc, validate_account_number) TEST (rpc, validate_account_invalid) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -400,28 +428,28 @@ TEST (rpc, validate_account_invalid) TEST (rpc, send) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "send"); request.put ("source", nano::test_genesis_key.pub.to_account ()); request.put ("destination", nano::test_genesis_key.pub.to_account ()); request.put ("amount", "100"); system.deadline_set (10s); - boost::thread thread2 ([&system]() { - while (system.nodes[0]->balance (nano::test_genesis_key.pub) == nano::genesis_amount) + boost::thread thread2 ([&system, node]() { + while (node->balance (nano::test_genesis_key.pub) == nano::genesis_amount) { ASSERT_NO_ERROR (system.poll ()); } @@ -442,13 +470,13 @@ TEST (rpc, send) TEST (rpc, send_fail) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -480,20 +508,20 @@ TEST (rpc, send_fail) TEST (rpc, send_work) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "send"); request.put ("source", nano::test_genesis_key.pub.to_account ()); @@ -508,7 +536,7 @@ TEST (rpc, send_work) } ASSERT_EQ (std::error_code (nano::error_common::invalid_work).message (), response.json.get ("error")); request.erase ("work"); - request.put ("work", nano::to_string_hex (*system.nodes[0]->work_generate_blocking (system.nodes[0]->latest (nano::test_genesis_key.pub)))); + request.put ("work", nano::to_string_hex (*node->work_generate_blocking (node->latest (nano::test_genesis_key.pub)))); test_response response2 (request, rpc.config.port, system.io_ctx); system.deadline_set (10s); while (response2.status == 0) @@ -519,28 +547,28 @@ TEST (rpc, send_work) std::string block_text (response2.json.get ("block")); nano::block_hash block; ASSERT_FALSE (block.decode_hex (block_text)); - ASSERT_TRUE (system.nodes[0]->ledger.block_exists (block)); - ASSERT_EQ (system.nodes[0]->latest (nano::test_genesis_key.pub), block); + ASSERT_TRUE (node->ledger.block_exists (block)); + ASSERT_EQ (node->latest (nano::test_genesis_key.pub), block); } TEST (rpc, send_work_disabled) { - nano::system system (24000, 0); - nano::node_config node_config (24000, system.logging); + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.work_threads = 0; - auto & node = *system.add_node (node_config); + auto & node = *add_ipc_enabled_node (system, node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node.wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "send"); request.put ("source", nano::test_genesis_key.pub.to_account ()); @@ -560,20 +588,20 @@ TEST (rpc, send_work_disabled) TEST (rpc, send_idempotent) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "send"); request.put ("source", nano::test_genesis_key.pub.to_account ()); @@ -590,8 +618,8 @@ TEST (rpc, send_idempotent) std::string block_text (response.json.get ("block")); nano::block_hash block; ASSERT_FALSE (block.decode_hex (block_text)); - ASSERT_TRUE (system.nodes[0]->ledger.block_exists (block)); - ASSERT_EQ (system.nodes[0]->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); + ASSERT_TRUE (node->ledger.block_exists (block)); + ASSERT_EQ (node->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); test_response response2 (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response2.status == 0) @@ -601,7 +629,7 @@ TEST (rpc, send_idempotent) ASSERT_EQ (200, response2.status); ASSERT_EQ ("", response2.json.get ("error", "")); ASSERT_EQ (block_text, response2.json.get ("block")); - ASSERT_EQ (system.nodes[0]->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); + ASSERT_EQ (node->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); request.erase ("id"); request.put ("id", "456def"); test_response response3 (request, rpc.config.port, system.io_ctx); @@ -614,15 +642,64 @@ TEST (rpc, send_idempotent) ASSERT_EQ (std::error_code (nano::error_common::insufficient_balance).message (), response3.json.get ("error")); } +TEST (rpc, send_epoch_2) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + + // Upgrade the genesis account to epoch 2 + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2)); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv, false); + + auto target_difficulty = nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_2, true, false, false)); + ASSERT_LT (node.network_params.network.publish_thresholds.entry, target_difficulty); + auto min_difficulty = node.network_params.network.publish_thresholds.entry; + + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + node.wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "send"); + request.put ("source", nano::test_genesis_key.pub.to_account ()); + request.put ("destination", nano::keypair ().pub.to_account ()); + request.put ("amount", "1"); + + // Test that the correct error is given if there is insufficient work + auto insufficient = system.work_generate_limited (nano::genesis_hash, min_difficulty, target_difficulty); + request.put ("work", nano::to_string_hex (insufficient)); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_common::invalid_work); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + } +} + TEST (rpc, stop) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -638,13 +715,13 @@ TEST (rpc, stop) TEST (rpc, wallet_add) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -653,7 +730,7 @@ TEST (rpc, wallet_add) key1.prv.data.encode_hex (key_text); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "wallet_add"); request.put ("key", key_text); @@ -671,19 +748,19 @@ TEST (rpc, wallet_add) TEST (rpc, wallet_password_valid) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "password_valid"); test_response response (request, rpc.config.port, system.io_ctx); @@ -699,19 +776,19 @@ TEST (rpc, wallet_password_valid) TEST (rpc, wallet_password_change) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "password_change"); request.put ("password", "test"); @@ -735,7 +812,8 @@ TEST (rpc, wallet_password_change) TEST (rpc, wallet_password_enter) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::raw_key password_l; password_l.data.clear (); @@ -745,17 +823,16 @@ TEST (rpc, wallet_password_enter) ASSERT_NO_ERROR (system.poll ()); system.wallet (0)->store.password.value (password_l); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "password_enter"); request.put ("password", ""); @@ -772,19 +849,19 @@ TEST (rpc, wallet_password_enter) TEST (rpc, wallet_representative) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "wallet_representative"); test_response response (request, rpc.config.port, system.io_ctx); @@ -800,19 +877,19 @@ TEST (rpc, wallet_representative) TEST (rpc, wallet_representative_set) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); nano::keypair key; request.put ("action", "wallet_representative_set"); @@ -824,26 +901,26 @@ TEST (rpc, wallet_representative_set) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); - ASSERT_EQ (key.pub, system.nodes[0]->wallets.items.begin ()->second->store.representative (transaction)); + auto transaction (node->wallets.tx_begin_read ()); + ASSERT_EQ (key.pub, node->wallets.items.begin ()->second->store.representative (transaction)); } TEST (rpc, wallet_representative_set_force) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); nano::keypair key; request.put ("action", "wallet_representative_set"); @@ -857,15 +934,15 @@ TEST (rpc, wallet_representative_set_force) } ASSERT_EQ (200, response.status); { - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); - ASSERT_EQ (key.pub, system.nodes[0]->wallets.items.begin ()->second->store.representative (transaction)); + auto transaction (node->wallets.tx_begin_read ()); + ASSERT_EQ (key.pub, node->wallets.items.begin ()->second->store.representative (transaction)); } nano::account representative (0); while (representative != key.pub) { - auto transaction (system.nodes[0]->store.tx_begin_read ()); + auto transaction (node->store.tx_begin_read ()); nano::account_info info; - if (!system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info)) + if (!node->store.account_get (transaction, nano::test_genesis_key.pub, info)) { representative = info.representative; } @@ -875,22 +952,22 @@ TEST (rpc, wallet_representative_set_force) TEST (rpc, account_list) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "account_list"); test_response response (request, rpc.config.port, system.io_ctx); @@ -918,20 +995,20 @@ TEST (rpc, account_list) TEST (rpc, wallet_key_valid) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "wallet_key_valid"); test_response response (request, rpc.config.port, system.io_ctx); @@ -947,13 +1024,13 @@ TEST (rpc, wallet_key_valid) TEST (rpc, wallet_create) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -969,22 +1046,22 @@ TEST (rpc, wallet_create) std::string wallet_text (response.json.get ("wallet")); nano::wallet_id wallet_id; ASSERT_FALSE (wallet_id.decode_hex (wallet_text)); - ASSERT_NE (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.find (wallet_id)); + ASSERT_NE (node->wallets.items.end (), node->wallets.items.find (wallet_id)); } TEST (rpc, wallet_create_seed) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::raw_key seed; nano::random_pool::generate_block (seed.data.bytes.data (), seed.data.bytes.size ()); auto prv = nano::deterministic_key (seed, 0); auto pub (nano::pub_key (prv)); - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -992,18 +1069,19 @@ TEST (rpc, wallet_create_seed) request.put ("action", "wallet_create"); request.put ("seed", seed.data.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); while (response.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); std::string wallet_text (response.json.get ("wallet")); nano::wallet_id wallet_id; ASSERT_FALSE (wallet_id.decode_hex (wallet_text)); - auto existing (system.nodes[0]->wallets.items.find (wallet_id)); - ASSERT_NE (system.nodes[0]->wallets.items.end (), existing); + auto existing (node->wallets.items.find (wallet_id)); + ASSERT_NE (node->wallets.items.end (), existing); { - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node->wallets.tx_begin_read ()); nano::raw_key seed0; existing->second->store.seed (seed0, transaction); ASSERT_EQ (seed, seed0); @@ -1018,20 +1096,20 @@ TEST (rpc, wallet_create_seed) TEST (rpc, wallet_export) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_export"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -1042,7 +1120,7 @@ TEST (rpc, wallet_export) std::string wallet_json (response.json.get ("json")); bool error (false); scoped_thread_name_io.reset (); - auto transaction (system.nodes[0]->wallets.tx_begin_write ()); + auto transaction (node->wallets.tx_begin_write ()); nano::kdf kdf; nano::wallet_store store (error, kdf, transaction, nano::genesis_account, 1, "0", wallet_json); ASSERT_FALSE (error); @@ -1051,15 +1129,15 @@ TEST (rpc, wallet_export) TEST (rpc, wallet_destroy) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto wallet_id (system.nodes[0]->wallets.items.begin ()->first); - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); + auto wallet_id (node->wallets.items.begin ()->first); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1073,25 +1151,25 @@ TEST (rpc, wallet_destroy) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - ASSERT_EQ (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.find (wallet_id)); + ASSERT_EQ (node->wallets.items.end (), node->wallets.items.find (wallet_id)); } TEST (rpc, account_move) { - nano::system system (24000, 1); - auto wallet_id (system.nodes[0]->wallets.items.begin ()->first); + nano::system system; + auto node = add_ipc_enabled_node (system); + auto wallet_id (node->wallets.items.begin ()->first); auto destination (system.wallet (0)); destination->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; auto source_id = nano::random_wallet_id (); - auto source (system.nodes[0]->wallets.create (source_id)); + auto source (node->wallets.create (source_id)); source->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1114,25 +1192,25 @@ TEST (rpc, account_move) ASSERT_EQ ("1", response.json.get ("moved")); ASSERT_TRUE (destination->exists (key.pub)); ASSERT_TRUE (destination->exists (nano::test_genesis_key.pub)); - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node->wallets.tx_begin_read ()); ASSERT_EQ (source->store.end (), source->store.begin (transaction)); } TEST (rpc, block) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "block"); - request.put ("hash", system.nodes[0]->latest (nano::genesis_account).to_string ()); + request.put ("hash", node->latest (nano::genesis_account).to_string ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -1147,13 +1225,13 @@ TEST (rpc, block) TEST (rpc, block_account) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1175,19 +1253,19 @@ TEST (rpc, block_account) TEST (rpc, chain) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); ASSERT_NE (nullptr, block); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1215,19 +1293,19 @@ TEST (rpc, chain) TEST (rpc, chain_limit) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); ASSERT_NE (nullptr, block); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1254,19 +1332,19 @@ TEST (rpc, chain_limit) TEST (rpc, chain_offset) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); ASSERT_NE (nullptr, block); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1294,27 +1372,27 @@ TEST (rpc, chain_offset) TEST (rpc, frontier) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); std::unordered_map source; { - auto transaction (system.nodes[0]->store.tx_begin_write ()); + auto transaction (node->store.tx_begin_write ()); for (auto i (0); i < 1000; ++i) { nano::keypair key; nano::block_hash hash; nano::random_pool::generate_block (hash.bytes.data (), hash.bytes.size ()); source[key.pub] = hash; - system.nodes[0]->store.confirmation_height_put (transaction, key.pub, 0); - system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + node->store.confirmation_height_put (transaction, key.pub, { 0, nano::block_hash (0) }); + node->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); } } scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1345,28 +1423,28 @@ TEST (rpc, frontier) TEST (rpc, frontier_limited) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); std::unordered_map source; { - auto transaction (system.nodes[0]->store.tx_begin_write ()); + auto transaction (node->store.tx_begin_write ()); for (auto i (0); i < 1000; ++i) { nano::keypair key; nano::block_hash hash; nano::random_pool::generate_block (hash.bytes.data (), hash.bytes.size ()); source[key.pub] = hash; - system.nodes[0]->store.confirmation_height_put (transaction, key.pub, 0); - system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + node->store.confirmation_height_put (transaction, key.pub, { 0, nano::block_hash (0) }); + node->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); } } scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1387,27 +1465,27 @@ TEST (rpc, frontier_limited) TEST (rpc, frontier_startpoint) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); std::unordered_map source; { - auto transaction (system.nodes[0]->store.tx_begin_write ()); + auto transaction (node->store.tx_begin_write ()); for (auto i (0); i < 1000; ++i) { nano::keypair key; nano::block_hash hash; nano::random_pool::generate_block (hash.bytes.data (), hash.bytes.size ()); source[key.pub] = hash; - system.nodes[0]->store.confirmation_height_put (transaction, key.pub, 0); - system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + node->store.confirmation_height_put (transaction, key.pub, { 0, nano::block_hash (0) }); + node->store.account_put (transaction, key.pub, nano::account_info (hash, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); } } scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1429,19 +1507,19 @@ TEST (rpc, frontier_startpoint) TEST (rpc, history) { - nano::system system (24000, 1); + nano::system system; + auto node0 = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); ASSERT_NE (nullptr, change); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, send); - auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); - auto node0 (system.nodes[0]); nano::genesis genesis; - nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (node0->latest (nano::genesis_account))); - nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (usend.hash ())); - nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (ureceive.hash ())); + nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (node0->latest (nano::genesis_account))); + nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (usend.hash ())); + nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (ureceive.hash ())); { auto transaction (node0->store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, usend).code); @@ -1449,10 +1527,10 @@ TEST (rpc, history) ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, uchange).code); } scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node0->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node0, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node0->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1485,11 +1563,11 @@ TEST (rpc, history) ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[1])); ASSERT_EQ ("receive", std::get<0> (history_l[2])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[2])); ASSERT_EQ ("send", std::get<0> (history_l[3])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[3])); ASSERT_EQ ("receive", std::get<0> (history_l[4])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[4])); @@ -1499,19 +1577,19 @@ TEST (rpc, history) TEST (rpc, account_history) { - nano::system system (24000, 1); + nano::system system; + auto node0 = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); ASSERT_NE (nullptr, change); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, send); - auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); - auto node0 (system.nodes[0]); nano::genesis genesis; - nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (node0->latest (nano::genesis_account))); - nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (usend.hash ())); - nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (ureceive.hash ())); + nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (node0->latest (nano::genesis_account))); + nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (usend.hash ())); + nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (ureceive.hash ())); { auto transaction (node0->store.tx_begin_write ()); ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, usend).code); @@ -1519,10 +1597,10 @@ TEST (rpc, account_history) ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, uchange).code); } scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node0->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node0, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node0->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1557,12 +1635,12 @@ TEST (rpc, account_history) ASSERT_EQ ("5", std::get<4> (history_l[1])); ASSERT_EQ ("receive", std::get<0> (history_l[2])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[2])); ASSERT_EQ ("4", std::get<4> (history_l[2])); ASSERT_EQ ("send", std::get<0> (history_l[3])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[3])); ASSERT_EQ ("3", std::get<4> (history_l[3])); ASSERT_EQ ("receive", std::get<0> (history_l[4])); @@ -1593,9 +1671,9 @@ TEST (rpc, account_history) // Test filtering scoped_thread_name_io.reset (); auto account2 (system.wallet (0)->deterministic_insert ()); - auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, account2, system.nodes[0]->config.receive_minimum.number ())); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, account2, node0->config.receive_minimum.number ())); ASSERT_NE (nullptr, send2); - auto receive2 (system.wallet (0)->receive_action (*send2, account2, system.nodes[0]->config.receive_minimum.number ())); + auto receive2 (system.wallet (0)->receive_action (*send2, account2, node0->config.receive_minimum.number ())); scoped_thread_name_io.renew (); // Test filter for send state blocks ASSERT_NE (nullptr, receive2); @@ -1640,20 +1718,20 @@ TEST (rpc, account_history) TEST (rpc, history_count) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); ASSERT_NE (nullptr, change); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, send); - auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1674,16 +1752,16 @@ TEST (rpc, history_count) TEST (rpc, process_block) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1701,7 +1779,7 @@ TEST (rpc, process_block) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->latest (nano::test_genesis_key.pub) != send.hash ()) + while (node1.latest (nano::test_genesis_key.pub) != send.hash ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -1724,16 +1802,16 @@ TEST (rpc, process_block) TEST (rpc, process_json_block) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1763,7 +1841,7 @@ TEST (rpc, process_json_block) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->latest (nano::test_genesis_key.pub) != send.hash ()) + while (node1.latest (nano::test_genesis_key.pub) != send.hash ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -1775,26 +1853,26 @@ TEST (rpc, process_json_block) TEST (rpc, process_block_with_work_watcher) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; node_config.work_watcher_period = 1s; - auto & node1 = *system.add_node (node_config); + node_config.max_work_generate_multiplier = 1e6; + auto & node1 = *add_ipc_enabled_node (system, node_config); nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); auto send (std::make_shared (nano::test_genesis_key.pub, latest, nano::test_genesis_key.pub, nano::genesis_amount - 100, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); - uint64_t difficulty1 (0); - nano::work_validate (*send, &difficulty1); - auto multiplier1 = nano::difficulty::to_multiplier (difficulty1, node1.network_params.network.publish_threshold); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); + auto difficulty1 (send->difficulty ()); + auto multiplier1 = nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, node1.network_params.network.publish_thresholds.epoch_1), node1.network_params.network.publish_thresholds.epoch_1); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "process"); - request.put ("work_watcher", true); + request.put ("watch_work", true); std::string json; send->serialize_json (json); request.put ("block", json); @@ -1806,13 +1884,13 @@ TEST (rpc, process_block_with_work_watcher) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->latest (nano::test_genesis_key.pub) != send->hash ()) + while (node1.latest (nano::test_genesis_key.pub) != send->hash ()) { ASSERT_NO_ERROR (system.poll ()); } system.deadline_set (10s); auto updated (false); - uint64_t updated_difficulty; + double updated_multiplier; while (!updated) { nano::unique_lock lock (node1.active.mutex); @@ -1821,31 +1899,71 @@ TEST (rpc, process_block_with_work_watcher) { node1.active.multipliers_cb.push_back (multiplier1 * (1 + i / 100.)); } - node1.active.update_active_difficulty (lock); + node1.active.update_active_multiplier (lock); auto const existing (node1.active.roots.find (send->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node1.active.roots.end ()); - updated = existing->difficulty != difficulty1; - updated_difficulty = existing->difficulty; + updated = existing->multiplier != multiplier1; + updated_multiplier = existing->multiplier; lock.unlock (); ASSERT_NO_ERROR (system.poll ()); } - ASSERT_GT (updated_difficulty, difficulty1); + ASSERT_GT (updated_multiplier, multiplier1); + + // Try without enable_control which watch_work requires if set to true + { + nano::rpc_config rpc_config (nano::get_available_port (), false); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + request.put ("watch_work", true); + std::string json; + send->serialize_json (json); + request.put ("block", json); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::rpc_control_disabled); + ASSERT_EQ (ec.message (), response.json.get ("error")); + } + + // Check no enable_control error message is present when not watching work + request.put ("watch_work", false); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::rpc_control_disabled); + ASSERT_NE (ec.message (), response.json.get ("error")); + } + } } TEST (rpc, process_block_no_work) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); send.block_work_set (0); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1866,16 +1984,18 @@ TEST (rpc, process_block_no_work) TEST (rpc, process_republish) { - nano::system system (24000, 2); + nano::system system (2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + auto & node3 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); - nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); + auto latest (node1.latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node3.work_generate_blocking (latest)); nano::node_rpc_config node_rpc_config; - nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::ipc::ipc_server ipc_server (node3, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node3.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1892,7 +2012,7 @@ TEST (rpc, process_republish) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[1]->latest (nano::test_genesis_key.pub) != send.hash ()) + while (node2.latest (nano::test_genesis_key.pub) != send.hash ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -1900,16 +2020,17 @@ TEST (rpc, process_republish) TEST (rpc, process_subtype_send) { - nano::system system (24000, 2); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + system.add_node (); scoped_io_thread_name_change scoped_thread_name_io; nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -1953,22 +2074,21 @@ TEST (rpc, process_subtype_send) TEST (rpc, process_subtype_open) { - nano::system system (24000, 2); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + auto & node2 = *system.add_node (); nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - { - auto transaction (node1.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); - } + ASSERT_EQ (nano::process_result::progress, node1.process (send).code); + ASSERT_EQ (nano::process_result::progress, node2.process (send).code); scoped_io_thread_name_change scoped_thread_name_io; - node1.active.start (std::make_shared (send)); + node1.active.insert (std::make_shared (send)); nano::state_block open (key.pub, 0, key.pub, nano::Gxrb_ratio, send.hash (), key.prv, key.pub, *node1.work_generate_blocking (key.pub)); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2004,7 +2124,7 @@ TEST (rpc, process_subtype_open) ASSERT_EQ (200, response3.status); ASSERT_EQ (open.hash ().to_string (), response3.json.get ("hash")); system.deadline_set (10s); - while (system.nodes[1]->latest (key.pub) != open.hash ()) + while (node2.latest (key.pub) != open.hash ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -2012,21 +2132,20 @@ TEST (rpc, process_subtype_open) TEST (rpc, process_subtype_receive) { - nano::system system (24000, 2); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + auto & node2 = *system.add_node (); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - { - auto transaction (node1.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); - } + ASSERT_EQ (nano::process_result::progress, node1.process (send).code); + ASSERT_EQ (nano::process_result::progress, node2.process (send).code); scoped_io_thread_name_change scoped_thread_name_io; - node1.active.start (std::make_shared (send)); + node1.active.insert (std::make_shared (send)); nano::state_block receive (nano::test_genesis_key.pub, send.hash (), nano::test_genesis_key.pub, nano::genesis_amount, send.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (send.hash ())); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2063,24 +2182,129 @@ TEST (rpc, process_subtype_receive) ASSERT_EQ (200, response3.status); ASSERT_EQ (receive.hash ().to_string (), response3.json.get ("hash")); system.deadline_set (10s); - while (system.nodes[1]->latest (nano::test_genesis_key.pub) != receive.hash ()) + while (node2.latest (nano::test_genesis_key.pub) != receive.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_ledger_insufficient_work) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + ASSERT_LT (node.network_params.network.publish_thresholds.entry, node.network_params.network.publish_thresholds.epoch_1); + auto latest (node.latest (nano::test_genesis_key.pub)); + auto min_difficulty = node.network_params.network.publish_thresholds.entry; + auto max_difficulty = node.network_params.network.publish_thresholds.epoch_1; + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work_generate_limited (latest, min_difficulty, max_difficulty)); + ASSERT_LT (send.difficulty (), max_difficulty); + ASSERT_GE (send.difficulty (), min_difficulty); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "send"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) { ASSERT_NO_ERROR (system.poll ()); } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_process::insufficient_work); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (response.json.get ("error"), ec.message ()); +} + +// Ensure that processing an old block with updated work floods it to peers +TEST (rpc, process_difficulty_update_flood) +{ + nano::system system (1); + auto & node_passive = *system.nodes[0]; + auto & node = *add_ipc_enabled_node (system); + + auto latest (node.latest (nano::test_genesis_key.pub)); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (latest)); + + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + boost::property_tree::ptree request; + request.put ("action", "process"); + // Must not watch work, otherwise the work watcher could update the block and flood it, whereas we want to ensure flooding happens on demand, without the work watcher + request.put ("watch_work", false); + { + std::string json; + send.serialize_json (json); + request.put ("block", json); + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + ASSERT_EQ (0, response.json.count ("error")); + } + + ASSERT_TIMELY (5s, node_passive.active.size () == 1 && node_passive.block (send.hash ()) != nullptr); + + // Update block work + node.work_generate_blocking (send, send.difficulty ()); + auto expected_multiplier = nano::normalized_multiplier (nano::difficulty::to_multiplier (send.difficulty (), nano::work_threshold (send.work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1); + + { + std::string json; + send.serialize_json (json); + request.put ("block", json); + std::error_code ec (nano::error_process::old); + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + } + + // Ensure the difficulty update occurs in both nodes + ASSERT_NO_ERROR (system.poll_until_true (5s, [&node, &node_passive, &send, expected_multiplier] { + nano::lock_guard guard (node.active.mutex); + auto const existing (node.active.roots.find (send.qualified_root ())); + EXPECT_NE (existing, node.active.roots.end ()); + + nano::lock_guard guard_passive (node_passive.active.mutex); + auto const existing_passive (node_passive.active.roots.find (send.qualified_root ())); + EXPECT_NE (existing_passive, node_passive.active.roots.end ()); + + bool updated = existing->multiplier == expected_multiplier; + bool updated_passive = existing_passive->multiplier == expected_multiplier; + + return updated && updated_passive; + })); } TEST (rpc, keepalive) { - nano::system system (24000, 1); - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + nano::system system; + auto node0 = add_ipc_enabled_node (system); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); node1->start (); system.nodes.push_back (node1); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; - nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::ipc::ipc_server ipc_server (*node0, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node0->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2090,8 +2314,8 @@ TEST (rpc, keepalive) auto port (boost::str (boost::format ("%1%") % node1->network.endpoint ().port ())); request.put ("address", address); request.put ("port", port); - ASSERT_EQ (nullptr, system.nodes[0]->network.udp_channels.channel (node1->network.endpoint ())); - ASSERT_EQ (0, system.nodes[0]->network.size ()); + ASSERT_EQ (nullptr, node0->network.udp_channels.channel (node1->network.endpoint ())); + ASSERT_EQ (0, node0->network.size ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -2100,9 +2324,9 @@ TEST (rpc, keepalive) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->network.find_channel (node1->network.endpoint ()) == nullptr) + while (node0->network.find_channel (node1->network.endpoint ()) == nullptr) { - ASSERT_EQ (0, system.nodes[0]->network.size ()); + ASSERT_EQ (0, node0->network.size ()); ASSERT_NO_ERROR (system.poll ()); } node1->stop (); @@ -2110,16 +2334,16 @@ TEST (rpc, keepalive) TEST (rpc, payment_init) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); auto wallet_id = nano::random_wallet_id (); auto wallet (node1->wallets.create (wallet_id)); ASSERT_TRUE (node1->wallets.items.find (wallet_id) != node1->wallets.items.end ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2138,16 +2362,16 @@ TEST (rpc, payment_init) TEST (rpc, payment_begin_end) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); auto wallet_id = nano::random_wallet_id (); auto wallet (node1->wallets.create (wallet_id)); ASSERT_TRUE (node1->wallets.items.find (wallet_id) != node1->wallets.items.end ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2171,13 +2395,13 @@ TEST (rpc, payment_begin_end) root1 = node1->ledger.latest_root (transaction, account); } uint64_t work (0); - while (!nano::work_validate (root1, work)) + while (nano::work_difficulty (nano::work_version::work_1, root1, work) >= nano::work_threshold_base (nano::work_version::work_1)) { ++work; ASSERT_LT (work, 50); } system.deadline_set (10s); - while (nano::work_validate (root1, work)) + while (nano::work_difficulty (nano::work_version::work_1, root1, work) < node1->default_difficulty (nano::work_version::work_1)) { auto ec = system.poll (); auto transaction (wallet->wallets.tx_begin_read ()); @@ -2204,17 +2428,17 @@ TEST (rpc, payment_begin_end) TEST (rpc, payment_end_nonempty) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto transaction (node1->wallets.tx_begin_read ()); system.wallet (0)->init_free_accounts (transaction); auto wallet_id (node1->wallets.items.begin ()->first); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2234,17 +2458,17 @@ TEST (rpc, payment_end_nonempty) TEST (rpc, payment_zero_balance) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto transaction (node1->wallets.tx_begin_read ()); system.wallet (0)->init_free_accounts (transaction); auto wallet_id (node1->wallets.items.begin ()->first); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2266,16 +2490,16 @@ TEST (rpc, payment_zero_balance) TEST (rpc, payment_begin_reuse) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); auto wallet_id = nano::random_wallet_id (); auto wallet (node1->wallets.create (wallet_id)); ASSERT_TRUE (node1->wallets.items.find (wallet_id) != node1->wallets.items.end ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2322,8 +2546,8 @@ TEST (rpc, payment_begin_reuse) TEST (rpc, payment_begin_locked) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); auto wallet_id = nano::random_wallet_id (); auto wallet (node1->wallets.create (wallet_id)); { @@ -2333,10 +2557,10 @@ TEST (rpc, payment_begin_locked) } scoped_io_thread_name_change scoped_thread_name_io; ASSERT_TRUE (node1->wallets.items.find (wallet_id) != node1->wallets.items.end ()); - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2355,16 +2579,16 @@ TEST (rpc, payment_begin_locked) TEST (rpc, payment_wait) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2410,15 +2634,17 @@ TEST (rpc, payment_wait) TEST (rpc, peers) { - nano::system system (24000, 2); + nano::system system; + auto node = add_ipc_enabled_node (system); + auto port = nano::get_available_port (); + system.add_node (nano::node_config (port, system.logging)); scoped_io_thread_name_change scoped_thread_name_io; - nano::endpoint endpoint (boost::asio::ip::address_v6::from_string ("fc00::1"), 4000); - auto node = system.nodes.front (); + nano::endpoint endpoint (boost::asio::ip::make_address_v6 ("fc00::1"), 4000); node->network.udp_channels.insert (endpoint, node->network_params.protocol.protocol_version); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2433,7 +2659,7 @@ TEST (rpc, peers) ASSERT_EQ (200, response.status); auto & peers_node (response.json.get_child ("peers")); ASSERT_EQ (2, peers_node.size ()); - ASSERT_EQ (std::to_string (node->network_params.protocol.protocol_version), peers_node.get ("[::1]:24001")); + ASSERT_EQ (std::to_string (node->network_params.protocol.protocol_version), peers_node.get ((boost::format ("[::1]:%1%") % port).str ())); // Previously "[::ffff:80.80.80.80]:4000", but IPv4 address cause "No such node thrown in the test body" issue with peers_node.get std::stringstream endpoint_text; endpoint_text << endpoint; @@ -2442,15 +2668,17 @@ TEST (rpc, peers) TEST (rpc, peers_node_id) { - nano::system system (24000, 2); + nano::system system; + auto node = add_ipc_enabled_node (system); + auto port = nano::get_available_port (); + system.add_node (nano::node_config (port, system.logging)); scoped_io_thread_name_change scoped_thread_name_io; - nano::endpoint endpoint (boost::asio::ip::address_v6::from_string ("fc00::1"), 4000); - auto node = system.nodes.front (); + nano::endpoint endpoint (boost::asio::ip::make_address_v6 ("fc00::1"), 4000); node->network.udp_channels.insert (endpoint, node->network_params.protocol.protocol_version); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2466,7 +2694,7 @@ TEST (rpc, peers_node_id) ASSERT_EQ (200, response.status); auto & peers_node (response.json.get_child ("peers")); ASSERT_EQ (2, peers_node.size ()); - auto tree1 (peers_node.get_child ("[::1]:24001")); + auto tree1 (peers_node.get_child ((boost::format ("[::1]:%1%") % port).str ())); ASSERT_EQ (std::to_string (node->network_params.protocol.protocol_version), tree1.get ("protocol_version")); ASSERT_EQ (system.nodes[1]->node_id.pub.to_node_id (), tree1.get ("node_id")); std::stringstream endpoint_text; @@ -2478,21 +2706,21 @@ TEST (rpc, peers_node_id) TEST (rpc, pending) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (5s); - while (system.nodes[0]->active.active (*block1)) + while (node->active.active (*block1)) { ASSERT_NO_ERROR (system.poll ()); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2617,23 +2845,62 @@ TEST (rpc, pending) check_block_response_count (0); } +TEST (rpc, pending_burn) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::account burn (0); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, burn, 100)); + scoped_io_thread_name_change scoped_thread_name_io; + system.deadline_set (5s); + while (node->active.active (*block1)) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "pending"); + request.put ("account", burn.to_account ()); + request.put ("count", "100"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & blocks_node (response.json.get_child ("blocks")); + ASSERT_EQ (1, blocks_node.size ()); + nano::block_hash hash (blocks_node.begin ()->second.get ("")); + ASSERT_EQ (block1->hash (), hash); + } +} + TEST (rpc, search_pending) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto wallet (system.nodes[0]->wallets.items.begin ()->first.to_string ()); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (latest)); + auto wallet (node->wallets.items.begin ()->first.to_string ()); + auto latest (node->latest (nano::test_genesis_key.pub)); + nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - node->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (latest)); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, block).code); + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, block).code); } scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2648,7 +2915,7 @@ TEST (rpc, search_pending) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) != nano::genesis_amount) + while (node->balance (nano::test_genesis_key.pub) != nano::genesis_amount) { ASSERT_NO_ERROR (system.poll ()); } @@ -2656,16 +2923,16 @@ TEST (rpc, search_pending) TEST (rpc, version) { - nano::system system (24000, 1); - auto node1 (system.nodes[0]); + nano::system system; + auto node1 = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2681,11 +2948,12 @@ TEST (rpc, version) ASSERT_EQ ("1", response1.json.get ("rpc_version")); ASSERT_EQ (200, response1.status); { - auto transaction (system.nodes[0]->store.tx_begin_read ()); + auto transaction (node1->store.tx_begin_read ()); ASSERT_EQ (std::to_string (node1->store.version_get (transaction)), response1.json.get ("store_version")); } ASSERT_EQ (std::to_string (node1->network_params.protocol.protocol_version), response1.json.get ("protocol_version")); ASSERT_EQ (boost::str (boost::format ("Nano %1%") % NANO_VERSION_STRING), response1.json.get ("node_vendor")); + ASSERT_EQ (node1->store.vendor_get (), response1.json.get ("store_vendor")); auto network_label (node1->network_params.network.get_current_network_as_string ()); ASSERT_EQ (network_label, response1.json.get ("network")); auto genesis_open (node1->latest (nano::test_genesis_key.pub)); @@ -2708,16 +2976,16 @@ TEST (rpc, version) TEST (rpc, work_generate) { - nano::system system (24000, 1); - auto node (system.nodes[0]); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2735,15 +3003,15 @@ TEST (rpc, work_generate) ASSERT_EQ (200, response.status); ASSERT_EQ (hash.to_string (), response.json.get ("hash")); auto work_text (response.json.get ("work")); - uint64_t work, result_difficulty; + uint64_t work; ASSERT_FALSE (nano::from_string_hex (work_text, work)); - ASSERT_FALSE (nano::work_validate (hash, work, &result_difficulty)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); auto response_difficulty_text (response.json.get ("difficulty")); uint64_t response_difficulty; ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); ASSERT_EQ (result_difficulty, response_difficulty); auto multiplier = response.json.get ("multiplier"); - ASSERT_NEAR (nano::difficulty::to_multiplier (result_difficulty, node->network_params.network.publish_threshold), multiplier, 1e-6); + ASSERT_NEAR (nano::difficulty::to_multiplier (result_difficulty, node->default_difficulty (nano::work_version::work_1)), multiplier, 1e-6); }; verify_response (request, hash); request.put ("use_peers", "true"); @@ -2753,14 +3021,14 @@ TEST (rpc, work_generate) TEST (rpc, work_generate_difficulty) { nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.max_work_generate_difficulty = 0xffff000000000000; - auto node = system.add_node (node_config); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.max_work_generate_multiplier = 1000; + auto node = add_ipc_enabled_node (system, node_config); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2781,15 +3049,14 @@ TEST (rpc, work_generate_difficulty) auto work_text (response.json.get ("work")); uint64_t work; ASSERT_FALSE (nano::from_string_hex (work_text, work)); - uint64_t result_difficulty; - ASSERT_FALSE (nano::work_validate (hash, work, &result_difficulty)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); auto response_difficulty_text (response.json.get ("difficulty")); uint64_t response_difficulty; ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); ASSERT_EQ (result_difficulty, response_difficulty); auto multiplier = response.json.get ("multiplier"); // Expected multiplier from base threshold, not from the given difficulty - ASSERT_EQ (nano::difficulty::to_multiplier (result_difficulty, node->network_params.network.publish_threshold), multiplier); + ASSERT_NEAR (nano::difficulty::to_multiplier (result_difficulty, node->default_difficulty (nano::work_version::work_1)), multiplier, 1e-10); ASSERT_GE (result_difficulty, difficulty); } { @@ -2805,12 +3072,11 @@ TEST (rpc, work_generate_difficulty) auto work_text (response.json.get ("work")); uint64_t work; ASSERT_FALSE (nano::from_string_hex (work_text, work)); - uint64_t result_difficulty; - ASSERT_FALSE (nano::work_validate (hash, work, &result_difficulty)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); ASSERT_GE (result_difficulty, difficulty); } { - uint64_t difficulty (node->config.max_work_generate_difficulty + 1); + uint64_t difficulty (node->max_work_generate_difficulty (nano::work_version::work_1) + 1); request.put ("difficulty", nano::to_string_hex (difficulty)); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -2827,14 +3093,14 @@ TEST (rpc, work_generate_difficulty) TEST (rpc, work_generate_multiplier) { nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.max_work_generate_difficulty = 0xffff000000000000; - auto node = system.add_node (node_config); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.max_work_generate_multiplier = 100; + auto node = add_ipc_enabled_node (system, node_config); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2855,11 +3121,11 @@ TEST (rpc, work_generate_multiplier) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - auto work_text (response.json.get ("work")); + auto work_text (response.json.get_optional ("work")); + ASSERT_TRUE (work_text.is_initialized ()); uint64_t work; - ASSERT_FALSE (nano::from_string_hex (work_text, work)); - uint64_t result_difficulty; - ASSERT_FALSE (nano::work_validate (hash, work, &result_difficulty)); + ASSERT_FALSE (nano::from_string_hex (*work_text, work)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); auto response_difficulty_text (response.json.get ("difficulty")); uint64_t response_difficulty; ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); @@ -2880,7 +3146,7 @@ TEST (rpc, work_generate_multiplier) ASSERT_EQ (response.json.get ("error"), ec.message ()); } { - double max_multiplier (nano::difficulty::to_multiplier (node->config.max_work_generate_difficulty, node->network_params.network.publish_threshold)); + double max_multiplier (nano::difficulty::to_multiplier (node->max_work_generate_difficulty (nano::work_version::work_1), node->default_difficulty (nano::work_version::work_1))); request.put ("multiplier", max_multiplier + 1); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -2894,18 +3160,255 @@ TEST (rpc, work_generate_multiplier) } } +TEST (rpc, work_generate_epoch_2) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + auto epoch1 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + auto verify_response = [node, &rpc, &system](auto & request, nano::block_hash const & hash, uint64_t & out_difficulty) { + request.put ("hash", hash.to_string ()); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto work_text (response.json.get ("work")); + uint64_t work{ 0 }; + ASSERT_FALSE (nano::from_string_hex (work_text, work)); + out_difficulty = nano::work_difficulty (nano::work_version::work_1, hash, work); + }; + request.put ("action", "work_generate"); + // Before upgrading to epoch 2 should use epoch_1 difficulty as default + { + unsigned const max_tries = 30; + uint64_t difficulty{ 0 }; + unsigned tries = 0; + while (++tries < max_tries) + { + verify_response (request, epoch1->hash (), difficulty); + if (difficulty < node->network_params.network.publish_thresholds.base) + { + break; + } + } + ASSERT_LT (tries, max_tries); + } + // After upgrading, should always use the higher difficulty by default + ASSERT_EQ (node->network_params.network.publish_thresholds.epoch_2, node->network_params.network.publish_thresholds.base); + scoped_thread_name_io.reset (); + auto epoch2 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2); + ASSERT_NE (nullptr, epoch2); + scoped_thread_name_io.renew (); + { + for (auto i = 0; i < 5; ++i) + { + uint64_t difficulty{ 0 }; + verify_response (request, epoch1->hash (), difficulty); + ASSERT_GE (difficulty, node->network_params.network.publish_thresholds.base); + } + } +} + +TEST (rpc, work_generate_block_high) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, *node->work_generate_blocking (key.pub)); + nano::block_hash hash (block.root ()); + auto block_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, block.block_work ())); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "true"); + boost::property_tree::ptree json; + block.serialize_json (json); + request.add_child ("block", json); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::block_work_enough).message (), response.json.get ("error")); + } +} + +TEST (rpc, work_generate_block_low) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, 0); + auto threshold (node->default_difficulty (block.work_version ())); + block.block_work_set (system.work_generate_limited (block.root (), threshold, nano::difficulty::from_multiplier (node->config.max_work_generate_multiplier / 10, threshold))); + nano::block_hash hash (block.root ()); + auto block_difficulty (block.difficulty ()); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("difficulty", nano::to_string_hex (block_difficulty + 1)); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto work_text (response.json.get_optional ("work")); + ASSERT_TRUE (work_text.is_initialized ()); + uint64_t work; + ASSERT_FALSE (nano::from_string_hex (*work_text, work)); + ASSERT_NE (block.block_work (), work); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); + auto response_difficulty_text (response.json.get ("difficulty")); + uint64_t response_difficulty; + ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); + ASSERT_EQ (result_difficulty, response_difficulty); + ASSERT_LT (block_difficulty, result_difficulty); + } +} + +TEST (rpc, work_generate_block_root_mismatch) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, *node->work_generate_blocking (key.pub)); + nano::block_hash hash (1); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::block_root_mismatch).message (), response.json.get ("error")); + } +} + +TEST (rpc, work_generate_block_ledger_epoch_2) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + auto epoch1 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + auto epoch2 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2); + ASSERT_NE (nullptr, epoch2); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send_block->hash (), key.prv, key.pub, 0); + auto threshold (nano::work_threshold (block.work_version (), nano::block_details (nano::epoch::epoch_2, false, true, false))); + block.block_work_set (system.work_generate_limited (block.root (), 1, threshold - 1)); + nano::block_hash hash (block.root ()); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + bool finished (false); + auto iteration (0); + while (!finished) + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto work_text (response.json.get_optional ("work")); + ASSERT_TRUE (work_text.is_initialized ()); + uint64_t work; + ASSERT_FALSE (nano::from_string_hex (*work_text, work)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); + auto response_difficulty_text (response.json.get ("difficulty")); + uint64_t response_difficulty; + ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); + ASSERT_EQ (result_difficulty, response_difficulty); + ASSERT_GE (result_difficulty, node->network_params.network.publish_thresholds.epoch_2_receive); + finished = result_difficulty < node->network_params.network.publish_thresholds.epoch_1; + ASSERT_LT (++iteration, 200); + } +} + TEST (rpc, work_cancel) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -2917,7 +3420,7 @@ TEST (rpc, work_cancel) system.deadline_set (10s); while (!done) { - system.work.generate (hash1, [&done](boost::optional work_a) { + system.work.generate (nano::work_version::work_1, hash1, node1.network_params.network.publish_thresholds.base, [&done](boost::optional work_a) { done = !work_a; }); test_response response1 (request1, rpc.config.port, system.io_ctx); @@ -2933,29 +3436,29 @@ TEST (rpc, work_cancel) TEST (rpc, work_peer_bad) { - nano::system system (24000, 2); - auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + auto & node2 = *system.add_node (); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); node2.config.work_peers.push_back (std::make_pair (boost::asio::ip::address_v6::any ().to_string (), 0)); nano::block_hash hash1 (1); std::atomic work (0); - node2.work_generate (hash1, [&work](boost::optional work_a) { + node2.work_generate (nano::work_version::work_1, hash1, node2.network_params.network.publish_thresholds.base, [&work](boost::optional work_a) { ASSERT_TRUE (work_a.is_initialized ()); work = *work_a; }); system.deadline_set (5s); - while (nano::work_validate (hash1, work)) + while (nano::work_difficulty (nano::work_version::work_1, hash1, work) < nano::work_threshold_base (nano::work_version::work_1)) { ASSERT_NO_ERROR (system.poll ()); } @@ -2963,29 +3466,29 @@ TEST (rpc, work_peer_bad) TEST (rpc, work_peer_one) { - nano::system system (24000, 2); - auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + auto & node2 = *system.add_node (); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); node2.config.work_peers.push_back (std::make_pair (node1.network.endpoint ().address ().to_string (), rpc.config.port)); nano::keypair key1; - uint64_t work (0); - node2.work_generate (key1.pub, [&work](boost::optional work_a) { + std::atomic work (0); + node2.work_generate (nano::work_version::work_1, key1.pub, node1.network_params.network.publish_thresholds.base, [&work](boost::optional work_a) { ASSERT_TRUE (work_a.is_initialized ()); work = *work_a; }); system.deadline_set (5s); - while (nano::work_validate (key1.pub, work)) + while (nano::work_difficulty (nano::work_version::work_1, key1.pub, work) < nano::work_threshold_base (nano::work_version::work_1)) { ASSERT_NO_ERROR (system.poll ()); } @@ -2993,34 +3496,28 @@ TEST (rpc, work_peer_one) TEST (rpc, work_peer_many) { - nano::system system1 (24000, 1); - nano::system system2 (24001, 1); - nano::system system3 (24002, 1); - nano::system system4 (24003, 1); + nano::system system1 (1); + nano::system system2; + nano::system system3 (1); + nano::system system4 (1); auto & node1 (*system1.nodes[0]); - auto & node2 (*system2.nodes[0]); - auto & node3 (*system3.nodes[0]); - auto & node4 (*system4.nodes[0]); + auto & node2 = *add_ipc_enabled_node (system2); + auto & node3 = *add_ipc_enabled_node (system3); + auto & node4 = *add_ipc_enabled_node (system4); nano::keypair key; - nano::rpc_config config2 (true); - config2.port += 0; + nano::rpc_config config2 (nano::get_available_port (), true); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node2.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server2 (node2, node_rpc_config); nano::ipc_rpc_processor ipc_rpc_processor2 (system2.io_ctx, config2); nano::rpc rpc2 (system2.io_ctx, config2, ipc_rpc_processor2); rpc2.start (); - nano::rpc_config config3 (true); - config3.port += 1; - enable_ipc_transport_tcp (node3.config.ipc_config.transport_tcp, node3.network_params.network.default_ipc_port + 1); + nano::rpc_config config3 (nano::get_available_port (), true); nano::ipc::ipc_server ipc_server3 (node3, node_rpc_config); nano::ipc_rpc_processor ipc_rpc_processor3 (system3.io_ctx, config3); nano::rpc rpc3 (system3.io_ctx, config3, ipc_rpc_processor3); rpc3.start (); - nano::rpc_config config4 (true); - config4.port += 2; - enable_ipc_transport_tcp (node4.config.ipc_config.transport_tcp, node4.network_params.network.default_ipc_port + 2); + nano::rpc_config config4 (nano::get_available_port (), true); nano::ipc::ipc_server ipc_server4 (node4, node_rpc_config); nano::ipc_rpc_processor ipc_rpc_processor4 (system4.io_ctx, config4); nano::rpc rpc4 (system2.io_ctx, config4, ipc_rpc_processor4); @@ -3029,15 +3526,14 @@ TEST (rpc, work_peer_many) node1.config.work_peers.push_back (std::make_pair (node3.network.endpoint ().address ().to_string (), rpc3.config.port)); node1.config.work_peers.push_back (std::make_pair (node4.network.endpoint ().address ().to_string (), rpc4.config.port)); - for (auto i (0); i < 10; ++i) + std::array, 10> works; + for (auto i (0); i < works.size (); ++i) { nano::keypair key1; - uint64_t work (0); - node1.work_generate (key1.pub, [&work](boost::optional work_a) { - ASSERT_TRUE (work_a.is_initialized ()); + node1.work_generate (nano::work_version::work_1, key1.pub, node1.network_params.network.publish_thresholds.base, [& work = works[i]](boost::optional work_a) { work = *work_a; }); - while (nano::work_validate (key1.pub, work)) + while (nano::work_difficulty (nano::work_version::work_1, key1.pub, works[i]) < nano::work_threshold_base (nano::work_version::work_1)) { system1.poll (); system2.poll (); @@ -3045,18 +3541,61 @@ TEST (rpc, work_peer_many) system4.poll (); } } + node1.stop (); +} + +TEST (rpc, work_version_invalid) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + nano::block_hash hash (1); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("version", "work_invalid"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::bad_work_version).message (), response.json.get ("error")); + } + request.put ("action", "work_validate"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::bad_work_version).message (), response.json.get ("error")); + } } TEST (rpc, block_count) { { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3078,12 +3617,12 @@ TEST (rpc, block_count) // Should be able to get all counts even when enable_control is false. { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (false); + nano::rpc_config rpc_config; + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3106,13 +3645,13 @@ TEST (rpc, block_count) TEST (rpc, frontier_count) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3130,13 +3669,13 @@ TEST (rpc, frontier_count) TEST (rpc, account_count) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3154,13 +3693,13 @@ TEST (rpc, account_count) TEST (rpc, available_supply) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3202,13 +3741,13 @@ TEST (rpc, available_supply) TEST (rpc, mrai_to_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3227,13 +3766,13 @@ TEST (rpc, mrai_to_raw) TEST (rpc, mrai_from_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3252,13 +3791,13 @@ TEST (rpc, mrai_from_raw) TEST (rpc, krai_to_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3277,13 +3816,13 @@ TEST (rpc, krai_to_raw) TEST (rpc, krai_from_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3302,13 +3841,13 @@ TEST (rpc, krai_from_raw) TEST (rpc, nano_to_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3327,13 +3866,13 @@ TEST (rpc, nano_to_raw) TEST (rpc, nano_from_raw) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3352,13 +3891,13 @@ TEST (rpc, nano_from_raw) TEST (rpc, account_representative) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3378,14 +3917,14 @@ TEST (rpc, account_representative) TEST (rpc, account_representative_set) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3393,7 +3932,7 @@ TEST (rpc, account_representative_set) nano::keypair rep; request.put ("account", nano::genesis_account.to_account ()); request.put ("representative", rep.pub.to_account ()); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("action", "account_representative_set"); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -3406,23 +3945,23 @@ TEST (rpc, account_representative_set) nano::block_hash hash; ASSERT_FALSE (hash.decode_hex (block_text1)); ASSERT_FALSE (hash.is_zero ()); - auto transaction (system.nodes[0]->store.tx_begin_read ()); - ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction, hash)); - ASSERT_EQ (rep.pub, system.nodes[0]->store.block_get (transaction, hash)->representative ()); + auto transaction (node->store.tx_begin_read ()); + ASSERT_TRUE (node->store.block_exists (transaction, hash)); + ASSERT_EQ (rep.pub, node->store.block_get (transaction, hash)->representative ()); } TEST (rpc, account_representative_set_work_disabled) { - nano::system system (24000, 0); - nano::node_config node_config (24000, system.logging); + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.work_threads = 0; - auto & node = *system.add_node (node_config); + auto & node = *add_ipc_enabled_node (system, node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3430,7 +3969,7 @@ TEST (rpc, account_representative_set_work_disabled) nano::keypair rep; request.put ("account", nano::genesis_account.to_account ()); request.put ("representative", rep.pub.to_account ()); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node.wallets.items.begin ()->first.to_string ()); request.put ("action", "account_representative_set"); { test_response response (request, rpc.config.port, system.io_ctx); @@ -3444,10 +3983,59 @@ TEST (rpc, account_representative_set_work_disabled) } } +TEST (rpc, account_representative_set_epoch_2) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + + // Upgrade the genesis account to epoch 2 + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2)); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv, false); + + auto target_difficulty = nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_2, false, false, false)); + ASSERT_LT (node.network_params.network.publish_thresholds.entry, target_difficulty); + auto min_difficulty = node.network_params.network.publish_thresholds.entry; + + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + node.wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "account_representative_set"); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + request.put ("representative", nano::keypair ().pub.to_account ()); + + // Test that the correct error is given if there is insufficient work + auto insufficient = system.work_generate_limited (nano::genesis_hash, min_difficulty, target_difficulty); + request.put ("work", nano::to_string_hex (insufficient)); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_common::invalid_work); + ASSERT_EQ (1, response.json.count ("error")); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + } +} + TEST (rpc, bootstrap) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); + nano::system system1 (1); auto latest (system1.nodes[0]->latest (nano::test_genesis_key.pub)); nano::send_block send (latest, nano::genesis_account, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system1.nodes[0]->work_generate_blocking (latest)); { @@ -3455,11 +4043,10 @@ TEST (rpc, bootstrap) ASSERT_EQ (nano::process_result::progress, system1.nodes[0]->ledger.process (transaction, send).code); } scoped_io_thread_name_change scoped_thread_name_io; - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3482,15 +4069,15 @@ TEST (rpc, bootstrap) TEST (rpc, account_remove) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); auto key1 (system0.wallet (0)->deterministic_insert ()); scoped_io_thread_name_change scoped_thread_name_io; ASSERT_TRUE (system0.wallet (0)->exists (key1)); - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3508,13 +4095,13 @@ TEST (rpc, account_remove) TEST (rpc, representatives) { - nano::system system0 (24000, 1); - auto & node = system0.nodes.front (); + nano::system system0; + auto node = add_ipc_enabled_node (system0); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3541,24 +4128,24 @@ TEST (rpc, representatives) // wallet_seed is only available over IPC's unsafe encoding, and when running on test network TEST (rpc, wallet_seed) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::raw_key seed; { - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node->wallets.tx_begin_read ()); system.wallet (0)->store.seed (seed, transaction); } scoped_io_thread_name_change scoped_thread_name_io; - auto & node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_seed"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response (request, rpc_config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -3574,7 +4161,8 @@ TEST (rpc, wallet_seed) TEST (rpc, wallet_change_seed) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); nano::raw_key seed; nano::random_pool::generate_block (seed.data.bytes.data (), seed.data.bytes.size ()); { @@ -3587,11 +4175,10 @@ TEST (rpc, wallet_change_seed) scoped_io_thread_name_change scoped_thread_name_io; auto prv = nano::deterministic_key (seed, 0); auto pub (nano::pub_key (prv)); - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3622,14 +4209,14 @@ TEST (rpc, wallet_change_seed) TEST (rpc, wallet_frontiers) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3654,17 +4241,16 @@ TEST (rpc, wallet_frontiers) TEST (rpc, work_validate) { - nano::network_params params; - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3682,14 +4268,15 @@ TEST (rpc, work_validate) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - std::string validate_text (response.json.get ("valid")); - ASSERT_EQ ("1", validate_text); + ASSERT_EQ (0, response.json.count ("valid")); + ASSERT_TRUE (response.json.get ("valid_all")); + ASSERT_TRUE (response.json.get ("valid_receive")); std::string difficulty_text (response.json.get ("difficulty")); uint64_t difficulty; ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); - ASSERT_GE (difficulty, params.network.publish_threshold); + ASSERT_GE (difficulty, node1.default_difficulty (nano::work_version::work_1)); double multiplier (response.json.get ("multiplier")); - ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, params.network.publish_threshold), 1e-6); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node1.default_difficulty (nano::work_version::work_1)), 1e-6); } uint64_t work2 (0); request.put ("work", nano::to_string_hex (work2)); @@ -3701,18 +4288,18 @@ TEST (rpc, work_validate) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - std::string validate_text (response.json.get ("valid")); - ASSERT_EQ ("0", validate_text); + ASSERT_EQ (0, response.json.count ("valid")); + ASSERT_FALSE (response.json.get ("valid_all")); + ASSERT_FALSE (response.json.get ("valid_receive")); std::string difficulty_text (response.json.get ("difficulty")); uint64_t difficulty; ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); - ASSERT_GE (params.network.publish_threshold, difficulty); + ASSERT_GE (node1.default_difficulty (nano::work_version::work_1), difficulty); double multiplier (response.json.get ("multiplier")); - ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, params.network.publish_threshold), 1e-6); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node1.default_difficulty (nano::work_version::work_1)), 1e-6); } - uint64_t result_difficulty; - ASSERT_FALSE (nano::work_validate (hash, work1, &result_difficulty)); - ASSERT_GE (result_difficulty, params.network.publish_threshold); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work1)); + ASSERT_GE (result_difficulty, node1.default_difficulty (nano::work_version::work_1)); request.put ("work", nano::to_string_hex (work1)); request.put ("difficulty", nano::to_string_hex (result_difficulty)); { @@ -3723,8 +4310,9 @@ TEST (rpc, work_validate) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - bool validate (response.json.get ("valid")); - ASSERT_TRUE (validate); + ASSERT_TRUE (response.json.get ("valid")); + ASSERT_TRUE (response.json.get ("valid_all")); + ASSERT_TRUE (response.json.get ("valid_receive")); } uint64_t difficulty4 (0xfff0000000000000); request.put ("work", nano::to_string_hex (work1)); @@ -3737,8 +4325,9 @@ TEST (rpc, work_validate) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - bool validate (response.json.get ("valid")); - ASSERT_EQ (result_difficulty >= difficulty4, validate); + ASSERT_EQ (result_difficulty >= difficulty4, response.json.get ("valid")); + ASSERT_EQ (result_difficulty >= node1.default_difficulty (nano::work_version::work_1), response.json.get ("valid_all")); + ASSERT_EQ (result_difficulty >= node1.network_params.network.publish_thresholds.epoch_2_receive, response.json.get ("valid_all")); } uint64_t work3 (*node1.work_generate_blocking (hash, difficulty4)); request.put ("work", nano::to_string_hex (work3)); @@ -3750,26 +4339,87 @@ TEST (rpc, work_validate) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - bool validate (response.json.get ("valid")); - ASSERT_TRUE (validate); + ASSERT_TRUE (response.json.get ("valid")); + ASSERT_TRUE (response.json.get ("valid_all")); + ASSERT_TRUE (response.json.get ("valid_receive")); } } +TEST (rpc, work_validate_epoch_2) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + auto epoch1 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1); + ASSERT_NE (nullptr, epoch1); + ASSERT_EQ (node->network_params.network.publish_thresholds.epoch_2, node->network_params.network.publish_thresholds.base); + auto work = system.work_generate_limited (epoch1->hash (), node->network_params.network.publish_thresholds.epoch_1, node->network_params.network.publish_thresholds.base); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "work_validate"); + request.put ("hash", epoch1->hash ().to_string ()); + request.put ("work", nano::to_string_hex (work)); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (0, response.json.count ("valid")); + ASSERT_TRUE (response.json.get ("valid_all")); + ASSERT_TRUE (response.json.get ("valid_receive")); + std::string difficulty_text (response.json.get ("difficulty")); + uint64_t difficulty{ 0 }; + ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); + double multiplier (response.json.get ("multiplier")); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node->network_params.network.publish_thresholds.epoch_1), 1e-6); + }; + // After upgrading, the higher difficulty is used to validate and calculate the multiplier + scoped_thread_name_io.reset (); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2)); + scoped_thread_name_io.renew (); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (0, response.json.count ("valid")); + ASSERT_FALSE (response.json.get ("valid_all")); + ASSERT_TRUE (response.json.get ("valid_receive")); + std::string difficulty_text (response.json.get ("difficulty")); + uint64_t difficulty{ 0 }; + ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); + double multiplier (response.json.get ("multiplier")); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node->default_difficulty (nano::work_version::work_1)), 1e-6); + }; +} + TEST (rpc, successors) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); ASSERT_NE (nullptr, block); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3807,8 +4457,9 @@ TEST (rpc, successors) TEST (rpc, bootstrap_any) { - nano::system system0 (24000, 1); - nano::system system1 (24001, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); + nano::system system1 (1); auto latest (system1.nodes[0]->latest (nano::test_genesis_key.pub)); nano::send_block send (latest, nano::genesis_account, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system1.nodes[0]->work_generate_blocking (latest)); { @@ -3816,11 +4467,10 @@ TEST (rpc, bootstrap_any) ASSERT_EQ (nano::process_result::progress, system1.nodes[0]->ledger.process (transaction, send).code); } scoped_io_thread_name_change scoped_thread_name_io; - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3837,20 +4487,21 @@ TEST (rpc, bootstrap_any) TEST (rpc, republish) { - nano::system system (24000, 2); + nano::system system; nano::keypair key; nano::genesis genesis; - auto & node1 (*system.nodes[0]); + auto & node1 = *add_ipc_enabled_node (system); + system.add_node (); auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); node1.process (send); nano::open_block open (send.hash (), key.pub, key.pub, key.prv, key.pub, *node1.work_generate_blocking (key.pub)); ASSERT_EQ (nano::process_result::progress, node1.process (open).code); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3919,7 +4570,8 @@ TEST (rpc, republish) TEST (rpc, deterministic_key) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); nano::raw_key seed; { auto transaction (system0.nodes[0]->wallets.tx_begin_read ()); @@ -3928,12 +4580,11 @@ TEST (rpc, deterministic_key) nano::account account0 (system0.wallet (0)->deterministic_insert ()); nano::account account1 (system0.wallet (0)->deterministic_insert ()); nano::account account2 (system0.wallet (0)->deterministic_insert ()); - auto & node = system0.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -3963,13 +4614,13 @@ TEST (rpc, deterministic_key) TEST (rpc, accounts_balances) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4000,14 +4651,14 @@ TEST (rpc, accounts_balances) TEST (rpc, accounts_frontiers) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4030,27 +4681,27 @@ TEST (rpc, accounts_frontiers) std::string account_text (frontiers.first); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); std::string frontier_text (frontiers.second.get ("")); - ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account), frontier_text); + ASSERT_EQ (node->latest (nano::genesis_account), frontier_text); } } TEST (rpc, accounts_pending) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (5s); - while (system.nodes[0]->active.active (*block1)) + while (node->active.active (*block1)) { ASSERT_NO_ERROR (system.poll ()); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4161,13 +4812,13 @@ TEST (rpc, accounts_pending) TEST (rpc, blocks) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4175,7 +4826,7 @@ TEST (rpc, blocks) request.put ("action", "blocks"); boost::property_tree::ptree entry; boost::property_tree::ptree peers_l; - entry.put ("", system.nodes[0]->latest (nano::genesis_account).to_string ()); + entry.put ("", node->latest (nano::genesis_account).to_string ()); peers_l.push_back (std::make_pair ("", entry)); request.add_child ("hashes", peers_l); test_response response (request, rpc.config.port, system.io_ctx); @@ -4188,7 +4839,7 @@ TEST (rpc, blocks) for (auto & blocks : response.json.get_child ("blocks")) { std::string hash_text (blocks.first); - ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account).to_string (), hash_text); + ASSERT_EQ (node->latest (nano::genesis_account).to_string (), hash_text); std::string blocks_text (blocks.second.get ("")); ASSERT_FALSE (blocks_text.empty ()); } @@ -4196,29 +4847,29 @@ TEST (rpc, blocks) TEST (rpc, wallet_info) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; system.wallet (0)->insert_adhoc (key.prv); auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); nano::account account (system.wallet (0)->deterministic_insert ()); { - auto transaction (system.nodes[0]->wallets.tx_begin_write ()); + auto transaction (node->wallets.tx_begin_write ()); system.wallet (0)->store.erase (transaction, account); } account = system.wallet (0)->deterministic_insert (); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_info"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -4242,14 +4893,14 @@ TEST (rpc, wallet_info) TEST (rpc, wallet_balances) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto & node = system0.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4296,22 +4947,22 @@ TEST (rpc, wallet_balances) TEST (rpc, pending_exists) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto hash0 (system.nodes[0]->latest (nano::genesis_account)); + auto hash0 (node->latest (nano::genesis_account)); auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (5s); - while (system.nodes[0]->active.active (*block1)) + while (node->active.active (*block1)) { ASSERT_NO_ERROR (system.poll ()); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4346,7 +4997,8 @@ TEST (rpc, pending_exists) TEST (rpc, wallet_pending) { - nano::system system0 (24000, 1); + nano::system system0; + auto node = add_ipc_enabled_node (system0); nano::keypair key1; system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system0.wallet (0)->insert_adhoc (key1.prv); @@ -4359,11 +5011,10 @@ TEST (rpc, wallet_pending) ++iterations; ASSERT_LT (iterations, 200); } - auto & node = system0.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system0.io_ctx, rpc_config); nano::rpc rpc (system0.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4468,13 +5119,13 @@ TEST (rpc, wallet_pending) TEST (rpc, receive_minimum) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4488,25 +5139,25 @@ TEST (rpc, receive_minimum) } ASSERT_EQ (200, response.status); std::string amount (response.json.get ("amount")); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), amount); + ASSERT_EQ (node->config.receive_minimum.to_string_dec (), amount); } TEST (rpc, receive_minimum_set) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "receive_minimum_set"); request.put ("amount", "100"); - ASSERT_NE (system.nodes[0]->config.receive_minimum.to_string_dec (), "100"); + ASSERT_NE (node->config.receive_minimum.to_string_dec (), "100"); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -4516,26 +5167,26 @@ TEST (rpc, receive_minimum_set) ASSERT_EQ (200, response.status); std::string success (response.json.get ("success")); ASSERT_TRUE (success.empty ()); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), "100"); + ASSERT_EQ (node->config.receive_minimum.to_string_dec (), "100"); } TEST (rpc, work_get) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto node = system.nodes.front (); + system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, node->latest (nano::test_genesis_key.pub)); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "work_get"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("account", nano::test_genesis_key.pub.to_account ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -4546,28 +5197,28 @@ TEST (rpc, work_get) ASSERT_EQ (200, response.status); std::string work_text (response.json.get ("work")); uint64_t work (1); - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); - system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); + auto transaction (node->wallets.tx_begin_read ()); + node->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); ASSERT_EQ (nano::to_string_hex (work), work_text); } TEST (rpc, wallet_work_get) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto node = system.nodes.front (); + system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, node->latest (nano::test_genesis_key.pub)); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_work_get"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -4575,35 +5226,35 @@ TEST (rpc, wallet_work_get) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + auto transaction (node->wallets.tx_begin_read ()); for (auto & works : response.json.get_child ("works")) { std::string account_text (works.first); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); std::string work_text (works.second.get ("")); uint64_t work (1); - system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); + node->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); ASSERT_EQ (nano::to_string_hex (work), work_text); } } TEST (rpc, work_set) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); uint64_t work0 (100); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "work_set"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("account", nano::test_genesis_key.pub.to_account ()); request.put ("work", nano::to_string_hex (work0)); test_response response (request, rpc.config.port, system.io_ctx); @@ -4616,27 +5267,27 @@ TEST (rpc, work_set) std::string success (response.json.get ("success")); ASSERT_TRUE (success.empty ()); uint64_t work1 (1); - auto transaction (system.nodes[0]->wallets.tx_begin_read ()); - system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work1); + auto transaction (node->wallets.tx_begin_read ()); + node->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work1); ASSERT_EQ (work1, work0); } TEST (rpc, search_pending_all) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (latest)); + auto latest (node->latest (nano::test_genesis_key.pub)); + nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - node->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (latest)); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, block).code); + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, block).code); } scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4650,7 +5301,7 @@ TEST (rpc, search_pending_all) } ASSERT_EQ (200, response.status); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) != nano::genesis_amount) + while (node->balance (nano::test_genesis_key.pub) != nano::genesis_amount) { ASSERT_NO_ERROR (system.poll ()); } @@ -4658,7 +5309,8 @@ TEST (rpc, search_pending_all) TEST (rpc, wallet_republish) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::genesis genesis; nano::keypair key; while (key.pub < nano::test_genesis_key.pub) @@ -4669,23 +5321,22 @@ TEST (rpc, wallet_republish) } system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - system.nodes[0]->process (send); + node1.process (send); nano::open_block open (send.hash (), key.pub, key.pub, key.prv, key.pub, *node1.work_generate_blocking (key.pub)); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + ASSERT_EQ (nano::process_result::progress, node1.process (open).code); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_republish"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node1.wallets.items.begin ()->first.to_string ()); request.put ("count", 1); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -4707,21 +5358,21 @@ TEST (rpc, wallet_republish) TEST (rpc, delegators) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - system.nodes[0]->process (send); + node1.process (send); nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, *node1.work_generate_blocking (key.pub)); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); + ASSERT_EQ (nano::process_result::progress, node1.process (open).code); scoped_io_thread_name_change scoped_thread_name_io; nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4748,21 +5399,21 @@ TEST (rpc, delegators) TEST (rpc, delegators_count) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); node1.process (send); nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, *node1.work_generate_blocking (key.pub)); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + ASSERT_EQ (nano::process_result::progress, node1.process (open).code); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4782,16 +5433,16 @@ TEST (rpc, delegators_count) TEST (rpc, account_info) { - nano::system system (24000, 1); + nano::system system; nano::keypair key; nano::genesis genesis; - auto & node1 (*system.nodes[0]); + auto & node1 = *add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4817,13 +5468,13 @@ TEST (rpc, account_info) scoped_thread_name_io.reset (); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - system.nodes[0]->process (send); + node1.process (send); auto time (nano::seconds_since_epoch ()); { auto transaction = node1.store.tx_begin_write (); - node1.store.confirmation_height_put (transaction, nano::test_genesis_key.pub, 1); + node1.store.confirmation_height_put (transaction, nano::test_genesis_key.pub, { 1, genesis.hash () }); } scoped_thread_name_io.renew (); @@ -4851,6 +5502,8 @@ TEST (rpc, account_info) ASSERT_EQ ("2", block_count); std::string confirmation_height (response.json.get ("confirmation_height")); ASSERT_EQ ("1", confirmation_height); + std::string confirmation_height_frontier (response.json.get ("confirmation_height_frontier")); + ASSERT_EQ (genesis.hash ().to_string (), confirmation_height_frontier); ASSERT_EQ (0, response.json.get ("account_version")); boost::optional weight (response.json.get_optional ("weight")); ASSERT_FALSE (weight.is_initialized ()); @@ -4883,16 +5536,16 @@ TEST (rpc, account_info) /** Make sure we can use json block literals instead of string as input */ TEST (rpc, json_block_input) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4900,16 +5553,17 @@ TEST (rpc, json_block_input) request.put ("action", "sign"); request.put ("json_block", "true"); std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node1.wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("account", key.pub.to_account ()); boost::property_tree::ptree json; send.serialize_json (json); request.add_child ("block", json); test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); while (response.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); @@ -4925,19 +5579,19 @@ TEST (rpc, json_block_input) /** Make sure we can receive json block literals instead of string as output */ TEST (rpc, json_block_output) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - system.nodes[0]->process (send); + node1.process (send); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -4961,21 +5615,21 @@ TEST (rpc, json_block_output) TEST (rpc, blocks_info) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - auto check_blocks = [&system](test_response & response) { + auto check_blocks = [node](test_response & response) { for (auto & blocks : response.json.get_child ("blocks")) { std::string hash_text (blocks.first); - ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account).to_string (), hash_text); + ASSERT_EQ (node->latest (nano::genesis_account).to_string (), hash_text); std::string account_text (blocks.second.get ("block_account")); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); std::string amount_text (blocks.second.get ("amount")); @@ -4995,7 +5649,7 @@ TEST (rpc, blocks_info) request.put ("action", "blocks_info"); boost::property_tree::ptree entry; boost::property_tree::ptree hashes; - entry.put ("", system.nodes[0]->latest (nano::genesis_account).to_string ()); + entry.put ("", node->latest (nano::genesis_account).to_string ()); hashes.push_back (std::make_pair ("", entry)); request.add_child ("hashes", hashes); { @@ -5059,8 +5713,8 @@ TEST (rpc, blocks_info) TEST (rpc, blocks_info_subtype) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); @@ -5071,10 +5725,10 @@ TEST (rpc, blocks_info_subtype) auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, key.pub)); ASSERT_NE (nullptr, change); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5107,14 +5761,14 @@ TEST (rpc, blocks_info_subtype) TEST (rpc, work_peers_all) { - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5172,18 +5826,18 @@ TEST (rpc, work_peers_all) TEST (rpc, block_count_type) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, send); - auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5210,7 +5864,8 @@ TEST (rpc, block_count_type) TEST (rpc, ledger) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -5226,10 +5881,10 @@ TEST (rpc, ledger) ASSERT_EQ (nano::process_result::progress, node1.process (open).code); auto time (nano::seconds_since_epoch ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5339,19 +5994,19 @@ TEST (rpc, ledger) TEST (rpc, accounts_create) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "accounts_create"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("count", "8"); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); @@ -5373,29 +6028,29 @@ TEST (rpc, accounts_create) TEST (rpc, block_create) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); auto latest (node1.latest (nano::test_genesis_key.pub)); auto send_work = *node1.work_generate_blocking (latest); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, send_work); auto open_work = *node1.work_generate_blocking (key.pub); nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, open_work); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "block_create"); request.put ("type", "send"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node1.wallets.items.begin ()->first.to_string ()); request.put ("account", nano::test_genesis_key.pub.to_account ()); request.put ("previous", latest.to_string ()); request.put ("amount", "340282366920938463463374607431768211355"); @@ -5410,6 +6065,8 @@ TEST (rpc, block_create) ASSERT_EQ (200, response.status); std::string send_hash (response.json.get ("hash")); ASSERT_EQ (send.hash ().to_string (), send_hash); + std::string send_difficulty (response.json.get ("difficulty")); + ASSERT_EQ (nano::to_string_hex (send.difficulty ()), send_difficulty); auto send_text (response.json.get ("block")); boost::property_tree::ptree block_l; std::stringstream block_stream (send_text); @@ -5417,7 +6074,7 @@ TEST (rpc, block_create) auto send_block (nano::deserialize_block_json (block_l)); ASSERT_EQ (send.hash (), send_block->hash ()); scoped_thread_name_io.reset (); - system.nodes[0]->process (send); + node1.process (send); scoped_thread_name_io.renew (); boost::property_tree::ptree request1; request1.put ("action", "block_create"); @@ -5443,7 +6100,7 @@ TEST (rpc, block_create) auto open_block (nano::deserialize_block_json (block_l)); ASSERT_EQ (open.hash (), open_block->hash ()); scoped_thread_name_io.reset (); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + ASSERT_EQ (nano::process_result::progress, node1.process (open).code); scoped_thread_name_io.renew (); request1.put ("representative", key.pub.to_account ()); test_response response2 (request1, rpc.config.port, system.io_ctx); @@ -5476,12 +6133,12 @@ TEST (rpc, block_create) scoped_thread_name_io.reset (); ASSERT_EQ (nano::process_result::progress, node1.process (change).code); nano::send_block send2 (send.hash (), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (send.hash ())); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (send2).code); + ASSERT_EQ (nano::process_result::progress, node1.process (send2).code); scoped_thread_name_io.renew (); boost::property_tree::ptree request2; request2.put ("action", "block_create"); request2.put ("type", "receive"); - request2.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request2.put ("wallet", node1.wallets.items.begin ()->first.to_string ()); request2.put ("account", key.pub.to_account ()); request2.put ("source", send2.hash ().to_string ()); request2.put ("previous", change.hash ().to_string ()); @@ -5499,33 +6156,33 @@ TEST (rpc, block_create) boost::property_tree::read_json (block_stream5, block_l); auto receive_block (nano::deserialize_block_json (block_l)); ASSERT_EQ (receive_hash, receive_block->hash ().to_string ()); - system.nodes[0]->process_active (std::move (receive_block)); - latest = system.nodes[0]->latest (key.pub); + node1.process_active (std::move (receive_block)); + latest = node1.latest (key.pub); ASSERT_EQ (receive_hash, latest.to_string ()); } TEST (rpc, block_create_state) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); boost::property_tree::ptree request; request.put ("action", "block_create"); request.put ("type", "state"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("account", nano::test_genesis_key.pub.to_account ()); request.put ("previous", genesis.hash ().to_string ()); request.put ("representative", nano::test_genesis_key.pub.to_account ()); request.put ("balance", (nano::genesis_amount - nano::Gxrb_ratio).convert_to ()); request.put ("link", key.pub.to_account ()); - request.put ("work", nano::to_string_hex (*system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto node = system.nodes.front (); + request.put ("work", nano::to_string_hex (*node->work_generate_blocking (genesis.hash ()))); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5546,13 +6203,14 @@ TEST (rpc, block_create_state) ASSERT_EQ (nano::block_type::state, state_block->type ()); ASSERT_EQ (state_hash, state_block->hash ().to_string ()); scoped_thread_name_io.reset (); - auto process_result (system.nodes[0]->process (*state_block)); + auto process_result (node->process (*state_block)); ASSERT_EQ (nano::process_result::progress, process_result.code); } TEST (rpc, block_create_state_open) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -5567,13 +6225,12 @@ TEST (rpc, block_create_state_open) request.put ("representative", nano::test_genesis_key.pub.to_account ()); request.put ("balance", nano::Gxrb_ratio.convert_to ()); request.put ("link", send_block->hash ().to_string ()); - request.put ("work", nano::to_string_hex (*system.nodes[0]->work_generate_blocking (key.pub))); - auto node = system.nodes.front (); + request.put ("work", nano::to_string_hex (*node->work_generate_blocking (key.pub))); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5593,11 +6250,15 @@ TEST (rpc, block_create_state_open) ASSERT_NE (nullptr, state_block); ASSERT_EQ (nano::block_type::state, state_block->type ()); ASSERT_EQ (state_hash, state_block->hash ().to_string ()); - ASSERT_TRUE (system.nodes[0]->latest (key.pub).is_zero ()); + auto difficulty (state_block->difficulty ()); + ASSERT_GT (difficulty, nano::work_threshold (state_block->work_version (), nano::block_details (nano::epoch::epoch_0, false, true, false))); + ASSERT_TRUE (node->latest (key.pub).is_zero ()); scoped_thread_name_io.reset (); - auto process_result (system.nodes[0]->process (*state_block)); + auto process_result (node->process (*state_block)); ASSERT_EQ (nano::process_result::progress, process_result.code); - ASSERT_FALSE (system.nodes[0]->latest (key.pub).is_zero ()); + ASSERT_EQ (state_block->sideband ().details.epoch, nano::epoch::epoch_0); + ASSERT_TRUE (state_block->sideband ().details.is_receive); + ASSERT_FALSE (node->latest (key.pub).is_zero ()); } // Missing "work" parameter should cause work to be generated for us. @@ -5610,7 +6271,8 @@ TEST (rpc, block_create_state_request_work) std::vector previous_test_input{ genesis.hash ().to_string (), std::string ("0") }; for (auto previous : previous_test_input) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -5618,17 +6280,16 @@ TEST (rpc, block_create_state_request_work) boost::property_tree::ptree request; request.put ("action", "block_create"); request.put ("type", "state"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); request.put ("account", nano::test_genesis_key.pub.to_account ()); request.put ("representative", nano::test_genesis_key.pub.to_account ()); request.put ("balance", (nano::genesis_amount - nano::Gxrb_ratio).convert_to ()); request.put ("link", key.pub.to_account ()); request.put ("previous", previous); - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5644,30 +6305,38 @@ TEST (rpc, block_create_state_request_work) boost::property_tree::read_json (block_stream, block_l); auto block (nano::deserialize_block_json (block_l)); ASSERT_NE (nullptr, block); - ASSERT_FALSE (nano::work_validate (*block)); + ASSERT_GE (block->difficulty (), node->default_difficulty (nano::work_version::work_1)); } } -TEST (rpc, block_hash) +TEST (rpc, block_create_open_epoch_v2) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); - auto & node1 (*system.nodes[0]); - nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2)); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("key", key.prv.data.to_string ()); + request.put ("account", key.pub.to_account ()); + request.put ("previous", 0); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", nano::Gxrb_ratio.convert_to ()); + request.put ("link", send_block->hash ().to_string ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; - nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - boost::property_tree::ptree request; - request.put ("action", "block_hash"); - std::string json; - send.serialize_json (json); - request.put ("block", json); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -5675,31 +6344,57 @@ TEST (rpc, block_hash) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - std::string send_hash (response.json.get ("hash")); - ASSERT_EQ (send.hash ().to_string (), send_hash); + std::string state_hash (response.json.get ("hash")); + auto state_text (response.json.get ("block")); + std::stringstream block_stream (state_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto state_block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, state_block); + ASSERT_EQ (nano::block_type::state, state_block->type ()); + ASSERT_EQ (state_hash, state_block->hash ().to_string ()); + auto difficulty (state_block->difficulty ()); + ASSERT_GT (difficulty, nano::work_threshold (state_block->work_version (), nano::block_details (nano::epoch::epoch_2, false, true, false))); + ASSERT_TRUE (node->latest (key.pub).is_zero ()); + scoped_thread_name_io.reset (); + auto process_result (node->process (*state_block)); + ASSERT_EQ (nano::process_result::progress, process_result.code); + ASSERT_EQ (state_block->sideband ().details.epoch, nano::epoch::epoch_2); + ASSERT_TRUE (state_block->sideband ().details.is_receive); + ASSERT_FALSE (node->latest (key.pub).is_zero ()); } -TEST (rpc, wallet_lock) +TEST (rpc, block_create_receive_epoch_v2) { - nano::system system (24000, 1); - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1)); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + nano::state_block open (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send_block->hash (), key.prv, key.pub, *node->work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, node->process (open).code); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2)); + auto send_block_2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("key", key.prv.data.to_string ()); + request.put ("account", key.pub.to_account ()); + request.put ("previous", open.hash ().to_string ()); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", (2 * nano::Gxrb_ratio).convert_to ()); + request.put ("link", send_block_2->hash ().to_string ()); scoped_io_thread_name_change scoped_thread_name_io; nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - boost::property_tree::ptree request; - std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); - { - auto transaction (system.wallet (0)->wallets.tx_begin_read ()); - ASSERT_TRUE (system.wallet (0)->store.valid_password (transaction)); - } - request.put ("wallet", wallet); - request.put ("action", "wallet_lock"); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -5707,29 +6402,55 @@ TEST (rpc, wallet_lock) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - std::string account_text1 (response.json.get ("locked")); - ASSERT_EQ (account_text1, "1"); - auto transaction (system.wallet (0)->wallets.tx_begin_read ()); - ASSERT_FALSE (system.wallet (0)->store.valid_password (transaction)); + std::string state_hash (response.json.get ("hash")); + auto state_text (response.json.get ("block")); + std::stringstream block_stream (state_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto state_block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, state_block); + ASSERT_EQ (nano::block_type::state, state_block->type ()); + ASSERT_EQ (state_hash, state_block->hash ().to_string ()); + auto difficulty (state_block->difficulty ()); + ASSERT_GT (difficulty, nano::work_threshold (state_block->work_version (), nano::block_details (nano::epoch::epoch_2, false, true, false))); + scoped_thread_name_io.reset (); + auto process_result (node->process (*state_block)); + ASSERT_EQ (nano::process_result::progress, process_result.code); + ASSERT_EQ (state_block->sideband ().details.epoch, nano::epoch::epoch_2); + ASSERT_TRUE (state_block->sideband ().details.is_receive); + ASSERT_FALSE (node->latest (key.pub).is_zero ()); } -TEST (rpc, wallet_locked) +TEST (rpc, block_create_send_epoch_v2) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1)); + ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2)); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + nano::state_block open (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send_block->hash (), key.prv, key.pub, *node->work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, node->process (open).code); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("key", key.prv.data.to_string ()); + request.put ("account", key.pub.to_account ()); + request.put ("previous", open.hash ().to_string ()); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", 0); + request.put ("link", nano::test_genesis_key.pub.to_string ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - boost::property_tree::ptree request; - std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); - request.put ("wallet", wallet); - request.put ("action", "wallet_locked"); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -5737,18 +6458,126 @@ TEST (rpc, wallet_locked) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - std::string account_text1 (response.json.get ("locked")); - ASSERT_EQ (account_text1, "0"); -} - -TEST (rpc, wallet_create_fail) -{ - nano::system system (24000, 1); - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); - nano::node_rpc_config node_rpc_config; + std::string state_hash (response.json.get ("hash")); + auto state_text (response.json.get ("block")); + std::stringstream block_stream (state_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto state_block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, state_block); + ASSERT_EQ (nano::block_type::state, state_block->type ()); + ASSERT_EQ (state_hash, state_block->hash ().to_string ()); + auto difficulty (state_block->difficulty ()); + ASSERT_GT (difficulty, nano::work_threshold (state_block->work_version (), nano::block_details (nano::epoch::epoch_2, true, false, false))); + scoped_thread_name_io.reset (); + auto process_result (node->process (*state_block)); + ASSERT_EQ (nano::process_result::progress, process_result.code); + ASSERT_EQ (state_block->sideband ().details.epoch, nano::epoch::epoch_2); + ASSERT_TRUE (state_block->sideband ().details.is_send); + ASSERT_FALSE (node->latest (key.pub).is_zero ()); +} + +TEST (rpc, block_hash) +{ + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + nano::keypair key; + auto latest (node1.latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_hash"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); +} + +TEST (rpc, wallet_lock) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + node->wallets.items.begin ()->first.encode_hex (wallet); + { + auto transaction (system.wallet (0)->wallets.tx_begin_read ()); + ASSERT_TRUE (system.wallet (0)->store.valid_password (transaction)); + } + request.put ("wallet", wallet); + request.put ("action", "wallet_lock"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "1"); + auto transaction (system.wallet (0)->wallets.tx_begin_read ()); + ASSERT_FALSE (system.wallet (0)->store.valid_password (transaction)); +} + +TEST (rpc, wallet_locked) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + node->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_locked"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "0"); +} + +TEST (rpc, wallet_create_fail) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); // lmdb_max_dbs should be removed once the wallet store is refactored to support more wallets. @@ -5771,28 +6600,28 @@ TEST (rpc, wallet_create_fail) TEST (rpc, wallet_ledger) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; nano::genesis genesis; system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); - auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto latest (node1.latest (nano::test_genesis_key.pub)); nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (latest)); - system.nodes[0]->process (send); + node1.process (send); nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, *node1.work_generate_blocking (key.pub)); ASSERT_EQ (nano::process_result::progress, node1.process (open).code); auto time (nano::seconds_since_epoch ()); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_ledger"); - request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node1.wallets.items.begin ()->first.to_string ()); request.put ("sorting", "1"); request.put ("count", "1"); test_response response (request, rpc.config.port, system.io_ctx); @@ -5849,19 +6678,19 @@ TEST (rpc, wallet_ledger) TEST (rpc, wallet_add_watch) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node->wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("action", "wallet_add_watch"); boost::property_tree::ptree entry; @@ -5900,22 +6729,24 @@ TEST (rpc, wallet_add_watch) TEST (rpc, online_reps) { - nano::system system (24000, 2); + nano::system system (1); + auto node1 (system.nodes[0]); + auto node2 = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_TRUE (system.nodes[1]->online_reps.online_stake () == system.nodes[1]->config.online_weight_minimum.number ()); + ASSERT_TRUE (node2->online_reps.online_stake () == node2->config.online_weight_minimum.number ()); auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); ASSERT_NE (nullptr, send_block); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (10s); - while (system.nodes[1]->online_reps.list ().empty ()) + while (node2->online_reps.list ().empty ()) { ASSERT_NO_ERROR (system.poll ()); } - enable_ipc_transport_tcp (system.nodes[1]->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*system.nodes[1], node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node2->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -5935,7 +6766,7 @@ TEST (rpc, online_reps) boost::optional weight (item->second.get_optional ("weight")); ASSERT_FALSE (weight.is_initialized ()); system.deadline_set (5s); - while (system.nodes[1]->block (send_block->hash ()) == nullptr) + while (node2->block (send_block->hash ()) == nullptr) { ASSERT_NO_ERROR (system.poll ()); } @@ -5952,24 +6783,24 @@ TEST (rpc, online_reps) ASSERT_NE (representatives2.end (), item2); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), item2->first); auto weight2 (item2->second.get ("weight")); - ASSERT_EQ (system.nodes[1]->weight (nano::test_genesis_key.pub).convert_to (), weight2); + ASSERT_EQ (node2->weight (nano::test_genesis_key.pub).convert_to (), weight2); //Test accounts filter scoped_thread_name_io.reset (); auto new_rep (system.wallet (1)->deterministic_insert ()); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, new_rep, system.nodes[0]->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, new_rep, node1->config.receive_minimum.number ())); scoped_thread_name_io.renew (); ASSERT_NE (nullptr, send); system.deadline_set (5s); - while (system.nodes[1]->block (send->hash ()) == nullptr) + while (node2->block (send->hash ()) == nullptr) { ASSERT_NO_ERROR (system.poll ()); } scoped_thread_name_io.reset (); - auto receive (system.wallet (1)->receive_action (*send, new_rep, system.nodes[0]->config.receive_minimum.number ())); + auto receive (system.wallet (1)->receive_action (*send, new_rep, node1->config.receive_minimum.number ())); scoped_thread_name_io.renew (); ASSERT_NE (nullptr, receive); system.deadline_set (5s); - while (system.nodes[1]->block (receive->hash ()) == nullptr) + while (node2->block (receive->hash ()) == nullptr) { ASSERT_NO_ERROR (system.poll ()); } @@ -5978,12 +6809,12 @@ TEST (rpc, online_reps) scoped_thread_name_io.renew (); ASSERT_NE (nullptr, change); system.deadline_set (5s); - while (system.nodes[1]->block (change->hash ()) == nullptr) + while (node2->block (change->hash ()) == nullptr) { ASSERT_NO_ERROR (system.poll ()); } system.deadline_set (5s); - while (system.nodes[1]->online_reps.list ().size () != 2) + while (node2->online_reps.list ().size () != 2) { ASSERT_NO_ERROR (system.poll ()); } @@ -6003,31 +6834,20 @@ TEST (rpc, online_reps) ASSERT_NE (representatives3.end (), item3); ASSERT_EQ (new_rep.to_account (), item3->first); ASSERT_EQ (representatives3.size (), 1); - system.nodes[1]->stop (); + node2->stop (); } -// If this test fails, try increasing the num_blocks size. TEST (rpc, confirmation_height_currently_processing) { - // The chains should be longer than the batch_write_size to test the amount of blocks confirmed is correct. nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); + auto node = add_ipc_enabled_node (system, node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - // Do enough blocks to reliably call RPC before the confirmation height has finished auto previous_genesis_chain_hash = node->latest (nano::test_genesis_key.pub); { - constexpr auto num_blocks = 1000; auto transaction = node->store.tx_begin_write (); - for (auto i = num_blocks; i > 0; --i) - { - nano::send_block send (previous_genesis_chain_hash, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio + i + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous_genesis_chain_hash)); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - previous_genesis_chain_hash = send.hash (); - } - nano::keypair key1; nano::send_block send (previous_genesis_chain_hash, key1.pub, nano::genesis_amount - nano::Gxrb_ratio - 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous_genesis_chain_hash)); ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); @@ -6042,49 +6862,47 @@ TEST (rpc, confirmation_height_currently_processing) frontier = node->store.block_get (transaction, previous_genesis_chain_hash); } - // Begin process for confirming the block (and setting confirmation height) - node->block_confirm (frontier); - boost::property_tree::ptree request; request.put ("action", "confirmation_height_currently_processing"); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - system.deadline_set (10s); - while (!node->pending_confirmation_height.is_processing_block (previous_genesis_chain_hash)) + // Begin process for confirming the block (and setting confirmation height) { - ASSERT_NO_ERROR (system.poll ()); - } + // Write guard prevents the confirmation height processor writing the blocks, so that we can inspect contents during the response + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + node->block_confirm (frontier); - // Make the request - { - test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (10s); - while (response.status == 0) + while (node->confirmation_height_processor.current () != frontier->hash ()) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (200, response.status); - auto hash (response.json.get ("hash")); - ASSERT_EQ (frontier->hash ().to_string (), hash); - } - // Wait until confirmation has been set - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, frontier->hash ())) + // Make the request { - break; + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto hash (response.json.get ("hash")); + ASSERT_EQ (frontier->hash ().to_string (), hash); } + } + // Wait until confirmation has been set and not processing anything + system.deadline_set (10s); + while (!node->confirmation_height_processor.current ().is_zero () || node->confirmation_height_processor.awaiting_processing_size () != 0) + { ASSERT_NO_ERROR (system.poll ()); } @@ -6104,22 +6922,22 @@ TEST (rpc, confirmation_height_currently_processing) TEST (rpc, confirmation_history) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_TRUE (system.nodes[0]->active.list_confirmed ().empty ()); + ASSERT_TRUE (node->active.list_recently_cemented ().empty ()); auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (10s); - while (system.nodes[0]->active.list_confirmed ().empty ()) + while (node->active.list_recently_cemented ().empty ()) { ASSERT_NO_ERROR (system.poll ()); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6140,33 +6958,35 @@ TEST (rpc, confirmation_history) ASSERT_EQ (1, item->second.count ("duration")); ASSERT_EQ (1, item->second.count ("time")); ASSERT_EQ (1, item->second.count ("request_count")); + ASSERT_EQ (1, item->second.count ("voters")); + ASSERT_GE (1, item->second.get ("blocks")); ASSERT_EQ (block->hash ().to_string (), hash); nano::amount tally_num; tally_num.decode_dec (tally); - assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio)); + debug_assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio)); system.stop (); } TEST (rpc, confirmation_history_hash) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - ASSERT_TRUE (system.nodes[0]->active.list_confirmed ().empty ()); + ASSERT_TRUE (node->active.list_recently_cemented ().empty ()); auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); auto send3 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); scoped_io_thread_name_change scoped_thread_name_io; system.deadline_set (10s); - while (system.nodes[0]->active.list_confirmed ().size () != 3) + while (node->active.list_recently_cemented ().size () != 3) { ASSERT_NO_ERROR (system.poll ()); } - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6191,26 +7011,26 @@ TEST (rpc, confirmation_history_hash) ASSERT_EQ (send2->hash ().to_string (), hash); nano::amount tally_num; tally_num.decode_dec (tally); - assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 2 * nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 3 * nano::Gxrb_ratio)); + debug_assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 2 * nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 3 * nano::Gxrb_ratio)); system.stop (); } TEST (rpc, block_confirm) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::genesis genesis; - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ()))); { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *send1).code); + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); } scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6229,14 +7049,14 @@ TEST (rpc, block_confirm) TEST (rpc, block_confirm_absent) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6255,17 +7075,15 @@ TEST (rpc, block_confirm_absent) TEST (rpc, block_confirm_confirmed) { - nano::system system (24000, 1); + nano::system system (1); auto path (nano::unique_path ()); nano::node_config config; - config.peering_port = 24001; + config.peering_port = nano::get_available_port (); config.callback_address = "localhost"; - config.callback_port = 24002; + config.callback_port = nano::get_available_port (); config.callback_target = "/"; config.logging.init (path); - auto node (std::make_shared (system.io_ctx, path, system.alarm, config, system.work)); - node->start (); - system.nodes.push_back (node); + auto node = add_ipc_enabled_node (system, config); nano::genesis genesis; { auto transaction (node->store.tx_begin_read ()); @@ -6273,10 +7091,10 @@ TEST (rpc, block_confirm_confirmed) } ASSERT_EQ (0, node->stats.count (nano::stat::type::error, nano::stat::detail::http_callback, nano::stat::dir::out)); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6292,9 +7110,9 @@ TEST (rpc, block_confirm_confirmed) ASSERT_EQ (200, response.status); ASSERT_EQ ("1", response.json.get ("started")); // Check confirmation history - auto confirmed (node->active.list_confirmed ()); + auto confirmed (node->active.list_recently_cemented ()); ASSERT_EQ (1, confirmed.size ()); - ASSERT_EQ (genesis.hash (), confirmed.begin ()->winner->hash ()); + ASSERT_EQ (nano::genesis_hash, confirmed.begin ()->winner->hash ()); // Check callback system.deadline_set (5s); while (node->stats.count (nano::stat::type::error, nano::stat::detail::http_callback, nano::stat::dir::out) == 0) @@ -6308,13 +7126,13 @@ TEST (rpc, block_confirm_confirmed) TEST (rpc, node_id) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6327,26 +7145,26 @@ TEST (rpc, node_id) ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); - ASSERT_EQ (system.nodes[0]->node_id.prv.data.to_string (), response.json.get ("private")); - ASSERT_EQ (system.nodes[0]->node_id.pub.to_account (), response.json.get ("as_account")); - ASSERT_EQ (system.nodes[0]->node_id.pub.to_node_id (), response.json.get ("node_id")); + ASSERT_EQ (node->node_id.prv.data.to_string (), response.json.get ("private")); + ASSERT_EQ (node->node_id.pub.to_account (), response.json.get ("as_account")); + ASSERT_EQ (node->node_id.pub.to_node_id (), response.json.get ("node_id")); } TEST (rpc, stats_clear) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key; - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); - system.nodes[0]->stats.inc (nano::stat::type::ledger, nano::stat::dir::in); - ASSERT_EQ (1, system.nodes[0]->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); + node->stats.inc (nano::stat::type::ledger, nano::stat::dir::in); + ASSERT_EQ (1, node->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); boost::property_tree::ptree request; request.put ("action", "stats_clear"); test_response response (request, rpc.config.port, system.io_ctx); @@ -6357,19 +7175,19 @@ TEST (rpc, stats_clear) } std::string success (response.json.get ("success")); ASSERT_TRUE (success.empty ()); - ASSERT_EQ (0, system.nodes[0]->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); - ASSERT_LE (system.nodes[0]->stats.last_reset ().count (), 5); + ASSERT_EQ (0, node->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); + ASSERT_LE (node->stats.last_reset ().count (), 5); } TEST (rpc, unchecked) { - nano::system system (24000, 1); + nano::system system; + auto & node = *add_ipc_enabled_node (system); nano::keypair key; - auto & node (*system.nodes[0]); - enable_ipc_transport_tcp (node.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6412,13 +7230,13 @@ TEST (rpc, unchecked) TEST (rpc, unchecked_get) { - nano::system system (24000, 1); + nano::system system; + auto & node = *add_ipc_enabled_node (system); nano::keypair key; - auto & node (*system.nodes[0]); - enable_ipc_transport_tcp (node.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6456,23 +7274,67 @@ TEST (rpc, unchecked_get) } } +TEST (rpc, unchecked_clear) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + nano::keypair key; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + auto open (std::make_shared (key.pub, 0, key.pub, 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); + node.process_active (open); + node.block_processor.flush (); + boost::property_tree::ptree request; + ASSERT_EQ (node.ledger.cache.unchecked_count, 1); + { + auto transaction = node.store.tx_begin_read (); + ASSERT_EQ (node.store.unchecked_count (transaction), 1); + } + request.put ("action", "unchecked_clear"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + + system.deadline_set (5s); + while (true) + { + auto transaction = node.store.tx_begin_read (); + if (node.store.unchecked_count (transaction) == 0) + { + break; + } + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (node.ledger.cache.unchecked_count, 0); +} + TEST (rpc, unopened) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::account account1 (1), account2 (account1.number () + 1); - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, account1, 1)); ASSERT_NE (nullptr, send); auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, account2, 10)); ASSERT_NE (nullptr, send2); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6558,18 +7420,18 @@ TEST (rpc, unopened) TEST (rpc, unopened_burn) { - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto genesis (node->latest (nano::test_genesis_key.pub)); ASSERT_FALSE (genesis.is_zero ()); auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::burn_account, 1)); ASSERT_NE (nullptr, send); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6588,13 +7450,13 @@ TEST (rpc, unopened_burn) TEST (rpc, unopened_no_accounts) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6613,13 +7475,13 @@ TEST (rpc, unopened_no_accounts) TEST (rpc, uptime) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6638,35 +7500,33 @@ TEST (rpc, uptime) TEST (rpc, wallet_history) { - nano::system system (24000, 1); - auto node0 (system.nodes[0]); - nano::genesis genesis; + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; + auto node = add_ipc_enabled_node (system, node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto timestamp1 (nano::seconds_since_epoch ()); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, send); - std::this_thread::sleep_for (std::chrono::milliseconds (1000)); auto timestamp2 (nano::seconds_since_epoch ()); - auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); nano::keypair key; - std::this_thread::sleep_for (std::chrono::milliseconds (1000)); auto timestamp3 (nano::seconds_since_epoch ()); - auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, node0->config.receive_minimum.number ())); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, node->config.receive_minimum.number ())); ASSERT_NE (nullptr, send2); system.deadline_set (10s); - auto node = system.nodes.front (); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "wallet_history"); - request.put ("wallet", node0->wallets.items.begin ()->first.to_string ()); + request.put ("wallet", node->wallets.items.begin ()->first.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); system.deadline_set (5s); while (response.status == 0) @@ -6683,19 +7543,19 @@ TEST (rpc, wallet_history) ASSERT_EQ (4, history_l.size ()); ASSERT_EQ ("send", std::get<0> (history_l[0])); ASSERT_EQ (key.pub.to_account (), std::get<1> (history_l[0])); - ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[0])); + ASSERT_EQ (node->config.receive_minimum.to_string_dec (), std::get<2> (history_l[0])); ASSERT_EQ (send2->hash ().to_string (), std::get<3> (history_l[0])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[0])); ASSERT_EQ (std::to_string (timestamp3), std::get<5> (history_l[0])); ASSERT_EQ ("receive", std::get<0> (history_l[1])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); - ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[1])); + ASSERT_EQ (node->config.receive_minimum.to_string_dec (), std::get<2> (history_l[1])); ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[1])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[1])); ASSERT_EQ (std::to_string (timestamp2), std::get<5> (history_l[1])); ASSERT_EQ ("send", std::get<0> (history_l[2])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); - ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (node->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[2])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[2])); ASSERT_EQ (std::to_string (timestamp1), std::get<5> (history_l[2])); @@ -6703,21 +7563,21 @@ TEST (rpc, wallet_history) ASSERT_EQ ("receive", std::get<0> (history_l[3])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); ASSERT_EQ (nano::genesis_amount.convert_to (), std::get<2> (history_l[3])); - ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ (nano::genesis_hash.to_string (), std::get<3> (history_l[3])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[3])); } TEST (rpc, sign_hash) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; - auto & node1 (*system.nodes[0]); nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6726,18 +7586,20 @@ TEST (rpc, sign_hash) request.put ("hash", send.hash ().to_string ()); request.put ("key", key.prv.data.to_string ()); test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); while (response.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); std::error_code ec (nano::error_rpc::sign_hash_disabled); ASSERT_EQ (response.json.get ("error"), ec.message ()); node_rpc_config.enable_sign_hash = true; test_response response2 (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); while (response2.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response2.status); nano::signature signature; @@ -6748,32 +7610,33 @@ TEST (rpc, sign_hash) TEST (rpc, sign_block) { - nano::system system (24000, 1); + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); nano::keypair key; system.wallet (0)->insert_adhoc (key.prv); - auto & node1 (*system.nodes[0]); nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node1.config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; request.put ("action", "sign"); std::string wallet; - system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + node1.wallets.items.begin ()->first.encode_hex (wallet); request.put ("wallet", wallet); request.put ("account", key.pub.to_account ()); std::string json; send.serialize_json (json); request.put ("block", json); test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); while (response.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response.status); auto contents (response.json.get ("block")); @@ -6788,13 +7651,13 @@ TEST (rpc, sign_block) TEST (rpc, memory_stats) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); @@ -6822,13 +7685,13 @@ TEST (rpc, memory_stats) TEST (rpc, block_confirmed) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6849,15 +7712,13 @@ TEST (rpc, block_confirmed) system.deadline_set (5s); while (response1.status == 0) { - system.poll (); + ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (200, response1.status); ASSERT_EQ (std::error_code (nano::error_blocks::not_found).message (), response1.json.get ("error")); scoped_thread_name_io.reset (); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key; - system.wallet (0)->insert_adhoc (key.prv); // Open an account directly in the ledger { @@ -6888,22 +7749,14 @@ TEST (rpc, block_confirmed) auto send = std::make_shared (latest, key.pub, 10, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); node->process_active (send); node->block_processor.flush (); - - // Wait until the confirmation height has been set - system.deadline_set (10s); - while (true) + node->block_confirm (send); { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, send->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); + auto election = node->active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node->active.mutex); + election->confirm_once (); } - - // Should no longer be processing the block after confirmation is set - ASSERT_FALSE (node->pending_confirmation_height.is_processing_block (send->hash ())); + ASSERT_TIMELY (3s, node->block_confirmed (send->hash ())); // Requesting confirmation for this should now succeed request.put ("hash", send->hash ().to_string ()); @@ -6918,18 +7771,24 @@ TEST (rpc, block_confirmed) ASSERT_TRUE (response3.json.get ("confirmed")); } -#if !NANO_ROCKSDB TEST (rpc, database_txn_tracker) { + // Don't test this in rocksdb mode + auto use_rocksdb_str = std::getenv ("TEST_USE_ROCKSDB"); + if (use_rocksdb_str && boost::lexical_cast (use_rocksdb_str) == 1) + { + return; + } + // First try when database tracking is disabled { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -6951,20 +7810,19 @@ TEST (rpc, database_txn_tracker) // Now try enabling it but with invalid amounts nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.diagnostics_config.txn_tracking.enable = true; - auto node = system.add_node (node_config); + auto node = add_ipc_enabled_node (system, node_config); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); boost::property_tree::ptree request; - // clang-format off auto check_not_correct_amount = [&system, &request, &rpc_port = rpc.config.port]() { test_response response (request, rpc_port, system.io_ctx); system.deadline_set (5s); @@ -6976,7 +7834,6 @@ TEST (rpc, database_txn_tracker) std::error_code ec (nano::error_common::invalid_amount); ASSERT_EQ (response.json.get ("error"), ec.message ()); }; - // clang-format on request.put ("action", "database_txn_tracker"); request.put ("min_read_time", "not a time"); @@ -6993,8 +7850,7 @@ TEST (rpc, database_txn_tracker) std::promise keep_txn_alive_promise; std::promise txn_created_promise; - // clang-format off - std::thread thread ([&store = node->store, &keep_txn_alive_promise, &txn_created_promise]() { + std::thread thread ([& store = node->store, &keep_txn_alive_promise, &txn_created_promise]() { // Use rpc_process_container as a placeholder as this thread is only instantiated by the daemon so won't be used nano::thread_role::set (nano::thread_role::name::rpc_process_container); @@ -7006,7 +7862,6 @@ TEST (rpc, database_txn_tracker) txn_created_promise.set_value (); keep_txn_alive_promise.get_future ().wait (); }); - // clang-format on txn_created_promise.get_future ().wait (); @@ -7014,7 +7869,7 @@ TEST (rpc, database_txn_tracker) request.put ("min_read_time", "1000"); test_response response (request, rpc.config.port, system.io_ctx); // It can take a long time to generate stack traces - system.deadline_set (30s); + system.deadline_set (60s); while (response.status == 0) { ASSERT_NO_ERROR (system.poll ()); @@ -7046,17 +7901,19 @@ TEST (rpc, database_txn_tracker) ASSERT_TRUE (!std::get<3> (json_l.front ()).empty ()); thread.join (); } -#endif TEST (rpc, active_difficulty) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); + // "Start" epoch 2 + node->ledger.cache.epoch_2_started = true; + ASSERT_EQ (node->default_difficulty (nano::work_version::work_1), node->network_params.network.publish_thresholds.epoch_2); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -7066,7 +7923,7 @@ TEST (rpc, active_difficulty) node->active.multipliers_cb.push_front (1.5); node->active.multipliers_cb.push_front (4.2); // Also pushes 1.0 to the front of multipliers_cb - node->active.update_active_difficulty (lock); + node->active.update_active_multiplier (lock); lock.unlock (); auto trend_size (node->active.multipliers_cb.size ()); ASSERT_NE (0, trend_size); @@ -7082,13 +7939,13 @@ TEST (rpc, active_difficulty) auto network_minimum_text (response.json.get ("network_minimum")); uint64_t network_minimum; ASSERT_FALSE (nano::from_string_hex (network_minimum_text, network_minimum)); - ASSERT_EQ (node->network_params.network.publish_threshold, network_minimum); + ASSERT_EQ (node->default_difficulty (nano::work_version::work_1), network_minimum); auto multiplier (response.json.get ("multiplier")); ASSERT_NEAR (expected_multiplier, multiplier, 1e-6); auto network_current_text (response.json.get ("network_current")); uint64_t network_current; ASSERT_FALSE (nano::from_string_hex (network_current_text, network_current)); - ASSERT_EQ (nano::difficulty::from_multiplier (expected_multiplier, node->network_params.network.publish_threshold), network_current); + ASSERT_EQ (nano::difficulty::from_multiplier (expected_multiplier, node->default_difficulty (nano::work_version::work_1)), network_current); ASSERT_EQ (response.json.not_found (), response.json.find ("difficulty_trend")); } // Test include_trend optional @@ -7124,14 +7981,14 @@ TEST (rpc, active_difficulty) TEST (rpc, simultaneous_calls) { // This tests simulatenous calls to the same node in different threads - nano::system system (24000, 1); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - auto node = system.nodes.front (); nano::thread_runner runner (system.io_ctx, node->config.io_threads); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; rpc_config.rpc_process.num_ipc_connections = 8; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); @@ -7151,8 +8008,7 @@ TEST (rpc, simultaneous_calls) std::atomic count{ num }; for (int i = 0; i < num; ++i) { - // clang-format off - std::thread ([&test_responses, &promise, &count, i, port = rpc.config.port ]() { + std::thread ([&test_responses, &promise, &count, i, port = rpc.config.port]() { test_responses[i]->run (port); if (--count == 0) { @@ -7160,7 +8016,6 @@ TEST (rpc, simultaneous_calls) } }) .detach (); - // clang-format on } promise.get_future ().wait (); @@ -7187,13 +8042,14 @@ TEST (rpc, simultaneous_calls) // This tests that the inprocess RPC (i.e without using IPC) works correctly TEST (rpc, in_process) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); scoped_io_thread_name_change scoped_thread_name_io; - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::node_rpc_config node_rpc_config; - nano::inprocess_rpc_handler inprocess_rpc_handler (*node, node_rpc_config); + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::inprocess_rpc_handler inprocess_rpc_handler (*node, ipc_server, node_rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, inprocess_rpc_handler); rpc.start (); boost::property_tree::ptree request; @@ -7215,12 +8071,12 @@ TEST (rpc, in_process) TEST (rpc_config, serialization) { nano::rpc_config config1; - config1.address = boost::asio::ip::address_v6::any (); + config1.address = boost::asio::ip::address_v6::any ().to_string (); config1.port = 10; config1.enable_control = true; config1.max_json_depth = 10; config1.rpc_process.io_threads = 2; - config1.rpc_process.ipc_address = boost::asio::ip::address_v6::any (); + config1.rpc_process.ipc_address = boost::asio::ip::address_v6::any ().to_string (); config1.rpc_process.ipc_port = 2000; config1.rpc_process.num_ipc_connections = 99; nano::jsonconfig tree; @@ -7272,13 +8128,12 @@ TEST (rpc_config, migrate) TEST (rpc, deprecated_account_format) { - nano::system system (24000, 1); - nano::genesis genesis; - auto node = system.nodes.front (); - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -7305,19 +8160,18 @@ TEST (rpc, deprecated_account_format) } ASSERT_EQ (200, response2.status); std::string frontier (response.json.get ("frontier")); - ASSERT_EQ (genesis.hash ().to_string (), frontier); + ASSERT_EQ (nano::genesis_hash.to_string (), frontier); boost::optional deprecated_account_format2 (response2.json.get_optional ("deprecated_account_format")); ASSERT_TRUE (deprecated_account_format2.is_initialized ()); } TEST (rpc, epoch_upgrade) { - nano::system system (24000, 1); - auto node = system.nodes.front (); + nano::system system; + auto node = add_ipc_enabled_node (system); nano::keypair key1, key2, key3; - nano::genesis genesis; nano::keypair epoch_signer (nano::test_genesis_key); - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 1, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); // to opened account + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - 1, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); // to opened account ASSERT_EQ (nano::process_result::progress, node->process (*send1).code); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); // to unopened account (pending) ASSERT_EQ (nano::process_result::progress, node->process (*send2).code); @@ -7338,10 +8192,10 @@ TEST (rpc, epoch_upgrade) ASSERT_EQ (info.epoch (), nano::epoch::epoch_0); } } - enable_ipc_transport_tcp (node->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -7357,6 +8211,14 @@ TEST (rpc, epoch_upgrade) } ASSERT_EQ (200, response.status); ASSERT_EQ ("1", response.json.get ("started")); + test_response response_fail (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response_fail.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response_fail.status); + ASSERT_EQ ("0", response_fail.json.get ("started")); system.deadline_set (5s); bool done (false); while (!done) @@ -7431,27 +8293,152 @@ TEST (rpc, epoch_upgrade) } } -TEST (rpc, account_lazy_start) +TEST (rpc, epoch_upgrade_multithreaded) { nano::system system; - nano::node_flags node_flags; - node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (24000, system.logging), node_flags); - nano::genesis genesis; - nano::keypair key; - // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); - auto open (std::make_shared (send1->hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); - ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.work_threads = 4; + auto node = add_ipc_enabled_node (system, node_config); + nano::keypair key1, key2, key3; + nano::keypair epoch_signer (nano::test_genesis_key); + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - 1, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); // to opened account + ASSERT_EQ (nano::process_result::progress, node->process (*send1).code); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); // to unopened account (pending) + ASSERT_EQ (nano::process_result::progress, node->process (*send2).code); + auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2->hash ()))); // to burn (0) + ASSERT_EQ (nano::process_result::progress, node->process (*send3).code); + nano::account max_account (std::numeric_limits::max ()); + auto send4 (std::make_shared (nano::test_genesis_key.pub, send3->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 4, max_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3->hash ()))); // to max account + ASSERT_EQ (nano::process_result::progress, node->process (*send4).code); + auto open (std::make_shared (key1.pub, 0, key1.pub, 1, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); + ASSERT_EQ (nano::process_result::progress, node->process (*open).code); + // Check accounts epochs + { + auto transaction (node->store.tx_begin_read ()); + ASSERT_EQ (2, node->store.account_count (transaction)); + for (auto i (node->store.latest_begin (transaction)); i != node->store.latest_end (); ++i) + { + nano::account_info info (i->second); + ASSERT_EQ (info.epoch (), nano::epoch::epoch_0); + } + } + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "epoch_upgrade"); + request.put ("threads", 2); + request.put ("epoch", 1); + request.put ("key", epoch_signer.prv.data.to_string ()); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ ("1", response.json.get ("started")); + system.deadline_set (5s); + bool done (false); + while (!done) + { + auto transaction (node->store.tx_begin_read ()); + done = (4 == node->store.account_count (transaction)); + ASSERT_NO_ERROR (system.poll ()); + } + // Check upgrade + { + auto transaction (node->store.tx_begin_read ()); + ASSERT_EQ (4, node->store.account_count (transaction)); + for (auto i (node->store.latest_begin (transaction)); i != node->store.latest_end (); ++i) + { + nano::account_info info (i->second); + ASSERT_EQ (info.epoch (), nano::epoch::epoch_1); + } + ASSERT_TRUE (node->store.account_exists (transaction, key1.pub)); + ASSERT_TRUE (node->store.account_exists (transaction, key2.pub)); + ASSERT_TRUE (node->store.account_exists (transaction, std::numeric_limits::max ())); + ASSERT_FALSE (node->store.account_exists (transaction, 0)); + } + + // Epoch 2 upgrade + auto genesis_latest (node->latest (nano::test_genesis_key.pub)); + auto send5 (std::make_shared (nano::test_genesis_key.pub, genesis_latest, nano::test_genesis_key.pub, nano::genesis_amount - 5, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis_latest))); // to burn (0) + ASSERT_EQ (nano::process_result::progress, node->process (*send5).code); + auto send6 (std::make_shared (nano::test_genesis_key.pub, send5->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 6, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5->hash ()))); // to key1 (again) + ASSERT_EQ (nano::process_result::progress, node->process (*send6).code); + auto key1_latest (node->latest (key1.pub)); + auto send7 (std::make_shared (key1.pub, key1_latest, key1.pub, 0, key3.pub, key1.prv, key1.pub, *system.work.generate (key1_latest))); // to key3 + ASSERT_EQ (nano::process_result::progress, node->process (*send7).code); + { + // Check pending entry + auto transaction (node->store.tx_begin_read ()); + nano::pending_info info; + ASSERT_FALSE (node->store.pending_get (transaction, nano::pending_key (key3.pub, send7->hash ()), info)); + ASSERT_EQ (nano::epoch::epoch_1, info.epoch); + } + + request.put ("epoch", 2); + test_response response2 (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ ("1", response2.json.get ("started")); + system.deadline_set (5s); + bool done2 (false); + while (!done2) + { + auto transaction (node->store.tx_begin_read ()); + done2 = (5 == node->store.account_count (transaction)); + ASSERT_NO_ERROR (system.poll ()); + } + // Check upgrade + { + auto transaction (node->store.tx_begin_read ()); + ASSERT_EQ (5, node->store.account_count (transaction)); + for (auto i (node->store.latest_begin (transaction)); i != node->store.latest_end (); ++i) + { + nano::account_info info (i->second); + ASSERT_EQ (info.epoch (), nano::epoch::epoch_2); + } + ASSERT_TRUE (node->store.account_exists (transaction, key1.pub)); + ASSERT_TRUE (node->store.account_exists (transaction, key2.pub)); + ASSERT_TRUE (node->store.account_exists (transaction, key3.pub)); + ASSERT_TRUE (node->store.account_exists (transaction, std::numeric_limits::max ())); + ASSERT_FALSE (node->store.account_exists (transaction, 0)); + } +} + +TEST (rpc, account_lazy_start) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_legacy_bootstrap = true; + auto node1 = system.add_node (node_flags); + nano::keypair key; + // Generating test chain + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); + ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); + auto open (std::make_shared (send1->hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); + ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); // Start lazy bootstrap with account - auto node2 = system.add_node (nano::node_config (24001, system.logging), node_flags); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.ipc_config.transport_tcp.enabled = true; + node_config.ipc_config.transport_tcp.port = nano::get_available_port (); + auto node2 = system.add_node (node_config, node_flags); node2->network.udp_channels.insert (node1->network.endpoint (), node1->network_params.protocol.protocol_version); - enable_ipc_transport_tcp (node2->config.ipc_config.transport_tcp); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node2, node_rpc_config); - nano::rpc_config rpc_config (true); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node_config.ipc_config.transport_tcp.port; nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); @@ -7478,3 +8465,564 @@ TEST (rpc, account_lazy_start) ASSERT_TRUE (node2->ledger.block_exists (send1->hash ())); ASSERT_TRUE (node2->ledger.block_exists (open->hash ())); } + +TEST (rpc, receive) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + auto wallet = system.wallet (0); + std::string wallet_text; + node.wallets.items.begin ()->first.encode_hex (wallet_text); + wallet->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key1; + wallet->insert_adhoc (key1.prv); + auto send1 (wallet->send_action (nano::test_genesis_key.pub, key1.pub, node.config.receive_minimum.number (), *node.work_generate_blocking (nano::genesis_hash))); + system.deadline_set (5s); + while (node.balance (nano::test_genesis_key.pub) == nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + while (!node.store.account_exists (node.store.tx_begin_read (), key1.pub)) + { + ASSERT_NO_ERROR (system.poll ()); + } + // Send below minimum receive amount + auto send2 (wallet->send_action (nano::test_genesis_key.pub, key1.pub, node.config.receive_minimum.number () - 1, *node.work_generate_blocking (send1->hash ()))); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "receive"); + request.put ("wallet", wallet_text); + request.put ("account", key1.pub.to_account ()); + request.put ("block", send2->hash ().to_string ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto receive_text (response.json.get ("block")); + nano::account_info info; + ASSERT_FALSE (node.store.account_get (node.store.tx_begin_read (), key1.pub, info)); + ASSERT_EQ (info.head, receive_text); + } + // Trying to receive the same block should fail with unreceivable + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_process::unreceivable).message (), response.json.get ("error")); + } + // Trying to receive a non-existing block should fail + request.put ("block", nano::block_hash (send2->hash ().number () + 1).to_string ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_blocks::not_found).message (), response.json.get ("error")); + } +} + +TEST (rpc, receive_unopened) +{ + nano::system system; + auto & node = *add_ipc_enabled_node (system); + auto wallet = system.wallet (0); + std::string wallet_text; + node.wallets.items.begin ()->first.encode_hex (wallet_text); + wallet->insert_adhoc (nano::test_genesis_key.prv); + // Test receiving for unopened account + nano::keypair key1; + auto send1 (wallet->send_action (nano::test_genesis_key.pub, key1.pub, node.config.receive_minimum.number () - 1, *node.work_generate_blocking (nano::genesis_hash))); + system.deadline_set (5s); + while (node.balance (nano::test_genesis_key.pub) == nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_FALSE (node.store.account_exists (node.store.tx_begin_read (), key1.pub)); + ASSERT_TRUE (node.store.block_exists (node.store.tx_begin_read (), send1->hash ())); + wallet->insert_adhoc (key1.prv); // should not auto receive, amount sent was lower than minimum + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "receive"); + request.put ("wallet", wallet_text); + request.put ("account", key1.pub.to_account ()); + request.put ("block", send1->hash ().to_string ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto receive_text (response.json.get ("block")); + nano::account_info info; + ASSERT_FALSE (node.store.account_get (node.store.tx_begin_read (), key1.pub, info)); + ASSERT_EQ (info.head, info.open_block); + ASSERT_EQ (info.head.to_string (), receive_text); + ASSERT_EQ (info.representative, nano::test_genesis_key.pub); + } + scoped_thread_name_io.reset (); + + // Test receiving for an unopened with a different wallet representative + nano::keypair key2; + auto prev_amount (node.balance (nano::test_genesis_key.pub)); + auto send2 (wallet->send_action (nano::test_genesis_key.pub, key2.pub, node.config.receive_minimum.number () - 1, *node.work_generate_blocking (send1->hash ()))); + system.deadline_set (5s); + while (node.balance (nano::test_genesis_key.pub) == prev_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_FALSE (node.store.account_exists (node.store.tx_begin_read (), key2.pub)); + ASSERT_TRUE (node.store.block_exists (node.store.tx_begin_read (), send2->hash ())); + nano::public_key rep; + wallet->store.representative_set (node.wallets.tx_begin_write (), rep); + wallet->insert_adhoc (key2.prv); // should not auto receive, amount sent was lower than minimum + scoped_thread_name_io.renew (); + request.put ("account", key2.pub.to_account ()); + request.put ("block", send2->hash ().to_string ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto receive_text (response.json.get ("block")); + nano::account_info info; + ASSERT_FALSE (node.store.account_get (node.store.tx_begin_read (), key2.pub, info)); + ASSERT_EQ (info.head, info.open_block); + ASSERT_EQ (info.head.to_string (), receive_text); + ASSERT_EQ (info.representative, rep); + } +} + +TEST (rpc, receive_work_disabled) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + auto & worker_node = *system.add_node (config); + config.peering_port = nano::get_available_port (); + config.work_threads = 0; + auto & node = *add_ipc_enabled_node (system, config); + auto wallet = system.wallet (1); + std::string wallet_text; + node.wallets.items.begin ()->first.encode_hex (wallet_text); + wallet->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key1; + nano::genesis genesis; + ASSERT_TRUE (worker_node.work_generation_enabled ()); + auto send1 (wallet->send_action (nano::test_genesis_key.pub, key1.pub, node.config.receive_minimum.number () - 1, *worker_node.work_generate_blocking (genesis.hash ()), false)); + ASSERT_TRUE (send1 != nullptr); + system.deadline_set (5s); + while (node.balance (nano::test_genesis_key.pub) == nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_FALSE (node.store.account_exists (node.store.tx_begin_read (), key1.pub)); + ASSERT_TRUE (node.store.block_exists (node.store.tx_begin_read (), send1->hash ())); + wallet->insert_adhoc (key1.prv); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "receive"); + request.put ("wallet", wallet_text); + request.put ("account", key1.pub.to_account ()); + request.put ("block", send1->hash ().to_string ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_common::disabled_work_generation).message (), response.json.get ("error")); + } +} + +TEST (rpc, telemetry_single) +{ + nano::system system (1); + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + // Wait until peers are stored as they are done in the background + auto peers_stored = false; + while (!peers_stored) + { + ASSERT_NO_ERROR (system.poll ()); + + auto transaction = system.nodes.back ()->store.tx_begin_read (); + peers_stored = system.nodes.back ()->store.peer_count (transaction) != 0; + } + + // Missing port + boost::property_tree::ptree request; + auto node = system.nodes.front (); + request.put ("action", "telemetry"); + request.put ("address", "not_a_valid_address"); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_rpc::requires_port_and_address).message (), response.json.get ("error")); + } + + // Missing address + request.erase ("address"); + request.put ("port", 65); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_rpc::requires_port_and_address).message (), response.json.get ("error")); + } + + // Try with invalid address + request.put ("address", "not_a_valid_address"); + request.put ("port", 65); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_common::invalid_ip_address).message (), response.json.get ("error")); + } + + // Then invalid port + request.put ("address", (boost::format ("%1%") % node->network.endpoint ().address ()).str ()); + request.put ("port", "invalid port"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_common::invalid_port).message (), response.json.get ("error")); + } + + // Use correctly formed address and port + request.put ("port", node->network.endpoint ().port ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + + nano::jsonconfig config (response.json); + nano::telemetry_data telemetry_data; + auto const should_ignore_identification_metrics = false; + ASSERT_FALSE (telemetry_data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data (telemetry_data, node->network_params, node->config.bandwidth_limit, node->active.active_difficulty (), node->node_id); + } +} + +TEST (rpc, telemetry_all) +{ + nano::system system (1); + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + // Wait until peers are stored as they are done in the background + auto peers_stored = false; + while (!peers_stored) + { + ASSERT_NO_ERROR (system.poll ()); + + auto transaction = node1.store.tx_begin_read (); + peers_stored = node1.store.peer_count (transaction) != 0; + } + + // First need to set up the cached data + std::atomic done{ false }; + auto node = system.nodes.front (); + node1.telemetry->get_metrics_single_peer_async (node1.network.find_channel (node->network.endpoint ()), [&done](nano::telemetry_data_response const & telemetry_data_response_a) { + ASSERT_FALSE (telemetry_data_response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + boost::property_tree::ptree request; + request.put ("action", "telemetry"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::jsonconfig config (response.json); + nano::telemetry_data telemetry_data; + auto const should_ignore_identification_metrics = true; + ASSERT_FALSE (telemetry_data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data_excluding_signature (telemetry_data, node->network_params, node->config.bandwidth_limit, node->active.active_difficulty ()); + ASSERT_FALSE (response.json.get_optional ("node_id").is_initialized ()); + ASSERT_FALSE (response.json.get_optional ("signature").is_initialized ()); + } + + request.put ("raw", "true"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + + // This may fail if the response has taken longer than the cache cutoff time. + auto & all_metrics = response.json.get_child ("metrics"); + auto & metrics = all_metrics.front ().second; + ASSERT_EQ (1, all_metrics.size ()); + + nano::jsonconfig config (metrics); + nano::telemetry_data data; + auto const should_ignore_identification_metrics = false; + ASSERT_FALSE (data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data (data, node->network_params, node->config.bandwidth_limit, node->active.active_difficulty (), node->node_id); + + ASSERT_EQ (node->network.endpoint ().address ().to_string (), metrics.get ("address")); + ASSERT_EQ (node->network.endpoint ().port (), metrics.get ("port")); +} + +// Also tests all forms of ipv4/ipv6 +TEST (rpc, telemetry_self) +{ + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + // Just to have peer count at 1 + node1.network.udp_channels.insert (nano::endpoint (boost::asio::ip::make_address_v6 ("::1"), nano::get_available_port ()), 0); + + boost::property_tree::ptree request; + request.put ("action", "telemetry"); + request.put ("address", "::1"); + request.put ("port", node1.network.endpoint ().port ()); + auto const should_ignore_identification_metrics = false; + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + nano::telemetry_data data; + nano::jsonconfig config (response.json); + ASSERT_FALSE (data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data (data, node1.network_params, node1.config.bandwidth_limit, node1.active.active_difficulty (), node1.node_id); + } + + request.put ("address", "[::1]"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + nano::telemetry_data data; + nano::jsonconfig config (response.json); + ASSERT_FALSE (data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data (data, node1.network_params, node1.config.bandwidth_limit, node1.active.active_difficulty (), node1.node_id); + } + + request.put ("address", "127.0.0.1"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + nano::telemetry_data data; + nano::jsonconfig config (response.json); + ASSERT_FALSE (data.deserialize_json (config, should_ignore_identification_metrics)); + nano::compare_default_telemetry_response_data (data, node1.network_params, node1.config.bandwidth_limit, node1.active.active_difficulty (), node1.node_id); + } + + // Incorrect port should fail + request.put ("port", "0"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_rpc::peer_not_found).message (), response.json.get ("error")); + } +} + +TEST (rpc, confirmation_active) +{ + nano::system system; + nano::node_config node_config; + node_config.ipc_config.transport_tcp.enabled = true; + node_config.ipc_config.transport_tcp.port = nano::get_available_port (); + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node1 (*system.add_node (node_config, node_flags)); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + nano::genesis genesis; + auto send1 (std::make_shared (genesis.hash (), nano::public_key (), nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + auto send2 (std::make_shared (send1->hash (), nano::public_key (), nano::genesis_amount - 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); + node1.process_active (send1); + node1.process_active (send2); + nano::blocks_confirm (node1, { send1, send2 }); + ASSERT_EQ (2, node1.active.size ()); + { + nano::lock_guard guard (node1.active.mutex); + auto info (node1.active.roots.find (send1->qualified_root ())); + ASSERT_NE (node1.active.roots.end (), info); + info->election->confirm_once (); + } + + boost::property_tree::ptree request; + request.put ("action", "confirmation_active"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & confirmations (response.json.get_child ("confirmations")); + ASSERT_EQ (1, confirmations.size ()); + ASSERT_EQ (send2->qualified_root ().to_string (), confirmations.front ().second.get ("")); + ASSERT_EQ (1, response.json.get ("unconfirmed")); + ASSERT_EQ (1, response.json.get ("confirmed")); + } +} + +TEST (rpc, confirmation_info) +{ + nano::system system; + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + nano::genesis genesis; + auto send (std::make_shared (genesis.hash (), nano::public_key (), nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); + node1.process_active (send); + node1.block_processor.flush (); + ASSERT_FALSE (node1.active.empty ()); + + boost::property_tree::ptree request; + request.put ("action", "confirmation_info"); + request.put ("root", send->qualified_root ().to_string ()); + request.put ("representatives", "true"); + request.put ("json_block", "true"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.count ("announcements")); + ASSERT_EQ (1, response.json.get ("voters")); + ASSERT_EQ (send->hash ().to_string (), response.json.get ("last_winner")); + auto & blocks (response.json.get_child ("blocks")); + ASSERT_EQ (1, blocks.size ()); + auto & representatives (blocks.front ().second.get_child ("representatives")); + ASSERT_EQ (1, representatives.size ()); + ASSERT_EQ (0, response.json.get ("total_tally")); + } +} diff --git a/nano/secure/CMakeLists.txt b/nano/secure/CMakeLists.txt index 2dda62429c..2098cceac3 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -35,15 +35,16 @@ add_library (secure ${PLATFORM_SECURE_SOURCE} ${CMAKE_BINARY_DIR}/bootstrap_weights_live.cpp ${CMAKE_BINARY_DIR}/bootstrap_weights_beta.cpp - common.hpp - common.cpp blockstore.hpp - blockstore_partial.hpp blockstore.cpp - epoch.hpp - epoch.cpp + blockstore_partial.hpp + buffer.hpp + common.hpp + common.cpp ledger.hpp ledger.cpp + network_filter.hpp + network_filter.cpp utility.hpp utility.cpp versioning.hpp diff --git a/nano/secure/blockstore.cpp b/nano/secure/blockstore.cpp index eec994937b..1babdb2f54 100644 --- a/nano/secure/blockstore.cpp +++ b/nano/secure/blockstore.cpp @@ -1,103 +1,6 @@ +#include #include -#include -#include - -nano::block_sideband::block_sideband (nano::block_type type_a, nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t height_a, uint64_t timestamp_a, nano::epoch epoch_a) : -type (type_a), -successor (successor_a), -account (account_a), -balance (balance_a), -height (height_a), -timestamp (timestamp_a), -epoch (epoch_a) -{ -} - -size_t nano::block_sideband::size (nano::block_type type_a) -{ - size_t result (0); - result += sizeof (successor); - if (type_a != nano::block_type::state && type_a != nano::block_type::open) - { - result += sizeof (account); - } - if (type_a != nano::block_type::open) - { - result += sizeof (height); - } - if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) - { - result += sizeof (balance); - } - result += sizeof (timestamp); - if (type_a == nano::block_type::state) - { - result += sizeof (epoch); - } - return result; -} - -void nano::block_sideband::serialize (nano::stream & stream_a) const -{ - nano::write (stream_a, successor.bytes); - if (type != nano::block_type::state && type != nano::block_type::open) - { - nano::write (stream_a, account.bytes); - } - if (type != nano::block_type::open) - { - nano::write (stream_a, boost::endian::native_to_big (height)); - } - if (type == nano::block_type::receive || type == nano::block_type::change || type == nano::block_type::open) - { - nano::write (stream_a, balance.bytes); - } - nano::write (stream_a, boost::endian::native_to_big (timestamp)); - if (type == nano::block_type::state) - { - nano::write (stream_a, epoch); - } -} - -bool nano::block_sideband::deserialize (nano::stream & stream_a) -{ - bool result (false); - try - { - nano::read (stream_a, successor.bytes); - if (type != nano::block_type::state && type != nano::block_type::open) - { - nano::read (stream_a, account.bytes); - } - if (type != nano::block_type::open) - { - nano::read (stream_a, height); - boost::endian::big_to_native_inplace (height); - } - else - { - height = 1; - } - if (type == nano::block_type::receive || type == nano::block_type::change || type == nano::block_type::open) - { - nano::read (stream_a, balance.bytes); - } - nano::read (stream_a, timestamp); - boost::endian::big_to_native_inplace (timestamp); - if (type == nano::block_type::state) - { - nano::read (stream_a, epoch); - } - } - catch (std::runtime_error &) - { - result = true; - } - - return result; -} - nano::summation_visitor::summation_visitor (nano::transaction const & transaction_a, nano::block_store const & store_a, bool is_v14_upgrade_a) : transaction (transaction_a), store (store_a), @@ -107,7 +10,7 @@ is_v14_upgrade (is_v14_upgrade_a) void nano::summation_visitor::send_block (nano::send_block const & block_a) { - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); if (current->type == summation_type::amount) { sum_set (block_a.hashables.balance.number ()); @@ -123,7 +26,7 @@ void nano::summation_visitor::send_block (nano::send_block const & block_a) void nano::summation_visitor::state_block (nano::state_block const & block_a) { - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); sum_set (block_a.hashables.balance.number ()); if (current->type == summation_type::amount) { @@ -138,7 +41,7 @@ void nano::summation_visitor::state_block (nano::state_block const & block_a) void nano::summation_visitor::receive_block (nano::receive_block const & block_a) { - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); if (current->type == summation_type::amount) { current->amount_hash = block_a.hashables.source; @@ -161,7 +64,7 @@ void nano::summation_visitor::receive_block (nano::receive_block const & block_a void nano::summation_visitor::open_block (nano::open_block const & block_a) { - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); if (current->type == summation_type::amount) { if (block_a.hashables.source != network_params.ledger.genesis_account) @@ -183,7 +86,7 @@ void nano::summation_visitor::open_block (nano::open_block const & block_a) void nano::summation_visitor::change_block (nano::change_block const & block_a) { - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); if (current->type == summation_type::amount) { sum_set (0); @@ -237,7 +140,7 @@ nano::uint128_t nano::summation_visitor::compute_internal (nano::summation_visit while (!frames.empty ()) { current = &frames.top (); - assert (current->type != summation_type::invalid && current != nullptr); + debug_assert (current->type != summation_type::invalid && current != nullptr); if (current->type == summation_type::balance) { @@ -259,7 +162,7 @@ nano::uint128_t nano::summation_visitor::compute_internal (nano::summation_visit else { auto block (block_get (transaction, current->balance_hash)); - assert (block != nullptr); + debug_assert (block != nullptr); block->visit (*this); } } @@ -292,7 +195,7 @@ nano::uint128_t nano::summation_visitor::compute_internal (nano::summation_visit } else { - assert (false); + debug_assert (false); sum_set (0); current->amount_hash = 0; } @@ -338,7 +241,7 @@ nano::uint128_t nano::summation_visitor::compute_balance (nano::block_hash const std::shared_ptr nano::summation_visitor::block_get (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { - return is_v14_upgrade ? store.block_get_v14 (transaction, hash_a) : store.block_get (transaction, hash_a); + return is_v14_upgrade ? store.block_get_v14 (transaction, hash_a) : store.block_get_no_sideband (transaction, hash_a); } nano::representative_visitor::representative_visitor (nano::transaction const & transaction_a, nano::block_store & store_a) : @@ -354,7 +257,7 @@ void nano::representative_visitor::compute (nano::block_hash const & hash_a) while (result.is_zero ()) { auto block (store.block_get (transaction, current)); - assert (block != nullptr); + debug_assert (block != nullptr); block->visit (*this); } } @@ -416,7 +319,7 @@ impl (std::move (write_transaction_impl)) /* * For IO threads, we do not want them to block on creating write transactions. */ - assert (nano::thread_role::get () != nano::thread_role::name::io); + debug_assert (nano::thread_role::get () != nano::thread_role::name::io); } void * nano::write_transaction::get_handle () const diff --git a/nano/secure/blockstore.hpp b/nano/secure/blockstore.hpp index 11a470b748..a2b7a14c10 100644 --- a/nano/secure/blockstore.hpp +++ b/nano/secure/blockstore.hpp @@ -1,11 +1,12 @@ #pragma once #include -#include #include +#include #include #include #include +#include #include #include @@ -16,6 +17,14 @@ namespace nano { +// Move to versioning with a specific version if required for a future upgrade +class state_block_w_sideband +{ +public: + std::shared_ptr state_block; + nano::block_sideband sideband; +}; + /** * Encapsulates database specific container */ @@ -92,6 +101,16 @@ class db_val static_assert (std::is_standard_layout::value, "Standard layout is required"); } + db_val (nano::confirmation_height_info const & val_a) : + buffer (std::make_shared> ()) + { + { + nano::vectorstream stream (*buffer); + val_a.serialize (stream); + } + convert_buffer_to_value (); + } + db_val (nano::block_info const & val_a) : db_val (sizeof (val_a), const_cast (&val_a)) { @@ -128,7 +147,7 @@ class db_val explicit operator nano::account_info () const { nano::account_info result; - assert (size () == result.db_size ()); + debug_assert (size () == result.db_size ()); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + result.db_size (), reinterpret_cast (&result)); return result; } @@ -136,7 +155,7 @@ class db_val explicit operator nano::account_info_v13 () const { nano::account_info_v13 result; - assert (size () == result.db_size ()); + debug_assert (size () == result.db_size ()); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + result.db_size (), reinterpret_cast (&result)); return result; } @@ -144,7 +163,7 @@ class db_val explicit operator nano::account_info_v14 () const { nano::account_info_v14 result; - assert (size () == result.db_size ()); + debug_assert (size () == result.db_size ()); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + result.db_size (), reinterpret_cast (&result)); return result; } @@ -152,7 +171,7 @@ class db_val explicit operator nano::block_info () const { nano::block_info result; - assert (size () == sizeof (result)); + debug_assert (size () == sizeof (result)); static_assert (sizeof (nano::block_info::account) + sizeof (nano::block_info::balance) == sizeof (result), "Packed class"); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + sizeof (result), reinterpret_cast (&result)); return result; @@ -161,7 +180,7 @@ class db_val explicit operator nano::pending_info_v14 () const { nano::pending_info_v14 result; - assert (size () == result.db_size ()); + debug_assert (size () == result.db_size ()); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + result.db_size (), reinterpret_cast (&result)); return result; } @@ -169,7 +188,7 @@ class db_val explicit operator nano::pending_info () const { nano::pending_info result; - assert (size () == result.db_size ()); + debug_assert (size () == result.db_size ()); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + result.db_size (), reinterpret_cast (&result)); return result; } @@ -177,26 +196,36 @@ class db_val explicit operator nano::pending_key () const { nano::pending_key result; - assert (size () == sizeof (result)); + debug_assert (size () == sizeof (result)); static_assert (sizeof (nano::pending_key::account) + sizeof (nano::pending_key::hash) == sizeof (result), "Packed class"); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + sizeof (result), reinterpret_cast (&result)); return result; } + explicit operator nano::confirmation_height_info () const + { + nano::bufferstream stream (reinterpret_cast (data ()), size ()); + nano::confirmation_height_info result; + bool error (result.deserialize (stream)); + (void)error; + debug_assert (!error); + return result; + } + explicit operator nano::unchecked_info () const { nano::bufferstream stream (reinterpret_cast (data ()), size ()); nano::unchecked_info result; bool error (result.deserialize (stream)); (void)error; - assert (!error); + debug_assert (!error); return result; } explicit operator nano::unchecked_key () const { nano::unchecked_key result; - assert (size () == sizeof (result)); + debug_assert (size () == sizeof (result)); static_assert (sizeof (nano::unchecked_key::previous) + sizeof (nano::pending_key::hash) == sizeof (result), "Packed class"); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + sizeof (result), reinterpret_cast (&result)); return result; @@ -233,7 +262,7 @@ class db_val std::array result; auto error = nano::try_read (stream, result); (void)error; - assert (!error); + debug_assert (!error); return result; } @@ -244,19 +273,33 @@ class db_val return result; } - explicit operator nano::state_block_w_sideband_v14 () const + explicit operator state_block_w_sideband () const { nano::bufferstream stream (reinterpret_cast (data ()), size ()); auto error (false); - nano::state_block_w_sideband_v14 state_block_w_sideband_v14; - state_block_w_sideband_v14.state_block = std::make_shared (error, stream); - assert (!error); + nano::state_block_w_sideband block_w_sideband; + block_w_sideband.state_block = std::make_shared (error, stream); + debug_assert (!error); - state_block_w_sideband_v14.sideband.type = nano::block_type::state; - error = state_block_w_sideband_v14.sideband.deserialize (stream); - assert (!error); + error = block_w_sideband.sideband.deserialize (stream, nano::block_type::state); + debug_assert (!error); - return state_block_w_sideband_v14; + return block_w_sideband; + } + + explicit operator state_block_w_sideband_v14 () const + { + nano::bufferstream stream (reinterpret_cast (data ()), size ()); + auto error (false); + nano::state_block_w_sideband_v14 block_w_sideband; + block_w_sideband.state_block = std::make_shared (error, stream); + debug_assert (!error); + + block_w_sideband.sideband.type = nano::block_type::state; + error = block_w_sideband.sideband.deserialize (stream); + debug_assert (!error); + + return block_w_sideband; } explicit operator nano::no_value () const @@ -277,7 +320,7 @@ class db_val nano::bufferstream stream (reinterpret_cast (data ()), size ()); auto error (false); auto result (std::make_shared (error, stream)); - assert (!error); + debug_assert (!error); return result; } @@ -311,7 +354,7 @@ class db_val nano::bufferstream stream (reinterpret_cast (data ()), size ()); auto error (false); auto result (nano::make_shared (error, stream)); - assert (!error); + debug_assert (!error); return result; } @@ -321,7 +364,7 @@ class db_val nano::bufferstream stream (reinterpret_cast (data ()), size ()); auto error (nano::try_read (stream, result)); (void)error; - assert (!error); + debug_assert (!error); boost::endian::big_to_native_inplace (result); return result; } @@ -351,28 +394,12 @@ class db_val T convert () const { T result; - assert (size () == sizeof (result)); + debug_assert (size () == sizeof (result)); std::copy (reinterpret_cast (data ()), reinterpret_cast (data ()) + sizeof (result), result.bytes.data ()); return result; } }; -class block_sideband final -{ -public: - block_sideband () = default; - block_sideband (nano::block_type, nano::account const &, nano::block_hash const &, nano::amount const &, uint64_t, uint64_t, nano::epoch); - void serialize (nano::stream &) const; - bool deserialize (nano::stream &); - static size_t size (nano::block_type); - nano::block_type type{ nano::block_type::invalid }; - nano::block_hash successor{ 0 }; - nano::account account{ 0 }; - nano::amount balance{ 0 }; - uint64_t height{ 0 }; - uint64_t timestamp{ 0 }; - nano::epoch epoch{ nano::epoch::epoch_0 }; -}; class transaction; class block_store; @@ -516,7 +543,7 @@ class store_iterator final impl->fill (current); return *this; } - nano::store_iterator & operator= (nano::store_iterator && other_a) + nano::store_iterator & operator= (nano::store_iterator && other_a) noexcept { impl = std::move (other_a.impl); current = std::move (other_a.current); @@ -626,7 +653,7 @@ class write_transaction final : public transaction std::unique_ptr impl; }; -class rep_weights; +class ledger_cache; /** * Manages block storage and iteration @@ -635,20 +662,22 @@ class block_store { public: virtual ~block_store () = default; - virtual void initialize (nano::write_transaction const &, nano::genesis const &, nano::rep_weights &, std::atomic &, std::atomic &) = 0; - virtual void block_put (nano::write_transaction const &, nano::block_hash const &, nano::block const &, nano::block_sideband const &) = 0; + virtual void initialize (nano::write_transaction const &, nano::genesis const &, nano::ledger_cache &) = 0; + virtual void block_put (nano::write_transaction const &, nano::block_hash const &, nano::block const &) = 0; virtual nano::block_hash block_successor (nano::transaction const &, nano::block_hash const &) const = 0; virtual void block_successor_clear (nano::write_transaction const &, nano::block_hash const &) = 0; - virtual std::shared_ptr block_get (nano::transaction const &, nano::block_hash const &, nano::block_sideband * = nullptr) const = 0; + virtual std::shared_ptr block_get (nano::transaction const &, nano::block_hash const &) const = 0; + virtual std::shared_ptr block_get_no_sideband (nano::transaction const &, nano::block_hash const &) const = 0; virtual std::shared_ptr block_get_v14 (nano::transaction const &, nano::block_hash const &, nano::block_sideband_v14 * = nullptr, bool * = nullptr) const = 0; virtual std::shared_ptr block_random (nano::transaction const &) = 0; - virtual void block_del (nano::write_transaction const &, nano::block_hash const &) = 0; + virtual void block_del (nano::write_transaction const &, nano::block_hash const &, nano::block_type) = 0; virtual bool block_exists (nano::transaction const &, nano::block_hash const &) = 0; virtual bool block_exists (nano::transaction const &, nano::block_type, nano::block_hash const &) = 0; virtual nano::block_counts block_count (nano::transaction const &) = 0; virtual bool root_exists (nano::transaction const &, nano::root const &) = 0; virtual bool source_exists (nano::transaction const &, nano::block_hash const &) = 0; virtual nano::account block_account (nano::transaction const &, nano::block_hash const &) const = 0; + virtual nano::account block_account_calculated (nano::block const &) const = 0; virtual void frontier_put (nano::write_transaction const &, nano::block_hash const &, nano::account const &) = 0; virtual nano::account frontier_get (nano::transaction const &, nano::block_hash const &) const = 0; @@ -659,33 +688,35 @@ class block_store virtual void account_del (nano::write_transaction const &, nano::account const &) = 0; virtual bool account_exists (nano::transaction const &, nano::account const &) = 0; virtual size_t account_count (nano::transaction const &) = 0; - virtual void confirmation_height_clear (nano::write_transaction const &, nano::account const & account, uint64_t existing_confirmation_height) = 0; + virtual void confirmation_height_clear (nano::write_transaction const &, nano::account const &, uint64_t) = 0; virtual void confirmation_height_clear (nano::write_transaction const &) = 0; - virtual nano::store_iterator latest_begin (nano::transaction const &, nano::account const &) = 0; - virtual nano::store_iterator latest_begin (nano::transaction const &) = 0; - virtual nano::store_iterator latest_end () = 0; + virtual nano::store_iterator latest_begin (nano::transaction const &, nano::account const &) const = 0; + virtual nano::store_iterator latest_begin (nano::transaction const &) const = 0; + virtual nano::store_iterator latest_end () const = 0; virtual void pending_put (nano::write_transaction const &, nano::pending_key const &, nano::pending_info const &) = 0; virtual void pending_del (nano::write_transaction const &, nano::pending_key const &) = 0; virtual bool pending_get (nano::transaction const &, nano::pending_key const &, nano::pending_info &) = 0; virtual bool pending_exists (nano::transaction const &, nano::pending_key const &) = 0; + virtual bool pending_any (nano::transaction const &, nano::account const &) = 0; virtual nano::store_iterator pending_begin (nano::transaction const &, nano::pending_key const &) = 0; virtual nano::store_iterator pending_begin (nano::transaction const &) = 0; virtual nano::store_iterator pending_end () = 0; virtual bool block_info_get (nano::transaction const &, nano::block_hash const &, nano::block_info &) const = 0; virtual nano::uint128_t block_balance (nano::transaction const &, nano::block_hash const &) = 0; - virtual nano::uint128_t block_balance_calculated (std::shared_ptr, nano::block_sideband const &) const = 0; + virtual nano::uint128_t block_balance_calculated (std::shared_ptr const &) const = 0; virtual nano::epoch block_version (nano::transaction const &, nano::block_hash const &) = 0; virtual void unchecked_clear (nano::write_transaction const &) = 0; virtual void unchecked_put (nano::write_transaction const &, nano::unchecked_key const &, nano::unchecked_info const &) = 0; virtual void unchecked_put (nano::write_transaction const &, nano::block_hash const &, std::shared_ptr const &) = 0; virtual std::vector unchecked_get (nano::transaction const &, nano::block_hash const &) = 0; + virtual bool unchecked_exists (nano::transaction const & transaction_a, nano::unchecked_key const & unchecked_key_a) = 0; virtual void unchecked_del (nano::write_transaction const &, nano::unchecked_key const &) = 0; - virtual nano::store_iterator unchecked_begin (nano::transaction const &) = 0; - virtual nano::store_iterator unchecked_begin (nano::transaction const &, nano::unchecked_key const &) = 0; - virtual nano::store_iterator unchecked_end () = 0; + virtual nano::store_iterator unchecked_begin (nano::transaction const &) const = 0; + virtual nano::store_iterator unchecked_begin (nano::transaction const &, nano::unchecked_key const &) const = 0; + virtual nano::store_iterator unchecked_end () const = 0; virtual size_t unchecked_count (nano::transaction const &) = 0; // Return latest vote for an account from store @@ -719,19 +750,20 @@ class block_store virtual nano::store_iterator peers_begin (nano::transaction const & transaction_a) const = 0; virtual nano::store_iterator peers_end () const = 0; - virtual void confirmation_height_put (nano::write_transaction const & transaction_a, nano::account const & account_a, uint64_t confirmation_height_a) = 0; - virtual bool confirmation_height_get (nano::transaction const & transaction_a, nano::account const & account_a, uint64_t & confirmation_height_a) = 0; + virtual void confirmation_height_put (nano::write_transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info const & confirmation_height_info_a) = 0; + virtual bool confirmation_height_get (nano::transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info & confirmation_height_info_a) = 0; virtual bool confirmation_height_exists (nano::transaction const & transaction_a, nano::account const & account_a) const = 0; virtual void confirmation_height_del (nano::write_transaction const & transaction_a, nano::account const & account_a) = 0; virtual uint64_t confirmation_height_count (nano::transaction const & transaction_a) = 0; - virtual nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a, nano::account const & account_a) = 0; - virtual nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a) = 0; - virtual nano::store_iterator confirmation_height_end () = 0; + virtual nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a, nano::account const & account_a) = 0; + virtual nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a) = 0; + virtual nano::store_iterator confirmation_height_end () = 0; virtual uint64_t block_account_height (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const = 0; virtual std::mutex & get_cache_mutex () = 0; virtual bool copy_db (boost::filesystem::path const & destination) = 0; + virtual void rebuild_db (nano::write_transaction const & transaction_a) = 0; /** Not applicable to all sub-classes */ virtual void serialize_mdb_tracker (boost::property_tree::ptree &, std::chrono::milliseconds, std::chrono::milliseconds) = 0; @@ -743,9 +775,11 @@ class block_store /** Start read-only transaction */ virtual nano::read_transaction tx_begin_read () = 0; + + virtual std::string vendor_get () const = 0; }; -std::unique_ptr make_store (nano::logger_mt & logger, boost::filesystem::path const & path, bool open_read_only = false, bool add_db_postfix = false, nano::rocksdb_config const & rocksdb_config = nano::rocksdb_config{}, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), int lmdb_max_dbs = 128, size_t batch_size = 512, bool backup_before_upgrade = false, bool rocksdb_backend = false); +std::unique_ptr make_store (nano::logger_mt & logger, boost::filesystem::path const & path, bool open_read_only = false, bool add_db_postfix = false, nano::rocksdb_config const & rocksdb_config = nano::rocksdb_config{}, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), nano::lmdb_config const & lmdb_config_a = nano::lmdb_config{}, size_t batch_size = 512, bool backup_before_upgrade = false, bool rocksdb_backend = false); } namespace std diff --git a/nano/secure/blockstore_partial.hpp b/nano/secure/blockstore_partial.hpp index 3c1bf275c3..51f6828e70 100644 --- a/nano/secure/blockstore_partial.hpp +++ b/nano/secure/blockstore_partial.hpp @@ -2,6 +2,9 @@ #include #include +#include + +#include namespace nano { @@ -24,25 +27,26 @@ class block_store_partial : public block_store * If using a different store version than the latest then you may need * to modify some of the objects in the store to be appropriate for the version before an upgrade. */ - void initialize (nano::write_transaction const & transaction_a, nano::genesis const & genesis_a, nano::rep_weights & rep_weights, std::atomic & cemented_count, std::atomic & block_count_cache) override + void initialize (nano::write_transaction const & transaction_a, nano::genesis const & genesis_a, nano::ledger_cache & ledger_cache_a) override { auto hash_l (genesis_a.hash ()); - assert (latest_begin (transaction_a) == latest_end ()); - nano::block_sideband sideband (nano::block_type::open, network_params.ledger.genesis_account, 0, network_params.ledger.genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0); - block_put (transaction_a, hash_l, *genesis_a.open, sideband); - ++block_count_cache; - confirmation_height_put (transaction_a, network_params.ledger.genesis_account, 1); - ++cemented_count; + debug_assert (latest_begin (transaction_a) == latest_end ()); + genesis_a.open->sideband_set (nano::block_sideband (network_params.ledger.genesis_account, 0, network_params.ledger.genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); + block_put (transaction_a, hash_l, *genesis_a.open); + ++ledger_cache_a.block_count; + confirmation_height_put (transaction_a, network_params.ledger.genesis_account, nano::confirmation_height_info{ 1, genesis_a.hash () }); + ++ledger_cache_a.cemented_count; account_put (transaction_a, network_params.ledger.genesis_account, { hash_l, network_params.ledger.genesis_account, genesis_a.open->hash (), std::numeric_limits::max (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0 }); - rep_weights.representation_put (network_params.ledger.genesis_account, std::numeric_limits::max ()); + ++ledger_cache_a.account_count; + ledger_cache_a.rep_weights.representation_put (network_params.ledger.genesis_account, std::numeric_limits::max ()); frontier_put (transaction_a, hash_l, network_params.ledger.genesis_account); } nano::uint128_t block_balance (nano::transaction const & transaction_a, nano::block_hash const & hash_a) override { - nano::block_sideband sideband; - auto block (block_get (transaction_a, hash_a, &sideband)); - nano::uint128_t result (block_balance_calculated (block, sideband)); + auto block (block_get (transaction_a, hash_a)); + release_assert (block); + nano::uint128_t result (block_balance_calculated (block)); return result; } @@ -52,11 +56,11 @@ class block_store_partial : public block_store return iterator != latest_end () && nano::account (iterator->first) == account_a; } - void confirmation_height_clear (nano::write_transaction const & transaction_a, nano::account const & account, uint64_t existing_confirmation_height) override + void confirmation_height_clear (nano::write_transaction const & transaction_a, nano::account const & account_a, uint64_t existing_confirmation_height_a) override { - if (existing_confirmation_height > 0) + if (existing_confirmation_height_a > 0) { - confirmation_height_put (transaction_a, account, 0); + confirmation_height_put (transaction_a, account_a, { 0, nano::block_hash{ 0 } }); } } @@ -64,7 +68,7 @@ class block_store_partial : public block_store { for (auto i (confirmation_height_begin (transaction_a)), n (confirmation_height_end ()); i != n; ++i) { - confirmation_height_clear (transaction_a, i->first, i->second); + confirmation_height_clear (transaction_a, i->first, i->second.height); } } @@ -74,6 +78,20 @@ class block_store_partial : public block_store return iterator != pending_end () && nano::pending_key (iterator->first) == key_a; } + bool pending_any (nano::transaction const & transaction_a, nano::account const & account_a) override + { + auto iterator (pending_begin (transaction_a, nano::pending_key (account_a, 0))); + return iterator != pending_end () && nano::pending_key (iterator->first).account == account_a; + } + + bool unchecked_exists (nano::transaction const & transaction_a, nano::unchecked_key const & unchecked_key_a) override + { + nano::db_val value; + auto status (get (transaction_a, tables::unchecked, nano::db_val (unchecked_key_a), value)); + release_assert (success (status) || not_found (status)); + return (success (status)); + } + std::vector unchecked_get (nano::transaction const & transaction_a, nano::block_hash const & hash_a) override { std::vector result; @@ -85,32 +103,30 @@ class block_store_partial : public block_store return result; } - void block_put (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a, nano::block const & block_a, nano::block_sideband const & sideband_a) override + void block_put (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a, nano::block const & block_a) override { - assert (block_a.type () == sideband_a.type); - assert (sideband_a.successor.is_zero () || block_exists (transaction_a, sideband_a.successor)); + debug_assert (block_a.sideband ().successor.is_zero () || block_exists (transaction_a, block_a.sideband ().successor)); std::vector vector; { nano::vectorstream stream (vector); block_a.serialize (stream); - sideband_a.serialize (stream); + block_a.sideband ().serialize (stream, block_a.type ()); } block_raw_put (transaction_a, vector, block_a.type (), hash_a); nano::block_predecessor_set predecessor (transaction_a, *this); block_a.visit (predecessor); - assert (block_a.previous ().is_zero () || block_successor (transaction_a, block_a.previous ()) == hash_a); + debug_assert (block_a.previous ().is_zero () || block_successor (transaction_a, block_a.previous ()) == hash_a); } // Converts a block hash to a block height uint64_t block_account_height (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const override { - nano::block_sideband sideband; - auto block = block_get (transaction_a, hash_a, &sideband); - assert (block != nullptr); - return sideband.height; + auto block = block_get (transaction_a, hash_a); + debug_assert (block != nullptr); + return block->sideband ().height; } - std::shared_ptr block_get (nano::transaction const & transaction_a, nano::block_hash const & hash_a, nano::block_sideband * sideband_a = nullptr) const override + std::shared_ptr block_get (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const override { nano::block_type type; auto value (block_raw_get (transaction_a, hash_a, type)); @@ -119,26 +135,38 @@ class block_store_partial : public block_store { nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); result = nano::deserialize_block (stream, type); - assert (result != nullptr); - if (sideband_a) + debug_assert (result != nullptr); + nano::block_sideband sideband; + if (full_sideband (transaction_a) || entry_has_sideband (value.size (), type)) { - sideband_a->type = type; - if (full_sideband (transaction_a) || entry_has_sideband (value.size (), type)) - { - auto error (sideband_a->deserialize (stream)); - (void)error; - assert (!error); - } - else - { - // Reconstruct sideband data for block. - sideband_a->account = block_account_computed (transaction_a, hash_a); - sideband_a->balance = block_balance_computed (transaction_a, hash_a); - sideband_a->successor = block_successor (transaction_a, hash_a); - sideband_a->height = 0; - sideband_a->timestamp = 0; - } + auto error (sideband.deserialize (stream, type)); + (void)error; + debug_assert (!error); + } + else + { + // Reconstruct sideband data for block. + sideband.account = block_account_computed (transaction_a, hash_a); + sideband.balance = block_balance_computed (transaction_a, hash_a); + sideband.successor = block_successor (transaction_a, hash_a); + sideband.height = 0; + sideband.timestamp = 0; } + result->sideband_set (sideband); + } + return result; + } + + std::shared_ptr block_get_no_sideband (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const override + { + nano::block_type type; + auto value (block_raw_get (transaction_a, hash_a, type)); + std::shared_ptr result; + if (value.size () != 0) + { + nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); + result = nano::deserialize_block (stream, type); + debug_assert (result != nullptr); } return result; } @@ -174,18 +202,24 @@ class block_store_partial : public block_store nano::account block_account (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const override { - nano::block_sideband sideband; - auto block (block_get (transaction_a, hash_a, &sideband)); - nano::account result (block->account ()); + auto block (block_get (transaction_a, hash_a)); + debug_assert (block != nullptr); + return block_account_calculated (*block); + } + + nano::account block_account_calculated (nano::block const & block_a) const override + { + debug_assert (block_a.has_sideband ()); + nano::account result (block_a.account ()); if (result.is_zero ()) { - result = sideband.account; + result = block_a.sideband ().account; } - assert (!result.is_zero ()); + debug_assert (!result.is_zero ()); return result; } - nano::uint128_t block_balance_calculated (std::shared_ptr block_a, nano::block_sideband const & sideband_a) const override + nano::uint128_t block_balance_calculated (std::shared_ptr const & block_a) const override { nano::uint128_t result; switch (block_a->type ()) @@ -193,7 +227,7 @@ class block_store_partial : public block_store case nano::block_type::open: case nano::block_type::receive: case nano::block_type::change: - result = sideband_a.balance.number (); + result = block_a->sideband ().balance.number (); break; case nano::block_type::send: result = boost::polymorphic_downcast (block_a.get ())->hashables.balance.number (); @@ -216,11 +250,11 @@ class block_store_partial : public block_store nano::block_hash result; if (value.size () != 0) { - assert (value.size () >= result.bytes.size ()); + debug_assert (value.size () >= result.bytes.size ()); nano::bufferstream stream (reinterpret_cast (value.data ()) + block_successor_offset (transaction_a, value.size (), type), result.bytes.size ()); auto error (nano::try_read (stream, result.bytes)); (void)error; - assert (!error); + debug_assert (!error); } else { @@ -238,7 +272,7 @@ class block_store_partial : public block_store { nano::block_type type; auto value (block_raw_get (transaction_a, hash_a, type)); - assert (value.size () != 0); + debug_assert (value.size () != 0); std::vector data (static_cast (value.data ()), static_cast (value.data ()) + value.size ()); std::fill_n (data.begin () + block_successor_offset (transaction_a, value.size (), type), sizeof (nano::block_hash), uint8_t{ 0 }); block_raw_put (transaction_a, data, type, hash_a); @@ -253,7 +287,7 @@ class block_store_partial : public block_store std::shared_ptr vote_current (nano::transaction const & transaction_a, nano::account const & account_a) override { - assert (!cache_mutex.try_lock ()); + debug_assert (!cache_mutex.try_lock ()); std::shared_ptr result; auto existing (vote_cache_l1.find (account_a)); auto have_existing (true); @@ -309,7 +343,7 @@ class block_store_partial : public block_store return result; } - nano::store_iterator unchecked_end () override + nano::store_iterator unchecked_end () const override { return nano::store_iterator (nullptr); } @@ -334,14 +368,14 @@ class block_store_partial : public block_store return nano::store_iterator (nullptr); } - nano::store_iterator latest_end () override + nano::store_iterator latest_end () const override { return nano::store_iterator (nullptr); } - nano::store_iterator confirmation_height_end () override + nano::store_iterator confirmation_height_end () override { - return nano::store_iterator (nullptr); + return nano::store_iterator (nullptr); } std::mutex & get_cache_mutex () override @@ -349,30 +383,32 @@ class block_store_partial : public block_store return cache_mutex; } - void block_del (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a) override + void block_del (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a, nano::block_type block_type_a) override { - auto status = del (transaction_a, tables::state_blocks, hash_a); - release_assert (success (status) || not_found (status)); - if (!success (status)) + auto table = tables::state_blocks; + switch (block_type_a) { - auto status = del (transaction_a, tables::send_blocks, hash_a); - release_assert (success (status) || not_found (status)); - if (!success (status)) - { - auto status = del (transaction_a, tables::receive_blocks, hash_a); - release_assert (success (status) || not_found (status)); - if (!success (status)) - { - auto status = del (transaction_a, tables::open_blocks, hash_a); - release_assert (success (status) || not_found (status)); - if (!success (status)) - { - auto status = del (transaction_a, tables::change_blocks, hash_a); - release_assert (success (status)); - } - } - } + case nano::block_type::open: + table = tables::open_blocks; + break; + case nano::block_type::receive: + table = tables::receive_blocks; + break; + case nano::block_type::send: + table = tables::send_blocks; + break; + case nano::block_type::change: + table = tables::change_blocks; + break; + case nano::block_type::state: + table = tables::state_blocks; + break; + default: + debug_assert (false); } + + auto status = del (transaction_a, table, hash_a); + release_assert (success (status)); } int version_get (nano::transaction const & transaction_a) const override @@ -384,7 +420,7 @@ class block_store_partial : public block_store if (!not_found (status)) { nano::uint256_union version_value (data); - assert (version_value.qwords[2] == 0 && version_value.qwords[1] == 0 && version_value.qwords[0] == 0); + debug_assert (version_value.qwords[2] == 0 && version_value.qwords[1] == 0 && version_value.qwords[0] == 0); result = version_value.number ().convert_to (); } return result; @@ -393,11 +429,10 @@ class block_store_partial : public block_store nano::epoch block_version (nano::transaction const & transaction_a, nano::block_hash const & hash_a) override { nano::db_val value; - nano::block_sideband sideband; - auto block = block_get (transaction_a, hash_a, &sideband); - if (sideband.type == nano::block_type::state) + auto block = block_get (transaction_a, hash_a); + if (block && block->type () == nano::block_type::state) { - return sideband.epoch; + return block->sideband ().details.epoch; } return nano::epoch::epoch_0; @@ -420,8 +455,8 @@ class block_store_partial : public block_store void pending_del (nano::write_transaction const & transaction_a, nano::pending_key const & key_a) override { - auto status1 = del (transaction_a, tables::pending, key_a); - release_assert (success (status1)); + auto status = del (transaction_a, tables::pending, key_a); + release_assert (success (status)); } bool pending_get (nano::transaction const & transaction_a, nano::pending_key const & key_a, nano::pending_info & pending_a) override @@ -475,7 +510,7 @@ class block_store_partial : public block_store void unchecked_del (nano::write_transaction const & transaction_a, nano::unchecked_key const & key_a) override { auto status (del (transaction_a, tables::unchecked, key_a)); - release_assert (success (status) || not_found (status)); + release_assert (success (status)); } std::shared_ptr vote_get (nano::transaction const & transaction_a, nano::account const & account_a) override @@ -486,7 +521,7 @@ class block_store_partial : public block_store if (success (status)) { std::shared_ptr result (value); - assert (result != nullptr); + debug_assert (result != nullptr); return result; } return nullptr; @@ -528,7 +563,7 @@ class block_store_partial : public block_store void account_put (nano::write_transaction const & transaction_a, nano::account const & account_a, nano::account_info const & info_a) override { // Check we are still in sync with other tables - assert (confirmation_height_exists (transaction_a, account_a)); + debug_assert (confirmation_height_exists (transaction_a, account_a)); nano::db_val info (info_a); auto status = put (transaction_a, tables::accounts, account_a, info); release_assert (success (status)); @@ -536,8 +571,8 @@ class block_store_partial : public block_store void account_del (nano::write_transaction const & transaction_a, nano::account const & account_a) override { - auto status1 = del (transaction_a, tables::accounts, account_a); - release_assert (success (status1)); + auto status = del (transaction_a, tables::accounts, account_a); + release_assert (success (status)); } bool account_get (nano::transaction const & transaction_a, nano::account const & account_a, nano::account_info & info_a) override @@ -661,7 +696,7 @@ class block_store_partial : public block_store } } } - assert (result != nullptr); + debug_assert (result != nullptr); return result; } @@ -670,24 +705,25 @@ class block_store_partial : public block_store return count (transaction_a, tables::confirmation_height); } - void confirmation_height_put (nano::write_transaction const & transaction_a, nano::account const & account_a, uint64_t confirmation_height_a) override + void confirmation_height_put (nano::write_transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info const & confirmation_height_info_a) override { - nano::db_val confirmation_height (confirmation_height_a); - auto status = put (transaction_a, tables::confirmation_height, account_a, confirmation_height); + nano::db_val confirmation_height_info (confirmation_height_info_a); + auto status = put (transaction_a, tables::confirmation_height, account_a, confirmation_height_info); release_assert (success (status)); } - bool confirmation_height_get (nano::transaction const & transaction_a, nano::account const & account_a, uint64_t & confirmation_height_a) override + bool confirmation_height_get (nano::transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info & confirmation_height_info_a) override { nano::db_val value; auto status = get (transaction_a, tables::confirmation_height, nano::db_val (account_a), value); release_assert (success (status) || not_found (status)); - confirmation_height_a = 0; + bool result (true); if (success (status)) { - confirmation_height_a = static_cast (value); + nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); + result = confirmation_height_info_a.deserialize (stream); } - return (!success (status)); + return result; } void confirmation_height_del (nano::write_transaction const & transaction_a, nano::account const & account_a) override @@ -701,12 +737,12 @@ class block_store_partial : public block_store return exists (transaction_a, tables::confirmation_height, nano::db_val (account_a)); } - nano::store_iterator latest_begin (nano::transaction const & transaction_a, nano::account const & account_a) override + nano::store_iterator latest_begin (nano::transaction const & transaction_a, nano::account const & account_a) const override { return make_iterator (transaction_a, tables::accounts, nano::db_val (account_a)); } - nano::store_iterator latest_begin (nano::transaction const & transaction_a) override + nano::store_iterator latest_begin (nano::transaction const & transaction_a) const override { return make_iterator (transaction_a, tables::accounts); } @@ -721,12 +757,12 @@ class block_store_partial : public block_store return make_iterator (transaction_a, tables::pending); } - nano::store_iterator unchecked_begin (nano::transaction const & transaction_a) override + nano::store_iterator unchecked_begin (nano::transaction const & transaction_a) const override { return make_iterator (transaction_a, tables::unchecked); } - nano::store_iterator unchecked_begin (nano::transaction const & transaction_a, nano::unchecked_key const & key_a) override + nano::store_iterator unchecked_begin (nano::transaction const & transaction_a, nano::unchecked_key const & key_a) const override { return make_iterator (transaction_a, tables::unchecked, nano::db_val (key_a)); } @@ -746,14 +782,14 @@ class block_store_partial : public block_store return make_iterator (transaction_a, tables::peers); } - nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a, nano::account const & account_a) override + nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a, nano::account const & account_a) override { - return make_iterator (transaction_a, tables::confirmation_height, nano::db_val (account_a)); + return make_iterator (transaction_a, tables::confirmation_height, nano::db_val (account_a)); } - nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a) override + nano::store_iterator confirmation_height_begin (nano::transaction const & transaction_a) override { - return make_iterator (transaction_a, tables::confirmation_height); + return make_iterator (transaction_a, tables::confirmation_height); } size_t unchecked_count (nano::transaction const & transaction_a) override @@ -765,7 +801,7 @@ class block_store_partial : public block_store nano::network_params network_params; std::unordered_map> vote_cache_l1; std::unordered_map> vote_cache_l2; - static int constexpr version{ 15 }; + static int constexpr version{ 18 }; template std::shared_ptr block_random (nano::transaction const & transaction_a, tables table_a) @@ -778,7 +814,7 @@ class block_store_partial : public block_store existing = make_iterator> (transaction_a, table_a); } auto end (nano::store_iterator> (nullptr)); - assert (existing != end); + debug_assert (existing != end); return block_get (transaction_a, nano::block_hash (existing->first)); } @@ -821,13 +857,13 @@ class block_store_partial : public block_store // Return account containing hash nano::account block_account_computed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { - assert (!full_sideband (transaction_a)); + debug_assert (!full_sideband (transaction_a)); nano::account result (0); auto hash (hash_a); while (result.is_zero ()) { - auto block (block_get (transaction_a, hash)); - assert (block); + auto block (block_get_no_sideband (transaction_a, hash)); + debug_assert (block); result = block->account (); if (result.is_zero ()) { @@ -850,20 +886,20 @@ class block_store_partial : public block_store if (result.is_zero ()) { auto successor (block_successor (transaction_a, hash)); - assert (!successor.is_zero ()); + debug_assert (!successor.is_zero ()); hash = successor; } } } } } - assert (!result.is_zero ()); + debug_assert (!result.is_zero ()); return result; } nano::uint128_t block_balance_computed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { - assert (!full_sideband (transaction_a)); + debug_assert (!full_sideband (transaction_a)); summation_visitor visitor (transaction_a, *this); return visitor.compute_balance (hash_a); } @@ -878,7 +914,7 @@ class block_store_partial : public block_store else { // Read old successor-only sideband - assert (entry_size_a == nano::block::size (type_a) + sizeof (nano::block_hash)); + debug_assert (entry_size_a == nano::block::size (type_a) + sizeof (nano::block_hash)); result = entry_size_a - sizeof (nano::block_hash); } return result; @@ -953,7 +989,7 @@ class block_store_partial : public block_store result = tables::state_blocks; break; default: - assert (false); + debug_assert (false); break; } return result; @@ -1009,7 +1045,7 @@ class block_predecessor_set : public nano::block_visitor auto hash (block_a.hash ()); nano::block_type type; auto value (store.block_raw_get (transaction, block_a.previous (), type)); - assert (value.size () != 0); + debug_assert (value.size () != 0); std::vector data (static_cast (value.data ()), static_cast (value.data ()) + value.size ()); std::copy (hash.bytes.begin (), hash.bytes.end (), data.begin () + store.block_successor_offset (transaction, value.size (), type)); store.block_raw_put (transaction, data, type, block_a.previous ()); diff --git a/nano/secure/buffer.hpp b/nano/secure/buffer.hpp new file mode 100644 index 0000000000..d1a4511789 --- /dev/null +++ b/nano/secure/buffer.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include + +#include + +namespace nano +{ +using bufferstream = boost::iostreams::stream_buffer>; +using vectorstream = boost::iostreams::stream_buffer>>; +} diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index f693260f2e..0cea331e3a 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -1,3 +1,4 @@ +#define IGNORE_GTEST_INCL #include #include #include @@ -5,10 +6,12 @@ #include #include +#include + #include #include +#include -#include #include #include @@ -26,7 +29,7 @@ namespace { char const * test_private_key_data = "34F0A37AAD20F4A260F0A5B3CB3D7FB50673212263E58A380BC10474BB039CE4"; char const * test_public_key_data = "B0311EA55708D6A53C75CDBF88300259C6D018522FE3D4D0A242E431F9E8B6D0"; // xrb_3e3j5tkog48pnny9dmfzj1r16pg8t1e76dz5tmac6iq689wyjfpiij4txtdo -char const * beta_public_key_data = "A59A439B34662385D48F7FF9CA50030F889BAA9AC320EA5A85AAD777CF82B088"; // nano_3betagfmasj5iqcayzzssba185wamgobois1xbfadcpqgz9r7e6a1zwztn5o +char const * beta_public_key_data = "259A4394DB16B1FFE5568476655E844BA59E8EB5D222F20D42E50684D0C16B54"; // nano_1betagcfp7ojzzkof35peohaakx7mt9ddnj4ya8n7sa8imae4ttnm16dm753 char const * live_public_key_data = "E89208DD038FBB269987689621D52292AE9C35941A7484756ECCED92A65093BA"; // xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3 char const * test_genesis_data = R"%%%({ "type": "open", @@ -39,11 +42,11 @@ char const * test_genesis_data = R"%%%({ char const * beta_genesis_data = R"%%%({ "type": "open", - "source": "A59A439B34662385D48F7FF9CA50030F889BAA9AC320EA5A85AAD777CF82B088", - "representative": "nano_3betagfmasj5iqcayzzssba185wamgobois1xbfadcpqgz9r7e6a1zwztn5o", - "account": "nano_3betagfmasj5iqcayzzssba185wamgobois1xbfadcpqgz9r7e6a1zwztn5o", - "work": "cb4efb49972c7106", - "signature": "C14ACC986C0561871B3382A93F5E64AFE55A3FA2EEE6892A7358DC817E2034AA56160CAA807A21EDFCFA528D034CD266F9ABFA4E2FF221D856255265BFDC0608" + "source": "259A4394DB16B1FFE5568476655E844BA59E8EB5D222F20D42E50684D0C16B54", + "representative": "nano_1betagcfp7ojzzkof35peohaakx7mt9ddnj4ya8n7sa8imae4ttnm16dm753", + "account": "nano_1betagcfp7ojzzkof35peohaakx7mt9ddnj4ya8n7sa8imae4ttnm16dm753", + "work": "7f5c2eb5e2658e81", + "signature": "DB9EFAC98A28EEA048E722F91C2A2720E1D8EF2A81453C80FC53B453180C0A264CE021D38D5B4540B1BBB0C378B80F2DF7389027593C08DDEF9F47934B9CF805" })%%%"; char const * live_genesis_data = R"%%%({ @@ -54,6 +57,14 @@ char const * live_genesis_data = R"%%%({ "work": "62f05417dd3fb691", "signature": "9F0C933C8ADE004D808EA1985FA746A7E95BA2A38F867640F53EC8F180BDFE9E2C1268DEAD7C2664F356E37ABA362BC58E46DBA03E523A7B5A19E4B6EB12BB02" })%%%"; + +std::shared_ptr parse_block_from_genesis_data (std::string const & genesis_data_a) +{ + boost::property_tree::ptree tree; + std::stringstream istream (genesis_data_a); + boost::property_tree::read_json (istream, tree); + return nano::deserialize_block_json (tree); +} } nano::network_params::network_params () : @@ -62,16 +73,17 @@ network_params (network_constants::active_network) } nano::network_params::network_params (nano::nano_networks network_a) : -network (network_a), protocol (network_a), ledger (network), voting (network), node (network), portmapping (network), bootstrap (network) +network (network_a), ledger (network), voting (network), node (network), portmapping (network), bootstrap (network) { unsigned constexpr kdf_full_work = 64 * 1024; unsigned constexpr kdf_test_work = 8; kdf_work = network.is_test_network () ? kdf_test_work : kdf_full_work; - header_magic_number = network.is_test_network () ? std::array{ { 'R', 'A' } } : network.is_beta_network () ? std::array{ { 'N', 'B' } } : std::array{ { 'R', 'C' } }; + header_magic_number = network.is_test_network () ? std::array{ { 'R', 'A' } } : network.is_beta_network () ? std::array{ { 'N', 'D' } } : std::array{ { 'R', 'C' } }; } -nano::protocol_constants::protocol_constants (nano::nano_networks network_a) +uint8_t nano::protocol_constants::protocol_version_min (bool use_epoch_2_min_version_a) const { + return use_epoch_2_min_version_a ? protocol_version_min_epoch_2 : protocol_version_min_pre_epoch_2; } nano::ledger_constants::ledger_constants (nano::network_constants & network_constants) : @@ -90,6 +102,7 @@ nano_beta_genesis (beta_genesis_data), nano_live_genesis (live_genesis_data), genesis_account (network_a == nano::nano_networks::nano_test_network ? nano_test_account : network_a == nano::nano_networks::nano_beta_network ? nano_beta_account : nano_live_account), genesis_block (network_a == nano::nano_networks::nano_test_network ? nano_test_genesis : network_a == nano::nano_networks::nano_beta_network ? nano_beta_genesis : nano_live_genesis), +genesis_hash (parse_block_from_genesis_data (genesis_block)->hash ()), genesis_amount (std::numeric_limits::max ()), burn_account (0) { @@ -99,7 +112,9 @@ burn_account (0) epochs.add (nano::epoch::epoch_1, genesis_account, epoch_link_v1); nano::link epoch_link_v2; - auto nano_live_epoch_v2_signer = genesis_account; + nano::account nano_live_epoch_v2_signer; + auto error (nano_live_epoch_v2_signer.decode_account ("nano_3qb6o6i1tkzr6jwr5s7eehfxwg9x6eemitdinbpi7u8bjjwsgqfj4wzser3x")); + debug_assert (!error); auto epoch_v2_signer (network_a == nano::nano_networks::nano_test_network ? nano_test_account : network_a == nano::nano_networks::nano_beta_network ? nano_beta_account : nano_live_epoch_v2_signer); const char * epoch_message_v2 ("epoch v2 block"); strncpy ((char *)epoch_link_v2.bytes.data (), epoch_message_v2, epoch_link_v2.bytes.size ()); @@ -124,13 +139,14 @@ nano::node_constants::node_constants (nano::network_constants & network_constant peer_interval = search_pending_interval; unchecked_cleaning_interval = std::chrono::minutes (30); process_confirmed_interval = network_constants.is_test_network () ? std::chrono::milliseconds (50) : std::chrono::milliseconds (500); + max_peers_per_ip = network_constants.is_test_network () ? 10 : 5; max_weight_samples = network_constants.is_live_network () ? 4032 : 864; weight_period = 5 * 60; // 5 minutes } nano::voting_constants::voting_constants (nano::network_constants & network_constants) { - max_cache = network_constants.is_test_network () ? 2 : 4 * 1024; + max_cache = network_constants.is_test_network () ? 2 : 64 * 1024; } nano::portmapping_constants::portmapping_constants (nano::network_constants & network_constants) @@ -146,6 +162,7 @@ nano::bootstrap_constants::bootstrap_constants (nano::network_constants & networ frontier_retry_limit = network_constants.is_test_network () ? 2 : 16; lazy_retry_limit = network_constants.is_test_network () ? 2 : frontier_retry_limit * 10; lazy_destinations_retry_limit = network_constants.is_test_network () ? 1 : frontier_retry_limit / 4; + gap_cache_bootstrap_start_interval = network_constants.is_test_network () ? std::chrono::milliseconds (5) : std::chrono::milliseconds (30 * 1000); } /* Convenience constants for core_test which is always on the test network */ @@ -159,7 +176,7 @@ nano::keypair const & nano::test_genesis_key (test_constants.test_genesis_key); nano::account const & nano::nano_test_account (test_constants.nano_test_account); std::string const & nano::nano_test_genesis (test_constants.nano_test_genesis); nano::account const & nano::genesis_account (test_constants.genesis_account); -std::string const & nano::genesis_block (test_constants.genesis_block); +nano::block_hash const & nano::genesis_hash (test_constants.genesis_hash); nano::uint128_t const & nano::genesis_amount (test_constants.genesis_amount); nano::account const & nano::burn_account (test_constants.burn_account); @@ -182,7 +199,7 @@ nano::keypair::keypair (std::string const & prv_a) { auto error (prv.data.decode_hex (prv_a)); (void)error; - assert (!error); + debug_assert (!error); ed25519_publickey (prv.data.bytes.data (), pub.bytes.data ()); } @@ -237,13 +254,13 @@ bool nano::account_info::operator!= (nano::account_info const & other_a) const size_t nano::account_info::db_size () const { - assert (reinterpret_cast (this) == reinterpret_cast (&head)); - assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&representative)); - assert (reinterpret_cast (&representative) + sizeof (representative) == reinterpret_cast (&open_block)); - assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); - assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); - assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); - assert (reinterpret_cast (&block_count) + sizeof (block_count) == reinterpret_cast (&epoch_m)); + debug_assert (reinterpret_cast (this) == reinterpret_cast (&head)); + debug_assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&representative)); + debug_assert (reinterpret_cast (&representative) + sizeof (representative) == reinterpret_cast (&open_block)); + debug_assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); + debug_assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); + debug_assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); + debug_assert (reinterpret_cast (&block_count) + sizeof (block_count) == reinterpret_cast (&epoch_m)); return sizeof (head) + sizeof (representative) + sizeof (open_block) + sizeof (balance) + sizeof (modified) + sizeof (block_count) + sizeof (epoch_m); } @@ -334,7 +351,7 @@ confirmed (confirmed_a) void nano::unchecked_info::serialize (nano::stream & stream_a) const { - assert (block != nullptr); + debug_assert (block != nullptr); nano::serialize_block (stream_a, *block); nano::write (stream_a, account.bytes); nano::write (stream_a, modified); @@ -376,6 +393,33 @@ uint16_t nano::endpoint_key::port () const return boost::endian::big_to_native (network_port); } +nano::confirmation_height_info::confirmation_height_info (uint64_t confirmation_height_a, nano::block_hash const & confirmed_frontier_a) : +height (confirmation_height_a), +frontier (confirmed_frontier_a) +{ +} + +void nano::confirmation_height_info::serialize (nano::stream & stream_a) const +{ + nano::write (stream_a, height); + nano::write (stream_a, frontier); +} + +bool nano::confirmation_height_info::deserialize (nano::stream & stream_a) +{ + auto error (false); + try + { + nano::read (stream_a, height); + nano::read (stream_a, frontier); + } + catch (std::runtime_error const &) + { + error = true; + } + return error; +} + nano::block_info::block_info (nano::account const & account_a, nano::amount const & balance_a) : account (account_a), balance (balance_a) @@ -517,8 +561,8 @@ nano::vote::vote (nano::account const & account_a, nano::raw_key const & prv_a, sequence (sequence_a), account (account_a) { - assert (!blocks_a.empty ()); - assert (blocks_a.size () <= 12); + debug_assert (!blocks_a.empty ()); + debug_assert (blocks_a.size () <= 12); blocks.reserve (blocks_a.size ()); std::copy (blocks_a.cbegin (), blocks_a.cend (), std::back_inserter (blocks)); signature = nano::sign_message (prv_a, account_a, hash ()); @@ -582,7 +626,7 @@ void nano::vote::serialize (nano::stream & stream_a, nano::block_type type) cons { if (block.which ()) { - assert (type == nano::block_type::not_a_block); + debug_assert (type == nano::block_type::not_a_block); write (stream_a, boost::get (block)); } else @@ -755,26 +799,20 @@ size_t nano::vote_uniquer::size () return votes.size (); } -namespace nano -{ -std::unique_ptr collect_seq_con_info (vote_uniquer & vote_uniquer, const std::string & name) +std::unique_ptr nano::collect_container_info (vote_uniquer & vote_uniquer, const std::string & name) { auto count = vote_uniquer.size (); auto sizeof_element = sizeof (vote_uniquer::value_type); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (seq_con_info{ "votes", count, sizeof_element })); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "votes", count, sizeof_element })); return composite; } -} nano::genesis::genesis () { static nano::network_params network_params; - boost::property_tree::ptree tree; - std::stringstream istream (network_params.ledger.genesis_block); - boost::property_tree::read_json (istream, tree); - open = nano::deserialize_block_json (tree); - assert (open != nullptr); + open = parse_block_from_genesis_data (network_params.ledger.genesis_block); + debug_assert (open != nullptr); } nano::block_hash nano::genesis::hash () const @@ -822,3 +860,12 @@ nano::block_hash const & nano::unchecked_key::key () const { return previous; } + +void nano::generate_cache::enable_all () +{ + reps = true; + cemented_count = true; + unchecked_count = true; + account_count = true; + epoch_2 = true; +} diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index cc7e82cf35..3081f06c53 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -4,14 +4,15 @@ #include #include #include +#include #include +#include #include -#include -#include #include -#include -#include +#include +#include +#include #include @@ -217,6 +218,24 @@ class block_counts final size_t change{ 0 }; size_t state{ 0 }; }; + +class confirmation_height_info final +{ +public: + confirmation_height_info () = default; + confirmation_height_info (uint64_t, nano::block_hash const &); + void serialize (nano::stream &) const; + bool deserialize (nano::stream &); + uint64_t height; + nano::block_hash frontier; +}; + +namespace confirmation_height +{ + /** When the uncemented count (block count - cemented count) is less than this use the unbounded processor */ + uint64_t const unbounded_cutoff{ 16384 }; +} + using vote_blocks_vec_iter = std::vector, nano::block_hash>>::const_iterator; class iterate_vote_blocks_as_hash final { @@ -275,13 +294,14 @@ class vote_uniquer final static unsigned constexpr cleanup_count = 2; }; -std::unique_ptr collect_seq_con_info (vote_uniquer & vote_uniquer, const std::string & name); +std::unique_ptr collect_container_info (vote_uniquer & vote_uniquer, const std::string & name); enum class vote_code { invalid, // Vote is not signed correctly replay, // Vote does not have the highest sequence number, it's a replay - vote // Vote has the highest sequence number + vote, // Vote has the highest sequence number + indeterminate // Unknown if replay or vote }; enum class process_result @@ -297,7 +317,8 @@ enum class process_result opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. balance_mismatch, // Balance and amount delta don't match representative_mismatch, // Representative is changed when it is not allowed - block_position // This block cannot follow the previous block + block_position, // This block cannot follow the previous block + insufficient_work // Insufficient work for this block, even though it passed the minimal validation }; class process_return final { @@ -308,6 +329,7 @@ class process_return final nano::account pending_account; boost::optional state_is_send; nano::signature_verification verified; + nano::amount previous_balance; }; enum class tally_result { @@ -330,24 +352,25 @@ class network_params; class protocol_constants { public: - protocol_constants (nano::nano_networks network_a); - /** Current protocol version */ - uint8_t protocol_version = 0x11; + uint8_t const protocol_version = 0x12; /** Minimum accepted protocol version */ - uint8_t protocol_version_min = 0x10; + uint8_t protocol_version_min (bool epoch_2_started) const; - /** Do not bootstrap from nodes older than this version. */ - uint8_t protocol_version_bootstrap_min = 0x10; + /** Do not request telemetry metrics to nodes older than this version */ + uint8_t const telemetry_protocol_version_min = 0x12; - /** Do not lazy bootstrap from nodes older than this version. */ - uint8_t protocol_version_bootstrap_lazy_min = 0x10; - - /** Do not start TCP realtime network connections to nodes older than this version */ - uint8_t tcp_realtime_protocol_version_min = 0x11; +private: + /* Minimum protocol version before an epoch 2 block is seen */ + uint8_t const protocol_version_min_pre_epoch_2 = 0x11; + /* Minimum protocol version after an epoch 2 block is seen */ + uint8_t const protocol_version_min_epoch_2 = 0x12; }; +// Some places use the decltype of protocol_version instead of protocol_version_min. To keep those checks simpler we check that the decltypes match ignoring differences in const +static_assert (std::is_same, decltype (protocol_constants ().protocol_version_min (false))>::value, "protocol_min should match"); + /** Genesis keys and ledger constants for network variants */ class ledger_constants { @@ -364,6 +387,7 @@ class ledger_constants std::string nano_live_genesis; nano::account genesis_account; std::string genesis_block; + nano::block_hash genesis_hash; nano::uint128_t genesis_amount; nano::account burn_account; nano::epochs epochs; @@ -394,6 +418,8 @@ class node_constants std::chrono::seconds peer_interval; std::chrono::minutes unchecked_cleaning_interval; std::chrono::milliseconds process_confirmed_interval; + /** Maximum number of peers per IP */ + size_t max_peers_per_ip; /** The maximum amount of samples for a 2 week period on live or 3 days on beta */ uint64_t max_weight_samples; @@ -428,6 +454,7 @@ class bootstrap_constants unsigned frontier_retry_limit; unsigned lazy_retry_limit; unsigned lazy_destinations_retry_limit; + std::chrono::milliseconds gap_cache_bootstrap_start_interval; }; /** Constants whose value depends on the active network */ @@ -452,5 +479,62 @@ class network_params bootstrap_constants bootstrap; }; +enum class confirmation_height_mode +{ + automatic, + unbounded, + bounded +}; + +/* Holds flags for various cacheable data. For most CLI operations caching is unnecessary + * (e.g getting the cemented block count) so it can be disabled for performance reasons. */ +class generate_cache +{ +public: + bool reps = true; + bool cemented_count = true; + bool unchecked_count = true; + bool account_count = true; + bool epoch_2 = true; + + void enable_all (); +}; + +/* Holds an in-memory cache of various counts */ +class ledger_cache +{ +public: + nano::rep_weights rep_weights; + std::atomic cemented_count{ 0 }; + std::atomic block_count{ 0 }; + std::atomic unchecked_count{ 0 }; + std::atomic account_count{ 0 }; + std::atomic epoch_2_started{ false }; +}; + +/* Defines the possible states for an election to stop in */ +enum class election_status_type : uint8_t +{ + ongoing = 0, + active_confirmed_quorum = 1, + active_confirmation_height = 2, + inactive_confirmation_height = 3, + stopped = 5 +}; + +/* Holds a summary of an election */ +class election_status final +{ +public: + std::shared_ptr winner; + nano::amount tally; + std::chrono::milliseconds election_end; + std::chrono::milliseconds election_duration; + unsigned confirmation_request_count; + unsigned block_count; + unsigned voter_count; + election_status_type type; +}; + nano::wallet_id random_wallet_id (); } diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 34a33ad45a..bbf39361b2 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace @@ -34,12 +35,12 @@ class rollback_visitor : public nano::block_visitor nano::account_info info; auto error (ledger.store.account_get (transaction, pending.source, info)); (void)error; - assert (!error); + debug_assert (!error); ledger.store.pending_del (transaction, key); - ledger.rep_weights.representation_add (info.representative, pending.amount.number ()); + ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ()); nano::account_info new_info (block_a.hashables.previous, info.representative, info.open_block, ledger.balance (transaction, block_a.hashables.previous), nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0); ledger.change_latest (transaction, pending.source, info, new_info); - ledger.store.block_del (transaction, hash); + ledger.store.block_del (transaction, hash, block_a.type ()); ledger.store.frontier_del (transaction, hash); ledger.store.frontier_put (transaction, block_a.hashables.previous, pending.source); ledger.store.block_successor_clear (transaction, block_a.hashables.previous); @@ -55,11 +56,11 @@ class rollback_visitor : public nano::block_visitor nano::account_info info; auto error (ledger.store.account_get (transaction, destination_account, info)); (void)error; - assert (!error); - ledger.rep_weights.representation_add (info.representative, 0 - amount); + debug_assert (!error); + ledger.cache.rep_weights.representation_add (info.representative, 0 - amount); nano::account_info new_info (block_a.hashables.previous, info.representative, info.open_block, ledger.balance (transaction, block_a.hashables.previous), nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0); ledger.change_latest (transaction, destination_account, info, new_info); - ledger.store.block_del (transaction, hash); + ledger.store.block_del (transaction, hash, block_a.type ()); ledger.store.pending_put (transaction, nano::pending_key (destination_account, block_a.hashables.source), { source_account, amount, nano::epoch::epoch_0 }); ledger.store.frontier_del (transaction, hash); ledger.store.frontier_put (transaction, block_a.hashables.previous, destination_account); @@ -72,10 +73,10 @@ class rollback_visitor : public nano::block_visitor auto amount (ledger.amount (transaction, block_a.hashables.source)); auto destination_account (ledger.account (transaction, hash)); auto source_account (ledger.account (transaction, block_a.hashables.source)); - ledger.rep_weights.representation_add (block_a.representative (), 0 - amount); + ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - amount); nano::account_info new_info; ledger.change_latest (transaction, destination_account, new_info, new_info); - ledger.store.block_del (transaction, hash); + ledger.store.block_del (transaction, hash, block_a.type ()); ledger.store.pending_put (transaction, nano::pending_key (destination_account, block_a.hashables.source), { source_account, amount, nano::epoch::epoch_0 }); ledger.store.frontier_del (transaction, hash); ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::open); @@ -88,14 +89,14 @@ class rollback_visitor : public nano::block_visitor nano::account_info info; auto error (ledger.store.account_get (transaction, account, info)); (void)error; - assert (!error); + debug_assert (!error); auto balance (ledger.balance (transaction, block_a.hashables.previous)); auto block = ledger.store.block_get (transaction, rep_block); release_assert (block != nullptr); auto representative = block->representative (); - ledger.rep_weights.representation_add (block_a.representative (), 0 - balance); - ledger.rep_weights.representation_add (representative, balance); - ledger.store.block_del (transaction, hash); + ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - balance); + ledger.cache.rep_weights.representation_add (representative, balance); + ledger.store.block_del (transaction, hash, block_a.type ()); nano::account_info new_info (block_a.hashables.previous, representative, info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0); ledger.change_latest (transaction, account, info, new_info); ledger.store.frontier_del (transaction, hash); @@ -114,15 +115,15 @@ class rollback_visitor : public nano::block_visitor auto balance (ledger.balance (transaction, block_a.hashables.previous)); auto is_send (block_a.hashables.balance < balance); // Add in amount delta - ledger.rep_weights.representation_add (block_a.representative (), 0 - block_a.hashables.balance.number ()); + ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - block_a.hashables.balance.number ()); nano::account representative{ 0 }; if (!rep_block_hash.is_zero ()) { // Move existing representation auto block (ledger.store.block_get (transaction, rep_block_hash)); - assert (block != nullptr); + debug_assert (block != nullptr); representative = block->representative (); - ledger.rep_weights.representation_add (representative, balance); + ledger.cache.rep_weights.representation_add (representative, balance); } nano::account_info info; @@ -146,7 +147,7 @@ class rollback_visitor : public nano::block_visitor ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::receive); } - assert (!error); + debug_assert (!error); auto previous_version (ledger.store.block_version (transaction, block_a.hashables.previous)); nano::account_info new_info (block_a.hashables.previous, representative, info.open_block, balance, nano::seconds_since_epoch (), info.block_count - 1, previous_version); ledger.change_latest (transaction, block_a.hashables.account, info, new_info); @@ -164,7 +165,7 @@ class rollback_visitor : public nano::block_visitor { ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::open); } - ledger.store.block_del (transaction, hash); + ledger.store.block_del (transaction, hash, block_a.type ()); } nano::write_transaction const & transaction; nano::ledger & ledger; @@ -172,18 +173,18 @@ class rollback_visitor : public nano::block_visitor bool error{ false }; }; -class ledger_processor : public nano::block_visitor +class ledger_processor : public nano::mutable_block_visitor { public: ledger_processor (nano::ledger &, nano::write_transaction const &, nano::signature_verification = nano::signature_verification::unknown); virtual ~ledger_processor () = default; - void send_block (nano::send_block const &) override; - void receive_block (nano::receive_block const &) override; - void open_block (nano::open_block const &) override; - void change_block (nano::change_block const &) override; - void state_block (nano::state_block const &) override; - void state_block_impl (nano::state_block const &); - void epoch_block_impl (nano::state_block const &); + void send_block (nano::send_block &) override; + void receive_block (nano::receive_block &) override; + void open_block (nano::open_block &) override; + void change_block (nano::change_block &) override; + void state_block (nano::state_block &) override; + void state_block_impl (nano::state_block &); + void epoch_block_impl (nano::state_block &); nano::ledger & ledger; nano::write_transaction const & transaction; nano::signature_verification verification; @@ -196,7 +197,7 @@ class ledger_processor : public nano::block_visitor // Returns true if this block which has an epoch link is correctly formed. bool ledger_processor::validate_epoch_block (nano::state_block const & block_a) { - assert (ledger.is_epoch_link (block_a.hashables.link)); + debug_assert (ledger.is_epoch_link (block_a.hashables.link)); nano::amount prev_balance (0); if (!block_a.hashables.previous.is_zero ()) { @@ -230,7 +231,7 @@ bool ledger_processor::validate_epoch_block (nano::state_block const & block_a) return (block_a.hashables.balance == prev_balance); } -void ledger_processor::state_block (nano::state_block const & block_a) +void ledger_processor::state_block (nano::state_block & block_a) { result.code = nano::process_result::progress; auto is_epoch_block = false; @@ -253,7 +254,7 @@ void ledger_processor::state_block (nano::state_block const & block_a) } } -void ledger_processor::state_block_impl (nano::state_block const & block_a) +void ledger_processor::state_block_impl (nano::state_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -267,7 +268,7 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); + debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); result.verified = nano::signature_verification::valid; result.code = block_a.hashables.account.is_zero () ? nano::process_result::opened_burn_account : nano::process_result::progress; // Is this for the burn account? (Unambiguous) if (result.code == nano::process_result::progress) @@ -276,11 +277,13 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) nano::account_info info; result.amount = block_a.hashables.balance; auto is_send (false); + auto is_receive (false); auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info)); if (!account_error) { - epoch = info.epoch (); // Account already exists + epoch = info.epoch (); + result.previous_balance = info.balance; result.code = block_a.hashables.previous.is_zero () ? nano::process_result::fork : nano::process_result::progress; // Has this account already been opened? (Ambigious) if (result.code == nano::process_result::progress) { @@ -288,6 +291,7 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) if (result.code == nano::process_result::progress) { is_send = block_a.hashables.balance < info.balance; + is_receive = !is_send && !block_a.hashables.link.is_zero (); result.amount = is_send ? (info.balance.number () - result.amount.number ()) : (result.amount.number () - info.balance.number ()); result.code = block_a.hashables.previous == info.head ? nano::process_result::progress : nano::process_result::fork; // Is the previous block the account's head block? (Ambigious) } @@ -296,9 +300,11 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) else { // Account does not yet exists + result.previous_balance = 0; result.code = block_a.previous ().is_zero () ? nano::process_result::progress : nano::process_result::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious) if (result.code == nano::process_result::progress) { + is_receive = true; result.code = !block_a.hashables.link.is_zero () ? nano::process_result::progress : nano::process_result::gap_source; // Is the first block receiving from a send ? (Unambigious) } } @@ -330,45 +336,49 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) } if (result.code == nano::process_result::progress) { - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block); - result.state_is_send = is_send; - nano::block_sideband sideband (nano::block_type::state, block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), epoch); - ledger.store.block_put (transaction, hash, block_a, sideband); - - if (!info.head.is_zero ()) + nano::block_details block_details (epoch, is_send, is_receive, false); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) { - // Move existing representation - ledger.rep_weights.representation_add (info.representative, 0 - info.balance.number ()); - } - // Add in amount delta - ledger.rep_weights.representation_add (block_a.representative (), block_a.hashables.balance.number ()); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); - if (is_send) - { - nano::pending_key key (block_a.hashables.link, hash); - nano::pending_info info (block_a.hashables.account, result.amount.number (), epoch); - ledger.store.pending_put (transaction, key, info); - } - else if (!block_a.hashables.link.is_zero ()) - { - ledger.store.pending_del (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link)); - } + if (!info.head.is_zero ()) + { + // Move existing representation + ledger.cache.rep_weights.representation_add (info.representative, 0 - info.balance.number ()); + } + // Add in amount delta + ledger.cache.rep_weights.representation_add (block_a.representative (), block_a.hashables.balance.number ()); - nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) - { - ledger.store.frontier_del (transaction, info.head); + if (is_send) + { + nano::pending_key key (block_a.hashables.link, hash); + nano::pending_info info (block_a.hashables.account, result.amount.number (), epoch); + ledger.store.pending_put (transaction, key, info); + } + else if (!block_a.hashables.link.is_zero ()) + { + ledger.store.pending_del (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link)); + } + + nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + { + ledger.store.frontier_del (transaction, info.head); + } + // Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks + result.account = block_a.hashables.account; } - // Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks - result.account = block_a.hashables.account; } } } } } -void ledger_processor::epoch_block_impl (nano::state_block const & block_a) +void ledger_processor::epoch_block_impl (nano::state_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -382,7 +392,7 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature)); + debug_assert (!validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature)); result.verified = nano::signature_verification::valid_epoch; result.code = block_a.hashables.account.is_zero () ? nano::process_result::opened_burn_account : nano::process_result::progress; // Is this for the burn account? (Unambiguous) if (result.code == nano::process_result::progress) @@ -392,6 +402,7 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) if (!account_error) { // Account already exists + result.previous_balance = info.balance; result.code = block_a.hashables.previous.is_zero () ? nano::process_result::fork : nano::process_result::progress; // Has this account already been opened? (Ambigious) if (result.code == nano::process_result::progress) { @@ -404,7 +415,14 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) } else { + result.previous_balance = 0; result.code = block_a.hashables.representative.is_zero () ? nano::process_result::progress : nano::process_result::representative_mismatch; + // Non-exisitng account should have pending entries + if (result.code == nano::process_result::progress) + { + bool pending_exists = ledger.store.pending_any (transaction, block_a.hashables.account); + result.code = pending_exists ? nano::process_result::progress : nano::process_result::block_position; + } } if (result.code == nano::process_result::progress) { @@ -417,16 +435,32 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) result.code = block_a.hashables.balance == info.balance ? nano::process_result::progress : nano::process_result::balance_mismatch; if (result.code == nano::process_result::progress) { - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); - result.account = block_a.hashables.account; - result.amount = 0; - nano::block_sideband sideband (nano::block_type::state, block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), epoch); - ledger.store.block_put (transaction, hash, block_a, sideband); - nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + nano::block_details block_details (epoch, false, false, true); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) { - ledger.store.frontier_del (transaction, info.head); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); + result.account = block_a.hashables.account; + result.amount = 0; + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + { + ledger.store.frontier_del (transaction, info.head); + } + if (epoch == nano::epoch::epoch_2) + { + if (!ledger.cache.epoch_2_started.exchange (true)) + { + // The first epoch 2 block has been seen + if (ledger.epoch_2_started_cb) + { + ledger.epoch_2_started_cb (); + } + } + } } } } @@ -436,7 +470,7 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) } } -void ledger_processor::change_block (nano::change_block const & block_a) +void ledger_processor::change_block (nano::change_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -457,8 +491,8 @@ void ledger_processor::change_block (nano::change_block const & block_a) nano::account_info info; auto latest_error (ledger.store.account_get (transaction, account, info)); (void)latest_error; - assert (!latest_error); - assert (info.head == block_a.hashables.previous); + debug_assert (!latest_error); + debug_assert (info.head == block_a.hashables.previous); // Validate block if not verified outside of ledger if (result.verified != nano::signature_verification::valid) { @@ -466,20 +500,26 @@ void ledger_processor::change_block (nano::change_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (account, hash, block_a.signature)); - result.verified = nano::signature_verification::valid; - nano::block_sideband sideband (nano::block_type::change, account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0); - ledger.store.block_put (transaction, hash, block_a, sideband); - auto balance (ledger.balance (transaction, block_a.hashables.previous)); - ledger.rep_weights.representation_add (block_a.representative (), balance); - ledger.rep_weights.representation_add (info.representative, 0 - balance); - nano::account_info new_info (hash, block_a.representative (), info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, account, info, new_info); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = 0; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + debug_assert (!validate_message (account, hash, block_a.signature)); + result.verified = nano::signature_verification::valid; + block_a.sideband_set (nano::block_sideband (account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + auto balance (ledger.balance (transaction, block_a.hashables.previous)); + ledger.cache.rep_weights.representation_add (block_a.representative (), balance); + ledger.cache.rep_weights.representation_add (info.representative, 0 - balance); + nano::account_info new_info (hash, block_a.representative (), info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, account, info, new_info); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = 0; + result.previous_balance = info.balance; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); + } } } } @@ -487,7 +527,7 @@ void ledger_processor::change_block (nano::change_block const & block_a) } } -void ledger_processor::send_block (nano::send_block const & block_a) +void ledger_processor::send_block (nano::send_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -512,29 +552,35 @@ void ledger_processor::send_block (nano::send_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (account, hash, block_a.signature)); - result.verified = nano::signature_verification::valid; - nano::account_info info; - auto latest_error (ledger.store.account_get (transaction, account, info)); - (void)latest_error; - assert (!latest_error); - assert (info.head == block_a.hashables.previous); - result.code = info.balance.number () >= block_a.hashables.balance.number () ? nano::process_result::progress : nano::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) if (result.code == nano::process_result::progress) { - auto amount (info.balance.number () - block_a.hashables.balance.number ()); - ledger.rep_weights.representation_add (info.representative, 0 - amount); - nano::block_sideband sideband (nano::block_type::send, account, 0, block_a.hashables.balance /* unused */, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0); - ledger.store.block_put (transaction, hash, block_a, sideband); - nano::account_info new_info (hash, info.representative, info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, account, info, new_info); - ledger.store.pending_put (transaction, nano::pending_key (block_a.hashables.destination, hash), { account, amount, nano::epoch::epoch_0 }); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = amount; - result.pending_account = block_a.hashables.destination; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); + debug_assert (!validate_message (account, hash, block_a.signature)); + result.verified = nano::signature_verification::valid; + nano::account_info info; + auto latest_error (ledger.store.account_get (transaction, account, info)); + (void)latest_error; + debug_assert (!latest_error); + debug_assert (info.head == block_a.hashables.previous); + result.code = info.balance.number () >= block_a.hashables.balance.number () ? nano::process_result::progress : nano::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + if (result.code == nano::process_result::progress) + { + auto amount (info.balance.number () - block_a.hashables.balance.number ()); + ledger.cache.rep_weights.representation_add (info.representative, 0 - amount); + block_a.sideband_set (nano::block_sideband (account, 0, block_a.hashables.balance /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, info.representative, info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, account, info, new_info); + ledger.store.pending_put (transaction, nano::pending_key (block_a.hashables.destination, hash), { account, amount, nano::epoch::epoch_0 }); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = amount; + result.pending_account = block_a.hashables.destination; + result.previous_balance = info.balance; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); + } } } } @@ -543,7 +589,7 @@ void ledger_processor::send_block (nano::send_block const & block_a) } } -void ledger_processor::receive_block (nano::receive_block const & block_a) +void ledger_processor::receive_block (nano::receive_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -568,7 +614,7 @@ void ledger_processor::receive_block (nano::receive_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (account, hash, block_a.signature)); + debug_assert (!validate_message (account, hash, block_a.signature)); result.verified = nano::signature_verification::valid; result.code = ledger.store.source_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless) if (result.code == nano::process_result::progress) @@ -586,22 +632,28 @@ void ledger_processor::receive_block (nano::receive_block const & block_a) result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed) if (result.code == nano::process_result::progress) { - auto new_balance (info.balance.number () + pending.amount.number ()); - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - assert (!error); - ledger.store.pending_del (transaction, key); - nano::block_sideband sideband (nano::block_type::receive, account, 0, new_balance, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0); - ledger.store.block_put (transaction, hash, block_a, sideband); - nano::account_info new_info (hash, info.representative, info.open_block, new_balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, account, info, new_info); - ledger.rep_weights.representation_add (info.representative, pending.amount.number ()); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = pending.amount; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + auto new_balance (info.balance.number () + pending.amount.number ()); + nano::account_info source_info; + auto error (ledger.store.account_get (transaction, pending.source, source_info)); + (void)error; + debug_assert (!error); + ledger.store.pending_del (transaction, key); + block_a.sideband_set (nano::block_sideband (account, 0, new_balance, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, info.representative, info.open_block, new_balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, account, info, new_info); + ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ()); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = pending.amount; + result.previous_balance = info.balance; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); + } } } } @@ -617,7 +669,7 @@ void ledger_processor::receive_block (nano::receive_block const & block_a) } } -void ledger_processor::open_block (nano::open_block const & block_a) +void ledger_processor::open_block (nano::open_block & block_a) { auto hash (block_a.hash ()); auto existing (ledger.store.block_exists (transaction, block_a.type (), hash)); @@ -631,7 +683,7 @@ void ledger_processor::open_block (nano::open_block const & block_a) } if (result.code == nano::process_result::progress) { - assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); + debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); result.verified = nano::signature_verification::valid; result.code = ledger.store.source_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block? (Harmless) if (result.code == nano::process_result::progress) @@ -651,20 +703,26 @@ void ledger_processor::open_block (nano::open_block const & block_a) result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed) if (result.code == nano::process_result::progress) { - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - assert (!error); - ledger.store.pending_del (transaction, key); - nano::block_sideband sideband (nano::block_type::open, block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0); - ledger.store.block_put (transaction, hash, block_a, sideband); - nano::account_info new_info (hash, block_a.representative (), hash, pending.amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - ledger.rep_weights.representation_add (block_a.representative (), pending.amount.number ()); - ledger.store.frontier_put (transaction, hash, block_a.hashables.account); - result.account = block_a.hashables.account; - result.amount = pending.amount; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + nano::account_info source_info; + auto error (ledger.store.account_get (transaction, pending.source, source_info)); + (void)error; + debug_assert (!error); + ledger.store.pending_del (transaction, key); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.representative (), hash, pending.amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + ledger.cache.rep_weights.representation_add (block_a.representative (), pending.amount.number ()); + ledger.store.frontier_put (transaction, hash, block_a.hashables.account); + result.account = block_a.hashables.account; + result.amount = pending.amount; + result.previous_balance = 0; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); + } } } } @@ -683,33 +741,42 @@ verification (verification_a) } } // namespace -nano::ledger::ledger (nano::block_store & store_a, nano::stat & stat_a, bool cache_reps_a, bool cache_cemented_count_a) : +nano::ledger::ledger (nano::block_store & store_a, nano::stat & stat_a, nano::generate_cache const & generate_cache_a, std::function epoch_2_started_cb_a) : store (store_a), stats (stat_a), -check_bootstrap_weights (true) +check_bootstrap_weights (true), +epoch_2_started_cb (epoch_2_started_cb_a) { if (!store.init_error ()) { auto transaction = store.tx_begin_read (); - if (cache_reps_a) + if (generate_cache_a.reps || generate_cache_a.account_count || generate_cache_a.epoch_2) { + bool epoch_2_started_l{ false }; for (auto i (store.latest_begin (transaction)), n (store.latest_end ()); i != n; ++i) { nano::account_info const & info (i->second); - rep_weights.representation_add (info.representative, info.balance.number ()); + cache.rep_weights.representation_add (info.representative, info.balance.number ()); + ++cache.account_count; + epoch_2_started_l = epoch_2_started_l || info.epoch () == nano::epoch::epoch_2; } + cache.epoch_2_started.store (epoch_2_started_l); } - if (cache_cemented_count_a) + if (generate_cache_a.cemented_count) { for (auto i (store.confirmation_height_begin (transaction)), n (store.confirmation_height_end ()); i != n; ++i) { - cemented_count += i->second; + cache.cemented_count += i->second.height; } } - // Cache block count - block_count_cache = store.block_count (transaction).sum (); + if (generate_cache_a.unchecked_count) + { + cache.unchecked_count = store.unchecked_count (transaction); + } + + cache.block_count = store.block_count (transaction).sum (); } } @@ -744,14 +811,14 @@ nano::uint128_t nano::ledger::account_pending (nano::transaction const & transac return result; } -nano::process_return nano::ledger::process (nano::write_transaction const & transaction_a, nano::block const & block_a, nano::signature_verification verification) +nano::process_return nano::ledger::process (nano::write_transaction const & transaction_a, nano::block & block_a, nano::signature_verification verification) { - assert (!nano::work_validate (block_a)); + debug_assert (!nano::work_validate_entry (block_a) || network_params.network.is_test_network ()); ledger_processor processor (*this, transaction_a, verification); block_a.visit (processor); if (processor.result.code == nano::process_result::progress) { - ++block_count_cache; + ++cache.block_count; } return processor.result; } @@ -759,7 +826,7 @@ nano::process_return nano::ledger::process (nano::write_transaction const & tran nano::block_hash nano::ledger::representative (nano::transaction const & transaction_a, nano::block_hash const & hash_a) { auto result (representative_calculated (transaction_a, hash_a)); - assert (result.is_zero () || store.block_exists (transaction_a, result)); + debug_assert (result.is_zero () || store.block_exists (transaction_a, result)); return result; } @@ -772,16 +839,12 @@ nano::block_hash nano::ledger::representative_calculated (nano::transaction cons bool nano::ledger::block_exists (nano::block_hash const & hash_a) { - auto transaction (store.tx_begin_read ()); - auto result (store.block_exists (transaction, hash_a)); - return result; + return store.block_exists (store.tx_begin_read (), hash_a); } bool nano::ledger::block_exists (nano::block_type type, nano::block_hash const & hash_a) { - auto transaction (store.tx_begin_read ()); - auto result (store.block_exists (transaction, type, hash_a)); - return result; + return store.block_exists (store.tx_begin_read (), type, hash_a); } std::string nano::ledger::block_text (char const * hash_a) @@ -803,13 +866,27 @@ std::string nano::ledger::block_text (nano::block_hash const & hash_a) bool nano::ledger::is_send (nano::transaction const & transaction_a, nano::state_block const & block_a) const { + /* + * if block_a does not have a sideband, then is_send() + * requires that the previous block exists in the database. + * This is because it must retrieve the balance of the previous block. + */ + debug_assert (block_a.has_sideband () || block_a.hashables.previous.is_zero () || store.block_exists (transaction_a, block_a.hashables.previous)); + bool result (false); - nano::block_hash previous (block_a.hashables.previous); - if (!previous.is_zero ()) + if (block_a.has_sideband ()) + { + result = block_a.sideband ().details.is_send; + } + else { - if (block_a.hashables.balance < balance (transaction_a, previous)) + nano::block_hash previous (block_a.hashables.previous); + if (!previous.is_zero ()) { - result = true; + if (block_a.hashables.balance < balance (transaction_a, previous)) + { + result = true; + } } } return result; @@ -838,7 +915,7 @@ nano::block_hash nano::ledger::block_source (nano::transaction const & transacti * passed in exist in the database. This is because it will try * to check account balances to determine if it is a send block. */ - assert (block_a.previous ().is_zero () || store.block_exists (transaction_a, block_a.previous ())); + debug_assert (block_a.previous ().is_zero () || store.block_exists (transaction_a, block_a.previous ())); // If block_a.source () is nonzero, then we have our source. // However, universal blocks will always return zero. @@ -856,7 +933,7 @@ nano::uint128_t nano::ledger::weight (nano::account const & account_a) { if (check_bootstrap_weights.load ()) { - if (block_count_cache < bootstrap_weight_max_blocks) + if (cache.block_count < bootstrap_weight_max_blocks) { auto weight = bootstrap_weights.find (account_a); if (weight != bootstrap_weights.end ()) @@ -869,13 +946,13 @@ nano::uint128_t nano::ledger::weight (nano::account const & account_a) check_bootstrap_weights = false; } } - return rep_weights.representation_get (account_a); + return cache.rep_weights.representation_get (account_a); } // Rollback blocks until `block_a' doesn't exist or it tries to penetrate the confirmation height bool nano::ledger::rollback (nano::write_transaction const & transaction_a, nano::block_hash const & block_a, std::vector> & list_a) { - assert (store.block_exists (transaction_a, block_a)); + debug_assert (store.block_exists (transaction_a, block_a)); auto account_l (account (transaction_a, block_a)); auto block_account_height (store.block_account_height (transaction_a, block_a)); rollback_visitor rollback (transaction_a, *this, list_a); @@ -883,21 +960,21 @@ bool nano::ledger::rollback (nano::write_transaction const & transaction_a, nano auto error (false); while (!error && store.block_exists (transaction_a, block_a)) { - uint64_t confirmation_height; - auto latest_error = store.confirmation_height_get (transaction_a, account_l, confirmation_height); - assert (!latest_error); + nano::confirmation_height_info confirmation_height_info; + auto latest_error = store.confirmation_height_get (transaction_a, account_l, confirmation_height_info); + debug_assert (!latest_error); (void)latest_error; - if (block_account_height > confirmation_height) + if (block_account_height > confirmation_height_info.height) { latest_error = store.account_get (transaction_a, account_l, account_info); - assert (!latest_error); + debug_assert (!latest_error); auto block (store.block_get (transaction_a, account_info.head)); list_a.push_back (block); block->visit (rollback); error = rollback.error; if (!error) { - --block_count_cache; + --cache.block_count; } } else @@ -957,70 +1034,104 @@ nano::root nano::ledger::latest_root (nano::transaction const & transaction_a, n } } -void nano::ledger::dump_account_chain (nano::account const & account_a) +void nano::ledger::dump_account_chain (nano::account const & account_a, std::ostream & stream) { auto transaction (store.tx_begin_read ()); auto hash (latest (transaction, account_a)); while (!hash.is_zero ()) { auto block (store.block_get (transaction, hash)); - assert (block != nullptr); - std::cerr << hash.to_string () << std::endl; + debug_assert (block != nullptr); + stream << hash.to_string () << std::endl; hash = block->previous (); } } -class block_fit_visitor : public nano::block_visitor +bool nano::ledger::could_fit (nano::transaction const & transaction_a, nano::block const & block_a) +{ + auto dependencies (dependent_blocks (transaction_a, block_a)); + return std::all_of (dependencies.begin (), dependencies.end (), [this, &transaction_a](nano::block_hash const & hash_a) { + return hash_a.is_zero () || store.block_exists (transaction_a, hash_a); + }); +} + +bool nano::ledger::can_vote (nano::transaction const & transaction_a, nano::block const & block_a) +{ + auto dependencies (dependent_blocks (transaction_a, block_a)); + return std::all_of (dependencies.begin (), dependencies.end (), [this, &transaction_a](nano::block_hash const & hash_a) { + auto result (hash_a.is_zero ()); + if (!result) + { + result = false; + auto block (store.block_get (transaction_a, hash_a)); + if (block != nullptr) + { + nano::confirmation_height_info height; + auto error = store.confirmation_height_get (transaction_a, block->account ().is_zero () ? block->sideband ().account : block->account (), height); + debug_assert (!error); + result = block->sideband ().height <= height.height; + } + } + return result; + }); +} + +bool nano::ledger::is_epoch_link (nano::link const & link_a) +{ + return network_params.ledger.epochs.is_epoch_link (link_a); +} + +class dependent_block_visitor : public nano::block_visitor { public: - block_fit_visitor (nano::ledger & ledger_a, nano::transaction const & transaction_a) : + dependent_block_visitor (nano::ledger & ledger_a, nano::transaction const & transaction_a) : ledger (ledger_a), transaction (transaction_a), - result (false) + result ({ 0, 0 }) { } void send_block (nano::send_block const & block_a) override { - result = ledger.store.block_exists (transaction, block_a.previous ()); + result[0] = block_a.previous (); } void receive_block (nano::receive_block const & block_a) override { - result = ledger.store.block_exists (transaction, block_a.previous ()); - result &= ledger.store.block_exists (transaction, block_a.source ()); + result[0] = block_a.previous (); + result[1] = block_a.source (); } void open_block (nano::open_block const & block_a) override { - result = ledger.store.block_exists (transaction, block_a.source ()); + if (block_a.source () != ledger.network_params.ledger.genesis_account) + { + result[0] = block_a.source (); + } } void change_block (nano::change_block const & block_a) override { - result = ledger.store.block_exists (transaction, block_a.previous ()); + result[0] = block_a.previous (); } void state_block (nano::state_block const & block_a) override { - result = block_a.previous ().is_zero () || ledger.store.block_exists (transaction, block_a.previous ()); - if (result && !ledger.is_send (transaction, block_a)) + result[0] = block_a.hashables.previous; + result[1] = block_a.hashables.link; + // ledger.is_send will check the sideband first, if block_a has a loaded sideband the check that previous block exists can be skipped + if (ledger.is_epoch_link (block_a.hashables.link) || ((block_a.has_sideband () || ledger.store.block_exists (transaction, block_a.hashables.previous)) && ledger.is_send (transaction, block_a))) { - result &= ledger.store.block_exists (transaction, block_a.hashables.link) || block_a.hashables.link.is_zero () || ledger.is_epoch_link (block_a.hashables.link); + result[1].clear (); } } nano::ledger & ledger; nano::transaction const & transaction; - bool result; + std::array result; }; -bool nano::ledger::could_fit (nano::transaction const & transaction_a, nano::block const & block_a) +std::array nano::ledger::dependent_blocks (nano::transaction const & transaction_a, nano::block const & block_a) { - block_fit_visitor visitor (*this, transaction_a); + dependent_block_visitor visitor (*this, transaction_a); block_a.visit (visitor); return visitor.result; } -bool nano::ledger::is_epoch_link (nano::link const & link_a) -{ - return network_params.ledger.epochs.is_epoch_link (link_a); -} - nano::account const & nano::ledger::epoch_signer (nano::link const & link_a) const { return network_params.ledger.epochs.signer (network_params.ledger.epochs.epoch (link_a)); @@ -1037,8 +1148,9 @@ void nano::ledger::change_latest (nano::write_transaction const & transaction_a, { if (old_a.head.is_zero () && new_a.open_block == new_a.head) { - assert (!store.confirmation_height_exists (transaction_a, account_a)); - store.confirmation_height_put (transaction_a, account_a, 0); + debug_assert (!store.confirmation_height_exists (transaction_a, account_a)); + store.confirmation_height_put (transaction_a, account_a, { 0, nano::block_hash (0) }); + ++cache.account_count; } if (!old_a.head.is_zero () && old_a.epoch () != new_a.epoch ()) { @@ -1051,6 +1163,8 @@ void nano::ledger::change_latest (nano::write_transaction const & transaction_a, { store.confirmation_height_del (transaction_a, account_a); store.account_del (transaction_a, account_a); + debug_assert (cache.account_count > 0); + --cache.account_count; } } @@ -1084,37 +1198,50 @@ std::shared_ptr nano::ledger::successor (nano::transaction const & { result = store.block_get (transaction_a, successor); } - assert (successor.is_zero () || result != nullptr); + debug_assert (successor.is_zero () || result != nullptr); return result; } std::shared_ptr nano::ledger::forked_block (nano::transaction const & transaction_a, nano::block const & block_a) { - assert (!store.block_exists (transaction_a, block_a.type (), block_a.hash ())); + debug_assert (!store.block_exists (transaction_a, block_a.type (), block_a.hash ())); auto root (block_a.root ()); - assert (store.block_exists (transaction_a, root) || store.account_exists (transaction_a, root)); + debug_assert (store.block_exists (transaction_a, root) || store.account_exists (transaction_a, root)); auto result (store.block_get (transaction_a, store.block_successor (transaction_a, root))); if (result == nullptr) { nano::account_info info; auto error (store.account_get (transaction_a, root, info)); (void)error; - assert (!error); + debug_assert (!error); result = store.block_get (transaction_a, info.open_block); - assert (result != nullptr); + debug_assert (result != nullptr); } return result; } +std::shared_ptr nano::ledger::backtrack (nano::transaction const & transaction_a, std::shared_ptr const & start_a, uint64_t jumps_a) +{ + auto block = start_a; + while (jumps_a > 0 && block != nullptr && !block->previous ().is_zero ()) + { + block = store.block_get (transaction_a, block->previous ()); + debug_assert (block != nullptr); + --jumps_a; + } + debug_assert (block == nullptr || block->previous ().is_zero () || jumps_a == 0); + return block; +} + bool nano::ledger::block_confirmed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const { auto confirmed (false); auto block_height (store.block_account_height (transaction_a, hash_a)); if (block_height > 0) // 0 indicates that the block doesn't exist { - uint64_t confirmation_height; - release_assert (!store.confirmation_height_get (transaction_a, account (transaction_a, hash_a), confirmation_height)); - confirmed = (confirmation_height >= block_height); + nano::confirmation_height_info confirmation_height_info; + release_assert (!store.confirmation_height_get (transaction_a, account (transaction_a, hash_a), confirmation_height_info)); + confirmed = (confirmation_height_info.height >= block_height); } return confirmed; } @@ -1131,15 +1258,12 @@ bool nano::ledger::block_not_confirmed_or_not_exists (nano::block const & block_ return result; } -namespace nano -{ -std::unique_ptr collect_seq_con_info (ledger & ledger, const std::string & name) +std::unique_ptr nano::collect_container_info (ledger & ledger, const std::string & name) { - auto composite = std::make_unique (name); auto count = ledger.bootstrap_weights_size.load (); auto sizeof_element = sizeof (decltype (ledger.bootstrap_weights)::value_type); - composite->add_component (std::make_unique (seq_con_info{ "bootstrap_weights", count, sizeof_element })); - composite->add_component (collect_seq_con_info (ledger.rep_weights, "rep_weights")); + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "bootstrap_weights", count, sizeof_element })); + composite->add_component (collect_container_info (ledger.cache.rep_weights, "rep_weights")); return composite; } -} diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index d8b6c85430..a246f0433c 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -1,19 +1,21 @@ #pragma once -#include #include #include +#include + namespace nano { class block_store; class stat; +class write_transaction; using tally_t = std::map, std::greater>; class ledger final { public: - ledger (nano::block_store &, nano::stat &, bool = true, bool = true); + ledger (nano::block_store &, nano::stat &, nano::generate_cache const & = nano::generate_cache (), std::function = nullptr); nano::account account (nano::transaction const &, nano::block_hash const &) const; nano::uint128_t amount (nano::transaction const &, nano::account const &); nano::uint128_t amount (nano::transaction const &, nano::block_hash const &); @@ -23,6 +25,7 @@ class ledger final nano::uint128_t weight (nano::account const &); std::shared_ptr successor (nano::transaction const &, nano::qualified_root const &); std::shared_ptr forked_block (nano::transaction const &, nano::block const &); + std::shared_ptr backtrack (nano::transaction const &, std::shared_ptr const &, uint64_t); bool block_confirmed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const; bool block_not_confirmed_or_not_exists (nano::block const & block_a) const; nano::block_hash latest (nano::transaction const &, nano::account const &); @@ -36,27 +39,28 @@ class ledger final bool is_send (nano::transaction const &, nano::state_block const &) const; nano::account const & block_destination (nano::transaction const &, nano::block const &); nano::block_hash block_source (nano::transaction const &, nano::block const &); - nano::process_return process (nano::write_transaction const &, nano::block const &, nano::signature_verification = nano::signature_verification::unknown); + nano::process_return process (nano::write_transaction const &, nano::block &, nano::signature_verification = nano::signature_verification::unknown); bool rollback (nano::write_transaction const &, nano::block_hash const &, std::vector> &); bool rollback (nano::write_transaction const &, nano::block_hash const &); void change_latest (nano::write_transaction const &, nano::account const &, nano::account_info const &, nano::account_info const &); - void dump_account_chain (nano::account const &); + void dump_account_chain (nano::account const &, std::ostream & = std::cout); bool could_fit (nano::transaction const &, nano::block const &); + bool can_vote (nano::transaction const &, nano::block const &); bool is_epoch_link (nano::link const &); + std::array dependent_blocks (nano::transaction const &, nano::block const &); nano::account const & epoch_signer (nano::link const &) const; nano::link const & epoch_link (nano::epoch) const; static nano::uint128_t const unit; nano::network_params network_params; nano::block_store & store; - std::atomic cemented_count{ 0 }; - std::atomic block_count_cache{ 0 }; - nano::rep_weights rep_weights; + nano::ledger_cache cache; nano::stat & stats; std::unordered_map bootstrap_weights; std::atomic bootstrap_weights_size{ 0 }; uint64_t bootstrap_weight_max_blocks{ 1 }; std::atomic check_bootstrap_weights; + std::function epoch_2_started_cb; }; -std::unique_ptr collect_seq_con_info (ledger & ledger, const std::string & name); +std::unique_ptr collect_container_info (ledger & ledger, const std::string & name); } diff --git a/nano/secure/network_filter.cpp b/nano/secure/network_filter.cpp new file mode 100644 index 0000000000..f9e6b0374a --- /dev/null +++ b/nano/secure/network_filter.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +nano::network_filter::network_filter (size_t size_a) : +items (size_a, nano::uint128_t{ 0 }) +{ + nano::random_pool::generate_block (key, key.size ()); +} + +bool nano::network_filter::apply (uint8_t const * bytes_a, size_t count_a, nano::uint128_t * digest_a) +{ + // Get hash before locking + auto digest (hash (bytes_a, count_a)); + + nano::lock_guard lock (mutex); + auto & element (get_element (digest)); + bool existed (element == digest); + if (!existed) + { + // Replace likely old element with a new one + element = digest; + } + if (digest_a) + { + *digest_a = digest; + } + return existed; +} + +void nano::network_filter::clear (nano::uint128_t const & digest_a) +{ + nano::lock_guard lock (mutex); + auto & element (get_element (digest_a)); + if (element == digest_a) + { + element = nano::uint128_t{ 0 }; + } +} + +void nano::network_filter::clear (std::vector const & digests_a) +{ + nano::lock_guard lock (mutex); + for (auto const & digest : digests_a) + { + auto & element (get_element (digest)); + if (element == digest) + { + element = nano::uint128_t{ 0 }; + } + } +} + +void nano::network_filter::clear (uint8_t const * bytes_a, size_t count_a) +{ + clear (hash (bytes_a, count_a)); +} + +template +void nano::network_filter::clear (OBJECT const & object_a) +{ + clear (hash (object_a)); +} + +void nano::network_filter::clear () +{ + nano::lock_guard lock (mutex); + items.assign (items.size (), nano::uint128_t{ 0 }); +} + +template +nano::uint128_t nano::network_filter::hash (OBJECT const & object_a) const +{ + std::vector bytes; + { + nano::vectorstream stream (bytes); + object_a->serialize (stream); + } + return hash (bytes.data (), bytes.size ()); +} + +nano::uint128_t & nano::network_filter::get_element (nano::uint128_t const & hash_a) +{ + debug_assert (!mutex.try_lock ()); + debug_assert (items.size () > 0); + size_t index (hash_a % items.size ()); + return items[index]; +} + +nano::uint128_t nano::network_filter::hash (uint8_t const * bytes_a, size_t count_a) const +{ + nano::uint128_union digest{ 0 }; + siphash_t siphash (key, static_cast (key.size ())); + siphash.CalculateDigest (digest.bytes.data (), bytes_a, count_a); + return digest.number (); +} + +// Explicitly instantiate +template nano::uint128_t nano::network_filter::hash (std::shared_ptr const &) const; +template void nano::network_filter::clear (std::shared_ptr const &); diff --git a/nano/secure/network_filter.hpp b/nano/secure/network_filter.hpp new file mode 100644 index 0000000000..0e42a2cce7 --- /dev/null +++ b/nano/secure/network_filter.hpp @@ -0,0 +1,85 @@ + +#pragma once + +#include + +#include +#include + +#include + +namespace nano +{ +/** + * A probabilistic duplicate filter based on directed map caches, using SipHash 2/4/128 + * The probability of false negatives (unique packet marked as duplicate) is the probability of a 128-bit SipHash collision. + * The probability of false positives (duplicate packet marked as unique) shrinks with a larger filter. + * @note This class is thread-safe. + */ +class network_filter final +{ +public: + network_filter () = delete; + network_filter (size_t size_a); + /** + * Reads \p count_a bytes starting from \p bytes_a and inserts the siphash digest in the filter. + * @param \p digest_a if given, will be set to the resulting siphash digest + * @warning will read out of bounds if [ \p bytes_a, \p bytes_a + \p count_a ] is not a valid range + * @return a boolean representing the previous existence of the hash in the filter. + **/ + bool apply (uint8_t const * bytes_a, size_t count_a, nano::uint128_t * digest_a = nullptr); + + /** + * Sets the corresponding element in the filter to zero, if it matches \p digest_a exactly. + **/ + void clear (nano::uint128_t const & digest_a); + + /** + * Clear many digests from the filter + **/ + void clear (std::vector const &); + + /** + * Reads \p count_a bytes starting from \p bytes_a and digests the contents. + * Then, sets the corresponding element in the filter to zero, if it matches the digest exactly. + * @warning will read out of bounds if [ \p bytes_a, \p bytes_a + \p count_a ] is not a valid range + **/ + void clear (uint8_t const * bytes_a, size_t count_a); + + /** + * Serializes \p object_a and clears the resulting siphash digest from the filter. + * @return a boolean representing the previous existence of the hash in the filter. + **/ + template + void clear (OBJECT const & object_a); + + /** Sets every element of the filter to zero, keeping its size and capacity. */ + void clear (); + + /** + * Serializes \p object_a and returns the resulting siphash digest + */ + template + nano::uint128_t hash (OBJECT const & object_a) const; + +private: + using siphash_t = CryptoPP::SipHash<2, 4, true>; + + /** + * Get element from digest. + * @note must have a lock on mutex + * @return a reference to the element with key \p hash_a + **/ + nano::uint128_t & get_element (nano::uint128_t const & hash_a); + + /** + * Hashes \p count_a bytes starting from \p bytes_a . + * @return the siphash digest of the contents in \p bytes_a . + **/ + nano::uint128_t hash (uint8_t const * bytes_a, size_t count_a) const; + + std::vector items; + CryptoPP::SecByteBlock key{ siphash_t::KEYLENGTH }; + std::mutex mutex; +}; +} diff --git a/nano/secure/plat/posix/working.cpp b/nano/secure/plat/posix/working.cpp index 9b56aca5dd..1d36b163ba 100644 --- a/nano/secure/plat/posix/working.cpp +++ b/nano/secure/plat/posix/working.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -8,7 +9,7 @@ namespace nano boost::filesystem::path app_path () { auto entry (getpwuid (getuid ())); - assert (entry != nullptr); + debug_assert (entry != nullptr); boost::filesystem::path result (entry->pw_dir); return result; } diff --git a/nano/secure/plat/windows/working.cpp b/nano/secure/plat/windows/working.cpp index 07e005e260..91cf371999 100644 --- a/nano/secure/plat/windows/working.cpp +++ b/nano/secure/plat/windows/working.cpp @@ -1,5 +1,7 @@ #include +#include + #include namespace nano @@ -14,7 +16,7 @@ boost::filesystem::path app_path () } else { - assert (false); + debug_assert (false); } return result; } diff --git a/nano/secure/utility.cpp b/nano/secure/utility.cpp index d4a16ac737..39cb553491 100644 --- a/nano/secure/utility.cpp +++ b/nano/secure/utility.cpp @@ -2,6 +2,8 @@ #include #include +#include + static std::vector all_unique_paths; boost::filesystem::path nano::working_path (bool legacy) diff --git a/nano/secure/utility.hpp b/nano/secure/utility.hpp index 716f0320d6..1682393648 100644 --- a/nano/secure/utility.hpp +++ b/nano/secure/utility.hpp @@ -1,24 +1,11 @@ #pragma once -#include -#include - #include -#include -#include -#include -#include - -#include -#include -#include -#include +#include namespace nano { -using bufferstream = boost::iostreams::stream_buffer>; -using vectorstream = boost::iostreams::stream_buffer>>; // OS-specific way of finding a path to a home directory. boost::filesystem::path working_path (bool = false); // Function to migrate working_path() from above from RaiBlocks to Nano diff --git a/nano/secure/versioning.cpp b/nano/secure/versioning.cpp index 0ed201d8ed..62f3f5041c 100644 --- a/nano/secure/versioning.cpp +++ b/nano/secure/versioning.cpp @@ -6,7 +6,7 @@ nano::account_info_v1::account_info_v1 (MDB_val const & val_a) { - assert (val_a.mv_size == sizeof (*this)); + debug_assert (val_a.mv_size == sizeof (*this)); static_assert (sizeof (head) + sizeof (rep_block) + sizeof (balance) + sizeof (modified) == sizeof (*this), "Class not packed"); std::copy (reinterpret_cast (val_a.mv_data), reinterpret_cast (val_a.mv_data) + sizeof (*this), reinterpret_cast (this)); } @@ -21,7 +21,7 @@ modified (modified_a) nano::pending_info_v3::pending_info_v3 (MDB_val const & val_a) { - assert (val_a.mv_size == sizeof (*this)); + debug_assert (val_a.mv_size == sizeof (*this)); static_assert (sizeof (source) + sizeof (amount) + sizeof (destination) == sizeof (*this), "Packed class"); std::copy (reinterpret_cast (val_a.mv_data), reinterpret_cast (val_a.mv_data) + sizeof (*this), reinterpret_cast (this)); } @@ -68,7 +68,7 @@ bool nano::pending_info_v14::operator== (nano::pending_info_v14 const & other_a) nano::account_info_v5::account_info_v5 (MDB_val const & val_a) { - assert (val_a.mv_size == sizeof (*this)); + debug_assert (val_a.mv_size == sizeof (*this)); static_assert (sizeof (head) + sizeof (rep_block) + sizeof (open_block) + sizeof (balance) + sizeof (modified) == sizeof (*this), "Class not packed"); std::copy (reinterpret_cast (val_a.mv_data), reinterpret_cast (val_a.mv_data) + sizeof (*this), reinterpret_cast (this)); } @@ -95,12 +95,12 @@ epoch (epoch_a) size_t nano::account_info_v13::db_size () const { - assert (reinterpret_cast (this) == reinterpret_cast (&head)); - assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&rep_block)); - assert (reinterpret_cast (&rep_block) + sizeof (rep_block) == reinterpret_cast (&open_block)); - assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); - assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); - assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); + debug_assert (reinterpret_cast (this) == reinterpret_cast (&head)); + debug_assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&rep_block)); + debug_assert (reinterpret_cast (&rep_block) + sizeof (rep_block) == reinterpret_cast (&open_block)); + debug_assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); + debug_assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); + debug_assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); return sizeof (head) + sizeof (rep_block) + sizeof (open_block) + sizeof (balance) + sizeof (modified) + sizeof (block_count); } @@ -118,13 +118,13 @@ epoch (epoch_a) size_t nano::account_info_v14::db_size () const { - assert (reinterpret_cast (this) == reinterpret_cast (&head)); - assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&rep_block)); - assert (reinterpret_cast (&rep_block) + sizeof (rep_block) == reinterpret_cast (&open_block)); - assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); - assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); - assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); - assert (reinterpret_cast (&block_count) + sizeof (block_count) == reinterpret_cast (&confirmation_height)); + debug_assert (reinterpret_cast (this) == reinterpret_cast (&head)); + debug_assert (reinterpret_cast (&head) + sizeof (head) == reinterpret_cast (&rep_block)); + debug_assert (reinterpret_cast (&rep_block) + sizeof (rep_block) == reinterpret_cast (&open_block)); + debug_assert (reinterpret_cast (&open_block) + sizeof (open_block) == reinterpret_cast (&balance)); + debug_assert (reinterpret_cast (&balance) + sizeof (balance) == reinterpret_cast (&modified)); + debug_assert (reinterpret_cast (&modified) + sizeof (modified) == reinterpret_cast (&block_count)); + debug_assert (reinterpret_cast (&block_count) + sizeof (block_count) == reinterpret_cast (&confirmation_height)); return sizeof (head) + sizeof (rep_block) + sizeof (open_block) + sizeof (balance) + sizeof (modified) + sizeof (block_count) + sizeof (confirmation_height); } diff --git a/nano/secure/versioning.hpp b/nano/secure/versioning.hpp index f9346cb857..6c29effa36 100644 --- a/nano/secure/versioning.hpp +++ b/nano/secure/versioning.hpp @@ -2,7 +2,6 @@ #include #include -#include struct MDB_val; diff --git a/nano/slow_test/node.cpp b/nano/slow_test/node.cpp index 4a9a1493eb..f09e092237 100644 --- a/nano/slow_test/node.cpp +++ b/nano/slow_test/node.cpp @@ -1,18 +1,24 @@ +#include #include #include +#include +#include #include #include #include -#include +#include + +#include +#include using namespace std::chrono_literals; TEST (system, generate_mass_activity) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; // Prevent blocks cementing auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); @@ -27,7 +33,7 @@ TEST (system, generate_mass_activity) TEST (system, generate_mass_activity_long) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; // Prevent blocks cementing auto node = system.add_node (node_config); system.wallet (0)->wallets.watcher->stop (); // Stop work watcher @@ -48,7 +54,7 @@ TEST (system, receive_while_synchronizing) std::vector threads; { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.enable_voting = false; // Prevent blocks cementing auto node = system.add_node (node_config); nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads); @@ -56,7 +62,7 @@ TEST (system, receive_while_synchronizing) uint32_t count (1000); system.generate_mass_activity (count, *system.nodes[0]); nano::keypair key; - auto node1 (std::make_shared (system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto channel (std::make_shared (node1->network.udp_channels, system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version)); node1->network.send_keepalive (channel); @@ -95,7 +101,7 @@ TEST (ledger, deep_account_compute) nano::ledger ledger (*store, stats); nano::genesis genesis; auto transaction (store->tx_begin_write ()); - store->initialize (transaction, genesis, ledger.rep_weights, ledger.cemented_count, ledger.block_count_cache); + store->initialize (transaction, genesis, ledger.cache); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key; auto balance (nano::genesis_amount - 1); @@ -127,7 +133,7 @@ TEST (wallet, multithreaded_send_async) { std::vector threads; { - nano::system system (24000, 1); + nano::system system (1); nano::keypair key; auto wallet_l (system.wallet (0)); wallet_l->insert_adhoc (nano::test_genesis_key.prv); @@ -158,7 +164,7 @@ TEST (wallet, multithreaded_send_async) TEST (store, load) { - nano::system system (24000, 1); + nano::system system (1); std::vector threads; for (auto i (0); i < 100; ++i) { @@ -170,7 +176,7 @@ TEST (store, load) { nano::account account; nano::random_pool::generate_block (account.bytes.data (), account.bytes.size ()); - system.nodes[0]->store.confirmation_height_put (transaction, account, 0); + system.nodes[0]->store.confirmation_height_put (transaction, account, { 0, nano::block_hash (0) }); system.nodes[0]->store.account_put (transaction, account, nano::account_info ()); } } @@ -185,7 +191,9 @@ TEST (store, load) // ulimit -n increasing may be required TEST (node, fork_storm) { - nano::system system (24000, 64); + nano::node_flags flags; + flags.disable_max_peers_per_ip = true; + nano::system system (64, nano::transport::transport_type::tcp, flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto previous (system.nodes[0]->latest (nano::test_genesis_key.pub)); auto balance (system.nodes[0]->balance (nano::test_genesis_key.pub)); @@ -307,7 +315,7 @@ TEST (broadcast, world_broadcast_simulate) case 2: break; default: - assert (false); + ASSERT_FALSE (true); break; } } @@ -360,7 +368,7 @@ TEST (broadcast, sqrt_broadcast_simulate) case 2: break; default: - assert (false); + ASSERT_FALSE (true); break; } } @@ -371,7 +379,7 @@ TEST (broadcast, sqrt_broadcast_simulate) TEST (peer_container, random_set) { - nano::system system (24000, 1); + nano::system system (1); auto old (std::chrono::steady_clock::now ()); auto current (std::chrono::steady_clock::now ()); for (auto i (0); i < 10000; ++i) @@ -389,7 +397,7 @@ TEST (peer_container, random_set) // Can take up to 2 hours TEST (store, unchecked_load) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); auto block (std::make_shared (0, 0, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); constexpr auto num_unchecked = 1000000; @@ -404,7 +412,7 @@ TEST (store, unchecked_load) TEST (store, vote_load) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); auto block (std::make_shared (0, 0, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); for (auto i (0); i < 1000000; ++i) @@ -416,7 +424,7 @@ TEST (store, vote_load) TEST (wallets, rep_scan) { - nano::system system (24000, 1); + nano::system system (1); auto & node (*system.nodes[0]); auto wallet (system.wallet (0)); { @@ -434,10 +442,9 @@ TEST (wallets, rep_scan) TEST (node, mass_vote_by_hash) { - nano::system system (24000, 1); + nano::system system (1); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::genesis genesis; - nano::block_hash previous (genesis.hash ()); + nano::block_hash previous (nano::genesis_hash); nano::keypair key; std::vector> blocks; for (auto i (0); i < 10000; ++i) @@ -457,20 +464,15 @@ namespace nano TEST (confirmation_height, many_accounts_single_confirmation) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.online_weight_minimum = 100; node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - // As this test can take a while extend the next frontier check - { - nano::lock_guard guard (node->active.mutex); - node->active.next_frontier_check = std::chrono::steady_clock::now () + 7200s; - } - - // The number of frontiers should be more than the batch_write_size to test the amount of blocks confirmed is correct. - auto num_accounts = nano::confirmation_height_processor::batch_write_size * 2 + 50; + // The number of frontiers should be more than the nano::confirmation_height::unbounded_cutoff to test the amount of blocks confirmed is correct. + node->confirmation_height_processor.batch_write_size = 500; + auto const num_accounts = nano::confirmation_height::unbounded_cutoff * 2 + 50; nano::keypair last_keypair = nano::test_genesis_key; auto last_open_hash = node->latest (nano::test_genesis_key.pub); { @@ -491,56 +493,69 @@ TEST (confirmation_height, many_accounts_single_confirmation) // Call block confirm on the last open block which will confirm everything { - auto transaction = node->store.tx_begin_read (); - auto block = node->store.block_get (transaction, last_open_hash); - node->block_confirm (block); + auto block = node->block (last_open_hash); + ASSERT_NE (nullptr, block); + auto election_insertion_result (node->active.insert (block)); + ASSERT_TRUE (election_insertion_result.inserted); + ASSERT_NE (nullptr, election_insertion_result.election); + nano::lock_guard guard (node->active.mutex); + election_insertion_result.election->confirm_once (); } - system.deadline_set (60s); - while (true) + system.deadline_set (120s); + auto transaction = node->store.tx_begin_read (); + while (!node->ledger.block_confirmed (transaction, last_open_hash)) { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, last_open_hash)) - { - break; - } - ASSERT_NO_ERROR (system.poll ()); + transaction.refresh (); } - auto transaction (node->store.tx_begin_read ()); // All frontiers (except last) should have 2 blocks and both should be confirmed for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) { auto & account = i->first; auto & account_info = i->second; auto count = (account != last_keypair.pub) ? 2 : 1; - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, account, confirmation_height)); - ASSERT_EQ (count, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, account, confirmation_height_info)); + ASSERT_EQ (count, confirmation_height_info.height); ASSERT_EQ (count, account_info.block_count); } + auto cemented_count = 0; + for (auto i (node->ledger.store.confirmation_height_begin (transaction)), n (node->ledger.store.confirmation_height_end ()); i != n; ++i) + { + cemented_count += i->second.height; + } + + ASSERT_EQ (cemented_count, node->ledger.cache.cemented_count); ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_accounts * 2 - 2); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), num_accounts * 2 - 2); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), 0); + + system.deadline_set (40s); + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + system.deadline_set (10s); + while (node->active.election_winner_details_size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } } -// Can take up to 10 minutes TEST (confirmation_height, many_accounts_many_confirmations) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.online_weight_minimum = 100; node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - // As this test can take a while extend the next frontier check - { - nano::lock_guard guard (node->active.mutex); - node->active.next_frontier_check = std::chrono::steady_clock::now () + 7200s; - } - - auto num_accounts = 10000; + node->confirmation_height_processor.batch_write_size = 500; + auto const num_accounts = nano::confirmation_height::unbounded_cutoff * 2 + 50; auto latest_genesis = node->latest (nano::test_genesis_key.pub); std::vector> open_blocks; { @@ -562,11 +577,48 @@ TEST (confirmation_height, many_accounts_many_confirmations) // Confirm all of the accounts for (auto & open_block : open_blocks) { - node->block_confirm (open_block); + auto election_insertion_result (node->active.insert (open_block)); + ASSERT_TRUE (election_insertion_result.inserted); + ASSERT_NE (nullptr, election_insertion_result.election); + nano::lock_guard guard (node->active.mutex); + election_insertion_result.election->confirm_once (); + } + + system.deadline_set (1500s); + auto const num_blocks_to_confirm = (num_accounts - 1) * 2; + while (node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != num_blocks_to_confirm) + { + ASSERT_NO_ERROR (system.poll ()); } + auto num_confirmed_bounded = node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in); + ASSERT_GE (num_confirmed_bounded, nano::confirmation_height::unbounded_cutoff); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), num_blocks_to_confirm - num_confirmed_bounded); + system.deadline_set (60s); - while (node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != (num_accounts - 1) * 2) + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction = node->store.tx_begin_read (); + auto cemented_count = 0; + for (auto i (node->ledger.store.confirmation_height_begin (transaction)), n (node->ledger.store.confirmation_height_end ()); i != n; ++i) + { + cemented_count += i->second.height; + } + + ASSERT_EQ (num_blocks_to_confirm + 1, cemented_count); + ASSERT_EQ (cemented_count, node->ledger.cache.cemented_count); + + system.deadline_set (20s); + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + system.deadline_set (10s); + while (node->active.election_winner_details_size () > 0) { ASSERT_NO_ERROR (system.poll ()); } @@ -575,7 +627,7 @@ TEST (confirmation_height, many_accounts_many_confirmations) TEST (confirmation_height, long_chains) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto node = system.add_node (node_config); nano::keypair key1; @@ -583,13 +635,8 @@ TEST (confirmation_height, long_chains) nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); system.wallet (0)->insert_adhoc (key1.prv); - // As this test can take a while extend the next frontier check - { - nano::lock_guard guard (node->active.mutex); - node->active.next_frontier_check = std::chrono::steady_clock::now () + 7200s; - } - - constexpr auto num_blocks = 10000; + node->confirmation_height_processor.batch_write_size = 500; + auto const num_blocks = nano::confirmation_height::unbounded_cutoff * 2 + 50; // First open the other account nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio + num_blocks + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest)); @@ -633,9 +680,15 @@ TEST (confirmation_height, long_chains) } // Call block confirm on the existing receive block on the genesis account which will confirm everything underneath on both accounts - node->block_confirm (receive1); + { + auto election_insertion_result (node->active.insert (receive1)); + ASSERT_TRUE (election_insertion_result.inserted); + ASSERT_NE (nullptr, election_insertion_result.election); + nano::lock_guard guard (node->active.mutex); + election_insertion_result.election->confirm_once (); + } - system.deadline_set (10s); + system.deadline_set (30s); while (true) { auto transaction = node->store.tx_begin_read (); @@ -650,34 +703,410 @@ TEST (confirmation_height, long_chains) auto transaction (node->store.tx_begin_read ()); nano::account_info account_info; ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (num_blocks + 2, confirmation_height); + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height_info)); + ASSERT_EQ (num_blocks + 2, confirmation_height_info.height); ASSERT_EQ (num_blocks + 3, account_info.block_count); // Includes the unpocketed send ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (num_blocks + 1, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height_info)); + ASSERT_EQ (num_blocks + 1, confirmation_height_info.height); ASSERT_EQ (num_blocks + 1, account_info.block_count); + auto cemented_count = 0; + for (auto i (node->ledger.store.confirmation_height_begin (transaction)), n (node->ledger.store.confirmation_height_end ()); i != n; ++i) + { + cemented_count += i->second.height; + } + + ASSERT_EQ (cemented_count, node->ledger.cache.cemented_count); ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_blocks * 2 + 2); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), num_blocks * 2 + 2); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), 0); + + system.deadline_set (40s); + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + system.deadline_set (10s); + while (node->active.election_winner_details_size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } } -// Can take up to 1 hour -TEST (confirmation_height, prioritize_frontiers_overwrite) +TEST (confirmation_height, dynamic_algorithm) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + nano::genesis genesis; + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto const num_blocks = nano::confirmation_height::unbounded_cutoff; + auto latest_genesis = node->latest (nano::test_genesis_key.pub); + std::vector> state_blocks; + for (auto i = 0; i < num_blocks; ++i) + { + auto send (std::make_shared (nano::test_genesis_key.pub, latest_genesis, nano::test_genesis_key.pub, nano::genesis_amount - i - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest_genesis))); + latest_genesis = send->hash (); + state_blocks.push_back (send); + } + { + auto transaction = node->store.tx_begin_write (); + for (auto const & block : state_blocks) + { + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *block).code); + } + } + + node->confirmation_height_processor.add (state_blocks.front ()->hash ()); + system.deadline_set (20s); + while (node->ledger.cache.cemented_count != 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + + node->confirmation_height_processor.add (latest_genesis); + + system.deadline_set (20s); + while (node->ledger.cache.cemented_count != num_blocks + 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_blocks); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), 1); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), num_blocks - 1); + system.deadline_set (10s); + while (node->active.election_winner_details_size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +/* + * This tests an issue of incorrect cemented block counts during the transition of conf height algorithms + * The scenario was as follows: + * - There is at least 1 pending write entry in the unbounded conf height processor + * - 0 blocks currently awaiting processing in the main conf height processor class + * - A block was confirmed when hit the chain in the pending write above but was not a block higher than it. + * - It must be in `confirmation_height_processor::pause ()` function so that `pause` is set (and the difference between the number + * of blocks uncemented is > unbounded_cutoff so that it hits the bounded processor), the main `run` loop on the conf height processor is iterated. + * + * This cause unbounded pending entries not to be written, and then the bounded processor would write them, causing some inconsistencies. +*/ +TEST (confirmation_height, dynamic_algorithm_no_transition_while_pending) +{ + // Repeat in case of intermittent issues not replicating the issue talked about above. + for (auto _ = 0; _ < 3; ++_) + { + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + + auto latest_genesis = node->latest (nano::test_genesis_key.pub); + std::vector> state_blocks; + auto const num_blocks = nano::confirmation_height::unbounded_cutoff - 2; + + auto add_block_to_genesis_chain = [&](nano::write_transaction & transaction) { + static int num = 0; + auto send (std::make_shared (nano::test_genesis_key.pub, latest_genesis, nano::test_genesis_key.pub, nano::genesis_amount - num - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest_genesis))); + latest_genesis = send->hash (); + state_blocks.push_back (send); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ++num; + }; + + for (auto i = 0; i < num_blocks; ++i) + { + auto transaction = node->store.tx_begin_write (); + add_block_to_genesis_chain (transaction); + } + + { + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + // To limit any data races we are not calling node.block_confirm + node->confirmation_height_processor.add (state_blocks.back ()->hash ()); + + nano::timer<> timer; + timer.start (); + while (node->confirmation_height_processor.current ().is_zero ()) + { + ASSERT_LT (timer.since_start (), 2s); + } + + // Pausing prevents any writes in the outer while loop in the confirmation height processor (implementation detail) + node->confirmation_height_processor.pause (); + + timer.restart (); + while (node->confirmation_height_processor.unbounded_processor.pending_writes_size == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + { + // Make it so that the number of blocks exceed the unbounded cutoff would go into the bounded processor (but shouldn't due to unbounded pending writes) + auto transaction = node->store.tx_begin_write (); + add_block_to_genesis_chain (transaction); + add_block_to_genesis_chain (transaction); + } + // Make sure this is at a height lower than the block in the add () call above + node->confirmation_height_processor.add (state_blocks.front ()->hash ()); + node->confirmation_height_processor.unpause (); + } + + system.deadline_set (10s); + while (node->ledger.cache.cemented_count != num_blocks + 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_blocks); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), 0); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), num_blocks); + system.deadline_set (10s); + while (node->active.election_winner_details_size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + } +} + +TEST (confirmation_height, many_accounts_send_receive_self) { nano::system system; - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.online_weight_minimum = 100; node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.active_elections_size = 400000; + nano::node_flags node_flags; + node_flags.confirmation_height_processor_mode = nano::confirmation_height_mode::unbounded; auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - // As this test can take a while extend the next frontier check +#ifndef NDEBUG + auto const num_accounts = 10000; +#else + auto const num_accounts = 100000; +#endif + + auto latest_genesis = node->latest (nano::test_genesis_key.pub); + std::vector keys; + std::vector> open_blocks; { + auto transaction = node->store.tx_begin_write (); + for (auto i = 0; i < num_accounts; ++i) + { + nano::keypair key; + keys.emplace_back (key); + + nano::send_block send (latest_genesis, key.pub, nano::genesis_amount - 1 - i, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest_genesis)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + auto open = std::make_shared (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open).code); + open_blocks.push_back (std::move (open)); + latest_genesis = send.hash (); + } + } + + // Confirm all of the accounts + for (auto & open_block : open_blocks) + { + node->block_confirm (open_block); + auto election = node->active.election (open_block->qualified_root ()); + ASSERT_NE (nullptr, election); nano::lock_guard guard (node->active.mutex); - node->active.next_frontier_check = std::chrono::steady_clock::now () + 7200s; + election->confirm_once (); + } + + system.deadline_set (100s); + auto num_blocks_to_confirm = num_accounts * 2; + while (node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != num_blocks_to_confirm) + { + ASSERT_NO_ERROR (system.poll ()); + } + + std::vector> send_blocks; + std::vector> receive_blocks; + + for (int i = 0; i < open_blocks.size (); ++i) + { + auto open_block = open_blocks[i]; + auto & keypair = keys[i]; + send_blocks.emplace_back (std::make_shared (open_block->hash (), keypair.pub, 1, keypair.prv, keypair.pub, *system.work.generate (open_block->hash ()))); + receive_blocks.emplace_back (std::make_shared (send_blocks.back ()->hash (), send_blocks.back ()->hash (), keypair.prv, keypair.pub, *system.work.generate (send_blocks.back ()->hash ()))); + } + + // Now send and receive to self + for (int i = 0; i < open_blocks.size (); ++i) + { + node->process_active (send_blocks[i]); + node->process_active (receive_blocks[i]); + } + + system.deadline_set (300s); + num_blocks_to_confirm = num_accounts * 4; + while (node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != num_blocks_to_confirm) + { + ASSERT_NO_ERROR (system.poll ()); + } + + system.deadline_set (200s); + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction = node->store.tx_begin_read (); + auto cemented_count = 0; + for (auto i (node->ledger.store.confirmation_height_begin (transaction)), n (node->ledger.store.confirmation_height_end ()); i != n; ++i) + { + cemented_count += i->second.height; + } + + ASSERT_EQ (num_blocks_to_confirm + 1, cemented_count); + ASSERT_EQ (cemented_count, node->ledger.cache.cemented_count); + + system.deadline_set (60s); + while ((node->ledger.cache.cemented_count - 1) != node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + system.deadline_set (60s); + while (node->active.election_winner_details_size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +// Same as the many_accounts_send_receive_self test, except works on the confirmation height processor directly +// as opposed to active transactions which implicitly calls confirmation height processor. +TEST (confirmation_height, many_accounts_send_receive_self_no_elections) +{ + nano::logger_mt logger; + auto path (nano::unique_path ()); + nano::mdb_store store (logger, path); + ASSERT_TRUE (!store.init_error ()); + nano::genesis genesis; + nano::stat stats; + nano::ledger ledger (store, stats); + nano::write_database_queue write_database_queue; + nano::work_pool pool (std::numeric_limits::max ()); + std::atomic stopped{ false }; + boost::latch initialized_latch{ 0 }; + + nano::block_hash block_hash_being_processed{ 0 }; + nano::confirmation_height_processor confirmation_height_processor{ ledger, write_database_queue, 10ms, logger, initialized_latch, confirmation_height_mode::automatic }; + + auto const num_accounts = 100000; + + auto latest_genesis = nano::genesis_hash; + std::vector keys; + std::vector> open_blocks; + + nano::system system; + + { + auto transaction (store.tx_begin_write ()); + store.initialize (transaction, genesis, ledger.cache); + + // Send from genesis account to all other accounts and create open block for them + for (auto i = 0; i < num_accounts; ++i) + { + nano::keypair key; + keys.emplace_back (key); + nano::send_block send (latest_genesis, key.pub, nano::genesis_amount - 1 - i, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *pool.generate (latest_genesis)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); + auto open = std::make_shared (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, *pool.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *open).code); + open_blocks.push_back (std::move (open)); + latest_genesis = send.hash (); + } + } + + for (auto & open_block : open_blocks) + { + confirmation_height_processor.add (open_block->hash ()); + } + + system.deadline_set (1000s); + auto num_blocks_to_confirm = num_accounts * 2; + while (stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != num_blocks_to_confirm) + { + ASSERT_NO_ERROR (system.poll ()); + } + + std::vector> send_blocks; + std::vector> receive_blocks; + + // Now add all send/receive blocks + { + auto transaction (store.tx_begin_write ()); + for (int i = 0; i < open_blocks.size (); ++i) + { + auto open_block = open_blocks[i]; + auto & keypair = keys[i]; + send_blocks.emplace_back (std::make_shared (open_block->hash (), keypair.pub, 1, keypair.prv, keypair.pub, *system.work.generate (open_block->hash ()))); + receive_blocks.emplace_back (std::make_shared (send_blocks.back ()->hash (), send_blocks.back ()->hash (), keypair.prv, keypair.pub, *system.work.generate (send_blocks.back ()->hash ()))); + + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send_blocks.back ()).code); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *receive_blocks.back ()).code); + } + } + + // Randomize the order that send and receive blocks are added to the confirmation height processor + std::random_device rd; + std::mt19937 g (rd ()); + std::shuffle (send_blocks.begin (), send_blocks.end (), g); + std::mt19937 g1 (rd ()); + std::shuffle (receive_blocks.begin (), receive_blocks.end (), g1); + + // Now send and receive to self + for (int i = 0; i < open_blocks.size (); ++i) + { + confirmation_height_processor.add (send_blocks[i]->hash ()); + confirmation_height_processor.add (receive_blocks[i]->hash ()); + } + + system.deadline_set (1000s); + num_blocks_to_confirm = num_accounts * 4; + while (stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in) != num_blocks_to_confirm) + { + ASSERT_NO_ERROR (system.poll ()); + } + + while (!confirmation_height_processor.current ().is_zero ()) + { + ASSERT_NO_ERROR (system.poll ()); } + auto transaction = store.tx_begin_read (); + auto cemented_count = 0; + for (auto i (ledger.store.confirmation_height_begin (transaction)), n (ledger.store.confirmation_height_end ()); i != n; ++i) + { + cemented_count += i->second.height; + } + + ASSERT_EQ (num_blocks_to_confirm + 1, cemented_count); + ASSERT_EQ (cemented_count, ledger.cache.cemented_count); +} + +// Can take up to 1 hour (recommend modifying test work difficulty base level to speed this up) +TEST (confirmation_height, prioritize_frontiers_overwrite) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto num_accounts = node->active.max_priority_cementable_frontiers * 2; nano::keypair last_keypair = nano::test_genesis_key; auto last_open_hash = node->latest (nano::test_genesis_key.pub); @@ -755,3 +1184,680 @@ TEST (confirmation_height, prioritize_frontiers_overwrite) } } } + +namespace +{ +class data +{ +public: + std::atomic awaiting_cache{ false }; + std::atomic keep_requesting_metrics{ true }; + std::shared_ptr node; + std::chrono::system_clock::time_point orig_time; + std::atomic_flag orig_time_set = ATOMIC_FLAG_INIT; +}; +class shared_data +{ +public: + nano::util::counted_completion write_completion{ 0 }; + std::atomic done{ false }; +}; + +template +void callback_process (shared_data & shared_data_a, data & data, T & all_node_data_a, std::chrono::system_clock::time_point last_updated) +{ + if (!data.orig_time_set.test_and_set ()) + { + data.orig_time = last_updated; + } + + if (data.awaiting_cache && data.orig_time != last_updated) + { + data.keep_requesting_metrics = false; + } + if (data.orig_time != last_updated) + { + data.awaiting_cache = true; + data.orig_time = last_updated; + } + shared_data_a.write_completion.increment (); +}; +} + +TEST (telemetry, ongoing_requests) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + ASSERT_EQ (0, node_client->telemetry->telemetry_data_size ()); + ASSERT_EQ (0, node_server->telemetry->telemetry_data_size ()); + ASSERT_EQ (0, node_client->stats.count (nano::stat::type::bootstrap, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (0, node_client->stats.count (nano::stat::type::bootstrap, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + + system.deadline_set (20s); + while (node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in) != 1 || node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in) != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Wait till the next ongoing will be called, and add a 1s buffer for the actual processing + auto time = std::chrono::steady_clock::now (); + while (std::chrono::steady_clock::now () < (time + node_client->telemetry->cache_plus_buffer_cutoff_time () + 1s)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (2, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (2, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (2, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + ASSERT_EQ (2, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (2, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (2, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); +} + +namespace nano +{ +namespace transport +{ + TEST (telemetry, simultaneous_requests) + { + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + const auto num_nodes = 4; + for (int i = 0; i < num_nodes; ++i) + { + system.add_node (node_flags); + } + + wait_peer_connections (system); + + std::vector threads; + const auto num_threads = 4; + + std::array node_data{}; + for (auto i = 0; i < num_nodes; ++i) + { + node_data[i].node = system.nodes[i]; + } + + shared_data shared_data; + + // Create a few threads where each node sends out telemetry request messages to all other nodes continuously, until the cache it reached and subsequently expired. + // The test waits until all telemetry_ack messages have been received. + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back ([&node_data, &shared_data]() { + while (std::any_of (node_data.cbegin (), node_data.cend (), [](auto const & data) { return data.keep_requesting_metrics.load (); })) + { + for (auto & data : node_data) + { + // Keep calling get_metrics_async until the cache has been saved and then become outdated (after a certain period of time) for each node + if (data.keep_requesting_metrics) + { + shared_data.write_completion.increment_required_count (); + + // Pick first peer to be consistent + auto peer = data.node->network.tcp_channels.channels[0].channel; + data.node->telemetry->get_metrics_single_peer_async (peer, [&shared_data, &data, &node_data](nano::telemetry_data_response const & telemetry_data_response_a) { + ASSERT_FALSE (telemetry_data_response_a.error); + callback_process (shared_data, data, node_data, telemetry_data_response_a.telemetry_data.timestamp); + }); + } + std::this_thread::sleep_for (1ms); + } + } + + shared_data.write_completion.await_count_for (20s); + shared_data.done = true; + }); + } + + system.deadline_set (30s); + while (!shared_data.done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_TRUE (std::all_of (node_data.begin (), node_data.end (), [](auto const & data) { return !data.keep_requesting_metrics; })); + + for (auto & thread : threads) + { + thread.join (); + } + } +} +} + +TEST (telemetry, under_load) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + auto node = system.add_node (node_config, node_flags); + node_config.peering_port = nano::get_available_port (); + auto node1 = system.add_node (node_config, node_flags); + nano::genesis genesis; + nano::keypair key; + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto latest_genesis = node->latest (nano::test_genesis_key.pub); + auto num_blocks = 150000; + auto send (std::make_shared (nano::test_genesis_key.pub, latest_genesis, nano::test_genesis_key.pub, nano::genesis_amount - num_blocks, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest_genesis))); + node->process_active (send); + latest_genesis = send->hash (); + auto open (std::make_shared (key.pub, 0, key.pub, num_blocks, send->hash (), key.prv, key.pub, *system.work.generate (key.pub))); + node->process_active (open); + auto latest_key = open->hash (); + + auto thread_func = [key1, &system, node, num_blocks](nano::keypair const & keypair, nano::block_hash const & latest, nano::uint128_t const initial_amount) { + auto latest_l = latest; + for (int i = 0; i < num_blocks; ++i) + { + auto send (std::make_shared (keypair.pub, latest_l, keypair.pub, initial_amount - i - 1, key1.pub, keypair.prv, keypair.pub, *system.work.generate (latest_l))); + latest_l = send->hash (); + node->process_active (send); + } + }; + + std::thread thread1 (thread_func, nano::test_genesis_key, latest_genesis, nano::genesis_amount - num_blocks); + std::thread thread2 (thread_func, key, latest_key, num_blocks); + + ASSERT_TIMELY (200s, node1->ledger.cache.block_count == num_blocks * 2 + 3); + + thread1.join (); + thread2.join (); + + for (auto const & node : system.nodes) + { + ASSERT_EQ (0, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::failed_send_telemetry_req)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::request_within_protection_cache_zone)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::unsolicited_telemetry_ack)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::no_response_received)); + } +} + +TEST (telemetry, all_peers_use_single_request_cache) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_ongoing_telemetry_requests = true; + node_flags.disable_initial_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + // Request telemetry metrics + nano::telemetry_data telemetry_data; + { + std::atomic done{ false }; + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &telemetry_data](nano::telemetry_data_response const & response_a) { + telemetry_data = response_a.telemetry_data; + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + auto responses = node_client->telemetry->get_metrics (); + ASSERT_EQ (telemetry_data, responses.begin ()->second); + + // Confirm only 1 request was made + ASSERT_EQ (1, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (0, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (1, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + ASSERT_EQ (0, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (1, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (0, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + + std::this_thread::sleep_for (node_server->telemetry->cache_plus_buffer_cutoff_time ()); + + // Should be empty + responses = node_client->telemetry->get_metrics (); + ASSERT_TRUE (responses.empty ()); + + { + std::atomic done{ false }; + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + node_client->telemetry->get_metrics_single_peer_async (channel, [&done, &telemetry_data](nano::telemetry_data_response const & response_a) { + telemetry_data = response_a.telemetry_data; + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + responses = node_client->telemetry->get_metrics (); + ASSERT_EQ (telemetry_data, responses.begin ()->second); + + ASSERT_EQ (2, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (0, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (2, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + ASSERT_EQ (0, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); + ASSERT_EQ (2, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in)); + ASSERT_EQ (0, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); +} + +TEST (telemetry, many_nodes) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_ongoing_telemetry_requests = true; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_request_loop = true; + // The telemetry responses can timeout if using a large number of nodes under sanitizers, so lower the number. + const auto num_nodes = (is_sanitizer_build || nano::running_within_valgrind ()) ? 4 : 10; + for (auto i = 0; i < num_nodes; ++i) + { + nano::node_config node_config (nano::get_available_port (), system.logging); + // Make a metric completely different for each node so we can check afterwards that there are no duplicates + node_config.bandwidth_limit = 100000 + i; + + auto node = std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, node_config, system.work, node_flags); + node->start (); + system.nodes.push_back (node); + } + + // Merge peers after creating nodes as some backends (RocksDB) can take a while to initialize nodes (Windows/Debug for instance) + // and timeouts can occur between nodes while starting up many nodes synchronously. + for (auto const & node : system.nodes) + { + for (auto const & other_node : system.nodes) + { + if (node != other_node) + { + node->network.merge_peer (other_node->network.endpoint ()); + } + } + } + + wait_peer_connections (system); + + // Give all nodes a non-default number of blocks + nano::keypair key; + nano::genesis genesis; + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Mxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ())); + for (auto node : system.nodes) + { + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + + // This is the node which will request metrics from all other nodes + auto node_client = system.nodes.front (); + + std::mutex mutex; + std::vector telemetry_datas; + auto peers = node_client->network.list (num_nodes - 1); + ASSERT_EQ (peers.size (), num_nodes - 1); + for (auto const & peer : peers) + { + node_client->telemetry->get_metrics_single_peer_async (peer, [&telemetry_datas, &mutex](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + nano::lock_guard guard (mutex); + telemetry_datas.push_back (response_a.telemetry_data); + }); + } + + system.deadline_set (20s); + nano::unique_lock lk (mutex); + while (telemetry_datas.size () != num_nodes - 1) + { + lk.unlock (); + ASSERT_NO_ERROR (system.poll ()); + lk.lock (); + } + + // Check the metrics + nano::network_params params; + for (auto & data : telemetry_datas) + { + ASSERT_EQ (data.unchecked_count, 0); + ASSERT_EQ (data.cemented_count, 1); + ASSERT_LE (data.peer_count, 9); + ASSERT_EQ (data.account_count, 1); + ASSERT_TRUE (data.block_count == 2); + ASSERT_EQ (data.protocol_version, params.protocol.telemetry_protocol_version_min); + ASSERT_GE (data.bandwidth_cap, 100000); + ASSERT_LT (data.bandwidth_cap, 100000 + system.nodes.size ()); + ASSERT_EQ (data.major_version, nano::get_major_node_version ()); + ASSERT_EQ (data.minor_version, nano::get_minor_node_version ()); + ASSERT_EQ (data.patch_version, nano::get_patch_node_version ()); + ASSERT_EQ (data.pre_release_version, nano::get_pre_release_node_version ()); + ASSERT_EQ (data.maker, 0); + ASSERT_LT (data.uptime, 100); + ASSERT_EQ (data.genesis_block, genesis.hash ()); + ASSERT_LE (data.timestamp, std::chrono::system_clock::now ()); + ASSERT_EQ (data.active_difficulty, system.nodes.front ()->active.active_difficulty ()); + } + + // We gave some nodes different bandwidth caps, confirm they are not all the same + auto bandwidth_cap = telemetry_datas.front ().bandwidth_cap; + telemetry_datas.erase (telemetry_datas.begin ()); + auto all_bandwidth_limits_same = std::all_of (telemetry_datas.begin (), telemetry_datas.end (), [bandwidth_cap](auto & telemetry_data) { + return telemetry_data.bandwidth_cap == bandwidth_cap; + }); + ASSERT_FALSE (all_bandwidth_limits_same); +} + +// Similar to signature_checker.boundary_checks but more exhaustive. Can take up to 1 minute +TEST (signature_checker, mass_boundary_checks) +{ + // sizes container must be in incrementing order + std::vector sizes{ 0, 1 }; + auto add_boundary = [&sizes](size_t boundary) { + sizes.insert (sizes.end (), { boundary - 1, boundary, boundary + 1 }); + }; + + for (auto i = 1; i <= 10; ++i) + { + add_boundary (nano::signature_checker::batch_size * i); + } + + for (auto num_threads = 0; num_threads < 5; ++num_threads) + { + nano::signature_checker checker (num_threads); + auto max_size = *(sizes.end () - 1); + std::vector hashes; + hashes.reserve (max_size); + std::vector messages; + messages.reserve (max_size); + std::vector lengths; + lengths.reserve (max_size); + std::vector pub_keys; + pub_keys.reserve (max_size); + std::vector signatures; + signatures.reserve (max_size); + nano::keypair key; + nano::state_block block (key.pub, 0, key.pub, 0, 0, key.prv, key.pub, 0); + + auto last_size = 0; + for (auto size : sizes) + { + // The size needed to append to existing containers, saves re-initializing from scratch each iteration + auto extra_size = size - last_size; + + std::vector verifications; + verifications.resize (size); + for (auto i (0); i < extra_size; ++i) + { + hashes.push_back (block.hash ()); + messages.push_back (hashes.back ().bytes.data ()); + lengths.push_back (sizeof (decltype (hashes)::value_type)); + pub_keys.push_back (block.hashables.account.bytes.data ()); + signatures.push_back (block.signature.bytes.data ()); + } + nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () }; + checker.verify (check); + bool all_valid = std::all_of (verifications.cbegin (), verifications.cend (), [](auto verification) { return verification == 1; }); + ASSERT_TRUE (all_valid); + last_size = size; + } + } +} + +// Test the node epoch_upgrader with a large number of accounts and threads +// Possible to manually add work peers +TEST (node, mass_epoch_upgrader) +{ + auto perform_test = [](size_t const batch_size) { + unsigned threads = 5; + size_t total_accounts = 2500; + +#ifndef NDEBUG + total_accounts /= 5; +#endif + + struct info + { + nano::keypair key; + nano::block_hash pending_hash; + }; + + std::vector opened (total_accounts / 2); + std::vector unopened (total_accounts / 2); + + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.work_threads = 4; + //node_config.work_peers = { { "192.168.1.101", 7000 } }; + auto & node = *system.add_node (node_config); + + auto balance = node.balance (nano::test_genesis_key.pub); + auto latest = node.latest (nano::test_genesis_key.pub); + nano::uint128_t amount = 1; + + // Send to all accounts + std::array *, 2> all{ &opened, &unopened }; + for (auto & accounts : all) + { + for (auto & info : *accounts) + { + balance -= amount; + nano::state_block_builder builder; + std::error_code ec; + auto block = builder + .account (nano::test_genesis_key.pub) + .previous (latest) + .balance (balance) + .link (info.key.pub) + .representative (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*node.work_generate_blocking (latest, nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_0, false, false, false)))) + .build (ec); + ASSERT_FALSE (ec); + ASSERT_NE (nullptr, block); + ASSERT_EQ (nano::process_result::progress, node.process (*block).code); + latest = block->hash (); + info.pending_hash = block->hash (); + } + } + ASSERT_EQ (1 + total_accounts, node.ledger.cache.block_count); + ASSERT_EQ (1, node.ledger.cache.account_count); + + // Receive for half of accounts + for (auto const & info : opened) + { + nano::state_block_builder builder; + std::error_code ec; + auto block = builder + .account (info.key.pub) + .previous (0) + .balance (amount) + .link (info.pending_hash) + .representative (info.key.pub) + .sign (info.key.prv, info.key.pub) + .work (*node.work_generate_blocking (info.key.pub, nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_0, false, false, false)))) + .build (ec); + ASSERT_FALSE (ec); + ASSERT_NE (nullptr, block); + ASSERT_EQ (nano::process_result::progress, node.process (*block).code); + } + ASSERT_EQ (1 + total_accounts + opened.size (), node.ledger.cache.block_count); + ASSERT_EQ (1 + opened.size (), node.ledger.cache.account_count); + + nano::keypair epoch_signer (nano::test_genesis_key); + + auto const block_count_before = node.ledger.cache.block_count.load (); + auto const total_to_upgrade = 1 + total_accounts; + std::cout << "Mass upgrading " << total_to_upgrade << " accounts" << std::endl; + while (node.ledger.cache.block_count != block_count_before + total_to_upgrade) + { + auto const pre_upgrade = node.ledger.cache.block_count.load (); + auto upgrade_count = std::min (batch_size, block_count_before + total_to_upgrade - pre_upgrade); + ASSERT_FALSE (node.epoch_upgrader (epoch_signer.prv.as_private_key (), nano::epoch::epoch_1, upgrade_count, threads)); + // Already ongoing - should fail + ASSERT_TRUE (node.epoch_upgrader (epoch_signer.prv.as_private_key (), nano::epoch::epoch_1, upgrade_count, threads)); + system.deadline_set (60s); + while (node.ledger.cache.block_count != pre_upgrade + upgrade_count) + { + ASSERT_NO_ERROR (system.poll ()); + std::this_thread::sleep_for (200ms); + std::cout << node.ledger.cache.block_count - block_count_before << " / " << total_to_upgrade << std::endl; + } + std::this_thread::sleep_for (50ms); + } + auto expected_blocks = block_count_before + total_accounts + 1; + ASSERT_EQ (expected_blocks, node.ledger.cache.block_count); + // Check upgrade + { + auto transaction (node.store.tx_begin_read ()); + ASSERT_EQ (expected_blocks, node.store.block_count (transaction).sum ()); + for (auto i (node.store.latest_begin (transaction)); i != node.store.latest_end (); ++i) + { + nano::account_info info (i->second); + ASSERT_EQ (info.epoch (), nano::epoch::epoch_1); + } + } + }; + // Test with a limited number of upgrades and an unlimited + perform_test (42); + perform_test (std::numeric_limits::max ()); +} + +TEST (node, mass_block_new) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (node_config); + node.network_params.network.request_interval_ms = 500; + +#ifndef NDEBUG + auto const num_blocks = 5000; +#else + auto const num_blocks = 50000; +#endif + std::cout << num_blocks << " x4 blocks" << std::endl; + + // Upgrade to epoch_2 + system.upgrade_genesis_epoch (node, nano::epoch::epoch_1); + system.upgrade_genesis_epoch (node, nano::epoch::epoch_2); + + auto next_block_count = num_blocks + 3; + auto process_all = [&](std::vector> const & blocks_a) { + for (auto const & block : blocks_a) + { + node.process_active (block); + } + ASSERT_TIMELY (200s, node.ledger.cache.block_count == next_block_count); + next_block_count += num_blocks; + node.block_processor.flush (); + // Clear all active + { + nano::lock_guard guard (node.active.mutex); + node.active.roots.clear (); + node.active.blocks.clear (); + } + }; + + nano::genesis genesis; + nano::keypair key; + std::vector keys (num_blocks); + nano::state_block_builder builder; + std::vector> send_blocks; + auto send_threshold (nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_2, true, false, false))); + auto latest_genesis = node.latest (nano::test_genesis_key.pub); + for (auto i = 0; i < num_blocks; ++i) + { + auto send = builder.make_block () + .account (nano::test_genesis_key.pub) + .previous (latest_genesis) + .balance (nano::genesis_amount - i - 1) + .representative (nano::test_genesis_key.pub) + .link (keys[i].pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*system.work.generate (nano::work_version::work_1, latest_genesis, send_threshold)) + .build (); + latest_genesis = send->hash (); + send_blocks.push_back (std::move (send)); + } + std::cout << "Send blocks built, start processing" << std::endl; + nano::timer<> timer; + timer.start (); + process_all (send_blocks); + std::cout << "Send blocks time: " << timer.stop ().count () << " " << timer.unit () << "\n\n"; + + std::vector> open_blocks; + auto receive_threshold (nano::work_threshold (nano::work_version::work_1, nano::block_details (nano::epoch::epoch_2, false, true, false))); + for (auto i = 0; i < num_blocks; ++i) + { + auto const & key = keys[i]; + auto open = builder.make_block () + .account (key.pub) + .previous (0) + .balance (1) + .representative (key.pub) + .link (send_blocks[i]->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (nano::work_version::work_1, key.pub, receive_threshold)) + .build (); + open_blocks.push_back (std::move (open)); + } + std::cout << "Open blocks built, start processing" << std::endl; + timer.restart (); + process_all (open_blocks); + std::cout << "Open blocks time: " << timer.stop ().count () << " " << timer.unit () << "\n\n"; + + // These blocks are from each key to themselves + std::vector> send_blocks2; + for (auto i = 0; i < num_blocks; ++i) + { + auto const & key = keys[i]; + auto const & latest = open_blocks[i]; + auto send2 = builder.make_block () + .account (key.pub) + .previous (latest->hash ()) + .balance (0) + .representative (key.pub) + .link (key.pub) + .sign (key.prv, key.pub) + .work (*system.work.generate (nano::work_version::work_1, latest->hash (), send_threshold)) + .build (); + send_blocks2.push_back (std::move (send2)); + } + std::cout << "Send2 blocks built, start processing" << std::endl; + timer.restart (); + process_all (send_blocks2); + std::cout << "Send2 blocks time: " << timer.stop ().count () << " " << timer.unit () << "\n\n"; + + // Each key receives the previously sent blocks + std::vector> receive_blocks; + for (auto i = 0; i < num_blocks; ++i) + { + auto const & key = keys[i]; + auto const & latest = send_blocks2[i]; + auto send2 = builder.make_block () + .account (key.pub) + .previous (latest->hash ()) + .balance (1) + .representative (key.pub) + .link (latest->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (nano::work_version::work_1, latest->hash (), receive_threshold)) + .build (); + receive_blocks.push_back (std::move (send2)); + } + std::cout << "Receive blocks built, start processing" << std::endl; + timer.restart (); + process_all (receive_blocks); + std::cout << "Receive blocks time: " << timer.stop ().count () << " " << timer.unit () << "\n\n"; +} diff --git a/rep_weights_beta.bin b/rep_weights_beta.bin index a76b59a205..ece928caae 100644 Binary files a/rep_weights_beta.bin and b/rep_weights_beta.bin differ diff --git a/rep_weights_live.bin b/rep_weights_live.bin index 447a0a4e0e..8bcb7d1885 100644 Binary files a/rep_weights_live.bin and b/rep_weights_live.bin differ diff --git a/util/build_prep/bootstrap_boost.sh b/util/build_prep/bootstrap_boost.sh index 47033144bf..1caddc25fb 100755 --- a/util/build_prep/bootstrap_boost.sh +++ b/util/build_prep/bootstrap_boost.sh @@ -5,17 +5,21 @@ buildArgs=() useClang='false' useLibCXX='false' keepArchive='false' +LINK_TYPE=('link=static') debugLevel=0 +buildThreads=1 buildCArgs=() buildCXXArgs=() buildLDArgs=() -boostVersion='1.67' -while getopts 'hmcCkpvB:' OPT; do +boostVersion='1.69' +while getopts 'hmscCkpvB:j:' OPT; do case "${OPT}" in h) echo "Usage: bootstrap_boost.sh [-hmcCkpv] [-B ]" echo " -h This help" + echo " -s Build Shared and static libs, default is static only" echo " -m Build a minimal set of libraries needed for Nano" + echo " -j Number of threads to build with" echo " -c Use Clang" echo " -C Use libc++ when using Clang" echo " -k Keep the downloaded archive file" @@ -25,9 +29,15 @@ while getopts 'hmcCkpvB:' OPT; do echo " -B Specify version of Boost to build" exit 0 ;; + s) + LINK_TYPE+=('link=shared') + ;; m) bootstrapArgs+=('--with-libraries=system,thread,log,filesystem,program_options') ;; + j) + buildThreads=${OPTARG} + ;; c) useClang='true' ;; @@ -73,21 +83,26 @@ if [ "${useClang}" = 'true' ]; then fi case "${boostVersion}" in - 1.67) - BOOST_BASENAME=boost_1_67_0 - BOOST_URL=https://dl.bintray.com/boostorg/release/1.67.0/source/${BOOST_BASENAME}.tar.bz2 - BOOST_ARCHIVE_SHA256='2684c972994ee57fc5632e03bf044746f6eb45d4920c343937a465fd67a5adba' - ;; 1.69) BOOST_BASENAME=boost_1_69_0 - BOOST_URL=https://dl.bintray.com/boostorg/release/1.69.0/source/${BOOST_BASENAME}.tar.bz2 + BOOST_URL=https://sourceforge.net/projects/boost/files/boost/1.69.0/${BOOST_BASENAME}.tar.bz2/download BOOST_ARCHIVE_SHA256='8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406' ;; 1.70) BOOST_BASENAME=boost_1_70_0 - BOOST_URL=https://dl.bintray.com/boostorg/release/1.70.0/source/${BOOST_BASENAME}.tar.bz2 + BOOST_URL=https://sourceforge.net/projects/boost/files/boost/1.70.0/${BOOST_BASENAME}.tar.bz2/download BOOST_ARCHIVE_SHA256='430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778' ;; + 1.72) + BOOST_BASENAME=boost_1_72_0 + BOOST_URL=https://sourceforge.net/projects/boost/files/boost/1.72.0/${BOOST_BASENAME}.tar.bz2/download + BOOST_ARCHIVE_SHA256='59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722' + ;; + 1.73) + BOOST_BASENAME=boost_1_73_0 + BOOST_URL=https://sourceforge.net/projects/boost/files/boost/1.73.0/${BOOST_BASENAME}.tar.bz2/download + BOOST_ARCHIVE_SHA256='4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402' + ;; *) echo "Unsupported Boost version: ${boostVersion}" >&2 exit 1 @@ -126,7 +141,7 @@ tar xf "${BOOST_ARCHIVE}" pushd "${BOOST_BASENAME}" ./bootstrap.sh "${bootstrapArgs[@]}" -./b2 -d${debugLevel} --prefix="${BOOST_ROOT}" link=static "${buildArgs[@]}" install +./b2 -d${debugLevel} -j${buildThreads} --prefix="${BOOST_ROOT}" ${LINK_TYPE[@]} "${buildArgs[@]}" install popd rm -rf "${BOOST_BASENAME}" diff --git a/util/build_prep/centos/prep.sh.in b/util/build_prep/centos/prep.sh.in index 3786ea6a4b..9299f62172 100644 --- a/util/build_prep/centos/prep.sh.in +++ b/util/build_prep/centos/prep.sh.in @@ -28,8 +28,8 @@ yes | yum install -y llvm-toolset-7-cmake devtoolset-7-llvm|| exit 1 exit 1 fi - if ! version_min 'boost --version' 1.66.999; then - echo "boost version too low (1.67.0+ required)" >&2 + if ! version_min 'boost --version' 1.68.999; then + echo "boost version too low (1.69.0+ required)" >&2 exit 1 fi diff --git a/util/build_prep/fetch_boost.sh b/util/build_prep/fetch_boost.sh index 0f1a88b640..8663143716 100755 --- a/util/build_prep/fetch_boost.sh +++ b/util/build_prep/fetch_boost.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash OS=`uname` -TRAVIS_COMPILER="${TRAVIS_COMPILER:-clang}" +COMPILER="${COMPILER:-clang}" pushd /tmp -wget -O boost-$OS-$TRAVIS_COMPILER-latest.tgz https://s3.us-east-2.amazonaws.com/repo.nano.org/artifacts/boost-$OS-$TRAVIS_COMPILER-latest.tgz -tar -zxf boost-$OS-$TRAVIS_COMPILER-latest.tgz +wget -O boost-$OS-$COMPILER.tgz https://s3.us-east-2.amazonaws.com/repo.nano.org/artifacts/boost-$OS-$COMPILER-1.70-full.tgz +tar -zxf boost-$OS-$COMPILER.tgz mv tmp/* . rm -fr tmp popd diff --git a/util/build_prep/macosx/prep.sh.in b/util/build_prep/macosx/prep.sh.in index d9ff2641c4..ea330278d0 100644 --- a/util/build_prep/macosx/prep.sh.in +++ b/util/build_prep/macosx/prep.sh.in @@ -50,8 +50,8 @@ if ! have boost; then exit 1 fi -if ! version_min 'boost --version' 1.66.999; then - echo "boost version too low (1.67.0+ required)" >&2 +if ! version_min 'boost --version' 1.68.999; then + echo "boost version too low (1.69.0+ required)" >&2 exit 1 fi diff --git a/util/build_prep/ubuntu/prep.sh.in b/util/build_prep/ubuntu/prep.sh.in index bf8233be75..b4dbb78f06 100644 --- a/util/build_prep/ubuntu/prep.sh.in +++ b/util/build_prep/ubuntu/prep.sh.in @@ -29,8 +29,8 @@ if ! have boost; then exit 1 fi -if ! version_min 'boost --version' 1.66.999; then - echo "boost version too low (1.67.0+ required)" >&2 +if ! version_min 'boost --version' 1.68.999; then + echo "boost version too low (1.69.0+ required)" >&2 exit 1 fi boost_dir="$(boost --install-prefix)" diff --git a/util/changelog.py b/util/changelog.py new file mode 100644 index 0000000000..556519c349 --- /dev/null +++ b/util/changelog.py @@ -0,0 +1,231 @@ +import argparse +import copy + +""" +Changelog generation script, requires PAT see https://github.com/settings/tokens +Caveats V20 and prior release tags are tips on their respective release branches +If you try to use a start tag with one of these a full changelog will be generated +since the commit wont appear in your iterations + +""" + +try: + from github import Github + from mdutils import MdUtils +except BaseException: + exit("Error: run 'pip install PyGithub mdutils'") + +SECTIONS = { + "Major Changes": [ + "major", + ], + "Protocol Changes": [ + "protocol change", + ], + "Node Configuration Updates": [ + "toml", + "configuration default change", + ], + "RPC Updates": [ + "rpc", + ], + "IPC Updates": [ + "ipc", + ], + "Websocket Updates": [ + "websockets", + ], + "CLI Updates": [ + "cli", + ], + "Deprecation/Removal": [ + "deprecation", + "removal", + ], + "Developer Wallet": [ + "qt wallet", + ], + "Developer/Debug Options": [ + "debug", + "logging", + ], + "Fixed Bugs": [ + "bug", + ], + "Implemented Enhancements": [ + "enhancement", + "functionality quality improvements", + "non-functional change", + "performance", + "quality improvements", + ], + "Build, Test, Automation, & Chores": [ + "build-error", + "documentation", + "routine", + "sanitizers", + "static-analysis", + "tool", + "unit test", + "universe", + ], + "Other": [] +} + + +class cliArgs(): + def __init__(self): + parse = argparse.ArgumentParser( + prog="changelog", + description="Generate Changelogs between tags or commits" + ) + parse.add_argument( + '-r', '--repo', + help=" to generate logs for", + type=str, action="store", + default='nanocurrency/nano-node', + ) + parse.add_argument( + '-s', '--start', + help="Starting reference for Changelog", + type=str, action="store", + required=True, + ) + parse.add_argument( + '-e', '--end', + help="Ending reference for Changelog", + type=str, action="store", + required=True, + ) + parse.add_argument( + '--pat', + help="Personal Access Token", + type=str, action="store", + required=True, + ) + options = parse.parse_args() + self.repo = options.repo + self.start = options.start + self.end = options.end + self.pat = options.pat + + def __repr__(self): + return "" \ + .format(self.repo, self.start, self.end, self.pat) + + def __str__(self): + return "Generating a changelog for {0} starting with {1} " \ + "and ending with {2}".format(self.repo, self.start, self.end) + + +class generateTree: + def __init__(self, args): + github = Github(args.pat) + self.repo = github.get_repo(args.repo) + self.start = args.start + self.end = args.end + try: + self.startCommit = self.repo.get_commit(args.start) + except BaseException: + exit("Error finding commit for " + args.start) + try: + self.endCommit = self.repo.get_commit(args.end) + except BaseException: + exit("Error finding commit for " + args.end) + commits = self.repo.get_commits(sha=self.endCommit.sha) + self.commits = {} + for commit in commits: + if commit.sha == self.startCommit.sha: + break + else: + for pull in commit.get_pulls(): + labels = [] + for label in pull.labels: + labels.append(label.name) + self.commits[pull.number] = { + "Title": pull.title, + "Url": pull.html_url, + "labels": labels + } + + def __repr__(self): + return " 0: + result[a] = sect[a] + return result + + +if __name__ == "__main__": + args = cliArgs() + repo = generateTree(args) + generateMarkdown(repo)