diff --git a/.github/workflows/beta_artifacts.yml b/.github/workflows/beta_artifacts.yml index 8301dcd5d3..04d2c76744 100644 --- a/.github/workflows/beta_artifacts.yml +++ b/.github/workflows/beta_artifacts.yml @@ -51,10 +51,22 @@ jobs: 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 }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} windows_job: runs-on: windows-latest diff --git a/.github/workflows/live_artifacts.yml b/.github/workflows/live_artifacts.yml index 77aeff8b8c..0c85a7fad0 100644 --- a/.github/workflows/live_artifacts.yml +++ b/.github/workflows/live_artifacts.yml @@ -50,10 +50,22 @@ jobs: 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 }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} windows_job: runs-on: windows-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index e19ba83d6a..5fbed76a89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,19 @@ cmake_minimum_required (VERSION 3.4) -if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12") +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_EQUAL "3.13") +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.11) + # compatibility for boost import targets use bundled 3.11 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 "") diff --git a/ci/actions/deploy.sh b/ci/actions/deploy.sh index e203ed034b..7d0fc51bba 100755 --- a/ci/actions/deploy.sh +++ b/ci/actions/deploy.sh @@ -12,7 +12,11 @@ else fi if [[ "$OS" == 'Linux' ]]; then + sha256sum $GITHUB_WORKSPACE/build/nano-node-*-Linux.tar.bz2 | cut -f1 -d' ' > $GITHUB_WORKSPACE/build/nano-node-$TAG-Linux.tar.bz2.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/build/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 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/windows/build.ps1 b/ci/actions/windows/build.ps1 index 9fe4dc7d47..5868c9b4c6 100644 --- a/ci/actions/windows/build.ps1 +++ b/ci/actions/windows/build.ps1 @@ -4,7 +4,8 @@ if (${env:artifact} -eq 1) { if ( ${env:BETA} -eq 1 ) { $env:NETWORK_CFG = "beta" $env:BUILD_TYPE = "RelWithDebInfo" - } else { + } + else { $env:NETWORK_CFG = "live" $env:BUILD_TYPE = "Release" } @@ -14,11 +15,13 @@ if (${env:artifact} -eq 1) { $env:CI = "-DCI_BUILD=ON" $env:RUN = "artifact" -} else { +} +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 { + } + else { $env:BUILD_TYPE = "Debug" $env:ROCKS_LIB = '-DROCKSDB_LIBRARIES="c:\vcpkg\installed\x64-windows-static\debug\lib\rocksdbd.lib"' } diff --git a/ci/actions/windows/deploy.ps1 b/ci/actions/windows/deploy.ps1 index 2e766bf23d..0dbfa460a4 100644 --- a/ci/actions/windows/deploy.ps1 +++ b/ci/actions/windows/deploy.ps1 @@ -1,12 +1,19 @@ $ErrorActionPreference = "Continue" if ( ${env:BETA} -eq 1 ) { - $network_cfg="beta" -} else { - $network_cfg="live" + $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.sh256" +(Get-FileHash $zip).hash | Out-file -FilePath "$zip.sh256" -$exe=Resolve-Path -Path $env:GITHUB_WORKSPACE\build\nano-node-*-win64.exe -$zip=Resolve-Path -Path $env:GITHUB_WORKSPACE\build\nano-node-*-win64.zip 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 "$zip" s3://repo.nano.org/$network_cfg/binaries/nano-node-$env:TAG-win64.zip --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers \ No newline at end of file +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/install_deps.ps1 b/ci/actions/windows/install_deps.ps1 index 29909727b7..4edbe17fb0 100644 --- a/ci/actions/windows/install_deps.ps1 +++ b/ci/actions/windows/install_deps.ps1 @@ -51,8 +51,8 @@ function Get-RedirectedUri { $qt5_root = "c:\qt" $rocksdb_url = Get-RedirectedUri "https://repo.nano.org/artifacts/rocksdb-msvc14.1-latest.7z" -$qt5base_url = Get-RedirectedUri "https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5131/qt.qt5.5131.win64_msvc2017_64/5.13.1-0-201909031231qtbase-Windows-Windows_10-MSVC2017-Windows-Windows_10-X86_64.7z" -$qt5winextra_url = Get-RedirectedUri "https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5131/qt.qt5.5131.win64_msvc2017_64/5.13.1-0-201909031231qtwinextras-Windows-Windows_10-MSVC2017-Windows-Windows_10-X86_64.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" diff --git a/cmake/legacyModules/FindBoost.cmake b/cmake/legacyModules/FindBoost.cmake new file mode 100644 index 0000000000..8d44aee0b9 --- /dev/null +++ b/cmake/legacyModules/FindBoost.cmake @@ -0,0 +1,2098 @@ +# 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_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_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) +cmake_policy(SET CMP0057 NEW) # if IN_LIST + +#------------------------------------------------------------------------------- +# Before we go searching, check whether boost-cmake is available, unless the +# user specifically asked NOT to search for boost-cmake. +# +# If Boost_DIR is set, this behaves as any find_package call would. If not, +# it looks at BOOST_ROOT and BOOSTROOT to find Boost. +# +if (NOT Boost_NO_BOOST_CMAKE) + # If Boost_DIR is not set, look for BOOSTROOT and BOOST_ROOT as alternatives, + # since these are more conventional for Boost. + if ("$ENV{Boost_DIR}" STREQUAL "") + if (NOT "$ENV{BOOST_ROOT}" STREQUAL "") + set(ENV{Boost_DIR} $ENV{BOOST_ROOT}) + elseif (NOT "$ENV{BOOSTROOT}" STREQUAL "") + set(ENV{Boost_DIR} $ENV{BOOSTROOT}) + endif() + endif() + + # Do the same find_package call but look specifically for the CMake version. + # Note that args are passed in the Boost_FIND_xxxxx variables, so there is no + # need to delegate them to this find_package call. + find_package(Boost QUIET NO_MODULE) + mark_as_advanced(Boost_DIR) + + # If we found boost-cmake, then we're done. Print out what we found. + # Otherwise let the rest of the module try to find it. + if (Boost_FOUND) + message(STATUS "Boost ${Boost_FIND_VERSION} found.") + if (Boost_FIND_COMPONENTS) + message(STATUS "Found Boost components:\n ${Boost_FIND_COMPONENTS}") + endif() + # Restore project's policies + cmake_policy(POP) + return() + endif() +endif() + + +#------------------------------------------------------------------------------- +# FindBoost functions & macros +# + +############################################ +# +# Check the existence of the libraries. +# +############################################ +# This macro was taken directly from the FindQt4.cmake file that is included +# with the CMake distribution. This is NOT my work. All work was done by the +# original authors of the FindQt4.cmake file. Only minor modifications were +# made to remove references to Qt and make this file more generally applicable +# And ELSE/ENDIF pairs were removed for readability. +######################################################################### + +macro(_Boost_ADJUST_LIB_VARS basename) + if(Boost_INCLUDE_DIR ) + if(Boost_${basename}_LIBRARY_DEBUG AND Boost_${basename}_LIBRARY_RELEASE) + # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for + # single-config generators, set optimized and debug libraries + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(_isMultiConfig OR CMAKE_BUILD_TYPE) + set(Boost_${basename}_LIBRARY optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) + else() + # For single-config generators where CMAKE_BUILD_TYPE has no value, + # just use the release libraries + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) + endif() + # FIXME: This probably should be set for both cases + set(Boost_${basename}_LIBRARIES optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) + endif() + + # if only the release version was found, set the debug variable also to the release version + if(Boost_${basename}_LIBRARY_RELEASE AND NOT Boost_${basename}_LIBRARY_DEBUG) + set(Boost_${basename}_LIBRARY_DEBUG ${Boost_${basename}_LIBRARY_RELEASE}) + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE}) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE}) + endif() + + # if only the debug version was found, set the release variable also to the debug version + if(Boost_${basename}_LIBRARY_DEBUG AND NOT Boost_${basename}_LIBRARY_RELEASE) + set(Boost_${basename}_LIBRARY_RELEASE ${Boost_${basename}_LIBRARY_DEBUG}) + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_DEBUG}) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_DEBUG}) + endif() + + # If the debug & release library ends up being the same, omit the keywords + if("${Boost_${basename}_LIBRARY_RELEASE}" STREQUAL "${Boost_${basename}_LIBRARY_DEBUG}") + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE} ) + endif() + + if(Boost_${basename}_LIBRARY AND Boost_${basename}_HEADER) + set(Boost_${basename}_FOUND ON) + if("x${basename}" STREQUAL "xTHREAD" AND NOT TARGET Threads::Threads) + string(APPEND Boost_ERROR_REASON_THREAD " (missing dependency: Threads)") + set(Boost_THREAD_FOUND OFF) + endif() + endif() + + endif() + # Make variables changeable to the advanced user + mark_as_advanced( + Boost_${basename}_LIBRARY_RELEASE + Boost_${basename}_LIBRARY_DEBUG + ) +endmacro() + +# Detect changes in used variables. +# Compares the current variable value with the last one. +# In short form: +# v != v_LAST -> CHANGED = 1 +# v is defined, v_LAST not -> CHANGED = 1 +# v is not defined, but v_LAST is -> CHANGED = 1 +# otherwise -> CHANGED = 0 +# CHANGED is returned in variable named ${changed_var} +macro(_Boost_CHANGE_DETECT changed_var) + set(${changed_var} 0) + foreach(v ${ARGN}) + if(DEFINED _Boost_COMPONENTS_SEARCHED) + if(${v}) + if(_${v}_LAST) + string(COMPARE NOTEQUAL "${${v}}" "${_${v}_LAST}" _${v}_CHANGED) + else() + set(_${v}_CHANGED 1) + endif() + elseif(_${v}_LAST) + set(_${v}_CHANGED 1) + endif() + if(_${v}_CHANGED) + set(${changed_var} 1) + endif() + else() + set(_${v}_CHANGED 0) + endif() + endforeach() +endmacro() + +# +# Find the given library (var). +# Use 'build_type' to support different lib paths for RELEASE or DEBUG builds +# +macro(_Boost_FIND_LIBRARY var build_type) + + find_library(${var} ${ARGN}) + + if(${var}) + # If this is the first library found then save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. + if(NOT Boost_LIBRARY_DIR_${build_type}) + get_filename_component(_dir "${${var}}" PATH) + set(Boost_LIBRARY_DIR_${build_type} "${_dir}" CACHE PATH "Boost library directory ${build_type}" FORCE) + endif() + elseif(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + # Try component-specific hints but do not save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. + find_library(${var} HINTS ${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT} ${ARGN}) + endif() + + # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is known then search only there. + if(Boost_LIBRARY_DIR_${build_type}) + set(_boost_LIBRARY_SEARCH_DIRS_${build_type} ${Boost_LIBRARY_DIR_${build_type}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " Boost_LIBRARY_DIR_${build_type} = ${Boost_LIBRARY_DIR_${build_type}}" + " _boost_LIBRARY_SEARCH_DIRS_${build_type} = ${_boost_LIBRARY_SEARCH_DIRS_${build_type}}") + endif() + endif() +endmacro() + +#------------------------------------------------------------------------------- + +# +# Runs compiler with "-dumpversion" and parses major/minor +# version with a regex. +# +function(_Boost_COMPILER_DUMPVERSION _OUTPUT_VERSION) + string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\1\\2" + _boost_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + + set(${_OUTPUT_VERSION} ${_boost_COMPILER_VERSION} PARENT_SCOPE) +endfunction() + +# +# Take a list of libraries with "thread" in it +# and prepend duplicates with "thread_${Boost_THREADAPI}" +# at the front of the list +# +function(_Boost_PREPEND_LIST_WITH_THREADAPI _output) + set(_orig_libnames ${ARGN}) + string(REPLACE "thread" "thread_${Boost_THREADAPI}" _threadapi_libnames "${_orig_libnames}") + set(${_output} ${_threadapi_libnames} ${_orig_libnames} PARENT_SCOPE) +endfunction() + +# +# If a library is found, replace its cache entry with its REALPATH +# +function(_Boost_SWAP_WITH_REALPATH _library _docstring) + if(${_library}) + get_filename_component(_boost_filepathreal ${${_library}} REALPATH) + unset(${_library} CACHE) + set(${_library} ${_boost_filepathreal} CACHE FILEPATH "${_docstring}") + endif() +endfunction() + +function(_Boost_CHECK_SPELLING _var) + if(${_var}) + string(TOUPPER ${_var} _var_UC) + message(FATAL_ERROR "ERROR: ${_var} is not the correct spelling. The proper spelling is ${_var_UC}.") + endif() +endfunction() + +# Guesses Boost's compiler prefix used in built library names +# Returns the guess by setting the variable pointed to by _ret +function(_Boost_GUESS_COMPILER_PREFIX _ret) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") + if(WIN32) + set (_boost_COMPILER "-iw") + else() + set (_boost_COMPILER "-il") + endif() + elseif (GHSMULTI) + set(_boost_COMPILER "-ghs") + elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10) + set(_boost_COMPILER "-vc141;-vc140") + elseif (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19) + set(_boost_COMPILER "-vc140") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18) + set(_boost_COMPILER "-vc120") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 17) + set(_boost_COMPILER "-vc110") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) + set(_boost_COMPILER "-vc100") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15) + set(_boost_COMPILER "-vc90") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) + set(_boost_COMPILER "-vc80") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.10) + set(_boost_COMPILER "-vc71") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) # Good luck! + set(_boost_COMPILER "-vc7") # yes, this is correct + else() # VS 6.0 Good luck! + set(_boost_COMPILER "-vc6") # yes, this is correct + endif() + elseif (BORLAND) + set(_boost_COMPILER "-bcb") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") + set(_boost_COMPILER "-sw") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "XL") + set(_boost_COMPILER "-xlc") + elseif (MINGW) + if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) + set(_boost_COMPILER "-mgw") # no GCC version encoding prior to 1.34 + else() + _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION) + set(_boost_COMPILER "-mgw${_boost_COMPILER_VERSION}") + endif() + elseif (UNIX) + if (CMAKE_COMPILER_IS_GNUCXX) + if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) + set(_boost_COMPILER "-gcc") # no GCC version encoding prior to 1.34 + else() + _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION) + # Determine which version of GCC we have. + if(APPLE) + if(Boost_MINOR_VERSION) + if(${Boost_MINOR_VERSION} GREATER 35) + # In Boost 1.36.0 and newer, the mangled compiler name used + # on Mac OS X/Darwin is "xgcc". + set(_boost_COMPILER "-xgcc${_boost_COMPILER_VERSION}") + else() + # In Boost <= 1.35.0, there is no mangled compiler name for + # the Mac OS X/Darwin version of GCC. + set(_boost_COMPILER "") + endif() + else() + # We don't know the Boost version, so assume it's + # pre-1.36.0. + set(_boost_COMPILER "") + endif() + else() + set(_boost_COMPILER "-gcc${_boost_COMPILER_VERSION}") + endif() + endif() + endif () + else() + # TODO at least Boost_DEBUG here? + set(_boost_COMPILER "") + endif() + set(${_ret} ${_boost_COMPILER} PARENT_SCOPE) +endfunction() + +# +# Get component dependencies. Requires the dependencies to have been +# defined for the Boost release version. +# +# component - the component to check +# _ret - list of library dependencies +# +function(_Boost_COMPONENT_DEPENDENCIES component _ret) + # Note: to add a new Boost release, run + # + # % cmake -DBOOST_DIR=/path/to/boost/source -P Utilities/Scripts/BoostScanDeps.cmake + # + # The output may be added in a new block below. If it's the same as + # the previous release, simply update the version range of the block + # for the previous release. Also check if any new components have + # been added, and add any new components to + # _Boost_COMPONENT_HEADERS. + # + # This information was originally generated by running + # BoostScanDeps.cmake against every boost release to date supported + # by FindBoost: + # + # % for version in /path/to/boost/sources/* + # do + # cmake -DBOOST_DIR=$version -P Utilities/Scripts/BoostScanDeps.cmake + # done + # + # The output was then updated by search and replace with these regexes: + # + # - Strip message(STATUS) prefix dashes + # s;^-- ;; + # - Indent + # s;^set(; set(;; + # - Add conditionals + # s;Scanning /path/to/boost/sources/boost_\(.*\)_\(.*\)_\(.*); elseif(NOT Boost_VERSION VERSION_LESS \10\20\3 AND Boost_VERSION VERSION_LESS xxxx); + # + # This results in the logic seen below, but will require the xxxx + # replacing with the following Boost release version (or the next + # minor version to be released, e.g. 1.59 was the latest at the time + # of writing, making 1.60 the next, so 106000 is the needed version + # number). Identical consecutive releases were then merged together + # by updating the end range of the first block and removing the + # following redundant blocks. + # + # Running the script against all historical releases should be + # required only if the BoostScanDeps.cmake script logic is changed. + # The addition of a new release should only require it to be run + # against the new release. + + # Handle Python version suffixes + if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") + set(component "${CMAKE_MATCH_1}") + set(component_python_version "${CMAKE_MATCH_2}") + endif() + + set(_Boost_IMPORTED_TARGETS TRUE) + if(Boost_VERSION VERSION_LESS 103300) + message(WARNING "Imported targets and dependency information not available for Boost version ${Boost_VERSION} (all versions older than 1.33)") + set(_Boost_IMPORTED_TARGETS FALSE) + elseif(NOT Boost_VERSION VERSION_LESS 103300 AND Boost_VERSION VERSION_LESS 103500) + set(_Boost_IOSTREAMS_DEPENDENCIES regex thread) + set(_Boost_REGEX_DEPENDENCIES thread) + set(_Boost_WAVE_DEPENDENCIES filesystem thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103500 AND Boost_VERSION VERSION_LESS 103600) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103600 AND Boost_VERSION VERSION_LESS 103800) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103800 AND Boost_VERSION VERSION_LESS 104300) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104300 AND Boost_VERSION VERSION_LESS 104400) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104400 AND Boost_VERSION VERSION_LESS 104500) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random serialization) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES serialization filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104500 AND Boost_VERSION VERSION_LESS 104700) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104700 AND Boost_VERSION VERSION_LESS 104800) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104800 AND Boost_VERSION VERSION_LESS 105000) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105000 AND Boost_VERSION VERSION_LESS 105300) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105300 AND Boost_VERSION VERSION_LESS 105400) + set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105400 AND Boost_VERSION VERSION_LESS 105500) + set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + 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 105500 AND Boost_VERSION VERSION_LESS 105600) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + 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 105600 AND Boost_VERSION VERSION_LESS 105900) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + 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 105900 AND Boost_VERSION VERSION_LESS 106000) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + 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 106000 AND Boost_VERSION VERSION_LESS 106100) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + 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 106100 AND Boost_VERSION VERSION_LESS 106200) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106200 AND Boost_VERSION VERSION_LESS 106300) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106300 AND Boost_VERSION VERSION_LESS 106500) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_COROUTINE2_DEPENDENCIES context fiber thread chrono system date_time) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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 mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106500 AND Boost_VERSION VERSION_LESS 106700) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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 mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + else() + if(NOT Boost_VERSION VERSION_LESS 106700) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system 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_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + endif() + if(NOT Boost_VERSION VERSION_LESS 106800) + message(WARNING "New Boost version may have incorrect or missing dependencies and imported targets") + endif() + endif() + + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) + + string(REGEX REPLACE ";" " " _boost_DEPS_STRING "${_Boost_${uppercomponent}_DEPENDENCIES}") + if (NOT _boost_DEPS_STRING) + set(_boost_DEPS_STRING "(none)") + endif() + # message(STATUS "Dependencies for Boost::${component}: ${_boost_DEPS_STRING}") +endfunction() + +# +# Get component headers. This is the primary header (or headers) for +# a given component, and is used to check that the headers are present +# as well as the library itself as an extra sanity check of the build +# environment. +# +# component - the component to check +# _hdrs +# +function(_Boost_COMPONENT_HEADERS component _hdrs) + # Handle Python version suffixes + if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") + set(component "${CMAKE_MATCH_1}") + set(component_python_version "${CMAKE_MATCH_2}") + endif() + + # Note: new boost components will require adding here. The header + # must be present in all versions of Boost providing a library. + set(_Boost_ATOMIC_HEADERS "boost/atomic.hpp") + set(_Boost_CHRONO_HEADERS "boost/chrono.hpp") + set(_Boost_CONTAINER_HEADERS "boost/container/container_fwd.hpp") + set(_Boost_CONTEXT_HEADERS "boost/context/all.hpp") + set(_Boost_COROUTINE_HEADERS "boost/coroutine/all.hpp") + set(_Boost_DATE_TIME_HEADERS "boost/date_time/date.hpp") + set(_Boost_EXCEPTION_HEADERS "boost/exception/exception.hpp") + set(_Boost_FIBER_HEADERS "boost/fiber/all.hpp") + set(_Boost_FILESYSTEM_HEADERS "boost/filesystem/path.hpp") + set(_Boost_GRAPH_HEADERS "boost/graph/adjacency_list.hpp") + set(_Boost_GRAPH_PARALLEL_HEADERS "boost/graph/adjacency_list.hpp") + set(_Boost_IOSTREAMS_HEADERS "boost/iostreams/stream.hpp") + set(_Boost_LOCALE_HEADERS "boost/locale.hpp") + set(_Boost_LOG_HEADERS "boost/log/core.hpp") + set(_Boost_LOG_SETUP_HEADERS "boost/log/detail/setup_config.hpp") + set(_Boost_MATH_HEADERS "boost/math_fwd.hpp") + set(_Boost_MATH_C99_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_C99F_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_C99L_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1F_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1L_HEADERS "boost/math/tr1.hpp") + set(_Boost_MPI_HEADERS "boost/mpi.hpp") + set(_Boost_MPI_PYTHON_HEADERS "boost/mpi/python/config.hpp") + set(_Boost_NUMPY_HEADERS "boost/python/numpy.hpp") + set(_Boost_PRG_EXEC_MONITOR_HEADERS "boost/test/prg_exec_monitor.hpp") + set(_Boost_PROGRAM_OPTIONS_HEADERS "boost/program_options.hpp") + set(_Boost_PYTHON_HEADERS "boost/python.hpp") + set(_Boost_RANDOM_HEADERS "boost/random.hpp") + set(_Boost_REGEX_HEADERS "boost/regex.hpp") + set(_Boost_SERIALIZATION_HEADERS "boost/serialization/serialization.hpp") + set(_Boost_SIGNALS_HEADERS "boost/signals.hpp") + set(_Boost_SYSTEM_HEADERS "boost/system/config.hpp") + set(_Boost_TEST_EXEC_MONITOR_HEADERS "boost/test/test_exec_monitor.hpp") + set(_Boost_THREAD_HEADERS "boost/thread.hpp") + set(_Boost_TIMER_HEADERS "boost/timer.hpp") + set(_Boost_TYPE_ERASURE_HEADERS "boost/type_erasure/config.hpp") + set(_Boost_UNIT_TEST_FRAMEWORK_HEADERS "boost/test/framework.hpp") + set(_Boost_WAVE_HEADERS "boost/wave.hpp") + set(_Boost_WSERIALIZATION_HEADERS "boost/archive/text_wiarchive.hpp") + if(WIN32) + set(_Boost_BZIP2_HEADERS "boost/iostreams/filter/bzip2.hpp") + set(_Boost_ZLIB_HEADERS "boost/iostreams/filter/zlib.hpp") + endif() + + string(TOUPPER ${component} uppercomponent) + set(${_hdrs} ${_Boost_${uppercomponent}_HEADERS} PARENT_SCOPE) + + string(REGEX REPLACE ";" " " _boost_HDRS_STRING "${_Boost_${uppercomponent}_HEADERS}") + if (NOT _boost_HDRS_STRING) + set(_boost_HDRS_STRING "(none)") + endif() + # message(STATUS "Headers for Boost::${component}: ${_boost_HDRS_STRING}") +endfunction() + +# +# Determine if any missing dependencies require adding to the component list. +# +# Sets _Boost_${COMPONENT}_DEPENDENCIES for each required component, +# plus _Boost_IMPORTED_TARGETS (TRUE if imported targets should be +# defined; FALSE if dependency information is unavailable). +# +# componentvar - the component list variable name +# extravar - the indirect dependency list variable name +# +# +function(_Boost_MISSING_DEPENDENCIES componentvar extravar) + # _boost_unprocessed_components - list of components requiring processing + # _boost_processed_components - components already processed (or currently being processed) + # _boost_new_components - new components discovered for future processing + # + list(APPEND _boost_unprocessed_components ${${componentvar}}) + + while(_boost_unprocessed_components) + list(APPEND _boost_processed_components ${_boost_unprocessed_components}) + foreach(component ${_boost_unprocessed_components}) + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + _Boost_COMPONENT_DEPENDENCIES("${component}" _Boost_${uppercomponent}_DEPENDENCIES) + set(_Boost_${uppercomponent}_DEPENDENCIES ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) + foreach(componentdep ${_Boost_${uppercomponent}_DEPENDENCIES}) + if (NOT ("${componentdep}" IN_LIST _boost_processed_components OR "${componentdep}" IN_LIST _boost_new_components)) + list(APPEND _boost_new_components ${componentdep}) + endif() + endforeach() + endforeach() + set(_boost_unprocessed_components ${_boost_new_components}) + unset(_boost_new_components) + endwhile() + set(_boost_extra_components ${_boost_processed_components}) + if(_boost_extra_components AND ${componentvar}) + list(REMOVE_ITEM _boost_extra_components ${${componentvar}}) + endif() + set(${componentvar} ${_boost_processed_components} PARENT_SCOPE) + set(${extravar} ${_boost_extra_components} PARENT_SCOPE) +endfunction() + +# +# Some boost libraries may require particular set of compler features. +# The very first one was `boost::fiber` introduced in Boost 1.62. +# One can check required compiler features of it in +# `${Boost_ROOT}/libs/fiber/build/Jamfile.v2`. +# +function(_Boost_COMPILER_FEATURES component _ret) + # Boost >= 1.62 and < 1.67 + if(NOT Boost_VERSION VERSION_LESS 106200 AND Boost_VERSION VERSION_LESS 106700) + set(_Boost_FIBER_COMPILER_FEATURES + cxx_alias_templates + cxx_auto_type + cxx_constexpr + cxx_defaulted_functions + cxx_final + cxx_lambdas + cxx_noexcept + cxx_nullptr + cxx_rvalue_references + cxx_thread_local + cxx_variadic_templates + ) + endif() + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_COMPILER_FEATURES} PARENT_SCOPE) +endfunction() + +# +# Update library search directory hint variable with paths used by prebuilt boost binaries. +# +# Prebuilt windows binaries (https://sourceforge.net/projects/boost/files/boost-binaries/) +# have library directories named using MSVC compiler version and architecture. +# This function would append corresponding directories if MSVC is a current compiler, +# so having `BOOST_ROOT` would be enough to specify to find everything. +# +function(_Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS componentlibvar basedir) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_arch_suffix 64) + else() + set(_arch_suffix 32) + endif() + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.10) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.1) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-12.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 17) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-11.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-10.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-9.0) + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-8.0) + endif() + set(${componentlibvar} ${${componentlibvar}} PARENT_SCOPE) + endif() +endfunction() + +# +# End functions/macros +# +#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------- +# main. +#------------------------------------------------------------------------------- + + +# If the user sets Boost_LIBRARY_DIR, use it as the default for both +# configurations. +if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR) + set(Boost_LIBRARY_DIR_RELEASE "${Boost_LIBRARY_DIR}") +endif() +if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR) + set(Boost_LIBRARY_DIR_DEBUG "${Boost_LIBRARY_DIR}") +endif() + +if(NOT DEFINED Boost_USE_DEBUG_LIBS) + set(Boost_USE_DEBUG_LIBS TRUE) +endif() +if(NOT DEFINED Boost_USE_RELEASE_LIBS) + set(Boost_USE_RELEASE_LIBS TRUE) +endif() +if(NOT DEFINED Boost_USE_MULTITHREADED) + set(Boost_USE_MULTITHREADED TRUE) +endif() +if(NOT DEFINED Boost_USE_DEBUG_RUNTIME) + set(Boost_USE_DEBUG_RUNTIME TRUE) +endif() + +# Check the version of Boost against the requested version. +if(Boost_FIND_VERSION AND NOT Boost_FIND_VERSION_MINOR) + message(SEND_ERROR "When requesting a specific version of Boost, you must provide at least the major and minor version numbers, e.g., 1.34") +endif() + +if(Boost_FIND_VERSION_EXACT) + # The version may appear in a directory with or without the patch + # level, even when the patch level is non-zero. + set(_boost_TEST_VERSIONS + "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}.${Boost_FIND_VERSION_PATCH}" + "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") +else() + # The user has not requested an exact version. Among known + # versions, find those that are acceptable to the user request. + # + # Note: When adding a new Boost release, also update the dependency + # information in _Boost_COMPONENT_DEPENDENCIES and + # _Boost_COMPONENT_HEADERS. See the instructions at the top of + # _Boost_COMPONENT_DEPENDENCIES. + set(_Boost_KNOWN_VERSIONS ${Boost_ADDITIONAL_VERSIONS} + "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" + "1.54.0" "1.54" "1.53.0" "1.53" "1.52.0" "1.52" "1.51.0" "1.51" + "1.50.0" "1.50" "1.49.0" "1.49" "1.48.0" "1.48" "1.47.0" "1.47" "1.46.1" + "1.46.0" "1.46" "1.45.0" "1.45" "1.44.0" "1.44" "1.43.0" "1.43" "1.42.0" "1.42" + "1.41.0" "1.41" "1.40.0" "1.40" "1.39.0" "1.39" "1.38.0" "1.38" "1.37.0" "1.37" + "1.36.1" "1.36.0" "1.36" "1.35.1" "1.35.0" "1.35" "1.34.1" "1.34.0" + "1.34" "1.33.1" "1.33.0" "1.33") + + set(_boost_TEST_VERSIONS) + if(Boost_FIND_VERSION) + set(_Boost_FIND_VERSION_SHORT "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") + # Select acceptable versions. + foreach(version ${_Boost_KNOWN_VERSIONS}) + if(NOT "${version}" VERSION_LESS "${Boost_FIND_VERSION}") + # This version is high enough. + list(APPEND _boost_TEST_VERSIONS "${version}") + elseif("${version}.99" VERSION_EQUAL "${_Boost_FIND_VERSION_SHORT}.99") + # This version is a short-form for the requested version with + # the patch level dropped. + list(APPEND _boost_TEST_VERSIONS "${version}") + endif() + endforeach() + else() + # Any version is acceptable. + set(_boost_TEST_VERSIONS "${_Boost_KNOWN_VERSIONS}") + endif() +endif() + +# The reason that we failed to find Boost. This will be set to a +# user-friendly message when we fail to find some necessary piece of +# Boost. +set(Boost_ERROR_REASON) + +if(Boost_DEBUG) + # Output some of their choices + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_MULTITHREADED = ${Boost_USE_MULTITHREADED}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_STATIC_LIBS = ${Boost_USE_STATIC_LIBS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_STATIC_RUNTIME = ${Boost_USE_STATIC_RUNTIME}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_ADDITIONAL_VERSIONS = ${Boost_ADDITIONAL_VERSIONS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_NO_SYSTEM_PATHS = ${Boost_NO_SYSTEM_PATHS}") +endif() + +# Supply Boost_LIB_DIAGNOSTIC_DEFINITIONS as a convenience target. It +# will only contain any interface definitions on WIN32, but is created +# on all platforms to keep end user code free from platform dependent +# code. Also provide convenience targets to disable autolinking and +# enable dynamic linking. +if(NOT TARGET Boost::diagnostic_definitions) + add_library(Boost::diagnostic_definitions INTERFACE IMPORTED) + add_library(Boost::disable_autolinking INTERFACE IMPORTED) + add_library(Boost::dynamic_linking INTERFACE IMPORTED) +endif() +if(WIN32) + # In windows, automatic linking is performed, so you do not have + # to specify the libraries. If you are linking to a dynamic + # runtime, then you can choose to link to either a static or a + # dynamic Boost library, the default is to do a static link. You + # can alter this for a specific library "whatever" by defining + # BOOST_WHATEVER_DYN_LINK to force Boost library "whatever" to be + # linked dynamically. Alternatively you can force all Boost + # libraries to dynamic link by defining BOOST_ALL_DYN_LINK. + + # This feature can be disabled for Boost library "whatever" by + # defining BOOST_WHATEVER_NO_LIB, or for all of Boost by defining + # BOOST_ALL_NO_LIB. + + # If you want to observe which libraries are being linked against + # then defining BOOST_LIB_DIAGNOSTIC will cause the auto-linking + # code to emit a #pragma message each time a library is selected + # for linking. + set(Boost_LIB_DIAGNOSTIC_DEFINITIONS "-DBOOST_LIB_DIAGNOSTIC") + set_target_properties(Boost::diagnostic_definitions PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_LIB_DIAGNOSTIC") + set_target_properties(Boost::disable_autolinking PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") + set_target_properties(Boost::dynamic_linking PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_DYN_LINK") +endif() + +_Boost_CHECK_SPELLING(Boost_ROOT) +_Boost_CHECK_SPELLING(Boost_LIBRARYDIR) +_Boost_CHECK_SPELLING(Boost_INCLUDEDIR) + +# Collect environment variable inputs as hints. Do not consider changes. +foreach(v BOOSTROOT BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR) + set(_env $ENV{${v}}) + if(_env) + file(TO_CMAKE_PATH "${_env}" _ENV_${v}) + else() + set(_ENV_${v} "") + endif() +endforeach() +if(NOT _ENV_BOOST_ROOT AND _ENV_BOOSTROOT) + set(_ENV_BOOST_ROOT "${_ENV_BOOSTROOT}") +endif() + +# Collect inputs and cached results. Detect changes since the last run. +if(NOT BOOST_ROOT AND BOOSTROOT) + set(BOOST_ROOT "${BOOSTROOT}") +endif() +set(_Boost_VARS_DIR + BOOST_ROOT + Boost_NO_SYSTEM_PATHS + ) + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Declared as CMake or Environmental Variables:") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_ROOT = ${BOOST_ROOT}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_INCLUDEDIR = ${BOOST_INCLUDEDIR}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_LIBRARYDIR = ${BOOST_LIBRARYDIR}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") +endif() + +# ------------------------------------------------------------------------ +# Search for Boost include DIR +# ------------------------------------------------------------------------ + +set(_Boost_VARS_INC BOOST_INCLUDEDIR Boost_INCLUDE_DIR Boost_ADDITIONAL_VERSIONS) +_Boost_CHANGE_DETECT(_Boost_CHANGE_INCDIR ${_Boost_VARS_DIR} ${_Boost_VARS_INC}) +# Clear Boost_INCLUDE_DIR if it did not change but other input affecting the +# location did. We will find a new one based on the new inputs. +if(_Boost_CHANGE_INCDIR AND NOT _Boost_INCLUDE_DIR_CHANGED) + unset(Boost_INCLUDE_DIR CACHE) +endif() + +if(NOT Boost_INCLUDE_DIR) + set(_boost_INCLUDE_SEARCH_DIRS "") + if(BOOST_INCLUDEDIR) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_INCLUDEDIR}) + elseif(_ENV_BOOST_INCLUDEDIR) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_INCLUDEDIR}) + endif() + + if( BOOST_ROOT ) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_ROOT}/include ${BOOST_ROOT}) + elseif( _ENV_BOOST_ROOT ) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_ROOT}/include ${_ENV_BOOST_ROOT}) + endif() + + if( Boost_NO_SYSTEM_PATHS) + 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_KNOWN_VERSIONS}) + string(REPLACE "." "_" ver "${ver}") + list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS "C:/local/boost_${ver}") + endforeach() + endif() + list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS + C:/boost/include + C:/boost + /sw/local/include + ) + endif() + + # Try to find Boost by stepping backwards through the Boost versions + # we know about. + # Build a list of path suffixes for each version. + set(_boost_PATH_SUFFIXES) + foreach(_boost_VER ${_boost_TEST_VERSIONS}) + # Add in a path suffix, based on the required version, ideally + # we could read this from version.hpp, but for that to work we'd + # need to know the include dir already + set(_boost_BOOSTIFIED_VERSION) + + # Transform 1.35 => 1_35 and 1.36.0 => 1_36_0 + if(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(_boost_BOOSTIFIED_VERSION + "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}_${CMAKE_MATCH_3}") + elseif(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)") + set(_boost_BOOSTIFIED_VERSION + "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}") + endif() + + list(APPEND _boost_PATH_SUFFIXES + "boost-${_boost_BOOSTIFIED_VERSION}" + "boost_${_boost_BOOSTIFIED_VERSION}" + "boost/boost-${_boost_BOOSTIFIED_VERSION}" + "boost/boost_${_boost_BOOSTIFIED_VERSION}" + ) + + endforeach() + + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Include debugging info:") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " _boost_INCLUDE_SEARCH_DIRS = ${_boost_INCLUDE_SEARCH_DIRS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " _boost_PATH_SUFFIXES = ${_boost_PATH_SUFFIXES}") + endif() + + # Look for a standard boost header file. + find_path(Boost_INCLUDE_DIR + NAMES boost/config.hpp + HINTS ${_boost_INCLUDE_SEARCH_DIRS} + PATH_SUFFIXES ${_boost_PATH_SUFFIXES} + ) +endif() + +# ------------------------------------------------------------------------ +# Extract version information from version.hpp +# ------------------------------------------------------------------------ + +# Set Boost_FOUND based only on header location and version. +# It will be updated below for component libraries. +if(Boost_INCLUDE_DIR) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "location of version.hpp: ${Boost_INCLUDE_DIR}/boost/version.hpp") + endif() + + # Extract Boost_VERSION and Boost_LIB_VERSION from version.hpp + set(Boost_VERSION 0) + set(Boost_LIB_VERSION "") + file(STRINGS "${Boost_INCLUDE_DIR}/boost/version.hpp" _boost_VERSION_HPP_CONTENTS REGEX "#define BOOST_(LIB_)?VERSION ") + set(_Boost_VERSION_REGEX "([0-9]+)") + set(_Boost_LIB_VERSION_REGEX "\"([0-9_]+)\"") + foreach(v VERSION LIB_VERSION) + if("${_boost_VERSION_HPP_CONTENTS}" MATCHES "#define BOOST_${v} ${_Boost_${v}_REGEX}") + set(Boost_${v} "${CMAKE_MATCH_1}") + endif() + endforeach() + unset(_boost_VERSION_HPP_CONTENTS) + + 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") + + string(APPEND Boost_ERROR_REASON + "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}\nBoost include path: ${Boost_INCLUDE_DIR}") + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "version.hpp reveals boost " + "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + endif() + + if(Boost_FIND_VERSION) + # Set Boost_FOUND based on requested version. + set(_Boost_VERSION "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + if("${_Boost_VERSION}" VERSION_LESS "${Boost_FIND_VERSION}") + set(Boost_FOUND 0) + set(_Boost_VERSION_AGE "old") + elseif(Boost_FIND_VERSION_EXACT AND + NOT "${_Boost_VERSION}" VERSION_EQUAL "${Boost_FIND_VERSION}") + set(Boost_FOUND 0) + set(_Boost_VERSION_AGE "new") + else() + set(Boost_FOUND 1) + endif() + if(NOT Boost_FOUND) + # State that we found a version of Boost that is too new or too old. + string(APPEND Boost_ERROR_REASON + "\nDetected version of Boost is too ${_Boost_VERSION_AGE}. Requested version was ${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") + if (Boost_FIND_VERSION_PATCH) + string(APPEND Boost_ERROR_REASON + ".${Boost_FIND_VERSION_PATCH}") + endif () + if (NOT Boost_FIND_VERSION_EXACT) + string(APPEND Boost_ERROR_REASON " (or newer)") + endif () + string(APPEND Boost_ERROR_REASON ".") + endif () + else() + # Caller will accept any Boost version. + set(Boost_FOUND 1) + endif() +else() + set(Boost_FOUND 0) + string(APPEND Boost_ERROR_REASON + "Unable to find the Boost header files. Please set BOOST_ROOT to the root directory containing Boost or BOOST_INCLUDEDIR to the directory containing Boost's headers.") +endif() + +# ------------------------------------------------------------------------ +# Prefix initialization +# ------------------------------------------------------------------------ + +set(Boost_LIB_PREFIX "") +if ( (GHSMULTI AND Boost_USE_STATIC_LIBS) OR + (WIN32 AND Boost_USE_STATIC_LIBS AND NOT CYGWIN) ) + set(Boost_LIB_PREFIX "lib") +endif() + +if ( NOT Boost_NAMESPACE ) + set(Boost_NAMESPACE "boost") +endif() + +# ------------------------------------------------------------------------ +# Suffix initialization and compiler suffix detection. +# ------------------------------------------------------------------------ + +set(_Boost_VARS_NAME + Boost_NAMESPACE + Boost_COMPILER + Boost_THREADAPI + Boost_USE_DEBUG_PYTHON + Boost_USE_MULTITHREADED + Boost_USE_STATIC_LIBS + Boost_USE_STATIC_RUNTIME + Boost_USE_STLPORT + Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS + ) +_Boost_CHANGE_DETECT(_Boost_CHANGE_LIBNAME ${_Boost_VARS_NAME}) + +# Setting some more suffixes for the library +if (Boost_COMPILER) + set(_boost_COMPILER ${Boost_COMPILER}) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "using user-specified Boost_COMPILER = ${_boost_COMPILER}") + endif() +else() + # Attempt to guess the compiler suffix + # NOTE: this is not perfect yet, if you experience any issues + # please report them and use the Boost_COMPILER variable + # to work around the problems. + _Boost_GUESS_COMPILER_PREFIX(_boost_COMPILER) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "guessed _boost_COMPILER = ${_boost_COMPILER}") + endif() +endif() + +set (_boost_MULTITHREADED "-mt") +if( NOT Boost_USE_MULTITHREADED ) + set (_boost_MULTITHREADED "") +endif() +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_MULTITHREADED = ${_boost_MULTITHREADED}") +endif() + +#====================== +# Systematically build up the Boost ABI tag for the 'tagged' and 'versioned' layouts +# http://boost.org/doc/libs/1_66_0/more/getting_started/windows.html#library-naming +# http://boost.org/doc/libs/1_66_0/boost/config/auto_link.hpp +# http://boost.org/doc/libs/1_66_0/tools/build/src/tools/common.jam +# http://boost.org/doc/libs/1_66_0/boostcpp.jam +set( _boost_RELEASE_ABI_TAG "-") +set( _boost_DEBUG_ABI_TAG "-") +# Key Use this library when: +# s linking statically to the C++ standard library and +# compiler runtime support libraries. +if(Boost_USE_STATIC_RUNTIME) + set( _boost_RELEASE_ABI_TAG "${_boost_RELEASE_ABI_TAG}s") + set( _boost_DEBUG_ABI_TAG "${_boost_DEBUG_ABI_TAG}s") +endif() +# g using debug versions of the standard and runtime +# support libraries +if(WIN32 AND Boost_USE_DEBUG_RUNTIME) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC" + OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang" + OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") + string(APPEND _boost_DEBUG_ABI_TAG "g") + endif() +endif() +# y using special debug build of python +if(Boost_USE_DEBUG_PYTHON) + string(APPEND _boost_DEBUG_ABI_TAG "y") +endif() +# d using a debug version of your code +string(APPEND _boost_DEBUG_ABI_TAG "d") +# p using the STLport standard library rather than the +# default one supplied with your compiler +if(Boost_USE_STLPORT) + string(APPEND _boost_RELEASE_ABI_TAG "p") + string(APPEND _boost_DEBUG_ABI_TAG "p") +endif() +# n using the STLport deprecated "native iostreams" feature +# removed from the documentation in 1.43.0 but still present in +# boost/config/auto_link.hpp +if(Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS) + string(APPEND _boost_RELEASE_ABI_TAG "n") + string(APPEND _boost_DEBUG_ABI_TAG "n") +endif() + +# -x86 Architecture and address model tag +# First character is the architecture, then word-size, either 32 or 64 +# Only used in 'versioned' layout, added in Boost 1.66.0 +set(_boost_ARCHITECTURE_TAG "") +# {CMAKE_CXX_COMPILER_ARCHITECTURE_ID} is not currently set for all compilers +if(NOT "x${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "x" AND NOT Boost_VERSION VERSION_LESS 106600) + string(APPEND _boost_ARCHITECTURE_TAG "-") + # This needs to be kept in-sync with the section of CMakePlatformId.h.in + # inside 'defined(_WIN32) && defined(_MSC_VER)' + if(${CMAKE_CXX_COMPILER_ARCHITECTURE_ID} STREQUAL "IA64") + string(APPEND _boost_ARCHITECTURE_TAG "i") + elseif(${CMAKE_CXX_COMPILER_ARCHITECTURE_ID} STREQUAL "X86" + OR ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID} STREQUAL "x64") + string(APPEND _boost_ARCHITECTURE_TAG "x") + elseif(${CMAKE_CXX_COMPILER_ARCHITECTURE_ID} MATCHES "^ARM") + string(APPEND _boost_ARCHITECTURE_TAG "a") + elseif(${CMAKE_CXX_COMPILER_ARCHITECTURE_ID} STREQUAL "MIPS") + string(APPEND _boost_ARCHITECTURE_TAG "m") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + string(APPEND _boost_ARCHITECTURE_TAG "64") + else() + string(APPEND _boost_ARCHITECTURE_TAG "32") + endif() +endif() + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_RELEASE_ABI_TAG = ${_boost_RELEASE_ABI_TAG}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_DEBUG_ABI_TAG = ${_boost_DEBUG_ABI_TAG}") +endif() + +# ------------------------------------------------------------------------ +# Begin finding boost libraries +# ------------------------------------------------------------------------ + +set(_Boost_VARS_LIB "") +foreach(c DEBUG RELEASE) + set(_Boost_VARS_LIB_${c} BOOST_LIBRARYDIR Boost_LIBRARY_DIR_${c}) + list(APPEND _Boost_VARS_LIB ${_Boost_VARS_LIB_${c}}) + _Boost_CHANGE_DETECT(_Boost_CHANGE_LIBDIR_${c} ${_Boost_VARS_DIR} ${_Boost_VARS_LIB_${c}} Boost_INCLUDE_DIR) + # Clear Boost_LIBRARY_DIR_${c} if it did not change but other input affecting the + # location did. We will find a new one based on the new inputs. + if(_Boost_CHANGE_LIBDIR_${c} AND NOT _Boost_LIBRARY_DIR_${c}_CHANGED) + unset(Boost_LIBRARY_DIR_${c} CACHE) + endif() + + # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is set, prefer its value. + if(Boost_LIBRARY_DIR_${c}) + set(_boost_LIBRARY_SEARCH_DIRS_${c} ${Boost_LIBRARY_DIR_${c}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + else() + set(_boost_LIBRARY_SEARCH_DIRS_${c} "") + if(BOOST_LIBRARYDIR) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_LIBRARYDIR}) + elseif(_ENV_BOOST_LIBRARYDIR) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_LIBRARYDIR}) + endif() + + if(BOOST_ROOT) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_ROOT}/lib ${BOOST_ROOT}/stage/lib) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${BOOST_ROOT}") + elseif(_ENV_BOOST_ROOT) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_ROOT}/lib ${_ENV_BOOST_ROOT}/stage/lib) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${_ENV_BOOST_ROOT}") + endif() + + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} + ${Boost_INCLUDE_DIR}/lib + ${Boost_INCLUDE_DIR}/../lib + ${Boost_INCLUDE_DIR}/stage/lib + ) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}/..") + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}") + if( Boost_NO_SYSTEM_PATHS ) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) + else() + 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() + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/boost") + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} PATHS + C:/boost/lib + C:/boost + /sw/local/lib + ) + endif() + endif() +endforeach() + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_LIBRARY_SEARCH_DIRS_RELEASE = ${_boost_LIBRARY_SEARCH_DIRS_RELEASE}" + "_boost_LIBRARY_SEARCH_DIRS_DEBUG = ${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") +endif() + +# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES +if( Boost_USE_STATIC_LIBS ) + set( _boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(WIN32) + list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 .lib .a) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif() +endif() + +# We want to use the tag inline below without risking double dashes +if(_boost_RELEASE_ABI_TAG) + if(${_boost_RELEASE_ABI_TAG} STREQUAL "-") + set(_boost_RELEASE_ABI_TAG "") + endif() +endif() +if(_boost_DEBUG_ABI_TAG) + if(${_boost_DEBUG_ABI_TAG} STREQUAL "-") + set(_boost_DEBUG_ABI_TAG "") + endif() +endif() + +# The previous behavior of FindBoost when Boost_USE_STATIC_LIBS was enabled +# on WIN32 was to: +# 1. Search for static libs compiled against a SHARED C++ standard runtime library (use if found) +# 2. Search for static libs compiled against a STATIC C++ standard runtime library (use if found) +# We maintain this behavior since changing it could break people's builds. +# To disable the ambiguous behavior, the user need only +# set Boost_USE_STATIC_RUNTIME either ON or OFF. +set(_boost_STATIC_RUNTIME_WORKAROUND false) +if(WIN32 AND Boost_USE_STATIC_LIBS) + if(NOT DEFINED Boost_USE_STATIC_RUNTIME) + set(_boost_STATIC_RUNTIME_WORKAROUND TRUE) + endif() +endif() + +# On versions < 1.35, remove the System library from the considered list +# since it wasn't added until 1.35. +if(Boost_VERSION AND Boost_FIND_COMPONENTS) + if(Boost_VERSION LESS 103500) + list(REMOVE_ITEM Boost_FIND_COMPONENTS system) + endif() +endif() + +# Additional components may be required via component dependencies. +# Add any missing components to the list. +_Boost_MISSING_DEPENDENCIES(Boost_FIND_COMPONENTS _Boost_EXTRA_FIND_COMPONENTS) + +# If thread is required, get the thread libs as a dependency +if("thread" IN_LIST Boost_FIND_COMPONENTS) + if(Boost_FIND_QUIETLY) + set(_Boost_find_quiet QUIET) + else() + set(_Boost_find_quiet "") + endif() + find_package(Threads ${_Boost_find_quiet}) + unset(_Boost_find_quiet) +endif() + +# If the user changed any of our control inputs flush previous results. +if(_Boost_CHANGE_LIBDIR_DEBUG OR _Boost_CHANGE_LIBDIR_RELEASE OR _Boost_CHANGE_LIBNAME) + foreach(COMPONENT ${_Boost_COMPONENTS_SEARCHED}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + foreach(c DEBUG RELEASE) + set(_var Boost_${UPPERCOMPONENT}_LIBRARY_${c}) + unset(${_var} CACHE) + set(${_var} "${_var}-NOTFOUND") + endforeach() + endforeach() + set(_Boost_COMPONENTS_SEARCHED "") +endif() + +foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + + set( _boost_docstring_release "Boost ${COMPONENT} library (release)") + set( _boost_docstring_debug "Boost ${COMPONENT} library (debug)") + + # Compute component-specific hints. + set(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT "") + if(${COMPONENT} STREQUAL "mpi" OR ${COMPONENT} STREQUAL "mpi_python" OR + ${COMPONENT} STREQUAL "graph_parallel") + foreach(lib ${MPI_CXX_LIBRARIES} ${MPI_C_LIBRARIES}) + if(IS_ABSOLUTE "${lib}") + get_filename_component(libdir "${lib}" PATH) + string(REPLACE "\\" "/" libdir "${libdir}") + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT ${libdir}) + endif() + endforeach() + endif() + + # Handle Python version suffixes + unset(COMPONENT_PYTHON_VERSION_MAJOR) + unset(COMPONENT_PYTHON_VERSION_MINOR) + if(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\$") + set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") + set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") + elseif(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\\.?([0-9])\$") + set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") + set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(COMPONENT_PYTHON_VERSION_MINOR "${CMAKE_MATCH_3}") + endif() + + unset(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + if (COMPONENT_PYTHON_VERSION_MINOR) + # Boost >= 1.67 + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + # Debian/Ubuntu (Some versions omit the 2 and/or 3 from the suffix) + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + # Gentoo + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + # RPMs + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + endif() + if (COMPONENT_PYTHON_VERSION_MAJOR AND NOT COMPONENT_PYTHON_VERSION_MINOR) + # Boost < 1.67 + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}") + endif() + + # Consolidate and report component-specific hints. + if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Component-specific library search names for ${COMPONENT_NAME}: " + "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME}") + endif() + endif() + if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Component-specific library search paths for ${COMPONENT}: " + "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT}") + endif() + endif() + + # + # Find headers + # + _Boost_COMPONENT_HEADERS("${COMPONENT}" Boost_${UPPERCOMPONENT}_HEADER_NAME) + # Look for a standard boost header file. + if(Boost_${UPPERCOMPONENT}_HEADER_NAME) + if(EXISTS "${Boost_INCLUDE_DIR}/${Boost_${UPPERCOMPONENT}_HEADER_NAME}") + set(Boost_${UPPERCOMPONENT}_HEADER ON) + else() + set(Boost_${UPPERCOMPONENT}_HEADER OFF) + endif() + else() + set(Boost_${UPPERCOMPONENT}_HEADER ON) + message(WARNING "No header defined for ${COMPONENT}; skipping header check") + endif() + + # + # Find RELEASE libraries + # + unset(_boost_RELEASE_NAMES) + foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} ) + endforeach() + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) + if(_boost_STATIC_RUNTIME_WORKAROUND) + set(_boost_RELEASE_STATIC_ABI_TAG "-s${_boost_RELEASE_ABI_TAG}") + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) + endforeach() + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) + endif() + endforeach() + if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") + _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_RELEASE_NAMES ${_boost_RELEASE_NAMES}) + endif() + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Searching for ${UPPERCOMPONENT}_LIBRARY_RELEASE: ${_boost_RELEASE_NAMES}") + endif() + + # if Boost_LIBRARY_DIR_RELEASE is not defined, + # but Boost_LIBRARY_DIR_DEBUG is, look there first for RELEASE libs + if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR_DEBUG) + list(INSERT _boost_LIBRARY_SEARCH_DIRS_RELEASE 0 ${Boost_LIBRARY_DIR_DEBUG}) + endif() + + # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. + string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_RELEASE}") + + if(Boost_USE_RELEASE_LIBS) + _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE RELEASE + NAMES ${_boost_RELEASE_NAMES} + HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} + NAMES_PER_DIR + DOC "${_boost_docstring_release}" + ) + endif() + + # + # Find DEBUG libraries + # + unset(_boost_DEBUG_NAMES) + foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} ) + endforeach() + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) + if(_boost_STATIC_RUNTIME_WORKAROUND) + set(_boost_DEBUG_STATIC_ABI_TAG "-s${_boost_DEBUG_ABI_TAG}") + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) + endforeach() + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) + endif() + endforeach() + if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") + _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_DEBUG_NAMES ${_boost_DEBUG_NAMES}) + endif() + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Searching for ${UPPERCOMPONENT}_LIBRARY_DEBUG: ${_boost_DEBUG_NAMES}") + endif() + + # if Boost_LIBRARY_DIR_DEBUG is not defined, + # but Boost_LIBRARY_DIR_RELEASE is, look there first for DEBUG libs + if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR_RELEASE) + list(INSERT _boost_LIBRARY_SEARCH_DIRS_DEBUG 0 ${Boost_LIBRARY_DIR_RELEASE}) + endif() + + # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. + string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") + + if(Boost_USE_DEBUG_LIBS) + _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG DEBUG + NAMES ${_boost_DEBUG_NAMES} + HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} + NAMES_PER_DIR + DOC "${_boost_docstring_debug}" + ) + endif () + + if(Boost_REALPATH) + _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE "${_boost_docstring_release}") + _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG "${_boost_docstring_debug}" ) + endif() + + _Boost_ADJUST_LIB_VARS(${UPPERCOMPONENT}) + + # Check if component requires some compiler features + _Boost_COMPILER_FEATURES(${COMPONENT} _Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) + +endforeach() + +# Restore the original find library ordering +if( Boost_USE_STATIC_LIBS ) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${_boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +endif() + +# ------------------------------------------------------------------------ +# End finding boost libraries +# ------------------------------------------------------------------------ + +set(Boost_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) +set(Boost_LIBRARY_DIRS) +if(Boost_LIBRARY_DIR_RELEASE) + list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_RELEASE}) +endif() +if(Boost_LIBRARY_DIR_DEBUG) + list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_DEBUG}) +endif() +if(Boost_LIBRARY_DIRS) + list(REMOVE_DUPLICATES Boost_LIBRARY_DIRS) +endif() + +# The above setting of Boost_FOUND was based only on the header files. +# Update it for the requested component libraries. +if(Boost_FOUND) + # The headers were found. Check for requested component libs. + set(_boost_CHECKED_COMPONENT FALSE) + set(_Boost_MISSING_COMPONENTS "") + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + set(_boost_CHECKED_COMPONENT TRUE) + if(NOT Boost_${UPPERCOMPONENT}_FOUND AND Boost_FIND_REQUIRED_${COMPONENT}) + list(APPEND _Boost_MISSING_COMPONENTS ${COMPONENT}) + endif() + endforeach() + if(_Boost_MISSING_COMPONENTS AND _Boost_EXTRA_FIND_COMPONENTS) + # Optional indirect dependencies are not counted as missing. + list(REMOVE_ITEM _Boost_MISSING_COMPONENTS ${_Boost_EXTRA_FIND_COMPONENTS}) + endif() + + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] Boost_FOUND = ${Boost_FOUND}") + endif() + + if (_Boost_MISSING_COMPONENTS) + set(Boost_FOUND 0) + # We were unable to find some libraries, so generate a sensible + # error message that lists the libraries we were unable to find. + string(APPEND Boost_ERROR_REASON + "\nCould not find the following") + if(Boost_USE_STATIC_LIBS) + string(APPEND Boost_ERROR_REASON " static") + endif() + string(APPEND Boost_ERROR_REASON + " Boost libraries:\n") + foreach(COMPONENT ${_Boost_MISSING_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + string(APPEND Boost_ERROR_REASON + " ${Boost_NAMESPACE}_${COMPONENT}${Boost_ERROR_REASON_${UPPERCOMPONENT}}\n") + endforeach() + + list(LENGTH Boost_FIND_COMPONENTS Boost_NUM_COMPONENTS_WANTED) + list(LENGTH _Boost_MISSING_COMPONENTS Boost_NUM_MISSING_COMPONENTS) + if (${Boost_NUM_COMPONENTS_WANTED} EQUAL ${Boost_NUM_MISSING_COMPONENTS}) + string(APPEND Boost_ERROR_REASON + "No Boost libraries were found. You may need to set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") + else () + string(APPEND Boost_ERROR_REASON + "Some (but not all) of the required Boost libraries were found. You may need to install these additional Boost libraries. Alternatively, set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") + endif () + endif () + + if( NOT Boost_LIBRARY_DIRS AND NOT _boost_CHECKED_COMPONENT ) + # Compatibility Code for backwards compatibility with CMake + # 2.4's FindBoost module. + + # Look for the boost library path. + # Note that the user may not have installed any libraries + # so it is quite possible the Boost_LIBRARY_DIRS may not exist. + set(_boost_LIB_DIR ${Boost_INCLUDE_DIR}) + + if("${_boost_LIB_DIR}" MATCHES "boost-[0-9]+") + get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) + endif() + + if("${_boost_LIB_DIR}" MATCHES "/include$") + # Strip off the trailing "/include" in the path. + get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) + endif() + + if(EXISTS "${_boost_LIB_DIR}/lib") + string(APPEND _boost_LIB_DIR /lib) + elseif(EXISTS "${_boost_LIB_DIR}/stage/lib") + string(APPEND _boost_LIB_DIR "/stage/lib") + else() + set(_boost_LIB_DIR "") + endif() + + if(_boost_LIB_DIR AND EXISTS "${_boost_LIB_DIR}") + set(Boost_LIBRARY_DIRS ${_boost_LIB_DIR}) + endif() + + endif() +else() + # Boost headers were not found so no components were found. + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + set(Boost_${UPPERCOMPONENT}_FOUND 0) + endforeach() +endif() + +# ------------------------------------------------------------------------ +# Add imported targets +# ------------------------------------------------------------------------ + +if(Boost_FOUND) + # For header-only libraries + if(NOT TARGET Boost::boost) + add_library(Boost::boost INTERFACE IMPORTED) + if(Boost_INCLUDE_DIRS) + set_target_properties(Boost::boost PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") + endif() + endif() + + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + if(_Boost_IMPORTED_TARGETS AND NOT TARGET Boost::${COMPONENT}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + if(Boost_${UPPERCOMPONENT}_FOUND) + if(Boost_USE_STATIC_LIBS) + add_library(Boost::${COMPONENT} STATIC IMPORTED) + else() + # Even if Boost_USE_STATIC_LIBS is OFF, we might have static + # libraries as a result. + add_library(Boost::${COMPONENT} UNKNOWN IMPORTED) + endif() + if(Boost_INCLUDE_DIRS) + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY}") + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${Boost_${UPPERCOMPONENT}_LIBRARY}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") + set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") + set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX" + IMPORTED_LOCATION_DEBUG "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") + endif() + if(_Boost_${UPPERCOMPONENT}_DEPENDENCIES) + unset(_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES) + foreach(dep ${_Boost_${UPPERCOMPONENT}_DEPENDENCIES}) + list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Boost::${dep}) + endforeach() + if(COMPONENT STREQUAL "thread") + list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Threads::Threads) + endif() + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_LINK_LIBRARIES "${_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES}") + endif() + if(_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_COMPILE_FEATURES "${_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES}") + endif() + endif() + endif() + endforeach() +endif() + +# ------------------------------------------------------------------------ +# Notification to end user about what was found +# ------------------------------------------------------------------------ + +set(Boost_LIBRARIES "") +if(Boost_FOUND) + if(NOT Boost_FIND_QUIETLY) + message(STATUS "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + if(Boost_FIND_COMPONENTS) + message(STATUS "Found the following Boost libraries:") + endif() + endif() + foreach( COMPONENT ${Boost_FIND_COMPONENTS} ) + string( TOUPPER ${COMPONENT} UPPERCOMPONENT ) + if( Boost_${UPPERCOMPONENT}_FOUND ) + if(NOT Boost_FIND_QUIETLY) + message (STATUS " ${COMPONENT}") + endif() + list(APPEND Boost_LIBRARIES ${Boost_${UPPERCOMPONENT}_LIBRARY}) + endif() + endforeach() +else() + if(Boost_FIND_REQUIRED) + message(SEND_ERROR "Unable to find the requested Boost libraries.\n${Boost_ERROR_REASON}") + else() + if(NOT Boost_FIND_QUIETLY) + # we opt not to automatically output Boost_ERROR_REASON here as + # it could be quite lengthy and somewhat imposing in its requests + # Since Boost is not always a required dependency we'll leave this + # up to the end-user. + if(Boost_DEBUG OR Boost_DETAILED_FAILURE_MSG) + message(STATUS "Could NOT find Boost\n${Boost_ERROR_REASON}") + else() + message(STATUS "Could NOT find Boost") + endif() + endif() + endif() +endif() + +# Configure display of cache entries in GUI. +foreach(v BOOSTROOT BOOST_ROOT ${_Boost_VARS_INC} ${_Boost_VARS_LIB}) + get_property(_type CACHE ${v} PROPERTY TYPE) + if(_type) + set_property(CACHE ${v} PROPERTY ADVANCED 1) + if("x${_type}" STREQUAL "xUNINITIALIZED") + if("x${v}" STREQUAL "xBoost_ADDITIONAL_VERSIONS") + set_property(CACHE ${v} PROPERTY TYPE STRING) + else() + set_property(CACHE ${v} PROPERTY TYPE PATH) + endif() + endif() + endif() +endforeach() + +# Record last used values of input variables so we can +# detect on the next run if the user changed them. +foreach(v + ${_Boost_VARS_INC} ${_Boost_VARS_LIB} + ${_Boost_VARS_DIR} ${_Boost_VARS_NAME} + ) + if(DEFINED ${v}) + set(_${v}_LAST "${${v}}" CACHE INTERNAL "Last used ${v} value.") + else() + unset(_${v}_LAST CACHE) + endif() +endforeach() + +# Maintain a persistent list of components requested anywhere since +# the last flush. +set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}") +list(APPEND _Boost_COMPONENTS_SEARCHED ${Boost_FIND_COMPONENTS}) +list(REMOVE_DUPLICATES _Boost_COMPONENTS_SEARCHED) +list(SORT _Boost_COMPONENTS_SEARCHED) +set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}" + CACHE INTERNAL "Components requested for this build tree.") + +# Restore project's policies +cmake_policy(POP) diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 947d5dff0d..06aac192f2 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable (core_test conflicts.cpp difficulty.cpp distributed_work.cpp + election.cpp entry.cpp epochs.cpp gap_cache.cpp diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 15b74bc141..85b8eeb337 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -7,30 +7,93 @@ using namespace std::chrono_literals; -TEST (active_transactions, confirm_one) +namespace nano { - nano::system system (1); - auto & node1 = *system.nodes[0]; - // Send and vote for a block before peering with node2 - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); +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); - while (!node1.active.empty () && !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ())) + // 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 ()); } - auto & node2 = *system.add_node (nano::node_config (nano::get_available_port (), system.logging)); + // At least one confirmation request + ASSERT_GT (election->confirmation_request_count, 0); + // 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); - // 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 ()); } - while (node2.ledger.cache.cemented_count < 2) + // 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, 0); +} } TEST (active_transactions, adjusted_difficulty_priority) @@ -59,6 +122,7 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty { nano::lock_guard active_guard (node1.active.mutex); + node1.active.update_adjusted_difficulty (); 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); @@ -76,7 +140,7 @@ TEST (active_transactions, adjusted_difficulty_priority) } } system.deadline_set (10s); - while (node1.ledger.cache.cemented_count < 5) + while (node1.ledger.cache.cemented_count < 5 || !node1.active.empty ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -105,6 +169,7 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty nano::lock_guard lock (node1.active.mutex); + node1.active.update_adjusted_difficulty (); uint64_t last_adjusted (0); for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i) { @@ -160,7 +225,8 @@ TEST (active_transactions, adjusted_difficulty_overflow_max) modify_difficulty (send2_root); modify_difficulty (open1_root); modify_difficulty (open2_root); - node1.active.adjust_difficulty (send2->hash ()); + node1.active.add_adjust_difficulty (send2->hash ()); + node1.active.update_adjusted_difficulty (); // 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 ()); @@ -214,7 +280,8 @@ TEST (active_transactions, adjusted_difficulty_overflow_min) modify_difficulty (open1_root); modify_difficulty (open2_root); modify_difficulty (send3_root); - node1.active.adjust_difficulty (send1->hash ()); + node1.active.add_adjust_difficulty (send1->hash ()); + node1.active.update_adjusted_difficulty (); // 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); @@ -336,6 +403,8 @@ TEST (active_transactions, prioritize_chains) } size_t seen (0); { + nano::lock_guard active_guard (node1.active.mutex); + node1.active.update_adjusted_difficulty (); auto it (node1.active.roots.get<1> ().begin ()); while (!node1.active.roots.empty () && it != node1.active.roots.get<1> ().end ()) { @@ -400,7 +469,7 @@ TEST (active_transactions, inactive_votes_cache_fork) while (!confirmed) { auto transaction (node.store.tx_begin_read ()); - confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()); + confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()) && node.active.empty (); ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); @@ -557,9 +626,11 @@ 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 ()); - auto updated = (existing1->difficulty > difficulty1) && (existing2->difficulty > difficulty2); - auto propogated = (existing3->difficulty > difficulty1) && (existing4->difficulty > difficulty2); - done = updated && propogated; + auto updated1 = existing1->difficulty > difficulty1; + auto updated2 = existing2->difficulty > difficulty2; + auto propogated1 = existing3->difficulty > difficulty1; + auto propogated2 = existing4->difficulty > difficulty2; + done = updated1 && updated2 && propogated1 && propogated2; } ASSERT_NO_ERROR (system.poll ()); } @@ -583,15 +654,28 @@ TEST (active_transactions, vote_replays) node.process_active (open1); node.block_processor.flush (); ASSERT_EQ (2, node.active.size ()); - // First vote is not a replay and confirms the election, second vote should be indeterminate since the election no longer exists + // 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 (1, node.active.size ()); + 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 should be indeterminate + system.deadline_set (3s); + while (node.active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (nano::vote_code::indeterminate, 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_TRUE (node.active.empty ()); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + system.deadline_set (3s); + while (!node.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1)); ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub)); @@ -607,7 +691,135 @@ TEST (active_transactions, vote_replays) 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)); + while (!node.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (0, node.active.size ()); 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 (); + { + 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 (); + 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->ledger.block_confirmed (node1->store.tx_begin_read (), block2->hash ())); + ASSERT_TRUE (node2->ledger.block_confirmed (node2->store.tx_begin_read (), block2->hash ())); +} + +namespace nano +{ +// 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; + + // 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).first); + 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); + 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 ())); +} +} diff --git a/nano/core_test/block_store.cpp b/nano/core_test/block_store.cpp index 289e4adc99..4e88ef970b 100644 --- a/nano/core_test/block_store.cpp +++ b/nano/core_test/block_store.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -90,7 +91,6 @@ TEST (block_store, block_details_serialization) TEST (block_store, sideband_serialization) { nano::block_sideband sideband1; - sideband1.type = nano::block_type::receive; sideband1.account = 1; sideband1.balance = 2; sideband1.height = 3; @@ -99,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); @@ -118,13 +117,13 @@ 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, false, false, false); - 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); @@ -141,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, false, false, false); - 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) @@ -164,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, false, false, false); - 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); @@ -183,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, false, false, false); - store->block_put (transaction, hash1, block, sideband); - nano::block_sideband sideband2 (nano::block_type::open, 0, 0, 0, 0, 0, nano::epoch::epoch_0, false, false, false); - 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); @@ -215,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, false, false, false); - 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, false, false, false); - 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); @@ -463,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, false, false, false); - store->block_put (transaction, block1.hash (), block1, sideband); + store->block_put (transaction, block1.hash (), block1); ASSERT_TRUE (store->block_exists (transaction, block1.hash ())); } @@ -560,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, false, false, false); - 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, false, false, false); - 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 ())); } @@ -755,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, false, false, false); - store->block_put (transaction, 0, send1, sideband1); - nano::block_sideband sideband2 (nano::block_type::send, 0, 0, 0, 0, 0, nano::epoch::epoch_0, false, false, false); - 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 ()); @@ -775,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, false, false, false); - 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 ()); @@ -1172,13 +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 ()); 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, false, false, false); - 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); @@ -1209,10 +1218,9 @@ TEST (mdb_block_store, upgrade_sideband_genesis) nano::ledger_cache ledger_cache; store.initialize (transaction, genesis, ledger_cache); modify_account_info_to_v13 (store, transaction, nano::genesis_account, nano::genesis_hash); - 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); 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; @@ -1226,10 +1234,9 @@ TEST (mdb_block_store, upgrade_sideband_genesis) 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) @@ -1265,14 +1272,12 @@ TEST (mdb_block_store, upgrade_sideband_two_blocks) 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) @@ -1314,18 +1319,15 @@ TEST (mdb_block_store, upgrade_sideband_two_accounts) 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) @@ -1406,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 ())); @@ -1452,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) @@ -1751,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.details.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.details.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); @@ -1818,7 +1805,7 @@ TEST (mdb_block_store, upgrade_v16_v17) 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 code = [&block1, &block2, &block3](auto confirmation_height, nano::block_hash const & expected_cemented_frontier) { auto path (nano::unique_path ()); nano::mdb_val value; { @@ -1937,124 +1924,112 @@ TEST (mdb_block_store, upgrade_v17_v18) // Check that sidebands are correctly populated { // Non-state unaffected - nano::block_sideband sideband; - auto block = store.block_get (transaction, send_zero.hash (), &sideband); + auto block = store.block_get (transaction, send_zero.hash ()); ASSERT_NE (block, nullptr); // All defaults - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_0); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_receive_zero.hash (), &sideband); + auto block = store.block_get (transaction, state_receive_zero.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_0); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, epoch.hash (), &sideband); + auto block = store.block_get (transaction, epoch.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_TRUE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - 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.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_receive.hash (), &sideband); + auto block = store.block_get (transaction, state_receive.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_change.hash (), &sideband); + auto block = store.block_get (transaction, state_change.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_send_change.hash (), &sideband); + auto block = store.block_get (transaction, state_send_change.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, epoch_first.hash (), &sideband); + auto block = store.block_get (transaction, epoch_first.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_2); - ASSERT_TRUE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_receive2.hash (), &sideband); + auto block = store.block_get (transaction, state_receive2.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_2); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_send2.hash (), &sideband); + auto block = store.block_get (transaction, state_send2.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_open.hash (), &sideband); + auto block = store.block_get (transaction, state_open.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); + 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 - nano::block_sideband sideband; - auto block = store.block_get (transaction, state_send_epoch_link.hash (), &sideband); + auto block = store.block_get (transaction, state_send_epoch_link.hash ()); ASSERT_NE (block, nullptr); - ASSERT_EQ (sideband.details.epoch, nano::epoch::epoch_1); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); + 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)); @@ -2089,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)); @@ -2166,8 +2141,8 @@ TEST (mdb_block_store, upgrade_confirmation_height_many) { 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, false, false, false); - 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); @@ -2224,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 (); @@ -2237,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, false, false, false); - store->block_put (write_transaction, hash1, block, sideband); + store->block_put (write_transaction, hash1, block); } read_transaction.renew (); @@ -2297,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); @@ -2310,23 +2284,22 @@ 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.details.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) { - 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); - ASSERT_LE (sideband.details.epoch, nano::epoch::max); + 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 (sideband.type, sideband.account, sideband.successor, sideband.balance, sideband.timestamp, sideband.height, sideband.details.epoch, false, false, false); + 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); + sideband_v15.serialize (stream, block_a.type ()); } MDB_val val{ data.size (), data.data () }; diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp index e9fda60ff9..f9cdab64ba 100644 --- a/nano/core_test/bootstrap.cpp +++ b/nano/core_test/bootstrap.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -337,6 +338,7 @@ TEST (bootstrap_processor, pull_diamond) TEST (bootstrap_processor, DISABLED_pull_requeue_network_error) { + // 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; @@ -359,10 +361,11 @@ TEST (bootstrap_processor, DISABLED_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); @@ -620,9 +623,9 @@ TEST (bootstrap_processor, lazy_hash) node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); node1->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true); { - auto attempt (node1->bootstrap_initiator.current_attempt ()); - ASSERT_NE (nullptr, attempt); - ASSERT_EQ (receive2->hash ().to_string (), attempt->id); + 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); @@ -660,9 +663,9 @@ TEST (bootstrap_processor, lazy_hash_bootstrap_id) 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 attempt (node1->bootstrap_initiator.current_attempt ()); - ASSERT_NE (nullptr, attempt); - ASSERT_EQ ("123456", attempt->id); + 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); @@ -858,9 +861,9 @@ TEST (bootstrap_processor, wallet_lazy_frontier) wallet->insert_adhoc (key2.prv); node1->bootstrap_wallet (); { - auto attempt (node1->bootstrap_initiator.current_attempt ()); - ASSERT_NE (nullptr, attempt); - ASSERT_EQ (key2.pub.to_account (), attempt->id); + 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); @@ -908,6 +911,61 @@ 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) { { diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index 714694648a..13ba95d6c2 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -308,7 +308,7 @@ TEST (confirmation_height, gap_live) 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 node1 = system.add_node (node_config, node_flags); + 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; @@ -317,72 +317,69 @@ TEST (confirmation_height, gap_live) 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)); - node1->work_generate_blocking (*send1); + 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)); - node1->work_generate_blocking (*send2); + 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)); - node1->work_generate_blocking (*send3); + node->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); + node->work_generate_blocking (*open1); auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); - node1->work_generate_blocking (*receive1); + node->work_generate_blocking (*receive1); auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); - node1->work_generate_blocking (*receive2); + node->work_generate_blocking (*receive2); - for (auto & node : system.nodes) - { - node->block_processor.add (send1); - node->block_processor.add (send2); - node->block_processor.add (send3); - node->block_processor.add (receive1); - node->block_processor.flush (); + node->block_processor.add (send1); + node->block_processor.add (send2); + node->block_processor.add (send3); + node->block_processor.add (receive1); + node->block_processor.flush (); - add_callback_stats (*node); + add_callback_stats (*node); - // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked - node->process_active (receive2); - node->block_processor.flush (); + // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked + node->process_active (receive2); + node->block_processor.flush (); - // Confirmation heights should not be updated - { - auto transaction = node->store.tx_begin_read (); - 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); - } + // Confirmation heights should not be updated + { + auto transaction = node->store.tx_begin_read (); + 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 (); + // Now complete the chain where the block comes in on the live network + node->process_active (open1); + node->block_processor.flush (); - 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 ()); - } + 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 ()); + } - // This should confirm the open block and the source of the receive blocks - auto transaction (node->store.tx_begin_read ()); - auto unchecked_count (node->store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 0); + // This should confirm the open block and the source of the receive blocks + auto transaction (node->store.tx_begin_read ()); + auto unchecked_count (node->store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 0); - 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); + 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 (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 (0, node->active.election_winner_details_size ()); - } + ASSERT_EQ (0, node->active.election_winner_details_size ()); }; test_mode (nano::confirmation_height_mode::bounded); @@ -805,7 +802,30 @@ TEST (confirmation_height, modified_chain) ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); + auto check_for_modified_chains = true; + if (mode_a == nano::confirmation_height_mode::unbounded) + { +#ifdef NDEBUG + // Unbounded processor in release config does not check that a chain has been modified prior to setting the confirmation height (as an optimization) + check_for_modified_chains = false; +#endif + } + + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height_get (node->store.tx_begin_read (), nano::test_genesis_key.pub, confirmation_height_info)); + if (check_for_modified_chains) + { + ASSERT_EQ (1, confirmation_height_info.height); + ASSERT_EQ (nano::genesis_hash, confirmation_height_info.frontier); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); + } + else + { + // A non-existent block is cemented, expected given these conditions but is of course incorrect. + ASSERT_EQ (2, confirmation_height_info.height); + ASSERT_EQ (send->hash (), confirmation_height_info.frontier); + } + ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -1187,13 +1207,8 @@ TEST (confirmation_height, dependent_election) add_callback_stats (*node); - // Wait until it has been processed + // Start an election and vote, should confirm the block node->block_confirm (send2); - system.deadline_set (10s); - while (node->active.size () > 0) - { - ASSERT_NO_ERROR (system.poll ()); - } { // The write guard prevents the confirmation height processor doing any writes. @@ -1426,17 +1441,6 @@ TEST (confirmation_height, election_winner_details_clearing) add_callback_stats (*node); node->block_confirm (send1); - system.deadline_set (10s); - while (node->active.size () > 0) - { - ASSERT_NO_ERROR (system.poll ()); - } - - ASSERT_EQ (0, node->active.list_confirmed ().size ()); - { - nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.blocks.size ()); - } system.deadline_set (10s); while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 2) diff --git a/nano/core_test/confirmation_solicitor.cpp b/nano/core_test/confirmation_solicitor.cpp index 8be720b051..ef97f49d9f 100644 --- a/nano/core_test/confirmation_solicitor.cpp +++ b/nano/core_test/confirmation_solicitor.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -9,53 +10,51 @@ using namespace std::chrono_literals; TEST (confirmation_solicitor, batches) { nano::system system; - 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; nano::node_flags node_flags; + node_flags.disable_request_loop = true; node_flags.disable_udp = false; - auto & node1 = *system.add_node (node_config, node_flags); - node_config.peering_port = nano::get_available_port (); - // To prevent races on the solicitor + auto & node1 = *system.add_node (node_flags); + // This tests instantiates a solicitor node_flags.disable_request_loop = true; - auto & node2 = *system.add_node (node_config, node_flags); + auto & node2 = *system.add_node (node_flags); // Solicitor will only solicit from this representative auto channel1 (node2.network.udp_channels.create (node1.network.endpoint ())); nano::representative representative (nano::test_genesis_key.pub, nano::genesis_amount, channel1); - // Lock active_transactions which uses the solicitor + + 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); + 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))); { - nano::lock_guard active_guard (node2.active.mutex); - std::vector representatives{ representative }; - node2.active.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); - 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))); + 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, false, nullptr)); - ASSERT_FALSE (node2.active.solicitor.add (*election)); + auto election (std::make_shared (node2, send, nullptr)); + ASSERT_FALSE (solicitor.add (*election)); } - ASSERT_EQ (1, node2.active.solicitor.max_confirm_req_batches); + ASSERT_EQ (1, solicitor.max_confirm_req_batches); // Reached the maximum amount of requests for the channel - auto election (std::make_shared (node2, send, false, nullptr)); - ASSERT_TRUE (node2.active.solicitor.add (*election)); + auto election (std::make_shared (node2, send, nullptr)); + 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 (node2.active.solicitor.broadcast (*election)); - system.deadline_set (5s); - while (node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out) < 1) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_FALSE (solicitor.broadcast (*election)); + } + system.deadline_set (5s); + while (node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out) < 1) + { + ASSERT_NO_ERROR (system.poll ()); } // From rep crawler - ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out)); system.deadline_set (5s); - node2.active.solicitor.flush (); - while (node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out) < 2) + solicitor.flush (); + while (node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out) == 1) { ASSERT_NO_ERROR (system.poll ()); } + ASSERT_LE (2, 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 3309435524..1cf633a39b 100644 --- a/nano/core_test/conflicts.cpp +++ b/nano/core_test/conflicts.cpp @@ -253,6 +253,7 @@ TEST (conflicts, adjusted_difficulty) std::unordered_map adjusted_difficulties; { nano::lock_guard guard (node1.active.mutex); + node1.active.update_adjusted_difficulty (); 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) { @@ -285,6 +286,7 @@ TEST (conflicts, adjusted_difficulty) } { nano::lock_guard guard (node1.active.mutex); + node1.active.update_adjusted_difficulty (); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), open_epoch2->hash ()); } } diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp new file mode 100644 index 0000000000..8ccc8af4d2 --- /dev/null +++ b/nano/core_test/election.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +#include + +TEST (election, construction) +{ + nano::system system (1); + nano::genesis genesis; + auto & node = *system.nodes[0]; + auto election = node.active.insert (genesis.open).first; + ASSERT_TRUE (election->idle ()); + election->transition_active (); + ASSERT_FALSE (election->idle ()); + election->transition_passive (); + ASSERT_FALSE (election->idle ()); +} 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/ledger.cpp b/nano/core_test/ledger.cpp index ed438fa646..08e0b6adc4 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -71,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) { @@ -92,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)); @@ -113,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); @@ -190,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)); @@ -199,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)); @@ -768,7 +795,7 @@ TEST (votes, add_one) auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); 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_EQ (nano::vote_code::indeterminate, node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); lock.lock (); ASSERT_EQ (2, election1.first->last_votes.size ()); auto existing1 (election1.first->last_votes.find (nano::test_genesis_key.pub)); @@ -1680,22 +1707,21 @@ TEST (ledger, state_send_receive) 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); ASSERT_TRUE (store->block_exists (transaction, send1.hash ())); - nano::block_sideband sideband; - auto send2 (store->block_get (transaction, send1.hash (), &sideband)); + auto send2 (store->block_get (transaction, send1.hash ())); ASSERT_NE (nullptr, send2); ASSERT_EQ (send1, *send2); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); 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_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); + 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 ())); - nano::block_sideband sideband2; - auto receive2 (store->block_get (transaction, receive1.hash (), &sideband2)); + auto receive2 (store->block_get (transaction, receive1.hash ())); ASSERT_NE (nullptr, receive2); ASSERT_EQ (receive1, *receive2); ASSERT_EQ (nano::genesis_amount, ledger.balance (transaction, receive1.hash ())); @@ -1703,9 +1729,10 @@ TEST (ledger, state_send_receive) 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_FALSE (sideband2.details.is_send); - ASSERT_TRUE (sideband2.details.is_receive); - ASSERT_FALSE (sideband2.details.is_epoch); + 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) @@ -1731,16 +1758,16 @@ TEST (ledger, state_receive) 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 ())); - nano::block_sideband sideband; - auto receive2 (store->block_get (transaction, receive1.hash (), &sideband)); + auto receive2 (store->block_get (transaction, receive1.hash ())); ASSERT_NE (nullptr, receive2); ASSERT_EQ (receive1, *receive2); 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_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); + 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) @@ -1758,17 +1785,17 @@ TEST (ledger, state_rep_change) 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 ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, change1).code); ASSERT_TRUE (store->block_exists (transaction, change1.hash ())); - nano::block_sideband sideband; - auto change2 (store->block_get (transaction, change1.hash (), &sideband)); + auto change2 (store->block_get (transaction, change1.hash ())); ASSERT_NE (nullptr, change2); ASSERT_EQ (change1, *change2); ASSERT_EQ (nano::genesis_amount, ledger.balance (transaction, change1.hash ())); 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_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); + 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) @@ -1797,17 +1824,17 @@ TEST (ledger, state_open) ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code); ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (destination.pub, send1.hash ()))); ASSERT_TRUE (store->block_exists (transaction, open1.hash ())); - nano::block_sideband sideband; - auto open2 (store->block_get (transaction, open1.hash (), &sideband)); + auto open2 (store->block_get (transaction, open1.hash ())); ASSERT_NE (nullptr, open2); ASSERT_EQ (open1, *open2); 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_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); + 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. @@ -2050,17 +2077,17 @@ TEST (ledger, state_send_change) 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 ())); ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); ASSERT_TRUE (store->block_exists (transaction, send1.hash ())); - nano::block_sideband sideband; - auto send2 (store->block_get (transaction, send1.hash (), &sideband)); + auto send2 (store->block_get (transaction, send1.hash ())); ASSERT_NE (nullptr, send2); ASSERT_EQ (send1, *send2); ASSERT_EQ (nano::genesis_amount - nano::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); 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_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); + 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) @@ -2087,17 +2114,17 @@ TEST (ledger, state_receive_change) nano::state_block receive1 (nano::genesis_account, send1.hash (), rep.pub, 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 ())); - nano::block_sideband sideband2; - auto receive2 (store->block_get (transaction, receive1.hash (), &sideband2)); + auto receive2 (store->block_get (transaction, receive1.hash ())); ASSERT_NE (nullptr, receive2); ASSERT_EQ (receive1, *receive2); ASSERT_EQ (nano::genesis_amount, ledger.balance (transaction, receive1.hash ())); 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_FALSE (sideband2.details.is_send); - ASSERT_TRUE (sideband2.details.is_receive); - ASSERT_FALSE (sideband2.details.is_epoch); + 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) @@ -2341,13 +2368,9 @@ TEST (ledger, epoch_blocks_v1_general) 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); - { - nano::block_sideband sideband; - (void)ledger.store.block_get (transaction, epoch1.hash (), &sideband); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_TRUE (sideband.details.is_epoch); - } + 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; @@ -2359,37 +2382,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); - { - nano::block_sideband sideband; - (void)ledger.store.block_get (transaction, epoch1.hash (), &sideband); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_TRUE (sideband.details.is_epoch); - } + 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); - { - nano::block_sideband sideband; - (void)ledger.store.block_get (transaction, send1.hash (), &sideband); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); - } + 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); - { - nano::block_sideband sideband; - (void)ledger.store.block_get (transaction, epoch4.hash (), &sideband); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_receive); - ASSERT_TRUE (sideband.details.is_epoch); - } + 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 ())); @@ -2399,13 +2410,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)); - { - nano::block_sideband sideband; - (void)ledger.store.block_get (transaction, receive2.hash (), &sideband); - ASSERT_FALSE (sideband.details.is_send); - ASSERT_TRUE (sideband.details.is_receive); - ASSERT_FALSE (sideband.details.is_epoch); - } + 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) @@ -2804,12 +2811,12 @@ TEST (ledger, unchecked_epoch_invalid) nano::account_info info; ASSERT_FALSE (node1.store.account_get (transaction, destination.pub, info)); ASSERT_NE (info.epoch (), nano::epoch::epoch_1); - nano::block_sideband sideband; - ASSERT_NE (nullptr, node1.store.block_get (transaction, epoch2->hash (), &sideband)); - ASSERT_EQ (nano::epoch::epoch_0, sideband.details.epoch); - ASSERT_TRUE (sideband.details.is_send); - ASSERT_FALSE (sideband.details.is_epoch); - ASSERT_FALSE (sideband.details.is_receive); + 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); } } diff --git a/nano/core_test/message_parser.cpp b/nano/core_test/message_parser.cpp index f67b2fd6e7..e13540a053 100644 --- a/nano/core_test/message_parser.cpp +++ b/nano/core_test/message_parser.cpp @@ -63,9 +63,10 @@ TEST (message_parser, exact_confirm_ack_size) { 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); 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); @@ -96,9 +97,10 @@ TEST (message_parser, exact_confirm_req_size) { 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); 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; @@ -128,9 +130,10 @@ TEST (message_parser, exact_confirm_req_hash_size) { 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); 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; @@ -160,9 +163,10 @@ TEST (message_parser, exact_publish_size) { 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); 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; @@ -192,9 +196,10 @@ TEST (message_parser, exact_keepalive_size) { 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); nano::keepalive message; std::vector bytes; { diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index 901d4ada2a..92535152b0 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -92,15 +92,21 @@ TEST (network, send_node_id_handshake) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (0, node0->network.size ()); - ASSERT_EQ (1, node1->network.size ()); + system.deadline_set (10s); + while (node0->network.size () != 0 && node1->network.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } 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 ()); } - ASSERT_EQ (1, node0->network.size ()); - ASSERT_EQ (1, node1->network.size ()); + 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)); @@ -879,6 +885,7 @@ TEST (network, replace_port) nano::system system; nano::node_flags node_flags; node_flags.disable_udp = false; + node_flags.disable_ongoing_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)); @@ -912,10 +919,94 @@ TEST (network, replace_port) ASSERT_EQ (node0->network.endpoint (), list2[0]->get_endpoint ()); // Remove correct peer (same node ID) node0->network.udp_channels.clean_node_id (nano::endpoint (node1->network.endpoint ().address (), 23000), node1->node_id.pub); - ASSERT_EQ (node0->network.udp_channels.size (), 0); + system.deadline_set (5s); + while (node0->network.udp_channels.size () > 1) + { + ASSERT_NO_ERROR (system.poll ()); + } node1->stop (); } +TEST (network, peer_max_tcp_attempts) +{ + 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) + { + 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 ()))); +} + +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); + + // 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) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +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 (bandwidth_limiter, validate) { diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 65bc68cd16..ca2511b499 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -555,7 +555,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; @@ -572,7 +572,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")); @@ -590,7 +590,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) @@ -1506,8 +1506,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 @@ -1741,9 +1743,6 @@ TEST (node, broadcast_elected) 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 ()); @@ -1784,14 +1783,14 @@ TEST (node, broadcast_elected) 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 ())) { @@ -2134,7 +2133,6 @@ TEST (node, rep_weight) 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.add (genesis.open->hash ()); node.rep_crawler.response (channel0, vote0); node.rep_crawler.response (channel1, vote1); node.rep_crawler.response (channel2, vote2); @@ -2217,7 +2215,6 @@ TEST (node, rep_remove) nano::amount amount100 (100); node.network.udp_channels.insert (endpoint0, node.network_params.protocol.protocol_version); auto vote1 = std::make_shared (keypair1.pub, keypair1.prv, 0, genesis.open); - node.rep_crawler.add (genesis.hash ()); node.rep_crawler.response (channel0, vote1); system.deadline_set (5s); while (node.rep_crawler.representative_count () != 1) @@ -2406,27 +2403,35 @@ TEST (node, balance_observer) } } -// ASSERT_NE (nullptr, attempt) sometimes fails -TEST (node, DISABLED_bootstrap_connection_scaling) +TEST (node, bootstrap_connection_scaling) { 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 @@ -2480,15 +2485,17 @@ TEST (node, block_confirm) 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, *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, nano::seconds_since_epoch ()); + node2.block_processor.add (send1_copy, nano::seconds_since_epoch ()); system.deadline_set (std::chrono::seconds (5)); - while (!node1.ledger.block_exists (send1->hash ()) || !node2.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 (node1.ledger.block_exists (send1->hash ())); - ASSERT_TRUE (node2.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 (node1.store.tx_begin_write ()); @@ -3210,7 +3217,7 @@ TEST (node, block_processor_full) { nano::system system; nano::node_flags node_flags; - node_flags.block_processor_full_size = 2; + 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)); @@ -3238,7 +3245,7 @@ TEST (node, block_processor_half_full) { nano::system system; nano::node_flags node_flags; - node_flags.block_processor_full_size = 4; + 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)); @@ -3414,6 +3421,14 @@ TEST (node, unchecked_cleanup) 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); @@ -3425,6 +3440,7 @@ TEST (node, unchecked_cleanup) } 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)); @@ -3433,6 +3449,7 @@ TEST (node, unchecked_cleanup) } 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)); @@ -3571,7 +3588,7 @@ TEST (node, bandwidth_limiter) nano::publish message (genesis.open); auto message_size = message.to_bytes ()->size (); auto message_limit = 4; // must be multiple of the number of channels - nano::node_config node_config (24000, system.logging); + nano::node_config node_config (nano::get_available_port (), system.logging); node_config.bandwidth_limit = message_limit * message_size; auto & node = *system.add_node (node_config); auto channel1 (node.network.udp_channels.create (node.network.endpoint ())); diff --git a/nano/core_test/node_telemetry.cpp b/nano/core_test/node_telemetry.cpp index c42b396cce..bff5609f4e 100644 --- a/nano/core_test/node_telemetry.cpp +++ b/nano/core_test/node_telemetry.cpp @@ -263,38 +263,28 @@ TEST (node_telemetry, no_peers) nano::system system (1); std::atomic done{ false }; - system.nodes[0]->telemetry.get_metrics_peers_async ([&done](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.telemetry_datas.empty ()); - ASSERT_FALSE (responses_a.all_received); - done = true; - }); - - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } + auto responses = system.nodes[0]->telemetry->get_metrics (); + ASSERT_TRUE (responses.empty ()); } -namespace nano -{ TEST (node_telemetry, basic) { nano::system system; - nano::node_flags node_flags; - node_flags.disable_ongoing_telemetry_requests = true; - auto node_client = system.add_node (node_flags); - auto node_server = system.add_node (node_flags); + auto node_client = system.add_node (); + auto node_server = system.add_node (); wait_peer_connections (system); // Request telemetry metrics - std::unordered_map all_telemetry_datas; + 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_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - all_telemetry_datas = responses_a.telemetry_datas; + 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; }); @@ -306,15 +296,14 @@ TEST (node_telemetry, basic) } // Check the metrics are correct - ASSERT_EQ (all_telemetry_datas.size (), 1); - compare_default_test_result_data (all_telemetry_datas.begin ()->second, *node_server); + compare_default_test_result_data (telemetry_data, *node_server); // Call again straight away. It should use the cache { std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_EQ (all_telemetry_datas, responses_a.telemetry_datas); - ASSERT_TRUE (responses_a.all_received); + 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; }); @@ -329,9 +318,9 @@ TEST (node_telemetry, basic) std::this_thread::sleep_for (nano::telemetry_cache_cutoffs::test); std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_NE (all_telemetry_datas, responses_a.telemetry_datas); - ASSERT_TRUE (responses_a.all_received); + 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; }); @@ -341,19 +330,36 @@ TEST (node_telemetry, basic) ASSERT_NO_ERROR (system.poll ()); } } -} TEST (node_telemetry, many_nodes) { nano::system system; // 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; + nano::node_flags node_flags; + node_flags.disable_ongoing_telemetry_requests = true; 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; - system.add_node (node_config); + + 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); @@ -371,25 +377,32 @@ TEST (node_telemetry, many_nodes) // This is the node which will request metrics from all other nodes auto node_client = system.nodes.front (); - std::atomic done{ false }; - std::unordered_map all_telemetry_datas; - node_client->telemetry.get_metrics_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - all_telemetry_datas = responses_a.telemetry_datas; - done = true; - }); + 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); - while (!done) + 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 & telemetry_data : all_telemetry_datas) + for (auto & data : telemetry_datas) { - auto & data = telemetry_data.second; ASSERT_EQ (data.unchecked_count, 0); ASSERT_EQ (data.cemented_count, 1); ASSERT_LE (data.peer_count, 9); @@ -408,10 +421,10 @@ TEST (node_telemetry, many_nodes) } // We gave some nodes different bandwidth caps, confirm they are not all the same - auto bandwidth_cap = all_telemetry_datas.begin ()->second.bandwidth_cap; - all_telemetry_datas.erase (all_telemetry_datas.begin ()); - auto all_bandwidth_limits_same = std::all_of (all_telemetry_datas.begin (), all_telemetry_datas.end (), [bandwidth_cap](auto & telemetry_data) { - return telemetry_data.second.bandwidth_cap == bandwidth_cap; + 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); } @@ -423,7 +436,7 @@ TEST (node_telemetry, receive_from_non_listening_channel) 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); + ASSERT_EQ (node->telemetry->telemetry_data_size (), 0); } TEST (node_telemetry, over_udp) @@ -438,10 +451,10 @@ TEST (node_telemetry, over_udp) wait_peer_connections (system); std::atomic done{ false }; - std::unordered_map all_telemetry_datas; - node_client->telemetry.get_metrics_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - all_telemetry_datas = responses_a.telemetry_datas; + 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); + compare_default_test_result_data (response_a.telemetry_data, *node_server); done = true; }); @@ -451,9 +464,6 @@ TEST (node_telemetry, over_udp) ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (all_telemetry_datas.size (), 1); - compare_default_test_result_data (all_telemetry_datas.begin ()->second, *node_server); - // Check channels are indeed udp ASSERT_EQ (1, node_client->network.size ()); auto list1 (node_client->network.list (2)); @@ -465,77 +475,7 @@ TEST (node_telemetry, over_udp) ASSERT_EQ (nano::transport::transport_type::udp, list2[0]->get_type ()); } -namespace nano -{ -TEST (node_telemetry, single_request) -{ - nano::system system; - nano::node_flags node_flags; - 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); - - // Request telemetry metrics - auto channel = node_client->network.find_channel (node_server->network.endpoint ()); - nano::telemetry_data telemetry_data; - { - std::atomic done{ false }; - - node_client->telemetry.get_metrics_single_peer_async (channel, [&done, &telemetry_data, &channel](nano::telemetry_data_response const & response_a) { - ASSERT_FALSE (response_a.error); - ASSERT_EQ (channel->get_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 - compare_default_test_result_data (telemetry_data, *node_server); - - // 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 (node_telemetry, single_request_invalid_channel) +TEST (node_telemetry, invalid_channel) { nano::system system (2); @@ -543,7 +483,7 @@ TEST (node_telemetry, single_request_invalid_channel) 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) { + node_client->telemetry->get_metrics_single_peer_async (nullptr, [&done](nano::telemetry_data_response const & response_a) { ASSERT_TRUE (response_a.error); done = true; }); @@ -555,7 +495,7 @@ TEST (node_telemetry, single_request_invalid_channel) } } -TEST (node_telemetry, blocking_single_and_random) +TEST (node_telemetry, blocking_request) { nano::system system (2); @@ -584,104 +524,15 @@ TEST (node_telemetry, blocking_single_and_random) system.deadline_set (10s); node_client->worker.push_task (call_system_poll); - // Blocking version of get_random_metrics_async - auto telemetry_data_responses = node_client->telemetry.get_metrics_peers (); - ASSERT_TRUE (telemetry_data_responses.all_received); - compare_default_test_result_data (telemetry_data_responses.telemetry_datas.begin ()->second, *node_server); - // 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 ())); + 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); compare_default_test_result_data (telemetry_data_response.telemetry_data, *node_server); - ASSERT_EQ (*telemetry_data_response.telemetry_data.timestamp, *telemetry_data_responses.telemetry_datas.begin ()->second.timestamp); done = true; promise.get_future ().wait (); } -namespace nano -{ -TEST (node_telemetry, multiple_single_request_clearing) -{ - nano::system system (2); - - auto node_client = system.nodes.front (); - auto node_server = system.nodes.back (); - - nano::node_config node_config (nano::get_available_port (), system.logging); - node_config.bandwidth_limit = 100000; - auto node_server1 = system.add_node (node_config); - - wait_peer_connections (system); - - // Request telemetry metrics - auto channel = node_client->network.find_channel (node_server->network.endpoint ()); - - std::atomic done{ false }; - std::chrono::system_clock::time_point last_updated; - node_client->telemetry.get_metrics_single_peer_async (channel, [&done, &last_updated](nano::telemetry_data_response const & response_a) { - ASSERT_FALSE (response_a.error); - last_updated = *response_a.telemetry_data.timestamp; - done = true; - }); - - ASSERT_EQ (1, node_client->telemetry.single_requests.size ()); - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - done = false; - // Make another request to keep the time updated - system.deadline_set (10s); - node_client->telemetry.get_metrics_single_peer_async (channel, [&done, last_updated](nano::telemetry_data_response const & response_a) { - ASSERT_FALSE (response_a.error); - ASSERT_EQ (last_updated, *response_a.telemetry_data.timestamp); - done = true; - }); - - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - done = false; - auto channel1 = node_client->network.find_channel (node_server1->network.endpoint ()); - node_client->telemetry.get_metrics_single_peer_async (channel1, [&done, &last_updated](nano::telemetry_data_response const & response_a) { - ASSERT_FALSE (response_a.error); - ASSERT_NE (last_updated, *response_a.telemetry_data.timestamp); - last_updated = *response_a.telemetry_data.timestamp; - done = true; - }); - - system.deadline_set (10s); - - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - done = false; - node_client->telemetry.get_metrics_single_peer_async (channel1, [&done, last_updated](nano::telemetry_data_response const & response_a) { - ASSERT_FALSE (response_a.error); - ASSERT_EQ (last_updated, *response_a.telemetry_data.timestamp); - done = true; - }); - - // single_requests should be removed as no more calls are being back - system.deadline_set (10s); - nano::unique_lock lk (node_client->telemetry.mutex); - while (!node_client->telemetry.single_requests.empty () || !done) - { - lk.unlock (); - ASSERT_NO_ERROR (system.poll ()); - lk.lock (); - } -} -} - TEST (node_telemetry, disconnects) { nano::system system (2); @@ -697,19 +548,7 @@ TEST (node_telemetry, disconnects) ASSERT_TRUE (channel); std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done](nano::telemetry_data_responses const & responses_a) { - ASSERT_FALSE (responses_a.all_received); - done = true; - }); - - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - done = false; - node_client->telemetry.get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + node_client->telemetry->get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { ASSERT_TRUE (response_a.error); done = true; }); @@ -721,7 +560,7 @@ TEST (node_telemetry, disconnects) } } -TEST (node_telemetry, batch_use_single_request_cache) +TEST (node_telemetry, all_peers_use_single_request_cache) { nano::system system; nano::node_flags node_flags; @@ -736,7 +575,7 @@ TEST (node_telemetry, batch_use_single_request_cache) { 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) { + 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; }); @@ -748,20 +587,8 @@ TEST (node_telemetry, batch_use_single_request_cache) } } - { - std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done, &telemetry_data](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - ASSERT_EQ (telemetry_data, responses_a.telemetry_datas.begin ()->second); - 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)); @@ -771,54 +598,17 @@ TEST (node_telemetry, batch_use_single_request_cache) 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)); - // Wait until there is something pending - system.deadline_set (10s); - while (node_client->telemetry.finished_single_requests_size () == 0) - { - ASSERT_NO_ERROR (system.poll ()); - } - - std::this_thread::sleep_for (nano::telemetry_cache_cutoffs::test); + std::this_thread::sleep_for (node_server->telemetry->cache_plus_buffer_cutoff_time ()); - system.deadline_set (10s); - std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done](nano::telemetry_data_responses const & responses_a) { - ASSERT_EQ (1, responses_a.telemetry_datas.size ()); - done = true; - }); + // Should be empty + responses = node_client->telemetry->get_metrics (); + ASSERT_TRUE (responses.empty ()); - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - ASSERT_EQ (0, node_client->telemetry.finished_single_requests_size ()); - 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 (node_telemetry, single_request_use_batch_cache) -{ - nano::system system (2); - - auto node_client = system.nodes.front (); - auto node_server = system.nodes.back (); - - wait_peer_connections (system); - - // Request batched metric first - std::unordered_map all_telemetry_datas; { std::atomic done{ false }; - node_client->telemetry.get_metrics_peers_async ([&done, &all_telemetry_datas](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - ASSERT_EQ (1, responses_a.telemetry_datas.size ()); - all_telemetry_datas = responses_a.telemetry_datas; + 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; }); @@ -829,26 +619,14 @@ TEST (node_telemetry, single_request_use_batch_cache) } } - 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, &all_telemetry_datas](nano::telemetry_data_response const & response_a) { - ASSERT_EQ (all_telemetry_datas.begin ()->second, response_a.telemetry_data); - ASSERT_FALSE (response_a.error); - 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); - // 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 (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 (1, node_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); + 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 (1, 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::in)); ASSERT_EQ (0, node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out)); } @@ -947,7 +725,7 @@ TEST (node_telemetry, dos_udp) } } -TEST (node_telemetry, disable_metrics_single) +TEST (node_telemetry, disable_metrics) { nano::system system (1); auto node_client = system.nodes.front (); @@ -962,7 +740,7 @@ TEST (node_telemetry, disable_metrics_single) ASSERT_TRUE (channel); std::atomic done{ false }; - node_client->telemetry.get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + node_client->telemetry->get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { ASSERT_TRUE (response_a.error); done = true; }); @@ -976,7 +754,7 @@ TEST (node_telemetry, disable_metrics_single) // 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_server](nano::telemetry_data_response const & response_a) { + node_server->telemetry->get_metrics_single_peer_async (channel1, [&done, node_server](nano::telemetry_data_response const & response_a) { ASSERT_FALSE (response_a.error); compare_default_test_result_data (response_a.telemetry_data, *node_server); done = true; @@ -989,62 +767,34 @@ TEST (node_telemetry, disable_metrics_single) } } -TEST (node_telemetry, disable_metrics_batch) -{ - nano::system system (1); - auto node_client = system.nodes.front (); - nano::node_flags 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_peers_async ([&done](nano::telemetry_data_responses const & responses_a) { - ASSERT_FALSE (responses_a.all_received); - done = true; - }); - - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - // It should still be able to receive metrics though - done = false; - node_server->telemetry.get_metrics_peers_async ([&done, node_server](nano::telemetry_data_responses const & responses_a) { - ASSERT_TRUE (responses_a.all_received); - compare_default_test_result_data (responses_a.telemetry_datas.begin ()->second, *node_server); - done = true; - }); - - system.deadline_set (10s); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - } -} - namespace { void wait_peer_connections (nano::system & system_a) { - system_a.deadline_set (10s); - auto peer_count = 0; - auto num_nodes = system_a.nodes.size (); - 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 (), 0, [](auto total, auto const & node) { - auto transaction = node->store.tx_begin_read (); - return total += node->store.peer_count (transaction); - }); - } + auto wait_peer_count = [&system_a](bool in_memory) { + auto num_nodes = system_a.nodes.size (); + system_a.deadline_set (20s); + auto 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 (), 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); } void compare_default_test_result_data (nano::telemetry_data const & telemetry_data_a, nano::node const & node_server_a) diff --git a/nano/core_test/request_aggregator.cpp b/nano/core_test/request_aggregator.cpp index 819dd04354..8043c6d272 100644 --- a/nano/core_test/request_aggregator.cpp +++ b/nano/core_test/request_aggregator.cpp @@ -52,7 +52,7 @@ TEST (request_aggregator, one) ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); - ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) == 2); } TEST (request_aggregator, one_update) @@ -91,7 +91,7 @@ TEST (request_aggregator, one_update) ASSERT_EQ (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_cached_hashes)); ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) == 1); } TEST (request_aggregator, two) @@ -136,7 +136,7 @@ TEST (request_aggregator, two) ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); - ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) == 2); // Make sure the cached vote is for both hashes auto vote1 (node.votes_cache.find (send1->hash ())); auto vote2 (node.votes_cache.find (send2->hash ())); @@ -231,7 +231,7 @@ TEST (request_aggregator, split) ASSERT_EQ (2, 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_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); - ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out)); + ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out) == 2); } TEST (request_aggregator, channel_lifetime) diff --git a/nano/core_test/socket.cpp b/nano/core_test/socket.cpp index 3ac1971cb4..cc54d11cee 100644 --- a/nano/core_test/socket.cpp +++ b/nano/core_test/socket.cpp @@ -11,7 +11,7 @@ TEST (socket, drop_policy) { auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node inactivenode (nano::unique_path (), nano::get_available_port (), node_flags); + nano::inactive_node inactivenode (nano::unique_path (), node_flags); auto node = inactivenode.node; nano::thread_runner runner (node->io_ctx, 1); @@ -61,7 +61,7 @@ TEST (socket, concurrent_writes) { auto node_flags = nano::inactive_node_flag_defaults (); node_flags.read_only = false; - nano::inactive_node inactivenode (nano::unique_path (), nano::get_available_port (), 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/testutil.hpp b/nano/core_test/testutil.hpp index 24202a4ccc..612f207e24 100644 --- a/nano/core_test/testutil.hpp +++ b/nano/core_test/testutil.hpp @@ -32,6 +32,14 @@ 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 { @@ -187,9 +195,14 @@ namespace util return val; } + void increment_required_count () + { + ++required_count; + } + private: std::atomic count{ 0 }; - unsigned required_count; + std::atomic required_count; }; } diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index c615379467..0239107919 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -120,6 +120,7 @@ TEST (toml, daemon_config_deserialize_defaults) [node.statistics.log] [node.statistics.sampling] [node.websocket] + [node.lmdb] [node.rocksdb] [opencl] [rpc] @@ -150,6 +151,7 @@ TEST (toml, daemon_config_deserialize_defaults) 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 +159,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); @@ -244,6 +246,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); @@ -388,6 +394,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) 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 @@ -493,6 +500,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 @@ -547,6 +559,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) 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); @@ -554,7 +567,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); @@ -642,6 +655,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); diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index de7a91a681..4ef4614cf6 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,33 @@ #include +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 }; diff --git a/nano/core_test/versioning.cpp b/nano/core_test/versioning.cpp index 7e5c8696d5..f47bec0b89 100644 --- a/nano/core_test/versioning.cpp +++ b/nano/core_test/versioning.cpp @@ -11,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, false, false, false); - 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); @@ -47,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, false, false, false); - 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); @@ -83,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, false, false, false); - 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); diff --git a/nano/core_test/wallets.cpp b/nano/core_test/wallets.cpp index d2bfe9775a..5d9ec86ca2 100644 --- a/nano/core_test/wallets.cpp +++ b/nano/core_test/wallets.cpp @@ -133,7 +133,7 @@ TEST (wallets, DISABLED_wallet_create_max) 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); @@ -160,7 +160,7 @@ TEST (wallets, reload) ASSERT_EQ (1, node1.wallets.items.size ()); { nano::lock_guard lock_wallet (node1.wallets.mutex); - nano::inactive_node node (node1.application_path, nano::get_available_port ()); + nano::inactive_node node (node1.application_path); auto wallet (node.node->wallets.create (one)); ASSERT_NE (wallet, nullptr); } diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index 5729a3a0ee..3e3bbceb80 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 @@ -34,6 +36,8 @@ add_library (nano_lib json_error_response.hpp jsonconfig.hpp jsonconfig.cpp + lmdbconfig.hpp + lmdbconfig.cpp locks.hpp locks.cpp logger_mt.hpp @@ -41,6 +45,7 @@ add_library (nano_lib memory.cpp numbers.hpp numbers.cpp + optional_ptr.hpp rep_weights.hpp rep_weights.cpp rocksdbconfig.hpp diff --git a/nano/lib/blocks.cpp b/nano/lib/blocks.cpp index 0907e376cc..7c0f23616a 100644 --- a/nano/lib/blocks.cpp +++ b/nano/lib/blocks.cpp @@ -9,6 +9,8 @@ #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 { @@ -136,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 }; @@ -176,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); @@ -660,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; @@ -896,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; @@ -1203,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; @@ -1364,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); @@ -1608,6 +1651,157 @@ 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; +} + +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); diff --git a/nano/lib/blocks.hpp b/nano/lib/blocks.hpp index 16053bea40..d0ee95c94f 100644 --- a/nano/lib/blocks.hpp +++ b/nano/lib/blocks.hpp @@ -1,8 +1,10 @@ #pragma once #include +#include #include #include +#include #include #include #include @@ -14,6 +16,7 @@ namespace nano { class block_visitor; +class mutable_block_visitor; enum class block_type : uint8_t { invalid = 0, @@ -24,6 +27,43 @@ 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); +}; + +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::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: @@ -31,6 +71,9 @@ class block 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; @@ -52,6 +95,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; @@ -66,6 +110,12 @@ class block 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; @@ -104,6 +154,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; @@ -148,6 +199,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; @@ -196,6 +248,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; @@ -240,6 +293,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; @@ -300,6 +354,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; @@ -321,6 +376,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 */ diff --git a/nano/secure/epoch.cpp b/nano/lib/epoch.cpp similarity index 98% rename from nano/secure/epoch.cpp rename to nano/lib/epoch.cpp index 352dd1f42c..226d1bb41c 100644 --- a/nano/secure/epoch.cpp +++ b/nano/lib/epoch.cpp @@ -1,5 +1,5 @@ +#include #include -#include nano::link const & nano::epochs::link (nano::epoch epoch_a) const { 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/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/numbers.cpp b/nano/lib/numbers.cpp index fba48e109a..542f4805f3 100644 --- a/nano/lib/numbers.cpp +++ b/nano/lib/numbers.cpp @@ -430,8 +430,11 @@ bool nano::validate_message (nano::public_key const & public_key, nano::uint256_ 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) 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/stats.cpp b/nano/lib/stats.cpp index dccbfeda17..4d2cab087b 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -444,6 +444,9 @@ std::string nano::stat::type_to_string (uint32_t key) case nano::stat::type::requests: res = "requests"; break; + case nano::stat::type::filter: + res = "filter"; + break; } return res; } @@ -694,6 +697,9 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::requests_unknown: res = "requests_unknown"; break; + case nano::stat::detail::duplicate_publish: + res = "duplicate_publish"; + break; } return res; } diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index 26f78afa5f..c559db493f 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -199,7 +199,8 @@ class stat final confirmation_height, drop, aggregator, - requests + requests, + filter, }; /** Optional detail type */ @@ -314,7 +315,10 @@ class stat final requests_generated_hashes, requests_cached_votes, requests_generated_votes, - requests_unknown + requests_unknown, + + // duplicate + duplicate_publish }; /** Direction of the stat. If the direction is irrelevant, use in */ diff --git a/nano/lib/threading.cpp b/nano/lib/threading.cpp index 2cabd6671b..7a5482ab07 100644 --- a/nano/lib/threading.cpp +++ b/nano/lib/threading.cpp @@ -51,6 +51,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) 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; diff --git a/nano/lib/threading.hpp b/nano/lib/threading.hpp index a1f43063a0..2ea5138059 100644 --- a/nano/lib/threading.hpp +++ b/nano/lib/threading.hpp @@ -25,6 +25,7 @@ namespace thread_role request_loop, wallet_actions, bootstrap_initiator, + bootstrap_connections, voting, signature_checking, rpc_request_processor, diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index d62865a1f2..fd71e43946 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -110,17 +110,11 @@ void nano::move_all_files_to_dir (boost::filesystem::path const & from, boost::f */ void assert_internal (const char * check_expr, const char * file, unsigned int line, bool is_release_assert) { - // Output stack trace + std::cerr << "Assertion (" << check_expr << ") failed " << file << ":" << line << "\n\n"; + + // Output stack trace to cerr auto backtrace_str = nano::generate_stacktrace (); - // Windows on Actions only outputs the first line of the stacktrace from standard error, use standard output -#if (defined(_WIN32) && CI) - std::cout << backtrace_str << std::endl; -#else std::cerr << backtrace_str << std::endl; -#endif - - std::cerr << "Assertion (" << check_expr << ") failed " << file << ":" << line << "\n" - << 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 it must be done before aborting diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index eeb5af6d03..ea0ef373b2 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -155,18 +155,14 @@ int main (int argc, char * const * argv) std::cerr << flags_ec.message () << std::endl; std::exit (1); } - auto config (vm.find ("config")); - if (config != vm.end ()) - { - flags.config_overrides = config->second.as> (); - } 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")) { @@ -226,11 +222,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))); @@ -243,11 +240,13 @@ int main (int argc, char * const * argv) else if (vm.count ("debug_dump_representatives")) { auto node_flags = nano::inactive_node_flag_defaults (); + nano::update_flags (node_flags, vm); node_flags.generate_cache.reps = true; - nano::inactive_node node (data_path, 24000, node_flags); - 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 ()); nano::uint128_t total; - auto rep_amounts = node.node->ledger.cache.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) { @@ -257,19 +256,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 ()) @@ -280,9 +280,8 @@ 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 inactive_node = nano::default_inactive_node (data_path, vm); + std::cout << boost::str (boost::format ("Frontier count: %1%\n") % inactive_node->node->ledger.cache.account_count); } else if (vm.count ("debug_mass_activity")) { @@ -716,7 +715,7 @@ int main (int argc, char * const * argv) nano::logging logging; auto path (nano::unique_path ()); logging.init (path); - auto node_flags = nano::node_flags (); + 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)); @@ -968,20 +967,21 @@ int main (int argc, char * const * argv) std::exit (0); }); - nano::inactive_node inactive_node_l (data_path); + auto inactive_node_l = nano::default_inactive_node (data_path, vm); 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); + 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 (); } else if (vm.count ("debug_validate_blocks")) { - 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 ()); 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) + for (auto i (node->store.latest_begin (transaction)), n (node->store.latest_end ()); i != n; ++i) { ++count; if ((count % 20000) == 0) @@ -991,7 +991,7 @@ int main (int argc, char * const * argv) nano::account_info const & info (i->second); nano::account const & account (i->first); nano::confirmation_height_info confirmation_height_info; - node.node->store.confirmation_height_get (transaction, account, confirmation_height_info); + node->store.confirmation_height_get (transaction, account, confirmation_height_info); if (confirmation_height_info.height > info.block_count) { @@ -1000,14 +1000,14 @@ int main (int argc, char * const * argv) 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) { @@ -1052,11 +1052,11 @@ 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) @@ -1073,7 +1073,7 @@ int main (int argc, char * const * argv) } else { - auto prev_balance (node.node->ledger.balance (transaction, block->previous ())); + auto prev_balance (node->ledger.balance (transaction, block->previous ())); if (block->balance () < prev_balance) { // State send @@ -1086,7 +1086,7 @@ int main (int argc, char * const * argv) // State change block_details_error = sideband.details.is_send || sideband.details.is_receive || sideband.details.is_epoch; } - else if (block->balance () == prev_balance && node.node->ledger.is_epoch_link (block->link ())) + 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; @@ -1095,7 +1095,7 @@ int main (int argc, char * const * argv) { // State receive block_details_error = !sideband.details.is_receive || sideband.details.is_send || sideband.details.is_epoch; - block_details_error |= !node.node->store.source_exists (transaction, block->link ()); + block_details_error |= !node->store.source_exists (transaction, block->link ()); } } } @@ -1126,11 +1126,11 @@ 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 @@ -1156,14 +1156,14 @@ int main (int argc, char * const * argv) } 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); } // Validate pending blocks count = 0; - 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) { ++count; if ((count % 200000) == 0) @@ -1173,7 +1173,7 @@ int main (int argc, char * const * argv) 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 ()); @@ -1184,7 +1184,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; } @@ -1202,13 +1202,13 @@ int main (int argc, char * const * argv) std::cerr << 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 ()); } // 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 ()); @@ -1222,17 +1222,18 @@ int main (int argc, char * const * argv) 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 (), 24001, node_flags); + 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->store.block_count (transaction).sum (); 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); @@ -1240,7 +1241,7 @@ 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; @@ -1284,10 +1285,11 @@ int main (int argc, char * const * argv) } 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 ())); } @@ -1296,7 +1298,8 @@ int main (int argc, char * const * argv) { auto node_flags = nano::inactive_node_flag_defaults (); node_flags.generate_cache.cemented_count = true; - nano::inactive_node node (data_path, 24000, node_flags); + 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")) @@ -1312,18 +1315,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); @@ -1335,7 +1339,7 @@ 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); diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index 570d3904cb..290808e3d2 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -64,7 +64,7 @@ nano::error read_wallet_config (nano::wallet_config & config_a, boost::filesyste } } -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; @@ -81,7 +81,7 @@ 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_wallet_config (wallet_config, data_path); @@ -331,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/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 278b149e99..b5c03a4121 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -26,12 +26,18 @@ add_library (node active_transactions.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 diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 7e9923d589..a2c8466959 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -1,8 +1,11 @@ #include #include #include +#include #include #include +#include +#include #include #include @@ -16,21 +19,16 @@ confirmation_height_processor (confirmation_height_processor_a), node (node_a), multipliers_cb (20, 1.), trended_active_difficulty (node_a.network_params.network.publish_threshold), -solicitor (node_a.network, node_a.network_params.network), -long_election_threshold (node_a.network_params.network.is_test_network () ? 2s : 24s), -election_request_delay (node_a.network_params.network.is_test_network () ? 0s : 1s), -election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 10s), -min_time_between_requests (node_a.network_params.network.is_test_network () ? 25ms : 3s), -min_time_between_floods (node_a.network_params.network.is_test_network () ? 50ms : 6s), -min_request_count_flood (node_a.network_params.network.is_test_network () ? 0 : 2), +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), 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](nano::block_w_sideband const & callback_data_a) { - this->block_cemented_callback (callback_data_a.block, callback_data_a.sideband); + 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 after a batch of blocks is written and observer calls finished @@ -38,8 +36,6 @@ thread ([this]() { this->block_already_cemented_callback (hash_a); }); - debug_assert (min_time_between_requests > std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - debug_assert (min_time_between_floods > std::chrono::milliseconds (node.network_params.network.request_interval_ms)); nano::unique_lock lock (mutex); condition.wait (lock, [& started = started] { return started; }); } @@ -66,11 +62,11 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran 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 = 1000; 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; + 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. @@ -104,8 +100,10 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran if (info.block_count > confirmation_height_info.height && !this->confirmation_height_processor.is_processing_block (info.head)) { auto block (this->node.store.block_get (transaction_a, info.head)); - if (this->insert (block, true).first) + auto election = this->insert (block); + if (election.second) { + election.first->transition_active (); ++elections_count; // Calculate votes for local representatives if (representative) @@ -124,7 +122,7 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran } } -void nano::active_transactions::block_cemented_callback (std::shared_ptr const & block_a, nano::block_sideband const & sideband_a) +void nano::active_transactions::block_cemented_callback (std::shared_ptr const & block_a) { auto transaction = node.store.tx_begin_read (); @@ -147,7 +145,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptrhash (), sideband_a, account, amount, is_state_send, pending_account); + 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); } else @@ -163,7 +161,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptr lk (mutex); - if (election->confirmed () && !election->stopped && election->status.winner->hash () == hash) + if (election->confirmed () && election->status.winner->hash () == hash) { add_confirmed (election->status, block_a->qualified_root ()); lk.unlock (); @@ -172,7 +170,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptrstatus.type = *election_status_type; election->status.confirmation_request_count = election->confirmation_request_count; node.observers.blocks.notify (election->status, account, amount, is_state_send); @@ -206,60 +204,9 @@ void nano::active_transactions::block_already_cemented_callback (nano::block_has election_winner_details.erase (hash_a); } -void nano::active_transactions::election_escalate (std::shared_ptr & election_l, nano::transaction const & transaction_l, size_t const & roots_size_l) -{ - constexpr unsigned high_confirmation_request_count{ 128 }; - // Log votes for very long unconfirmed elections - if (election_l->confirmation_request_count % (4 * high_confirmation_request_count) == 1) - { - 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 ()) - { - 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)) - { - insert_impl (std::move (previous_l), true); - escalated_l = true; - } - } - /* 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) - { - 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 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)) - { - insert_impl (std::move (source_l), true); - escalated_l = true; - } - } - } - if (escalated_l) - { - election_l->update_dependent (); - } - } -} - void nano::active_transactions::request_confirm (nano::unique_lock & lock_a) { debug_assert (!mutex.try_lock ()); - auto transaction_l (node.store.tx_begin_read ()); - std::unordered_set inactive_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) @@ -273,31 +220,22 @@ void nano::active_transactions::request_confirm (nano::unique_lock & 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); + search_frontiers (node.store.tx_begin_read ()); lock_a.lock (); + update_adjusted_difficulty (); // New roots sorting } } // Only representatives ready to receive batched confirm_req - lock_a.unlock (); - solicitor.prepare (node.rep_crawler.representatives (node.network_params.protocol.tcp_realtime_protocol_version_min)); - lock_a.lock (); + nano::confirmation_solicitor solicitor (node.network, node.network_params.network); + solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits::max (), node.network_params.protocol.tcp_realtime_protocol_version_min)); - auto const now (std::chrono::steady_clock::now ()); - // Any new election started from process_live only gets requests after at least 1 second - auto cutoff_l (now - election_request_delay); - // Elections taking too long get escalated - auto long_election_cutoff_l (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 (now - election_time_to_live); - // Rate-limitting floods - auto const flood_cutoff (now - min_time_between_floods); - // Rate-limitting confirmation requests - auto const request_cutoff (now - min_time_between_requests); - - auto roots_size_l (roots.size ()); - auto & sorted_roots_l = roots.get (); - size_t count_l{ 0 }; + 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 () : node.config.active_elections_size / 10); + 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 @@ -306,58 +244,32 @@ void nano::active_transactions::request_confirm (nano::unique_lock & * 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); - if (election_l->confirmed () || (election_l->confirmation_request_count != 0 && !node.ledger.could_fit (transaction_l, *election_l->status.winner))) - { - election_l->stop (); - } - // Erase finished elections - if ((election_l->stopped)) + auto & election_l (i->election); + unconfirmed_count_l += !election_l->confirmed (); + 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)) { - inactive_l.insert (root_l); + election_l->cleanup (); + i = sorted_roots_l.erase (i); } - // 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)) + else { - election_l->stop (); - inactive_l.insert (root_l); - } - // Attempt obtaining votes - else if (election_l->skip_delay || election_l->election_start < cutoff_l) - { - // Broadcast the winner when elections are taking longer to confirm - if (election_l->confirmation_request_count >= min_request_count_flood && election_l->last_broadcast < flood_cutoff && !solicitor.broadcast (*election_l)) - { - election_l->last_broadcast = now; - } - // Rate-limited requests for confirmation - else if (election_l->last_request < request_cutoff && !solicitor.add (*election_l)) - { - ++election_l->confirmation_request_count; - election_l->last_request = now; - } - // 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); - } + ++i; } } lock_a.unlock (); solicitor.flush (); lock_a.lock (); - // Erase inactive elections - for (auto i (inactive_l.begin ()), n (inactive_l.end ()); i != n; ++i) + + // This is updated after the loop to ensure slow machines don't do the full check often + if (check_all_elections_l) { - auto root_it (roots.get ().find (*i)); - if (root_it != roots.get ().end ()) + last_check_all_elections = std::chrono::steady_clock::now (); + if (node.config.logging.timing_logging () && this_loop_target_l > node.config.active_elections_size / 10) { - root_it->election->clear_blocks (); - root_it->election->clear_dependent (); - roots.get ().erase (root_it); + 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 ())); } } } @@ -380,6 +292,7 @@ void nano::active_transactions::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_adjusted_difficulty (); update_active_difficulty (lock); request_confirm (lock); @@ -566,7 +479,7 @@ void nano::active_transactions::stop () roots.clear (); } -std::pair, bool> nano::active_transactions::insert_impl (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) +std::pair, bool> nano::active_transactions::insert_impl (std::shared_ptr block_a, std::function)> const & confirmation_action_a) { std::pair, bool> result = { nullptr, false }; if (!stopped) @@ -579,11 +492,11 @@ std::pair, bool> nano::active_transactions::inse { result.second = true; auto hash (block_a->hash ()); - result.first = nano::make_shared (node, block_a, skip_delay_a, confirmation_action_a); + result.first = nano::make_shared (node, block_a, confirmation_action_a); auto difficulty (block_a->difficulty ()); roots.get ().emplace (nano::conflict_info{ root, difficulty, difficulty, result.first }); blocks.emplace (hash, result.first); - adjust_difficulty (hash); + add_adjust_difficulty (hash); result.first->insert_inactive_votes_cache (hash); } } @@ -595,10 +508,10 @@ std::pair, bool> nano::active_transactions::inse return result; } -std::pair, bool> nano::active_transactions::insert (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) +std::pair, bool> nano::active_transactions::insert (std::shared_ptr block_a, std::function)> const & confirmation_action_a) { nano::lock_guard lock (mutex); - return insert_impl (block_a, skip_delay_a, confirmation_action_a); + return insert_impl (block_a, confirmation_action_a); } // Validate a vote and apply it to the current election if one exists @@ -671,6 +584,18 @@ bool nano::active_transactions::active (nano::block const & block_a) return active (block_a.qualified_root ()); } +std::shared_ptr nano::active_transactions::election (nano::qualified_root const & root_a) const +{ + std::shared_ptr result; + nano::lock_guard lock (mutex); + auto existing = roots.get ().find (root_a); + if (existing != roots.get ().end ()) + { + result = existing->election; + } + return result; +} + void nano::active_transactions::update_difficulty (std::shared_ptr block_a) { nano::unique_lock lock (mutex); @@ -688,97 +613,111 @@ void nano::active_transactions::update_difficulty (std::shared_ptr info_a.difficulty = difficulty; }); existing_election->election->publish (block_a); - adjust_difficulty (block_a->hash ()); + add_adjust_difficulty (block_a->hash ()); } } } -void nano::active_transactions::adjust_difficulty (nano::block_hash const & hash_a) +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_difficulty () { debug_assert (!mutex.try_lock ()); - std::deque> remaining_blocks; - remaining_blocks.emplace_back (hash_a, 0); 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 ()) - { - 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.get ().find (root)); - if (existing_root != roots.get ().end ()) + auto existing (blocks.find (hash)); + if (existing != blocks.end () && !existing->second->confirmed () && existing->second->status.winner->hash () == hash) { - 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) { - highest_level = level; + remaining_blocks.emplace_back (source, level + 1); } - else if (level < lowest_level) + auto link (existing->second->status.winner->link ()); + if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous) { - lowest_level = level; + 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.get ().find (root)); + if (existing_root != roots.get ().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) + { + 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; - debug_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; - debug_assert (std::numeric_limits::min () == average + lowest_level - limiter); - } + 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; + debug_assert (std::numeric_limits::max () == average + highest_level - limiter); + } + else if (average < std::numeric_limits::min () - lowest_level) + { + // Lowest adjusted difficulty value should be std::numeric_limits::min () + limiter = std::numeric_limits::min () - average + lowest_level; + debug_assert (std::numeric_limits::min () == average + lowest_level - limiter); + } - // Set adjusted difficulty - for (auto & item : elections_list) - { - auto existing_root (roots.get ().find (item.first)); - uint64_t difficulty_a = average + item.second - limiter; - roots.get ().modify (existing_root, [difficulty_a](nano::conflict_info & info_a) { - info_a.adjusted_difficulty = difficulty_a; - }); + // Set adjusted difficulty + for (auto & item : elections_list) + { + auto existing_root (roots.get ().find (item.first)); + uint64_t difficulty_a = average + item.second - limiter; + if (existing_root->adjusted_difficulty != difficulty_a) + { + roots.get ().modify (existing_root, [difficulty_a](nano::conflict_info & info_a) { + info_a.adjusted_difficulty = difficulty_a; + }); + } + } } } } @@ -793,10 +732,9 @@ void nano::active_transactions::update_active_difficulty (nano::unique_lock active_root_difficulties; active_root_difficulties.reserve (std::min (sorted_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) { - if (!it->election->confirmed () && !it->election->stopped && it->election->election_start < cutoff) + if (!it->election->confirmed () && !it->election->idle ()) { active_root_difficulties.push_back (it->adjusted_difficulty); } @@ -865,9 +803,8 @@ void nano::active_transactions::erase (nano::block const & block_a) 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 (); + root_it->election->cleanup (); + root_it->election->adjust_dependent_difficulty (); roots.get ().erase (root_it); 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 ())); } @@ -894,7 +831,7 @@ bool nano::active_transactions::publish (std::shared_ptr block_a) { auto election (existing->election); result = election->publish (block_a); - if (!result && !election->confirmed ()) + if (!result) { blocks.emplace (block_a->hash (), election); } @@ -910,7 +847,7 @@ boost::optional nano::active_transactions::confirm_b auto existing (blocks.find (hash)); if (existing != blocks.end ()) { - if (!existing->second->confirmed () && !existing->second->stopped && existing->second->status.winner->hash () == hash) + if (!existing->second->confirmed () && 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; @@ -1012,18 +949,13 @@ nano::inactive_cache_information nano::active_transactions::find_inactive_votes_ } else { - return nano::inactive_cache_information{ std::chrono::steady_clock::time_point{}, 0, std::vector{} }; + return nano::inactive_cache_information{}; } } void nano::active_transactions::erase_inactive_votes_cache (nano::block_hash const & hash_a) { - auto & inactive_by_hash (inactive_votes_cache.get ()); - auto existing (inactive_by_hash.find (hash_a)); - if (existing != inactive_by_hash.end ()) - { - inactive_by_hash.erase (existing); - } + inactive_votes_cache.get ().erase (hash_a); } bool nano::active_transactions::inactive_votes_bootstrap_check (std::vector const & voters_a, nano::block_hash const & hash_a, bool & confirmed_a) diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index d1c68f45db..e1decad227 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -1,12 +1,6 @@ #pragma once #include -#include -#include -#include -#include -#include -#include #include #include @@ -74,6 +68,8 @@ class inactive_cache_information final // Holds all active blocks i.e. recently added blocks that need confirmation class active_transactions final { + friend class nano::election; + // clang-format off class tag_account {}; class tag_difficulty {}; @@ -90,15 +86,17 @@ class active_transactions final // Start an election for a block // Call action with confirmed block, may be different than what we started with // clang-format off - std::pair, bool> insert (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + std::pair, bool> insert (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); // clang-format on // 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 &); + std::shared_ptr election (nano::qualified_root const &) const; void update_difficulty (std::shared_ptr); - void adjust_difficulty (nano::block_hash const &); + void add_adjust_difficulty (nano::block_hash const &); + void update_adjusted_difficulty (); void update_active_difficulty (nano::unique_lock &); uint64_t active_difficulty (); uint64_t limited_active_difficulty (); @@ -109,7 +107,7 @@ class active_transactions final void stop (); bool publish (std::shared_ptr block_a); boost::optional confirm_block (nano::transaction const &, std::shared_ptr); - void block_cemented_callback (std::shared_ptr const & block_a, nano::block_sideband const & sideband_a); + void block_cemented_callback (std::shared_ptr const & block_a); void block_already_cemented_callback (nano::block_hash const &); // clang-format off boost::multi_index_container multipliers_cb; uint64_t trended_active_difficulty; size_t priority_cementable_frontiers_size (); @@ -139,7 +137,6 @@ class active_transactions final size_t inactive_votes_cache_size (); size_t election_winner_details_size (); void add_election_winner_details (nano::block_hash const &, std::shared_ptr const &); - nano::confirmation_solicitor solicitor; private: std::mutex election_winner_details_mutex; @@ -147,11 +144,10 @@ class active_transactions final // Call action with confirmed block, may be different than what we started with // clang-format off - std::pair, bool> insert_impl (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + std::pair, bool> insert_impl (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); // clang-format on void request_loop (); void search_frontiers (nano::transaction const &); - void election_escalate (std::shared_ptr &, nano::transaction const &, size_t const &); void request_confirm (nano::unique_lock &); nano::account next_frontier_account{ 0 }; std::chrono::steady_clock::time_point next_frontier_check{ std::chrono::steady_clock::now () }; @@ -159,18 +155,12 @@ class active_transactions final bool started{ false }; std::atomic stopped{ false }; - // Minimum time an election must be active before escalation - std::chrono::seconds const long_election_threshold; - // Delay until requesting confirmation for an election - std::chrono::milliseconds const election_request_delay; + // 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; - // Minimum time between confirmation requests for an election - std::chrono::milliseconds const min_time_between_requests; - // Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations - std::chrono::milliseconds const min_time_between_floods; - // Minimum election request count to start broadcasting blocks, as a backup to requesting confirmations - size_t const min_request_count_flood; // clang-format off boost::multi_index_container adjust_difficulty_list; // clang-format off using ordered_cache = boost::multi_index_container const &, nano::block_hash const &, bool &); - static size_t constexpr dropped_elections_cache_max{ 32 * 1024 }; boost::thread thread; + friend class active_transactions_dropped_cleanup_Test; friend class confirmation_height_prioritize_frontiers_Test; friend class confirmation_height_prioritize_frontiers_overwrite_Test; friend std::unique_ptr collect_container_info (active_transactions &, const std::string &); diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 21eb936d17..af31c8cb8b 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -41,12 +42,13 @@ void nano::block_processor::stop () void nano::block_processor::flush () { node.checker.flush (); + flushing = true; nano::unique_lock lock (mutex); 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 () @@ -57,12 +59,12 @@ size_t nano::block_processor::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) @@ -75,24 +77,17 @@ void nano::block_processor::add (nano::unchecked_info const & info_a) { bool should_notify{ false }; { - 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 ()) + 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 ())) { - 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 - { - blocks.push_back (info_a); - should_notify = true; - } - blocks_filter.insert (filter_hash); + state_block_signature_verification.add (info_a); + } + else + { + should_notify = true; + blocks.push_back (info_a); } } - if (should_notify) { condition.notify_all (); @@ -135,13 +130,13 @@ void nano::block_processor::process_blocks () } } -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; @@ -184,7 +179,6 @@ void nano::block_processor::process_verified_state_blocks (std::deque & lock_ lock_a.lock (); timer_l.start (); // 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 (should_log (first_time)) - { - log_this_record = true; - } - } - else - { - if (((blocks.size () + state_block_signature_verification.size () + forced.size ()) > 64 && should_log (false))) - { - log_this_record = true; - } - } - - if (log_this_record) + if ((blocks.size () + state_block_signature_verification.size () + forced.size () > 64) && should_log ()) { - first_time = false; 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; @@ -234,7 +210,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 { @@ -281,9 +256,9 @@ void nano::block_processor::process_batch (nano::unique_lock & lock_ 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 (10)) { - 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 ())); } } @@ -296,7 +271,11 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: } // Start collecting quorum on block - node.active.insert (block_a, false); + auto election = node.active.insert (block_a); + if (election.second) + { + election.first->transition_passive (); + } // Announce block contents to the network if (initial_publish_a) @@ -478,58 +457,36 @@ void nano::block_processor::queue_unchecked (nano::write_transaction const & tra { if (!node.flags.disable_block_processor_unchecked_deletion) { - if (!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; - } + 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); } 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) -{ - 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; -} - void nano::block_processor::requeue_invalid (nano::block_hash const & hash_a, nano::unchecked_info const & info_a) { debug_assert (hash_a == info_a.block->hash ()); - auto attempt (node.bootstrap_initiator.current_attempt ()); - if (attempt != nullptr && attempt->mode == nano::bootstrap_mode::lazy) - { - attempt->lazy_requeue (hash_a, info_a.block->previous (), info_a.confirmed); - } + node.bootstrap_initiator.lazy_requeue (hash_a, info_a.block->previous (), info_a.confirmed); } std::unique_ptr nano::collect_container_info (block_processor & block_processor, const std::string & name) { size_t blocks_count; - size_t blocks_filter_count; size_t forced_count; { nano::lock_guard guard (block_processor.mutex); blocks_count = block_processor.blocks.size (); - blocks_filter_count = block_processor.blocks_filter.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{ "blocks_filter", blocks_filter_count, sizeof (decltype (block_processor.blocks_filter)::value_type) })); composite->add_component (std::make_unique (container_info{ "forced", forced_count, sizeof (decltype (block_processor.forced)::value_type) })); composite->add_component (collect_container_info (block_processor.generator, "generator")); return composite; -} \ No newline at end of file +} diff --git a/nano/node/blockprocessor.hpp b/nano/node/blockprocessor.hpp index 7c1e75c3cd..e87882c4fa 100644 --- a/nano/node/blockprocessor.hpp +++ b/nano/node/blockprocessor.hpp @@ -40,12 +40,13 @@ class block_processor final 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, const bool = false); nano::process_return process_one (nano::write_transaction const &, std::shared_ptr, const bool = false); nano::vote_generator generator; + std::atomic flushing{ false }; // Delay required for average network propagartion before requesting confirmation static std::chrono::milliseconds constexpr confirmation_request_delay{ 1500 }; @@ -61,8 +62,6 @@ class block_processor final std::chrono::steady_clock::time_point next_log; std::deque blocks; std::deque> forced; - nano::block_hash filter_item (nano::block_hash const &, nano::signature const &); - std::unordered_set blocks_filter; nano::condition_variable condition; nano::node & node; nano::write_database_queue & write_database_queue; diff --git a/nano/node/bootstrap/bootstrap.cpp b/nano/node/bootstrap/bootstrap.cpp index ab1f56f20c..8de574f3e5 100644 --- a/nano/node/bootstrap/bootstrap.cpp +++ b/nano/node/bootstrap/bootstrap.cpp @@ -1,1548 +1,277 @@ -#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 unsigned nano::bootstrap_limits::requeued_pulls_processed_blocks_factor; -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, std::string id_a) : -next_log (std::chrono::steady_clock::now ()), -node (node_a), -mode (mode_a), -id (id_a) -{ - if (id.empty ()) - { - nano::random_constants constants; - id = constants.random_128.to_string (); - } - node->logger.always_log (boost::str (boost::format ("Starting bootstrap attempt id %1%") % 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 bootstrap attempt id %1%") % 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::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 () -{ - debug_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 () -{ - debug_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) -{ - condition.wait (lock_a, [& stopped = stopped, &idle = idle] { return stopped || !idle.empty (); }); - 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 / nano::bootstrap_limits::requeued_pulls_processed_blocks_factor)) - { - 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))) - { - debug_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) - { - debug_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)); - 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->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); - debug_assert (!frontiers_confirmed); - condition.wait (lock_a, [& stopped = stopped] { return !stopped; }); - 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::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; -} - -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::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 - debug_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 () -{ - debug_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) - { - last_lazy_flush = std::chrono::steady_clock::now (); - nano::lock_guard lazy_lock (lazy_mutex); - debug_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); - } - } - uint64_t read_count (0); - 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 (); - // 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_finished () -{ - if (stopped) - { - return true; - } - bool result (true); - uint64_t read_count (0); - 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. - } - // 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_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_clear () -{ - debug_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 () +nano::bootstrap_initiator::bootstrap_initiator (nano::node & node_a) : +node (node_a) { - debug_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 ()) - { - unsigned iterations (0); - while (still_pulling () && !lazy_has_expired ()) - { - 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 (); - } - } - // Flushing lazy pulls - lazy_pull_flush (); - // Check if some blocks required for backlog were processed. Start destinations check - if (pulls.empty ()) - { - lazy_backlog_cleanup (); - lazy_destinations_flush (); - } - } - if (!stopped) + 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) { - 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) - { - pulls.clear (); - lazy_clear (); - mode = nano::bootstrap_mode::wallet_lazy; - lock.unlock (); - lazy_lock.unlock (); - wallet_run (); - lock.lock (); - } - // Fallback to legacy bootstrap - else if (runs_count < 3 && !lazy_keys.empty () && !node->flags.disable_legacy_bootstrap) - { - pulls.clear (); - lazy_clear (); - mode = nano::bootstrap_mode::legacy; - lock.unlock (); - lazy_lock.unlock (); - run (); - lock.lock (); - } + bootstrap_initiator_threads.push_back (boost::thread ([this]() { + nano::thread_role::set (nano::thread_role::name::bootstrap_initiator); + run_bootstrap (); + })); } - 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) +nano::bootstrap_initiator::~bootstrap_initiator () { - 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; + stop (); } -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 (bool force, 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 ()) - { - // 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); - ++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 ()); - } - // 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); - 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) + if (force) { - stop_pull = true; + stop_attempts (); } - return stop_pull; -} - -void nano::bootstrap_attempt::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) + nano::unique_lock lock (mutex); + if (!stopped && find_attempt (nano::bootstrap_mode::legacy) == 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 }); - } - } + 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 (); } } -void nano::bootstrap_attempt::lazy_block_state_backlog_check (std::shared_ptr block_a, nano::block_hash const & hash_a) +void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bool add_to_peers, bool frontiers_confirmed, std::string id_a) { - // Search unknown state blocks balances - auto find_state (lazy_state_backlog.find (hash_a)); - if (find_state != lazy_state_backlog.end ()) + if (add_to_peers) { - 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 (!node.flags.disable_udp) { - 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); - } + node.network.udp_channels.insert (nano::transport::map_endpoint_to_v6 (endpoint_a), node.network_params.protocol.protocol_version); } - // Assumption for other legacy block types - else if (lazy_undefined_links.find (next_block.link) == lazy_undefined_links.end ()) + else if (!node.flags.disable_tcp_realtime) { - 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); + node.network.merge_peer (nano::transport::map_endpoint_to_v6 (endpoint_a)); } - lazy_state_backlog.erase (find_state); } -} - -void nano::bootstrap_attempt::lazy_backlog_cleanup () -{ - uint64_t read_count (0); - 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;) + if (!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 + 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) { - lazy_add (it->first, it->second.retry_limit); - ++it; + excluded_peers.remove (nano::transport::map_endpoint_to_tcp (endpoint_a)); } - // We don't want to open read transactions for too long - ++read_count; - if (read_count % batch_read_size == 0) + if (!excluded_peers.check (nano::transport::map_endpoint_to_tcp (endpoint_a))) { - transaction.refresh (); + connections->add_connection (endpoint_a); } + legacy_attempt->frontiers_confirmed = frontiers_confirmed; } + condition.notify_all (); } -void nano::bootstrap_attempt::lazy_destinations_increment (nano::account const & destination_a) +void nano::bootstrap_initiator::bootstrap_lazy (nano::hash_or_account const & hash_or_account_a, bool force, bool confirmed, std::string id_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) + auto lazy_attempt (current_lazy_attempt ()); + if (lazy_attempt == nullptr || force) { - // Update accounts counter for send blocks - auto existing (lazy_destinations.get ().find (destination_a)); - if (existing != lazy_destinations.get ().end ()) + if (force) { - lazy_destinations.get ().modify (existing, [](nano::lazy_destinations_item & item_a) { - ++item_a.count; - }); + stop_attempts (); } - else + 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_destinations.emplace (nano::lazy_destinations_item{ destination_a, 1 }); + 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); } } -} - -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; - } + lazy_attempt->lazy_start (hash_or_account_a, confirmed); } - return result; + condition.notify_all (); } -void nano::bootstrap_attempt::request_pending (nano::unique_lock & lock_a) +void nano::bootstrap_initiator::bootstrap_wallet (std::deque & accounts_a) { - auto connection_l (connection (lock_a)); - if (connection_l) + 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 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 (); - }); + 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::requeue_pending (nano::account const & account_a) -{ - auto account (account_a); + else { - nano::lock_guard lock (mutex); - wallet_accounts.push_front (account); - condition.notify_all (); + wallet_attempt->wallet_start (accounts_a); } + condition.notify_all (); } -void nano::bootstrap_attempt::wallet_start (std::deque & accounts_a) -{ - nano::lock_guard lock (mutex); - wallet_accounts.swap (accounts_a); -} - -bool nano::bootstrap_attempt::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 () +void nano::bootstrap_initiator::run_bootstrap () { - debug_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) + while (!stopped) { - if (!wallet_accounts.empty ()) + if (has_new_attempts ()) { - request_pending (lock); + auto attempt (new_attempt ()); + lock.unlock (); + if (attempt != nullptr) + { + attempt->run (); + remove_attempt (attempt); + } + lock.lock (); } else { condition.wait (lock); } } - if (!stopped) - { - node->logger.try_log ("Completed wallet lazy pulls"); - ++runs_count; - // Start lazy bootstrap if some lazy keys were inserted - if (!lazy_finished ()) - { - lock.unlock (); - total_blocks = 0; - lazy_run (); - lock.lock (); - } - } - 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 (); -}) +void nano::bootstrap_initiator::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a, bool confirmed_a) { + auto lazy_attempt (current_lazy_attempt ()); + if (lazy_attempt != nullptr) + { + lazy_attempt->lazy_requeue (hash_a, previous_a, confirmed_a); + } } -nano::bootstrap_initiator::~bootstrap_initiator () +void nano::bootstrap_initiator::add_observer (std::function const & observer_a) { - stop (); + nano::lock_guard lock (observers_mutex); + observers.push_back (observer_a); } -void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a) +bool nano::bootstrap_initiator::in_progress () { - nano::unique_lock lock (mutex); - if (force && attempt != nullptr) - { - attempt->stop (); - condition.wait (lock, [& attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - } - 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 (), nano::bootstrap_mode::legacy, id_a); - condition.notify_all (); - } + nano::lock_guard lock (mutex); + return !attempts_list.empty (); } -void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bool add_to_peers, bool frontiers_confirmed, std::string id_a) +std::shared_ptr nano::bootstrap_initiator::find_attempt (nano::bootstrap_mode mode_a) { - if (add_to_peers) - { - if (!node.flags.disable_udp) - { - node.network.udp_channels.insert (nano::transport::map_endpoint_to_v6 (endpoint_a), node.network_params.protocol.protocol_version); - } - else if (!node.flags.disable_tcp_realtime) - { - node.network.merge_peer (nano::transport::map_endpoint_to_v6 (endpoint_a)); - } - } - nano::unique_lock lock (mutex); - if (!stopped) + for (auto & i : attempts_list) { - if (attempt != nullptr) - { - attempt->stop (); - condition.wait (lock, [& attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - } - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); - attempt = std::make_shared (node.shared (), nano::bootstrap_mode::legacy, id_a); - 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))) + if (i->mode == mode_a) { - attempt->add_connection (endpoint_a); + return i; } - attempt->frontiers_confirmed = frontiers_confirmed; - condition.notify_all (); } + return nullptr; } -void nano::bootstrap_initiator::bootstrap_lazy (nano::hash_or_account const & hash_or_account_a, bool force, bool confirmed, std::string id_a) +void nano::bootstrap_initiator::remove_attempt (std::shared_ptr attempt_a) { + nano::unique_lock lock (mutex); + auto attempt (std::find (attempts_list.begin (), attempts_list.end (), attempt_a)); + if (attempt != attempts_list.end ()) { - nano::unique_lock lock (mutex); - if (force && attempt != nullptr) - { - attempt->stop (); - condition.wait (lock, [& attempt = attempt, &stopped = stopped] { return stopped || attempt == nullptr; }); - } - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_lazy, nano::stat::dir::out); - if (attempt == nullptr) - { - attempt = std::make_shared (node.shared (), nano::bootstrap_mode::lazy, id_a.empty () ? hash_or_account_a.to_string () : id_a); - } - attempt->lazy_start (hash_or_account_a, confirmed); + 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_wallet (std::deque & accounts_a) +std::shared_ptr nano::bootstrap_initiator::new_attempt () { + 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.exchange (true)) { - std::string id (!accounts_a.empty () ? accounts_a[0].to_account () : ""); - attempt = std::make_shared (node.shared (), nano::bootstrap_mode::wallet_lazy, id); + return i; } - attempt->wallet_start (accounts_a); } - condition.notify_all (); + return nullptr; } -void nano::bootstrap_initiator::run_bootstrap () +bool nano::bootstrap_initiator::has_new_attempts () { - nano::unique_lock lock (mutex); - while (!stopped) + for (auto & i : attempts_list) { - 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 + if (!i->started) { - condition.wait (lock); + return true; } } + return false; } -void nano::bootstrap_initiator::add_observer (std::function const & observer_a) +std::shared_ptr nano::bootstrap_initiator::current_attempt () { - nano::lock_guard lock (observers_mutex); - observers.push_back (observer_a); + nano::lock_guard lock (mutex); + return find_attempt (nano::bootstrap_mode::legacy); } -bool nano::bootstrap_initiator::in_progress () +std::shared_ptr nano::bootstrap_initiator::current_lazy_attempt () { - return current_attempt () != nullptr; + nano::lock_guard lock (mutex); + return find_attempt (nano::bootstrap_mode::lazy); } -std::shared_ptr nano::bootstrap_initiator::current_attempt () +std::shared_ptr nano::bootstrap_initiator::current_wallet_attempt () { nano::lock_guard lock (mutex); - return attempt; + return find_attempt (nano::bootstrap_mode::wallet_lazy); +} + +void nano::bootstrap_initiator::stop_attempts () +{ + 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 (); + } } } } @@ -1695,3 +424,41 @@ void nano::bootstrap_excluded_peers::remove (nano::tcp_endpoint const & endpoint nano::lock_guard guard (excluded_peers_mutex); peers.get ().erase (endpoint_a); } + +void nano::bootstrap_attempts::add (std::shared_ptr attempt_a) +{ + 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 (); +} + +std::shared_ptr nano::bootstrap_attempts::find (uint64_t incremental_id_a) +{ + nano::lock_guard lock (bootstrap_attempts_mutex); + auto find_attempt (attempts.find (incremental_id_a)); + if (find_attempt != attempts.end ()) + { + return find_attempt->second; + } + else + { + return nullptr; + } +} + +size_t nano::bootstrap_attempts::size () +{ + 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 e43779281b..74d829c9a8 100644 --- a/nano/node/bootstrap/bootstrap.hpp +++ b/nano/node/bootstrap/bootstrap.hpp @@ -1,10 +1,8 @@ #pragma once #include +#include #include -#include -#include -#include #include #include @@ -13,178 +11,30 @@ #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, std::string id_a = ""); - ~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); - std::string mode_text (); - /** 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::string id; - 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 - { - }; - // 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 }; - std::mutex lazy_mutex; - // Wallet lazy bootstrap - std::deque wallet_accounts; - /** 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_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 { @@ -246,6 +96,18 @@ class bootstrap_excluded_peers final constexpr static std::chrono::hours exclude_time_hours = std::chrono::hours (1); constexpr static std::chrono::hours exclude_remove_hours = std::chrono::hours (24); }; +class bootstrap_attempts final +{ +public: + 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 { @@ -257,23 +119,33 @@ class bootstrap_initiator final 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_container_info (bootstrap_initiator & bootstrap_initiator, const std::string & name); }; @@ -282,8 +154,7 @@ std::unique_ptr collect_container_info (bootstrap_init 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; diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp new file mode 100644 index 0000000000..32c9b0b6db --- /dev/null +++ b/nano/node/bootstrap/bootstrap_attempt.cpp @@ -0,0 +1,604 @@ +#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), +mode (mode_a), +incremental_id (incremental_id_a), +id (id_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::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->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; + 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); + ++pulling; + lock_a.lock (); + 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..4a7b6d40ad --- /dev/null +++ b/nano/node/bootstrap/bootstrap_attempt.hpp @@ -0,0 +1,86 @@ +#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 (); + 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 384600ad38..68b41672f5 100644 --- a/nano/node/bootstrap/bootstrap_bulk_pull.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_pull.cpp @@ -1,28 +1,32 @@ #include #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) : +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 () @@ -31,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 ())); @@ -46,11 +50,8 @@ 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->pulling; + attempt->condition.notify_all (); } void nano::bulk_pull_client::request () @@ -74,13 +75,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 ( @@ -104,7 +103,7 @@ void nano::bulk_pull_client::request () void nano::bulk_pull_client::throttled_receive_block () { debug_assert (!network_error); - if (!connection->node->block_processor.half_full ()) + if (!connection->node->block_processor.half_full () && !connection->node->block_processor.flushing) { receive_block (); } @@ -112,7 +111,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 (); } @@ -192,7 +191,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; } @@ -244,22 +243,22 @@ void nano::bulk_pull_client::received_block (boost::system::error_code const & e { connection->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 @@ -282,21 +281,19 @@ 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->pulling; + attempt->condition.notify_all (); } void nano::bulk_pull_account_client::request () @@ -307,13 +304,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 ( @@ -324,7 +319,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 ())); @@ -368,7 +363,7 @@ void nano::bulk_pull_account_client::receive_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); } } } @@ -376,17 +371,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 ())); @@ -395,7 +390,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)); 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 fc22904631..ef53fa96b8 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.cpp @@ -1,12 +1,13 @@ -#include +#include #include #include #include #include -nano::bulk_push_client::bulk_push_client (std::shared_ptr const & connection_a) : -connection (connection_a) +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) { } @@ -43,16 +44,7 @@ void nano::bulk_push_client::push () { 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) { diff --git a/nano/node/bootstrap/bootstrap_bulk_push.hpp b/nano/node/bootstrap/bootstrap_bulk_push.hpp index b84eaac61a..9ffececaee 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.hpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.hpp @@ -6,17 +6,19 @@ namespace nano { +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 (); 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..25a1129f30 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_connections.cpp @@ -0,0 +1,497 @@ +#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 (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); +} + +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; + } +} + +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.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 + 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.bootstrap_initiator.excluded_peers.check (endpoint)) + { + connect_client (endpoint); + endpoints.insert (endpoint); + nano::lock_guard lock (mutex); + new_connections_empty = false; + } + else if (connections_count == 0) + { + 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->pulling; + attempt_l->condition.notify_all (); + 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->pulling; + attempt_l->condition.notify_all (); + 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->pulling; + attempt_l->condition.notify_all (); + 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..652e20133f --- /dev/null +++ b/nano/node/bootstrap/bootstrap_connections.hpp @@ -0,0 +1,70 @@ +#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; + std::shared_ptr node; + std::shared_ptr connections; + 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{ 0 }; + std::atomic pending_stop{ false }; + std::atomic hard_stop{ false }; +}; + +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 1456451287..f5335e1936 100644 --- a/nano/node/bootstrap/bootstrap_frontier.cpp +++ b/nano/node/bootstrap/bootstrap_frontier.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -36,8 +36,9 @@ void nano::frontier_req_client::run () 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) @@ -76,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; @@ -118,7 +119,7 @@ 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 ())); } @@ -147,7 +148,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con } 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; @@ -158,12 +159,12 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con else { debug_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)); + 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 (); } @@ -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); } } } diff --git a/nano/node/bootstrap/bootstrap_frontier.hpp b/nano/node/bootstrap/bootstrap_frontier.hpp index ad3c4962b5..90a4419e60 100644 --- a/nano/node/bootstrap/bootstrap_frontier.hpp +++ b/nano/node/bootstrap/bootstrap_frontier.hpp @@ -7,11 +7,12 @@ namespace nano { +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 (); @@ -19,6 +20,7 @@ class frontier_req_client final : public std::enable_shared_from_this 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 e8c9cc1168..d4f98f8868 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -408,15 +408,24 @@ void nano::bootstrap_server::receive_publish_action (boost::system::error_code c { if (!ec) { - auto error (false); - nano::bufferstream stream (receive_buffer->data (), size_a); - auto request (std::make_unique (error, stream, header_a)); - if (!error) + nano::uint128_t digest; + if (!node->network.publish_filter.apply (receive_buffer->data (), size_a, &digest)) { - if (is_realtime_connection ()) + auto error (false); + nano::bufferstream stream (receive_buffer->data (), size_a); + auto request (std::make_unique (error, stream, header_a, digest)); + if (!error) { - add_request (std::unique_ptr (request.release ())); + 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 (); } } diff --git a/nano/node/cli.cpp b/nano/node/cli.cpp index 35080e6aa7..12cf05b612 100644 --- a/nano/node/cli.cpp +++ b/nano/node/cli.cpp @@ -96,6 +96,7 @@ void nano::add_node_flag_options (boost::program_options::options_description & ("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") ("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") @@ -119,8 +120,11 @@ 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); + 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)) { @@ -134,6 +138,7 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o 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) { @@ -167,6 +172,12 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o { 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; } @@ -185,7 +196,8 @@ bool copy_database (boost::filesystem::path const & data_path, boost::program_op 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")) @@ -245,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 ()); @@ -430,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 ()); @@ -447,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 ()); @@ -464,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 ()); @@ -481,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 ()); @@ -498,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"); @@ -592,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 (); @@ -611,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 { @@ -655,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 ()); @@ -709,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 ()); @@ -791,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) @@ -833,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)) @@ -888,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 { @@ -937,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) { @@ -979,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) { @@ -990,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"; } } @@ -1024,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 ()); @@ -1039,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 ())) @@ -1092,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)); @@ -1130,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); @@ -1169,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 ()); @@ -1185,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) diff --git a/nano/node/common.cpp b/nano/node/common.cpp index d57558897e..9b07ffc1b9 100644 --- a/nano/node/common.cpp +++ b/nano/node/common.cpp @@ -312,6 +312,10 @@ std::string nano::message_parser::status_string () { return "invalid_network"; } + case nano::message_parser::parse_status::duplicate_publish_message: + { + return "duplicate_publish_message"; + } } debug_assert (false); @@ -319,7 +323,8 @@ std::string nano::message_parser::status_string () 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) : +publish_filter (publish_filter_a), block_uniquer (block_uniquer_a), vote_uniquer (vote_uniquer_a), visitor (visitor_a), @@ -355,7 +360,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: @@ -412,10 +425,10 @@ 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)) @@ -593,8 +606,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) { diff --git a/nano/node/common.hpp b/nano/node/common.hpp index 4cf7273208..cd9612fda8 100644 --- a/nano/node/common.hpp +++ b/nano/node/common.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -247,18 +248,20 @@ class message_parser final 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 &); 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; @@ -282,13 +285,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; 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 { diff --git a/nano/node/confirmation_height_bounded.cpp b/nano/node/confirmation_height_bounded.cpp index e03f304011..529c3e6046 100644 --- a/nano/node/confirmation_height_bounded.cpp +++ b/nano/node/confirmation_height_bounded.cpp @@ -8,7 +8,7 @@ #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, 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) : +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, 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), @@ -67,13 +67,12 @@ void nano::confirmation_height_bounded::process () current = hash_to_process.top; auto top_level_hash = current; - nano::block_sideband sideband; - auto block = ledger.store.block_get (transaction, current, &sideband); + auto block = ledger.store.block_get (transaction, current); debug_assert (block != nullptr); nano::account account (block->account ()); if (account.is_zero ()) { - account = sideband.account; + 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 @@ -90,13 +89,13 @@ void nano::confirmation_height_bounded::process () (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 >= sideband.height && current == original_hash) + if (first_iter && confirmation_height_info.height >= block->sideband ().height && current == original_hash) { notify_block_already_cemented_observers_callback (original_hash); } } - auto block_height = sideband.height; + 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 @@ -199,14 +198,14 @@ void nano::confirmation_height_bounded::process () 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; - nano::block_sideband sideband; if (confirmation_height_info_a.height != 0) { if (block_height_a > confirmation_height_info_a.height) { - release_assert (ledger.store.block_get (transaction_a, confirmation_height_info_a.frontier, &sideband) != nullptr); - least_unconfirmed_hash = sideband.successor; - block_height_a = sideband.height + 1; + 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 @@ -225,14 +224,13 @@ bool nano::confirmation_height_bounded::iterate (nano::read_transaction const & bool reached_target = false; bool hit_receive = false; auto hash = bottom_hash_a; - nano::block_sideband sideband; 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, &sideband); + auto block = ledger.store.block_get (transaction_a, hash); auto source (block->source ()); if (source.is_zero ()) { @@ -243,6 +241,7 @@ bool nano::confirmation_height_bounded::iterate (nano::read_transaction const & { 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 @@ -261,7 +260,7 @@ bool nano::confirmation_height_bounded::iterate (nano::read_transaction const & } else { - hash = sideband.successor; + hash = block->sideband ().successor; } } @@ -337,7 +336,7 @@ bool nano::confirmation_height_bounded::cement_blocks () { // 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; + std::vector> cemented_blocks; { // 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 })); @@ -354,9 +353,9 @@ bool nano::confirmation_height_bounded::cement_blocks () // Extra debug checks nano::confirmation_height_info confirmation_height_info; debug_assert (!ledger.store.confirmation_height_get (transaction, account, confirmation_height_info)); - nano::block_sideband sideband; - debug_assert (ledger.store.block_get (transaction, confirmed_frontier, &sideband)); - debug_assert (sideband.height == confirmation_height_info.height + num_blocks_cemented); + 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; @@ -370,7 +369,6 @@ bool nano::confirmation_height_bounded::cement_blocks () // Some blocks need to be cemented at least if (pending.top_height > confirmation_height_info.height) { - nano::block_sideband sideband; // The highest hash which will be cemented nano::block_hash new_cemented_frontier; uint64_t num_blocks_confirmed = 0; @@ -385,8 +383,8 @@ bool nano::confirmation_height_bounded::cement_blocks () } else { - auto block = ledger.store.block_get (transaction, confirmation_height_info.frontier, &sideband); - new_cemented_frontier = sideband.successor; + 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; } @@ -394,7 +392,7 @@ bool nano::confirmation_height_bounded::cement_blocks () auto total_blocks_cemented = 0; auto num_blocks_iterated = 0; - auto block = ledger.store.block_get (transaction, new_cemented_frontier, &sideband); + 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. @@ -409,7 +407,7 @@ bool nano::confirmation_height_bounded::cement_blocks () return true; } - cemented_blocks.emplace_back (block, sideband); + cemented_blocks.emplace_back (block); // We have likely hit a long chain, flush these callbacks and continue if (cemented_blocks.size () == confirmation_height::batch_write_size) @@ -427,8 +425,8 @@ bool nano::confirmation_height_bounded::cement_blocks () auto last_iteration = (num_blocks_confirmed - num_blocks_iterated) == 1; if (!last_iteration) { - new_cemented_frontier = sideband.successor; - block = ledger.store.block_get (transaction, new_cemented_frontier, &sideband); + new_cemented_frontier = block->sideband ().successor; + block = ledger.store.block_get (transaction, new_cemented_frontier); } else { diff --git a/nano/node/confirmation_height_bounded.hpp b/nano/node/confirmation_height_bounded.hpp index 8a444245b5..4d32ad36dd 100644 --- a/nano/node/confirmation_height_bounded.hpp +++ b/nano/node/confirmation_height_bounded.hpp @@ -15,7 +15,7 @@ class write_database_queue; 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 &, std::function const &)> const &, std::function const &, std::function const &); + confirmation_height_bounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &, std::atomic &, nano::block_hash const &, std::function> const &)> const &, std::function const &, std::function const &); bool pending_empty () const; void prepare_new (); void process (); @@ -114,7 +114,7 @@ class confirmation_height_bounded final nano::logger_mt & logger; std::atomic & stopped; nano::block_hash const & original_hash; - std::function const &)> notify_observers_callback; + std::function> const &)> notify_observers_callback; std::function notify_block_already_cemented_observers_callback; std::function awaiting_processing_size_callback; diff --git a/nano/node/confirmation_height_processor.cpp b/nano/node/confirmation_height_processor.cpp index 42d6b85ec7..cad2739572 100644 --- a/nano/node/confirmation_height_processor.cpp +++ b/nano/node/confirmation_height_processor.cpp @@ -34,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 ()) { @@ -162,7 +165,7 @@ void nano::confirmation_height_processor::set_next_hash () } // Not thread-safe, only call before this processor has begun cementing -void nano::confirmation_height_processor::add_cemented_observer (std::function const & callback_a) +void nano::confirmation_height_processor::add_cemented_observer (std::function)> const & callback_a) { cemented_observers.push_back (callback_a); } @@ -173,7 +176,7 @@ void nano::confirmation_height_processor::add_block_already_cemented_observer (s block_already_cemented_observers.push_back (callback_a); } -void nano::confirmation_height_processor::notify_observers (std::vector const & cemented_blocks) +void nano::confirmation_height_processor::notify_observers (std::vector> const & cemented_blocks) { for (auto const & block_callback_data : cemented_blocks) { diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index facc3e53e5..ea56f97511 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -35,7 +35,7 @@ class confirmation_height_processor final bool is_processing_block (nano::block_hash const &); nano::block_hash current (); - void add_cemented_observer (std::function const &); + void add_cemented_observer (std::function)> const &); void add_block_already_cemented_observer (std::function const &); private: @@ -51,7 +51,7 @@ class confirmation_height_processor final nano::condition_variable condition; std::atomic stopped{ false }; - std::vector> cemented_observers; + std::vector)>> cemented_observers; std::vector> block_already_cemented_observers; nano::ledger & ledger; @@ -61,7 +61,7 @@ class confirmation_height_processor final std::thread thread; void set_next_hash (); - void notify_observers (std::vector const &); + void notify_observers (std::vector> const &); void notify_observers (nano::block_hash const &); friend 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 index bdca0bb28c..b2399839d3 100644 --- a/nano/node/confirmation_height_unbounded.cpp +++ b/nano/node/confirmation_height_unbounded.cpp @@ -5,7 +5,7 @@ #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, 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) : +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, 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), @@ -49,16 +49,14 @@ void nano::confirmation_height_unbounded::process () } } - std::shared_ptr block; - nano::block_sideband sideband; - get_block_and_sideband (current, read_transaction, block, sideband); + auto block (get_block_and_sideband (current, read_transaction)); nano::account account (block->account ()); if (account.is_zero ()) { - account = sideband.account; + account = block->sideband ().account; } - auto block_height = sideband.height; + 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 ()) @@ -164,10 +162,7 @@ void nano::confirmation_height_unbounded::collect_unconfirmed_receive_and_source bool hit_receive = false; while ((num_to_confirm > 0) && !hash.is_zero () && !stopped) { - std::shared_ptr block; - nano::block_sideband sideband; - get_block_and_sideband (hash, transaction_a, block, sideband); - + auto block (get_block_and_sideband (hash, transaction_a)); if (block) { auto source (block->source ()); @@ -330,11 +325,10 @@ bool nano::confirmation_height_unbounded::cement_blocks () { #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); + auto block = ledger.store.block_get (transaction, pending.hash); static nano::network_constants network_constants; debug_assert (network_constants.is_test_network () || block != nullptr); - debug_assert (network_constants.is_test_network () || sideband.height == pending.height); + debug_assert (network_constants.is_test_network () || block->sideband ().height == pending.height); if (!block) { @@ -355,7 +349,7 @@ bool nano::confirmation_height_unbounded::cement_blocks () // 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::vector callback_data; + std::vector> callback_data; callback_data.reserve (pending.block_callback_data.size ()); std::transform (pending.block_callback_data.begin (), pending.block_callback_data.end (), std::back_inserter (callback_data), [& block_cache = block_cache](auto const & hash_a) { debug_assert (block_cache.find (hash_a) != block_cache.end ()); @@ -373,19 +367,19 @@ bool nano::confirmation_height_unbounded::cement_blocks () return false; } -void nano::confirmation_height_unbounded::get_block_and_sideband (nano::block_hash const & hash_a, nano::transaction const & transaction_a, std::shared_ptr & block_a, nano::block_sideband & sideband_a) +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 ()) { - block_a = block_cache_it->second.block; - sideband_a = block_cache_it->second.sideband; + return block_cache_it->second; } else { - block_a = ledger.store.block_get (transaction_a, hash_a, &sideband_a); - block_cache.emplace (std::piecewise_construct, std::forward_as_tuple (hash_a), std::forward_as_tuple (block_a, sideband_a)); + auto block (ledger.store.block_get (transaction_a, hash_a)); + block_cache.emplace (hash_a, block); ++block_cache_size; + return block; } } diff --git a/nano/node/confirmation_height_unbounded.hpp b/nano/node/confirmation_height_unbounded.hpp index 6ce3eefcbf..194599a5e1 100644 --- a/nano/node/confirmation_height_unbounded.hpp +++ b/nano/node/confirmation_height_unbounded.hpp @@ -16,7 +16,7 @@ class write_database_queue; 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 &, std::function const &)> const &, std::function const &, std::function const &); + confirmation_height_unbounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger_mt &, std::atomic &, nano::block_hash const &, std::function> const &)> const &, std::function const &, std::function const &); bool pending_empty () const; void prepare_new (); void process (); @@ -55,9 +55,9 @@ class confirmation_height_unbounded final std::unordered_map confirmed_iterated_pairs; std::atomic confirmed_iterated_pairs_size{ 0 }; - std::unordered_map block_cache; + std::unordered_map> block_cache; std::atomic block_cache_size{ 0 }; - void get_block_and_sideband (nano::block_hash const &, nano::transaction const &, std::shared_ptr &, nano::block_sideband &); + std::shared_ptr get_block_and_sideband (nano::block_hash const &, nano::transaction const &); std::deque pending_writes; std::atomic pending_writes_size{ 0 }; std::vector orig_block_callback_data; @@ -90,7 +90,7 @@ class confirmation_height_unbounded final nano::logger_mt & logger; std::atomic & stopped; nano::block_hash const & original_hash; - std::function const &)> notify_observers_callback; + std::function> const &)> notify_observers_callback; std::function notify_block_already_cemented_observers_callback; std::function awaiting_processing_size_callback; diff --git a/nano/node/election.cpp b/nano/node/election.cpp index bda1bdb33b..828e176e5e 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -1,22 +1,32 @@ +#include #include +#include #include #include +using namespace std::chrono; + +int constexpr nano::election::passive_duration_factor; +int constexpr nano::election::active_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) : confirmation_action (confirmation_action_a), -confirmed_m (false), +state_start (std::chrono::steady_clock::now ()), 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, 1, 0, nano::election_status_type::ongoing }), -skip_delay (skip_delay_a), -stopped (false) +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 }) { 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); @@ -26,7 +36,7 @@ stopped (false) void nano::election::confirm_once (nano::election_status_type type_a) { debug_assert (!node.active.mutex.try_lock ()); - if (!confirmed_m.exchange (true)) + if (state_m.exchange (nano::election::state_t::confirmed) != nano::election::state_t::confirmed) { 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); @@ -48,30 +58,249 @@ void nano::election::confirm_once (nano::election_status_type type_a) node_l->process_confirmed (status_l, this_l); confirmation_action_l (status_l.winner); }); - clear_blocks (); - clear_dependent (); - node.active.roots.erase (status.winner->qualified_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 { - debug_assert (!node.active.mutex.try_lock ()); - 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; - status.block_count = nano::narrow_cast (blocks.size ()); - status.voter_count = nano::narrow_cast (last_votes.size ()); - status.type = nano::election_status_type::stopped; + 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::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; + } } } -bool nano::election::confirmed () +void nano::election::transition_passive () { - return confirmed_m; + 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 () +{ + auto transaction = node.store.tx_begin_read (); + bool escalated_l (false); + std::shared_ptr previous_l; + auto previous_hash_l (status.winner->previous ()); + if (!previous_hash_l.is_zero () && node.active.blocks.find (previous_hash_l) == node.active.blocks.end ()) + { + previous_l = node.store.block_get (transaction, previous_hash_l); + if (previous_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, previous_hash_l)) + { + auto election = node.active.insert_impl (previous_l); + if (election.second) + { + election.first->transition_active (); + escalated_l = true; + } + } + } + /* 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) + { + auto source_hash_l (node.ledger.block_source (transaction, *status.winner)); + if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && node.active.blocks.find (source_hash_l) == node.active.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)) + { + auto election = node.active.insert_impl (source_l); + if (election.second) + { + election.first->transition_active (); + escalated_l = true; + } + } + } + } + if (escalated_l) + { + update_dependent (); + } +} + +void nano::election::broadcast_block (nano::confirmation_solicitor & solicitor_a) +{ + if (base_latency () * 20 < 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::unique_lock lock (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: + broadcast_block (solicitor_a); + send_confirm_req (solicitor_a); + if (base_latency () * active_duration_factor < std::chrono::steady_clock::now () - state_start) + { + state_change (nano::election::state_t::active, nano::election::state_t::backtracking); + lock.unlock (); + activate_dependencies (); + lock.lock (); + } + 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 @@ -132,7 +361,7 @@ void nano::election::confirm_if_quorum () node.block_processor.force (block_l); status.winner = block_l; update_dependent (); - node.active.adjust_difficulty (winner_hash_l); + node.active.add_adjust_difficulty (winner_hash_l); } if (have_quorum (tally_l, sum)) { @@ -189,10 +418,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; } @@ -217,8 +446,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) { @@ -276,7 +506,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 ()) { @@ -286,31 +516,41 @@ 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_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 - debug_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) + { + // 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 (nano::block_hash const & hash_a) diff --git a/nano/node/election.hpp b/nano/node/election.hpp index cfa76733a6..5d51a9cb70 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -12,27 +12,8 @@ namespace nano { class channel; +class confirmation_solicitor; class node; -enum class election_status_type : uint8_t -{ - ongoing = 0, - active_confirmed_quorum = 1, - active_confirmation_height = 2, - inactive_confirmation_height = 3, - stopped = 5 -}; -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; -}; class vote_info final { public: @@ -50,11 +31,40 @@ 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; - std::atomic confirmed_m; + +private: // State management + enum class state_t + { + idle, + passive, + active, + backtracking, + confirmed, + expired_confirmed, + expired_unconfirmed + }; + static int constexpr passive_duration_factor = 5; + static int constexpr active_duration_factor = 30; + static int constexpr confirmed_duration_factor = 5; + std::atomic state_m = { state_t::idle }; + + // Protects state_start, last_vote and last_block + 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 (); public: - election (nano::node &, std::shared_ptr, bool const, std::function)> const &); + election (nano::node &, std::shared_ptr, std::function)> const &); nano::election_vote_result vote (nano::account, uint64_t, nano::block_hash); nano::tally_t tally (); // Check if we have vote quorum @@ -66,22 +76,30 @@ 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 adjust_dependent_difficulty (); void insert_inactive_votes_cache (nano::block_hash const &); - void stop (); - bool confirmed (); + // 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; - bool stopped; - std::unordered_map last_tally; unsigned confirmation_request_count{ 0 }; - std::chrono::steady_clock::time_point last_broadcast; - std::chrono::steady_clock::time_point last_request; + std::unordered_map last_tally; std::unordered_set dependent_blocks; std::chrono::seconds late_blocks_delay{ 5 }; }; diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 699c46c75b..0a34ee2578 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -969,19 +970,18 @@ 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); @@ -1120,19 +1120,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); @@ -1679,41 +1678,39 @@ void nano::json_handler::bootstrap_lazy () */ void nano::json_handler::bootstrap_status () { - auto attempt (node.bootstrap_initiator.current_attempt ()); - if (attempt != nullptr) - { - nano::lock_guard lock (attempt->mutex); - nano::lock_guard lazy_lock (attempt->lazy_mutex); - response_l.put ("id", attempt->id); - 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)); - response_l.put ("mode", attempt->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 ()); - } - else + 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; { - response_l.put ("active", "0"); + 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 ())); } + response_l.add_child ("connections", connections); + boost::property_tree::ptree attempts; + { + 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 +1754,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,15 +1765,24 @@ 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 (); } @@ -1843,11 +1850,10 @@ 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 ()) { - auto election (conflict_info->election); 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 ()); @@ -2558,8 +2564,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) @@ -2573,8 +2578,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) { @@ -2586,7 +2591,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 ()) @@ -3978,29 +3983,36 @@ void nano::json_handler::telemetry () if (!ec) { debug_assert (channel); - 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 err = telemetry_response_a.telemetry_data.serialize_json (config_l); - auto const & ptree = config_l.get_tree (); - - if (!err) + if (node.telemetry) + { + node.telemetry->get_metrics_single_peer_async (channel, [rpc_l](auto const & telemetry_response_a) { + if (!telemetry_response_a.error) { - rpc_l->response_l.insert (rpc_l->response_l.begin (), ptree.begin (), ptree.end ()); + nano::jsonconfig config_l; + auto err = telemetry_response_a.telemetry_data.serialize_json (config_l); + 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; } - } - else - { - rpc_l->ec = nano::error_rpc::generic; - } - rpc_l->response_errors (); - }); + rpc_l->response_errors (); + }); + } + else + { + response_errors (); + } } else { @@ -4013,11 +4025,13 @@ void nano::json_handler::telemetry () // setting "raw" to true returns metrics from all nodes requested. auto raw = request.get_optional ("raw"); auto output_raw = raw.value_or (false); - node.telemetry.get_metrics_peers_async ([rpc_l, output_raw](telemetry_data_responses const & telemetry_responses_a) { + if (node.telemetry) + { + auto telemetry_responses = node.telemetry->get_metrics (); if (output_raw) { boost::property_tree::ptree metrics; - for (auto & telemetry_metrics : telemetry_responses_a.telemetry_datas) + for (auto & telemetry_metrics : telemetry_responses) { nano::jsonconfig config_l; auto err = telemetry_metrics.second.serialize_json (config_l); @@ -4029,18 +4043,18 @@ void nano::json_handler::telemetry () } else { - rpc_l->ec = nano::error_rpc::generic; + ec = nano::error_rpc::generic; } } - rpc_l->response_l.put_child ("metrics", metrics); + response_l.put_child ("metrics", metrics); } else { nano::jsonconfig config_l; std::vector telemetry_datas; - telemetry_datas.reserve (telemetry_responses_a.telemetry_datas.size ()); - std::transform (telemetry_responses_a.telemetry_datas.begin (), telemetry_responses_a.telemetry_datas.end (), std::back_inserter (telemetry_datas), [](auto const & endpoint_telemetry_data) { + 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; }); @@ -4050,16 +4064,16 @@ void nano::json_handler::telemetry () if (!err) { - rpc_l->response_l.insert (rpc_l->response_l.begin (), ptree.begin (), ptree.end ()); + response_l.insert (response_l.begin (), ptree.begin (), ptree.end ()); } else { - rpc_l->ec = nano::error_rpc::generic; + ec = nano::error_rpc::generic; } } + } - rpc_l->response_errors (); - }); + response_errors (); } } @@ -4581,9 +4595,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; diff --git a/nano/node/lmdb/lmdb.cpp b/nano/node/lmdb/lmdb.cpp index f013e06fb7..97c70ded12 100644 --- a/nano/node/lmdb/lmdb.cpp +++ b/nano/node/lmdb/lmdb.cpp @@ -40,9 +40,9 @@ 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, bool backup_before_upgrade) : 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) { @@ -91,7 +91,7 @@ txn_tracking_enabled (txn_tracking_config_a.enable) if (needs_vacuuming && !network_constants.is_test_network ()) { logger.always_log ("Preparing vacuum..."); - auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_max_dbs); + 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"); } } @@ -103,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"; @@ -112,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; @@ -119,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 ()); @@ -507,7 +511,7 @@ 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)); @@ -628,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, false, false, false); + 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 () }; @@ -732,27 +736,25 @@ void nano::mdb_store::upgrade_v16_to_v17 (nano::write_transaction const & transa 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 - nano::block_sideband sideband; - auto block = block_get (transaction_a, account_info.open_block, &sideband); + 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, sideband.successor, &sideband); + block = block_get (transaction_a, block->sideband ().successor); debug_assert (block); ++height; } - debug_assert (sideband.height == confirmation_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 - nano::block_sideband sideband; - auto block = block_get (transaction_a, account_info.head, &sideband); - auto height = sideband.height; + 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 ()); @@ -820,13 +822,13 @@ void nano::mdb_store::upgrade_v17_to_v18 (nano::write_transaction const & transa is_receive = true; } - nano::block_sideband new_sideband (sideband.type, sideband.account, sideband.successor, sideband.balance, sideband.height, sideband.timestamp, sideband.details.epoch, is_send, is_receive, is_epoch); + 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); + 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); @@ -1305,4 +1307,4 @@ bool nano::mdb_store::upgrade_counters::are_equal () const } // Explicitly instantiate -template class nano::block_store_partial; \ No newline at end of file +template class nano::block_store_partial; diff --git a/nano/node/lmdb/lmdb.hpp b/nano/node/lmdb/lmdb.hpp index a0cc27f845..e46ebdf203 100644 --- a/nano/node/lmdb/lmdb.hpp +++ b/nano/node/lmdb/lmdb.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -36,7 +37,7 @@ 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; @@ -261,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 { diff --git a/nano/node/lmdb/lmdb_env.cpp b/nano/node/lmdb/lmdb_env.cpp index 61bc9f37f8..2c474ac200 100644 --- a/nano/node/lmdb/lmdb_env.cpp +++ b/nano/node/lmdb/lmdb_env.cpp @@ -2,12 +2,12 @@ #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) +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 ()) @@ -18,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; @@ -34,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; } @@ -69,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 6bd3560c6d..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,8 +12,50 @@ 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; nano::read_transaction tx_begin_read (mdb_txn_callbacks txn_callbacks = mdb_txn_callbacks{}) const; diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 26a4c98b77..bb12834854 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -11,10 +11,12 @@ #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), node (node_a), +publish_filter (256 * 1024), udp_channels (node_a, port_a), tcp_channels (node_a), port (port_a), @@ -390,6 +392,7 @@ 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); } } @@ -427,22 +430,25 @@ 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 ()) + if (!vote_block.which ()) { - node.process_active (block); - } - else - { - 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.vote_processor.vote (message_a.vote, channel); } - node.vote_processor.vote (message_a.vote, channel); } void bulk_pull (nano::bulk_pull const &) override { @@ -489,7 +495,10 @@ class network_message_visitor : public nano::message_visitor 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); - node.telemetry.add (message_a.data, channel->get_endpoint (), message_a.is_empty_payload ()); + if (node.telemetry) + { + node.telemetry->set (message_a.data, channel->get_endpoint (), message_a.is_empty_payload ()); + } } nano::node & node; std::shared_ptr channel; @@ -812,6 +821,11 @@ void nano::message_buffer_manager::stop () 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 ()); @@ -819,7 +833,7 @@ boost::optional nano::syn_cookies::assign (nano::endpoint c 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 ()) { diff --git a/nano/node/network.hpp b/nano/node/network.hpp index 99c68bbc45..a1c8781011 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -3,13 +3,13 @@ #include #include #include +#include #include #include #include #include - namespace nano { class channel; @@ -71,6 +71,7 @@ class message_buffer_manager final 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 @@ -90,6 +91,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 { @@ -152,6 +154,7 @@ class network final std::vector packet_processing_threads; nano::bandwidth_limiter limiter; nano::node & node; + nano::network_filter publish_filter; nano::transport::udp_channels udp_channels; nano::transport::tcp_channels tcp_channels; std::atomic port{ 0 }; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index e4ab7e18a1..65cce5ad3d 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -1,6 +1,9 @@ +#include #include +#include #include #include +#include #include #include #include @@ -94,15 +97,15 @@ 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.generate_cache), checker (config.signature_checker_threads), network (*this, config.peering_port), -telemetry (network, alarm, worker, flags.disable_ongoing_telemetry_requests), +telemetry (std::make_shared (network, alarm, worker, flags.disable_ongoing_telemetry_requests)), bootstrap_initiator (*this), bootstrap (config.peering_port, *this), application_path (application_path_a), @@ -129,6 +132,8 @@ startup_time (std::chrono::steady_clock::now ()) { if (!init_error ()) { + telemetry->start (); + if (config.websocket_config.enabled) { auto endpoint_l (nano::tcp_endpoint (boost::asio::ip::make_address_v6 (config.websocket_config.address), config.websocket_config.port)); @@ -523,29 +528,29 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ())) { std::weak_ptr this_w (shared_from_this ()); - if (active.insert (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))); - } - } - } - }) - .first) + auto election = active.insert (ledger_block, [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.second) { 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.first->transition_active (); } } } @@ -562,7 +567,10 @@ std::unique_ptr nano::collect_container_info (no 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")); - composite->add_component (collect_container_info (node.telemetry, "telemetry")); + 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")); @@ -586,7 +594,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)); @@ -675,7 +683,11 @@ void nano::node::stop () active.stop (); confirmation_height_processor.stop (); network.stop (); - telemetry.stop (); + if (telemetry) + { + telemetry->stop (); + telemetry = nullptr; + } if (websocket_server) { websocket_server->stop (); @@ -894,11 +906,15 @@ 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 ()); @@ -914,6 +930,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); } } @@ -931,13 +948,16 @@ void nano::node::unchecked_cleanup () { auto key (cleaning_list.front ()); cleaning_list.pop_front (); - if (!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 () @@ -1065,8 +1085,11 @@ void nano::node::add_initial_peers () void nano::node::block_confirm (std::shared_ptr block_a) { - active.insert (block_a, false); - network.broadcast_confirm_req (block_a); + auto election = active.insert (block_a); + if (election.second) + { + election.first->transition_active (); + } // Calculate votes for local representatives if (config.enable_voting && wallets.rep_counts ().voting > 0 && active.active (*block_a)) { @@ -1185,18 +1208,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; @@ -1300,23 +1323,38 @@ bool nano::node::init_error () const return store.init_error () || wallets_store.init_error (); } -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 (); } @@ -1338,7 +1376,7 @@ nano::node_flags const & nano::inactive_node_flag_defaults () 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]() { @@ -1369,5 +1407,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 a6aa072473..a24bc7282b 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -7,11 +7,12 @@ #include #include #include +#include #include #include #include +#include #include -#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include @@ -46,7 +48,6 @@ namespace websocket { class listener; } - class node; class telemetry; class work_pool; @@ -101,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_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 &, std::shared_ptr const &, uint8_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 &); @@ -165,7 +166,7 @@ class node final : public std::enable_shared_from_this nano::ledger ledger; nano::signature_checker checker; nano::network network; - nano::telemetry telemetry; + std::shared_ptr telemetry; nano::bootstrap_initiator bootstrap_initiator; nano::bootstrap_listener bootstrap; boost::filesystem::path application_path; @@ -209,14 +210,12 @@ 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/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 28661615c3..74ff6542c3 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -80,7 +79,8 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const 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"); @@ -109,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); @@ -160,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 (); } @@ -302,11 +306,34 @@ 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); + 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 (); @@ -444,7 +471,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 ()); @@ -738,7 +765,7 @@ 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); diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index 368bc6b29d..0e776dff4a 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -63,13 +64,14 @@ class node_config 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; @@ -80,7 +82,7 @@ class node_config /** 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 }; @@ -95,6 +97,7 @@ class node_config 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 &); @@ -128,6 +131,8 @@ class node_flags final bool disable_block_processor_unchecked_deletion{ false }; bool disable_block_processor_republishing{ false }; bool disable_ongoing_telemetry_requests{ 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 }; nano::confirmation_height_mode confirmation_height_processor_mode{ nano::confirmation_height_mode::automatic }; diff --git a/nano/node/portmapping.hpp b/nano/node/portmapping.hpp index ccf754d45f..3828785bf5 100644 --- a/nano/node/portmapping.hpp +++ b/nano/node/portmapping.hpp @@ -59,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 5e458d0df0..edc87d24b0 100644 --- a/nano/node/repcrawler.cpp +++ b/nano/node/repcrawler.cpp @@ -14,12 +14,6 @@ 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); @@ -323,14 +317,14 @@ void nano::rep_crawler::update_weights () } } -std::vector nano::rep_crawler::representatives (size_t count_a, boost::optional const & opt_version_min_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)); 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 () && i->channel->get_network_version () >= version_min) + if (i->weight > weight_a && i->channel->get_network_version () >= version_min) { result.push_back (*i); } @@ -338,19 +332,9 @@ std::vector nano::rep_crawler::representatives (size_t cou return result; } -std::vector nano::rep_crawler::principal_representatives (size_t count_a) +std::vector nano::rep_crawler::principal_representatives (size_t count_a, boost::optional const & opt_version_min_a) { - std::vector result; - auto minimum = node.minimum_principal_weight (); - 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 > minimum) - { - result.push_back (*i); - } - } - return result; + return representatives (count_a, node.minimum_principal_weight (), opt_version_min_a); } std::vector> nano::rep_crawler::representative_endpoints (size_t count_a) diff --git a/nano/node/repcrawler.hpp b/nano/node/repcrawler.hpp index 3421d28faf..5454b4f024 100644 --- a/nano/node/repcrawler.hpp +++ b/nano/node/repcrawler.hpp @@ -80,9 +80,6 @@ class rep_crawler final /** 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 &); @@ -105,11 +102,11 @@ class rep_crawler final /** 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, optionally with a minimum version \p opt_version_min_a */ - std::vector 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 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. */ - std::vector principal_representatives (size_t count_a = std::numeric_limits::max ()); + /** 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); @@ -150,6 +147,9 @@ class rep_crawler final /** 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 index ac54f4d89c..9bbd3cdb10 100644 --- a/nano/node/request_aggregator.cpp +++ b/nano/node/request_aggregator.cpp @@ -13,7 +13,6 @@ nano::request_aggregator::request_aggregator (nano::network_constants const & ne 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), -max_consecutive_requests (network_constants_a.is_test_network () ? 1 : 10), stats (stats_a), votes_cache (cache_a), store (store_a), @@ -68,7 +67,6 @@ void nano::request_aggregator::run () lock.unlock (); condition.notify_all (); lock.lock (); - unsigned consecutive_requests = 0; while (!stopped) { if (!requests.empty ()) @@ -85,31 +83,28 @@ void nano::request_aggregator::run () auto channel = pool.channel; // Safely erase this pool requests_by_deadline.erase (front); - if (!remaining.empty ()) + lock.unlock (); + // Send cached votes + for (auto const & vote : remaining.first) { - lock.unlock (); - // Generate votes for the remaining hashes - generate (transaction, std::move (remaining), channel); - consecutive_requests = 0; - lock.lock (); + nano::confirm_ack confirm (vote); + channel->send (confirm); } - else if (++consecutive_requests == max_consecutive_requests) + if (!remaining.second.empty ()) { - lock.unlock (); - consecutive_requests = 0; - lock.lock (); + // Generate votes for the remaining hashes + generate (transaction, std::move (remaining.second), channel); } + lock.lock (); } else { - consecutive_requests = 0; auto deadline = front->deadline; condition.wait_until (lock, deadline, [this, &deadline]() { return this->stopped || deadline < std::chrono::steady_clock::now (); }); } } else { - consecutive_requests = 0; condition.wait_for (lock, small_delay, [this]() { return this->stopped || !this->requests.empty (); }); } } @@ -139,7 +134,7 @@ bool nano::request_aggregator::empty () return size () == 0; } -std::vector nano::request_aggregator::aggregate (nano::transaction const & transaction_a, channel_pool & pool_a) const +std::pair>, std::vector> nano::request_aggregator::aggregate (nano::transaction const & transaction_a, channel_pool & pool_a) const { // Unique hashes using pair = decltype (pool_a.hashes_roots)::value_type; @@ -205,14 +200,9 @@ std::vector nano::request_aggregator::aggregate (nano::transac // 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); - pool_a.channel->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; + return { cached_votes, to_generate }; } void nano::request_aggregator::generate (nano::transaction const & transaction_a, std::vector hashes_a, std::shared_ptr & channel_a) const diff --git a/nano/node/request_aggregator.hpp b/nano/node/request_aggregator.hpp index f1b5dee8d1..a94cd87515 100644 --- a/nano/node/request_aggregator.hpp +++ b/nano/node/request_aggregator.hpp @@ -74,12 +74,10 @@ class request_aggregator final private: void run (); /** Aggregate and send cached votes for \p pool_a, returning the leftovers that were not found in cached votes **/ - std::vector aggregate (nano::transaction const &, channel_pool & pool_a) const; + std::pair>, std::vector> aggregate (nano::transaction const &, channel_pool & pool_a) const; /** Generate and send votes from \p hashes_a to \p channel_a, does not need a lock on the mutex **/ void generate (nano::transaction const &, std::vector hashes_a, std::shared_ptr & channel_a) const; - unsigned const max_consecutive_requests; - nano::stat & stats; nano::votes_cache & votes_cache; nano::block_store & store; diff --git a/nano/node/rocksdb/rocksdb.cpp b/nano/node/rocksdb/rocksdb.cpp index ce9ff1f4fd..6eb2b5d22b 100644 --- a/nano/node/rocksdb/rocksdb.cpp +++ b/nano/node/rocksdb/rocksdb.cpp @@ -215,7 +215,7 @@ 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) { debug_assert (transaction_a.contains (table_a)); - // RocksDB errors when trying to delete an entry which doesn't exist. It is a pre-condition that the key exists + // 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 @@ -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,6 +381,7 @@ 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) @@ -388,8 +389,18 @@ size_t nano::rocksdb_store::count (nano::transaction const & transaction_a, tabl ++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)); } diff --git a/nano/node/state_block_signature_verification.cpp b/nano/node/state_block_signature_verification.cpp index 3e9b9cd6ed..4402ef222a 100644 --- a/nano/node/state_block_signature_verification.cpp +++ b/nano/node/state_block_signature_verification.cpp @@ -148,9 +148,9 @@ void nano::state_block_signature_verification::verify_state_blocks (std::deque std::chrono::milliseconds (10)) { - logger.try_log (boost::str (boost::format ("Batch verified %1% state blocks in %2% %3%") % size % timer_l.stop ().count () % timer_l.unit ())); + 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); } diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp index a9e506610a..bb30a9a226 100644 --- a/nano/node/telemetry.cpp +++ b/nano/node/telemetry.cpp @@ -13,176 +13,187 @@ #include #include +using namespace std::chrono_literals; + nano::telemetry::telemetry (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a, bool disable_ongoing_requests_a) : network (network_a), alarm (alarm_a), worker (worker_a), -batch_request (std::make_shared (network, alarm, worker)) +disable_ongoing_requests (disable_ongoing_requests_a) { - // Before callbacks are called with the batch request, check if any of the single request data can be appended to give - batch_request->pre_callback_callback = [this](std::unordered_map & datas_a, std::mutex & mutex_a) { - nano::lock_guard guard (this->mutex); - for (auto & single_request : single_requests) - { - nano::lock_guard guard (single_request.second.impl->mutex); - if (!single_request.second.impl->cached_telemetry_data.empty ()) - { - nano::lock_guard batch_request_guard (mutex_a); - auto it = this->batch_request->cached_telemetry_data.find (single_request.first); - if (it != this->batch_request->cached_telemetry_data.cend () && single_request.second.last_updated > it->second.last_updated) - { - it->second = single_request.second.impl->cached_telemetry_data.begin ()->second; - } - else - { - datas_a.emplace (single_request.first, single_request.second.impl->cached_telemetry_data.begin ()->second.data); - } - } - } - - for (auto & pending : finished_single_requests) - { - nano::lock_guard batch_request_guard (mutex_a); - auto it = this->batch_request->cached_telemetry_data.find (pending.first); - if (it != this->batch_request->cached_telemetry_data.cend () && pending.second.last_updated > it->second.last_updated) - { - it->second = pending.second; - } - else - { - datas_a.emplace (pending.first, pending.second.data); - } - } - finished_single_requests.clear (); - }; +} - if (!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 (); + ongoing_req_all_peers (std::chrono::milliseconds (0)); } } void nano::telemetry::stop () { - nano::lock_guard guard (mutex); - batch_request = nullptr; - single_requests.clear (); stopped = true; } -void nano::telemetry::add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a, bool is_empty_a) +void nano::telemetry::set (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a, bool is_empty_a) { - nano::lock_guard guard (mutex); if (!stopped) { - batch_request->add (telemetry_data_a, endpoint_a, is_empty_a); - - for (auto & request : single_requests) + nano::lock_guard guard (mutex); + auto it = recent_or_initial_request_telemetry_data.find (endpoint_a); + if (it == recent_or_initial_request_telemetry_data.cend ()) { - request.second.impl->add (telemetry_data_a, endpoint_a, is_empty_a); + // Not requesting telemetry data from this peer so ignore it + return; } + + recent_or_initial_request_telemetry_data.modify (it, [&telemetry_data_a](nano::telemetry_info & telemetry_info_a) { + telemetry_info_a.data = telemetry_data_a; + telemetry_info_a.undergoing_request = false; + }); + + channel_processed (endpoint_a, is_empty_a); } } -void nano::telemetry::ongoing_req_all_peers () +std::chrono::milliseconds nano::telemetry::cache_plus_buffer_cutoff_time () const { - alarm.add (std::chrono::steady_clock::now () + batch_request->cache_cutoff + batch_request->alarm_cutoff, [this, telemetry_impl_w = std::weak_ptr (batch_request)]() { - if (auto batch_telemetry_impl = telemetry_impl_w.lock ()) - { - nano::lock_guard guard (this->mutex); - if (!this->stopped) - { - auto peers = this->network.list (std::numeric_limits::max (), network_params.protocol.telemetry_protocol_version_min, false); - // If exists in single_requests don't request because they will just be rejected by other peers until the next round - auto const & single_requests = this->single_requests; - peers.erase (std::remove_if (peers.begin (), peers.end (), [&single_requests](auto const & channel_a) { - return single_requests.count (channel_a->get_endpoint ()) > 0; - }), - peers.cend ()); - if (!peers.empty ()) - { - batch_telemetry_impl->get_metrics_async (peers, [](nano::telemetry_data_responses const &) { - // Intentionally empty, just using to refresh the cache - }); - } - - this->ongoing_req_all_peers (); - } - } - }); + // 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; } -void nano::telemetry::get_metrics_peers_async (std::function const & callback_a) +bool nano::telemetry::within_cache_plus_buffer_cutoff (telemetry_info const & telemetry_info) const { - auto peers = network.list (std::numeric_limits::max (), network_params.protocol.telemetry_protocol_version_min, false); - nano::lock_guard guard (mutex); - if (!stopped && !peers.empty ()) - { - // If exists in single_requests, don't request because they will just be rejected by other nodes, instead all it as additional values - peers.erase (std::remove_if (peers.begin (), peers.end (), [& single_requests = this->single_requests](auto const & channel_a) { - return single_requests.count (channel_a->get_endpoint ()) > 0; - }), - peers.cend ()); - - batch_request->get_metrics_async (peers, [callback_a](nano::telemetry_data_responses const & telemetry_data_responses) { - callback_a (telemetry_data_responses); - }); - } - else - { - const auto all_received = false; - callback_a (nano::telemetry_data_responses{ {}, all_received }); - } + 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; } -nano::telemetry_data_responses nano::telemetry::get_metrics_peers () +bool nano::telemetry::within_cache_cutoff (telemetry_info const & telemetry_info) const { - std::promise promise; - get_metrics_peers_async ([&promise](telemetry_data_responses const & telemetry_data_responses_a) { - promise.set_value (telemetry_data_responses_a); - }); - - return promise.get_future ().get (); + auto is_within = (telemetry_info.last_response + cache_cutoff) >= std::chrono::steady_clock::now (); + return !telemetry_info.awaiting_first_response () && is_within; } -// After a request is made to a single peer we want to remove it from the container after the peer has not been requested for a while (cache_cutoff). -void nano::telemetry::ongoing_single_request_cleanup (nano::endpoint const & endpoint_a, nano::telemetry::single_request_data const & single_request_data_a) +void nano::telemetry::ongoing_req_all_peers (std::chrono::milliseconds next_request_interval) { - alarm.add (std::chrono::steady_clock::now () + single_request_data_a.impl->cache_cutoff, [this, telemetry_impl_w = std::weak_ptr (single_request_data_a.impl), &single_request_data_a, &endpoint_a]() { - if (auto telemetry_impl = telemetry_impl_w.lock ()) + 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 ()) { - nano::lock_guard guard (this->mutex); - nano::lock_guard guard_telemetry_impl (telemetry_impl->mutex); - if (std::chrono::steady_clock::now () - telemetry_impl->cache_cutoff > single_request_data_a.last_updated && telemetry_impl->callbacks.empty ()) + // 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) { - // This will be picked up by the batch request next round - if (!telemetry_impl->cached_telemetry_data.empty ()) + class tag_channel { - this->finished_single_requests[endpoint_a] = telemetry_impl->cached_telemetry_data.begin ()->second; + }; + + 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 (); + } + }; + + namespace mi = boost::multi_index; + boost::multi_index_container, + mi::const_mem_fun>, + mi::hashed_unique, + mi::member, &channel_wrapper::channel>>>> + peers; + + { + auto temp_peers = this_l->network.list (std::numeric_limits::max (), this_l->network_params.protocol.telemetry_protocol_version_min, false); + peers.insert (temp_peers.begin (), temp_peers.end ()); } - this->single_requests.erase (endpoint_a); - } - else - { - // Request is still active, so call again - this->ongoing_single_request_cleanup (endpoint_a, single_request_data_a); + + { + // 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 + }); + } + + 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 ()) + { + // Use the default request time unless a telemetry request cache expires sooner + // Find the closest time with doesn't + auto range = boost::make_iterator_range (this_l->recent_or_initial_request_telemetry_data.get ()); + for (auto i : range) + { + if (peers.count (i.endpoint) == 0) + { + auto const last_response = i.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 ()); + } + break; + } + } + } + + this_l->ongoing_req_all_peers (std::chrono::milliseconds (next_round)); } } }); } -void nano::telemetry::update_cleanup_data (nano::endpoint const & endpoint_a, nano::telemetry::single_request_data & single_request_data_a, bool is_new_a) +std::unordered_map nano::telemetry::get_metrics () { - if (is_new_a) - { - // Clean this request up when it isn't being used anymore - ongoing_single_request_cleanup (endpoint_a, single_request_data_a); - } - else - { - // Ensure that refreshed flag is reset so we don't delete it before processing - single_request_data_a.last_updated = std::chrono::steady_clock::now (); - } + 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) @@ -199,7 +210,6 @@ void nano::telemetry::get_metrics_single_peer_async (std::shared_ptr guard (mutex); if (!stopped) { if (channel_a && (channel_a->get_network_version () >= network_params.protocol.telemetry_protocol_version_min)) @@ -211,42 +221,38 @@ void nano::telemetry::get_metrics_single_peer_async (std::shared_ptr 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)) { - nano::lock_guard guard (batch_request->mutex); - auto it = batch_request->cached_telemetry_data.find (channel_a->get_endpoint ()); - if (it != batch_request->cached_telemetry_data.cend ()) - { - add_callback_async (it->second.data, it->first); - return; - } + add_callback_async (it->data, it->endpoint); } - // Next check single requests which finished and are awaiting batched requests - auto it = finished_single_requests.find (channel_a->get_endpoint ()); - if (it != finished_single_requests.cend ()) + else { - add_callback_async (it->second.data, it->first); - return; - } - - auto pair = single_requests.emplace (channel_a->get_endpoint (), single_request_data{ std::make_shared (network, alarm, worker), std::chrono::steady_clock::now () }); - auto & single_request_data_it = pair.first; - update_cleanup_data (single_request_data_it->first, single_request_data_it->second, pair.second); - - single_request_data_it->second.impl->get_metrics_async ({ channel_a }, [callback_a, channel_a](telemetry_data_responses const & telemetry_data_responses_a) { - // There should only be 1 response, so if this hasn't been received then conclude it is an error. - auto const error = !telemetry_data_responses_a.all_received; - if (!error) + if (it != recent_or_initial_request_telemetry_data.cend () && it->undergoing_request) { - debug_assert (telemetry_data_responses_a.telemetry_datas.size () == 1); - auto it = telemetry_data_responses_a.telemetry_datas.begin (); - callback_a ({ it->second, it->first, error }); + // A request is currently undergoing, add the callback + debug_assert (callbacks.count (it->endpoint) > 0); + callbacks[it->endpoint].push_back (callback_a); } else { - callback_a ({ nano::telemetry_data{}, channel_a->get_endpoint (), error }); + if (it == recent_or_initial_request_telemetry_data.cend ()) + { + 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 { @@ -269,252 +275,147 @@ nano::telemetry_data_response nano::telemetry::get_metrics_single_peer (std::sha return promise.get_future ().get (); } -size_t nano::telemetry::telemetry_data_size () +void nano::telemetry::fire_request_message (std::shared_ptr const & channel) { - nano::lock_guard guard (mutex); - auto total = std::accumulate (single_requests.begin (), single_requests.end (), static_cast (0), [](size_t total, auto & single_request) { - return total += single_request.second.impl->telemetry_data_size (); - }); + // Fire off a telemetry request to all passed in channels + debug_assert (channel->get_network_version () >= network_params.protocol.telemetry_protocol_version_min); - if (batch_request) + uint64_t round_l; { - total += batch_request->telemetry_data_size (); + auto it = recent_or_initial_request_telemetry_data.find (channel->get_endpoint ()); + recent_or_initial_request_telemetry_data.modify (it, [](nano::telemetry_info & telemetry_info_a) { + ++telemetry_info_a.round; + }); + round_l = it->round; } - return total; -} - -size_t nano::telemetry::finished_single_requests_size () -{ - nano::lock_guard guard (mutex); - return finished_single_requests.size (); -} -nano::telemetry_impl::telemetry_impl (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a) : -alarm_cutoff (is_sanitizer_build || nano::running_within_valgrind () ? 6 : 3), -network (network_a), -alarm (alarm_a), -worker (worker_a) -{ -} + std::weak_ptr this_w (shared_from_this ()); + nano::telemetry_req message; + // clang-format off + channel->send (message, [this_w, endpoint = channel->get_endpoint ()](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 + nano::lock_guard guard (this_l->mutex); + this_l->channel_processed (endpoint, true); + } + } + }, + nano::buffer_drop_policy::no_socket_drop); + // clang-format on -void nano::telemetry_impl::flush_callbacks_async () -{ - // Post to worker so that it's truly async and not on the calling thread (same problem as std::async otherwise) - worker.push_task ([this_w = std::weak_ptr (shared_from_this ())]() { + // If no response is seen after a certain period of time remove it + alarm.add (std::chrono::steady_clock::now () + response_time_cutoff, [round_l, this_w, endpoint = channel->get_endpoint ()]() { if (auto this_l = this_w.lock ()) { - nano::unique_lock lk (this_l->mutex); - // Invoke all callbacks, it's possible that during the mutex unlock other callbacks were added, - // so check again and invoke those too - this_l->invoking = true; - while (!this_l->callbacks.empty ()) + 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) { - lk.unlock (); - this_l->invoke_callbacks (); - lk.lock (); + this_l->channel_processed (endpoint, true); } - this_l->invoking = false; } }); } -void nano::telemetry_impl::get_metrics_async (std::deque> const & channels_a, std::function const & callback_a) +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 ()) { - nano::unique_lock lk (mutex); - callbacks.push_back (callback_a); - if (callbacks.size () > 1 || invoking) - { - // This means we already have at least one pending result already, so it will handle calls this callback when it completes - return; - } - - // Check if we can just return cached results - if (channels_a.empty () || std::chrono::steady_clock::now () <= (last_time + cache_cutoff)) + recent_or_initial_request_telemetry_data.modify (it, [](nano::telemetry_info & telemetry_info_a) { + telemetry_info_a.last_response = std::chrono::steady_clock::now (); + }); + if (error_a) { - flush_callbacks_async (); - return; + recent_or_initial_request_telemetry_data.erase (endpoint_a); } - - failed.clear (); - debug_assert (required_responses.empty ()); - std::transform (channels_a.begin (), channels_a.end (), std::inserter (required_responses, required_responses.end ()), [](auto const & channel) { - return channel->get_endpoint (); - }); + flush_callbacks_async (endpoint_a, error_a); } - - fire_request_messages (channels_a); } -void nano::telemetry_impl::add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a, bool is_empty_a) +void nano::telemetry::flush_callbacks_async (nano::endpoint const & endpoint_a, bool error_a) { - nano::unique_lock lk (mutex); - if (required_responses.find (endpoint_a) == required_responses.cend ()) - { - // Not requesting telemetry data from this channel so ignore it - return; - } - - if (!is_empty_a) - { - current_telemetry_data_responses[endpoint_a] = { telemetry_data_a, std::chrono::steady_clock::now () }; - } - channel_processed (lk, endpoint_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_impl::invoke_callbacks () +void nano::telemetry::invoke_callbacks (nano::endpoint const & endpoint_a, bool error_a) { - decltype (callbacks) callbacks_l; - bool all_received; - std::unordered_map cached_responses_l; + std::vector> callbacks_l; + telemetry_data_response response_data{ nano::telemetry_data (), endpoint_a, error_a }; { - // Copy callbacks so that they can be called outside of holding the lock + // Copy data so that it can be used outside of holding the lock nano::lock_guard guard (mutex); - callbacks_l = callbacks; - cached_responses_l.reserve (cached_telemetry_data.size ()); - std::transform (cached_telemetry_data.begin (), cached_telemetry_data.end (), std::inserter (cached_responses_l, cached_responses_l.end ()), [](auto const & endpoint_telemetry_data) { - return std::pair{ endpoint_telemetry_data.first, endpoint_telemetry_data.second.data }; - }); - current_telemetry_data_responses.clear (); - callbacks.clear (); - all_received = failed.empty (); + 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); } - if (pre_callback_callback) - { - pre_callback_callback (cached_responses_l, mutex); - } // Need to account for nodes which disable telemetry data in responses - bool all_received_l = !cached_responses_l.empty () && all_received; for (auto & callback : callbacks_l) { - callback ({ cached_responses_l, all_received_l }); - } -} - -void nano::telemetry_impl::channel_processed (nano::unique_lock & lk_a, nano::endpoint const & endpoint_a) -{ - debug_assert (lk_a.owns_lock ()); - auto num_removed = required_responses.erase (endpoint_a); - if (num_removed > 0 && required_responses.empty ()) - { - debug_assert (lk_a.owns_lock ()); - cached_telemetry_data = current_telemetry_data_responses; - - last_time = std::chrono::steady_clock::now (); - flush_callbacks_async (); - } -} - -void nano::telemetry_impl::fire_request_messages (std::deque> const & channels) -{ - uint64_t round_l; - { - nano::lock_guard guard (mutex); - ++round; - round_l = round; - } - - // Fire off a telemetry request to all passed in channels - nano::telemetry_req message; - for (auto & channel : channels) - { - debug_assert (channel->get_network_version () >= network_params.protocol.telemetry_protocol_version_min); - - std::weak_ptr this_w (shared_from_this ()); - // clang-format off - channel->send (message, [this_w, endpoint = channel->get_endpoint ()](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 - nano::unique_lock lk (this_l->mutex); - this_l->failed.push_back (endpoint); - this_l->channel_processed (lk, endpoint); - } - } - }, - nano::buffer_drop_policy::no_socket_drop); - // clang-format on - - // If no response is seen after a certain period of time, remove it from the list of expected responses. However, only if it is part of the same round. - alarm.add (std::chrono::steady_clock::now () + alarm_cutoff, [this_w, endpoint = channel->get_endpoint (), round_l]() { - if (auto this_l = this_w.lock ()) - { - nano::unique_lock lk (this_l->mutex); - if (this_l->round == round_l && this_l->required_responses.find (endpoint) != this_l->required_responses.cend ()) - { - this_l->failed.push_back (endpoint); - this_l->channel_processed (lk, endpoint); - } - } - }); + callback (response_data); } } -size_t nano::telemetry_impl::telemetry_data_size () +size_t nano::telemetry::telemetry_data_size () { nano::lock_guard guard (mutex); - return current_telemetry_data_responses.size (); + return recent_or_initial_request_telemetry_data.size (); } -bool nano::telemetry_data_time_pair::operator== (telemetry_data_time_pair const & telemetry_data_time_pair_a) const +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) { - return data == telemetry_data_time_pair_a.data && last_updated == telemetry_data_time_pair_a.last_updated; } -bool nano::telemetry_data_time_pair::operator!= (telemetry_data_time_pair const & telemetry_data_time_pair_a) const +bool nano::telemetry_info::awaiting_first_response () const { - return !(*this == telemetry_data_time_pair_a); + return data == nano::telemetry_data (); } std::unique_ptr nano::collect_container_info (telemetry & telemetry, const std::string & name) { - size_t single_requests_count; - { - nano::lock_guard guard (telemetry.mutex); - single_requests_count = telemetry.single_requests.size (); - } - auto composite = std::make_unique (name); - if (telemetry.batch_request) + size_t callbacks_count; { - composite->add_component (collect_container_info (*telemetry.batch_request, "batch_request")); + 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{ "single_requests", single_requests_count, sizeof (decltype (telemetry.single_requests)::value_type) })); - composite->add_component (std::make_unique (container_info{ "finished_single_requests", telemetry.finished_single_requests_size (), sizeof (decltype (telemetry.finished_single_requests)::value_type) })); - return composite; -} -std::unique_ptr nano::collect_container_info (telemetry_impl & telemetry_impl, const std::string & name) -{ - size_t callback_count; - size_t all_telemetry_data_count; - size_t cached_telemetry_data_count; - size_t required_responses_count; - { - nano::lock_guard guard (telemetry_impl.mutex); - callback_count = telemetry_impl.callbacks.size (); - all_telemetry_data_count = telemetry_impl.current_telemetry_data_responses.size (); - cached_telemetry_data_count = telemetry_impl.cached_telemetry_data.size (); - required_responses_count = telemetry_impl.required_responses.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) })); - auto composite = std::make_unique (name); - composite->add_component (std::make_unique (container_info{ "callbacks", callback_count, sizeof (decltype (telemetry_impl.callbacks)::value_type) })); - composite->add_component (std::make_unique (container_info{ "current_telemetry_data_responses", all_telemetry_data_count, sizeof (decltype (telemetry_impl.current_telemetry_data_responses)::value_type) })); - composite->add_component (std::make_unique (container_info{ "cached_telemetry_data", cached_telemetry_data_count, sizeof (decltype (telemetry_impl.cached_telemetry_data)::value_type) })); - composite->add_component (std::make_unique (container_info{ "required_responses", required_responses_count, sizeof (decltype (telemetry_impl.required_responses)::value_type) })); return composite; } nano::telemetry_data nano::consolidate_telemetry_data (std::vector const & telemetry_datas) { - std::vector telemetry_data_time_pairs; - telemetry_data_time_pairs.reserve (telemetry_datas.size ()); - if (telemetry_datas.empty ()) { return {}; diff --git a/nano/node/telemetry.hpp b/nano/node/telemetry.hpp index 4406e7cd24..ebf9d4336c 100644 --- a/nano/node/telemetry.hpp +++ b/nano/node/telemetry.hpp @@ -3,30 +3,26 @@ #include #include +#include +#include +#include +#include + #include #include -#include + +namespace mi = boost::multi_index; namespace nano { class network; class alarm; class worker; -class telemetry; namespace transport { class channel; } -class telemetry_data_time_pair -{ -public: - nano::telemetry_data data; - std::chrono::steady_clock::time_point last_updated; - bool operator== (telemetry_data_time_pair const &) const; - bool operator!= (telemetry_data_time_pair const &) const; -}; - /* * Holds a response from a telemetry request */ @@ -38,106 +34,48 @@ class telemetry_data_response bool error{ true }; }; -/* - * Holds many responses from telemetry requests - */ -class telemetry_data_responses -{ -public: - std::unordered_map telemetry_datas; - bool all_received{ false }; -}; - -/* - * This class requests node telemetry metrics and invokes any callbacks - * which have been aggregated. Further calls to get_metrics_async may return cached telemetry metrics - * if they are within cache_cutoff time from the latest request. - */ -class telemetry_impl : public std::enable_shared_from_this +class telemetry_info final { public: - telemetry_impl (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a); + 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; -private: - // Class only available to the telemetry class - void get_metrics_async (std::deque> const & channels_a, std::function const & callback_a); - void add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a, bool is_empty_a); - size_t telemetry_data_size (); - - nano::network_params network_params; - // 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) }; - std::chrono::seconds const alarm_cutoff; - - // All data in this chunk is protected by this mutex - std::mutex mutex; - std::vector> callbacks; - std::chrono::steady_clock::time_point last_time = std::chrono::steady_clock::now () - cache_cutoff; - /* The responses received during this request round */ - std::unordered_map current_telemetry_data_responses; - /* The metrics for the last request round */ - std::unordered_map cached_telemetry_data; - std::unordered_set required_responses; + nano::endpoint endpoint; + nano::telemetry_data data; + std::chrono::steady_clock::time_point last_response; + bool undergoing_request{ false }; uint64_t round{ 0 }; - /* Currently executing callbacks */ - bool invoking{ false }; - std::vector failed; - - nano::network & network; - nano::alarm & alarm; - nano::worker & worker; - - std::function & data_a, std::mutex &)> pre_callback_callback; - - void invoke_callbacks (); - void channel_processed (nano::unique_lock & lk_a, nano::endpoint const & endpoint_a); - void flush_callbacks_async (); - void fire_request_messages (std::deque> const & channels); - - friend std::unique_ptr collect_container_info (telemetry_impl &, const std::string &); - friend nano::telemetry; - friend class node_telemetry_single_request_Test; - friend class node_telemetry_basic_Test; - friend class node_telemetry_ongoing_requests_Test; }; -std::unique_ptr collect_container_info (telemetry_impl & telemetry_impl, const std::string & name); - /* - * This class has 2 main operations: - * Request metrics from specific single peers (single_requests) - * - If this peer is in the batched request, it will use the value from that, otherwise send a telemetry_req message (non-droppable) - * Request metrics from all peers (batched_request) - * - This is polled every minute. - * - If a single request is currently underway, do not request because other peers will just reject if within a hotzone time - * - This will be proactively added when callbacks are called inside pre_callback_callback + * 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 +class telemetry : public std::enable_shared_from_this { public: - telemetry (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a, bool disable_ongoing_requests_a); - - /* - * Add telemetry metrics received from this endpoint. - * Should this be unsolicited, it will not be added. - * Some peers may have disabled responding with telemetry data, need to account for this - */ - void add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a, bool is_empty_a); + telemetry (nano::network &, nano::alarm &, nano::worker &, bool); + void start (); + void stop (); /* - * Collects metrics from all known peers and invokes the callback when complete. + * Set the telemetry data associated with this peer */ - void get_metrics_peers_async (std::function const & callback_a); + void set (nano::telemetry_data const &, nano::endpoint const &, bool); /* - * A blocking version of get_metrics_peers_async (). + * This returns what ever is in the cache */ - telemetry_data_responses get_metrics_peers (); + std::unordered_map get_metrics (); /* * This makes a telemetry request to the specific channel */ - void get_metrics_single_peer_async (std::shared_ptr const &, std::function const & callback_a); + void get_metrics_single_peer_async (std::shared_ptr const &, std::function const &); /* * A blocking version of get_metrics_single_peer_async @@ -150,50 +88,59 @@ class telemetry size_t telemetry_data_size (); /* - * Return the number of finished_single_requests elements + * Returns the time for the cache, response and a small buffer for alarm operations to be scheduled and completed */ - size_t finished_single_requests_size (); - - /* - * Stop the telemetry processor - */ - void stop (); + 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; + std::atomic stopped{ false }; nano::network_params network_params; - - class single_request_data - { - public: - std::shared_ptr impl; - std::chrono::steady_clock::time_point last_updated{ std::chrono::steady_clock::now () }; - }; + bool disable_ongoing_requests; std::mutex mutex; - /* Requests telemetry data from a random selection of peers */ - std::shared_ptr batch_request; - /* Any requests to specific individual peers is maintained here */ - std::unordered_map single_requests; - /* This holds data from single_requests after the cache is removed */ - std::unordered_map finished_single_requests; - bool stopped{ false }; - - void update_cleanup_data (nano::endpoint const & endpoint_a, nano::telemetry::single_request_data & single_request_data_a, bool is_new_a); - void ongoing_single_request_cleanup (nano::endpoint const & endpoint_a, nano::telemetry::single_request_data const & single_request_data_a); - void ongoing_req_all_peers (); - - friend class node_telemetry_multiple_single_request_clearing_Test; - friend class node_telemetry_ongoing_requests_Test; - friend std::unique_ptr collect_container_info (telemetry &, const std::string &); + // 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{ is_sanitizer_build || nano::running_within_valgrind () ? 6 : 3 }; + + std::unordered_map>> callbacks; + + void ongoing_req_all_peers (std::chrono::milliseconds); + + void fire_request_message (std::shared_ptr const & channel); + 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 & telemetry_info) const; + friend std::unique_ptr collect_container_info (telemetry & telemetry, const std::string & name); }; 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_time_pair consolidate_telemetry_data_time_pairs (std::vector const & telemetry_data_time_pairs); nano::telemetry_data local_telemetry_data (nano::ledger_cache const &, nano::network &, uint64_t, nano::network_params const &, std::chrono::steady_clock::time_point); } \ No newline at end of file diff --git a/nano/node/testing.cpp b/nano/node/testing.cpp index 45c6e09290..fa2f25bb5d 100644 --- a/nano/node/testing.cpp +++ b/nano/node/testing.cpp @@ -37,6 +37,7 @@ std::shared_ptr nano::system::add_node (nano::node_config const & no nodes.push_back (node); if (nodes.size () > 1) { + debug_assert (nodes.size () - 1 <= node->network_params.node.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) { @@ -178,6 +179,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 diff --git a/nano/node/testing.hpp b/nano/node/testing.hpp index 59d74afc3e..4b1a860d59 100644 --- a/nano/node/testing.hpp +++ b/nano/node/testing.hpp @@ -38,6 +38,7 @@ class system final * @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); diff --git a/nano/node/transport/tcp.cpp b/nano/node/transport/tcp.cpp index 450a266d6a..42d669f2da 100644 --- a/nano/node/transport/tcp.cpp +++ b/nano/node/transport/tcp.cpp @@ -116,6 +116,7 @@ bool nano::transport::tcp_channels::insert (std::shared_ptr ().erase (node_id); } channels.get ().emplace (channel_a, socket_a, bootstrap_server_a); + attempts.get ().erase (endpoint); error = false; lock.unlock (); node.network.channel_observer (channel_a); @@ -349,8 +350,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; } @@ -396,8 +405,8 @@ 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); // 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) { @@ -545,6 +554,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 ()) { @@ -576,6 +586,7 @@ void nano::transport::tcp_channels::start_tcp_receive_node_id (std::shared_ptrnetwork.tcp_channels.remove_node_id_handshake_socket (socket_l); + socket_l->close (); } } }; @@ -677,6 +688,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 29525bb72b..6dd4738a19 100644 --- a/nano/node/transport/tcp.hpp +++ b/nano/node/transport/tcp.hpp @@ -78,7 +78,7 @@ namespace transport class tcp_channels final { friend class nano::transport::channel_tcp; - friend class node_telemetry_simultaneous_single_and_all_requests_Test; + friend class node_telemetry_simultaneous_requests_Test; public: tcp_channels (nano::node &); @@ -131,6 +131,9 @@ namespace transport class last_bootstrap_attempt_tag { }; + class last_attempt_tag + { + }; class node_id_tag { }; @@ -171,10 +174,12 @@ namespace transport { public: nano::tcp_endpoint endpoint; + 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) + endpoint (endpoint_a), + address (endpoint_a.address ()) { } }; @@ -196,9 +201,11 @@ namespace transport channels; boost::multi_index_container, mi::member>, - mi::ordered_non_unique< + mi::hashed_non_unique, + mi::member>, + mi::ordered_non_unique, mi::member>>> attempts; // clang-format on diff --git a/nano/node/transport/transport.hpp b/nano/node/transport/transport.hpp index 6de3dafbb1..6a0e7e635e 100644 --- a/nano/node/transport/transport.hpp +++ b/nano/node/transport/transport.hpp @@ -43,8 +43,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 { diff --git a/nano/node/transport/udp.cpp b/nano/node/transport/udp.cpp index 993ee9bc9c..4610f93690 100644 --- a/nano/node/transport/udp.cpp +++ b/nano/node/transport/udp.cpp @@ -116,6 +116,7 @@ std::shared_ptr nano::transport::udp_channels::ins { result = std::make_shared (*this, endpoint_a, network_version_a); channels.get ().insert (result); + attempts.get ().erase (endpoint_a); lock.unlock (); node.network.channel_observer (result); } @@ -543,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); 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); @@ -591,15 +600,12 @@ void nano::transport::udp_channels::receive_action (nano::message_buffer * data_ 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 { @@ -633,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; } @@ -677,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 () diff --git a/nano/node/transport/udp.hpp b/nano/node/transport/udp.hpp index c201619a8a..50f0149244 100644 --- a/nano/node/transport/udp.hpp +++ b/nano/node/transport/udp.hpp @@ -124,6 +124,9 @@ namespace transport class last_bootstrap_attempt_tag { }; + class last_attempt_tag + { + }; class node_id_tag { }; @@ -191,9 +194,9 @@ namespace transport boost::multi_index_container< endpoint_attempt, mi::indexed_by< - mi::hashed_unique< + mi::hashed_unique, mi::member>, - mi::ordered_non_unique< + mi::ordered_non_unique, mi::member>>> attempts; // clang-format on diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 0a705dd2a0..9a344ea3fa 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -380,7 +380,9 @@ void nano::wallet_store::initialize (nano::transaction const & transaction_a, bo { 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; } @@ -1987,8 +1989,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)) { } diff --git a/nano/node/wallet.hpp b/nano/node/wallet.hpp index dd8de020ea..aa5577a05a 100644 --- a/nano/node/wallet.hpp +++ b/nano/node/wallet.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -110,7 +111,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: @@ -257,7 +258,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 4cb686e711..9e38c5726f 100644 --- a/nano/node/websocket.cpp +++ b/nano/node/websocket.cpp @@ -2,7 +2,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/nano/node/write_database_queue.cpp b/nano/node/write_database_queue.cpp index 3378f0a787..c350f049a7 100644 --- a/nano/node/write_database_queue.cpp +++ b/nano/node/write_database_queue.cpp @@ -77,6 +77,9 @@ nano::write_guard nano::write_database_queue::pop () void nano::write_database_queue::stop () { - stopped = true; + { + nano::lock_guard guard (mutex); + stopped = true; + } cv.notify_all (); } diff --git a/nano/node/write_database_queue.hpp b/nano/node/write_database_queue.hpp index b2657c00bc..e18803482c 100644 --- a/nano/node/write_database_queue.hpp +++ b/nano/node/write_database_queue.hpp @@ -2,7 +2,6 @@ #include -#include #include #include #include @@ -53,6 +52,6 @@ class write_database_queue final std::mutex mutex; nano::condition_variable cv; std::function guard_finish_callback; - std::atomic stopped{ false }; + bool stopped{ false }; }; } diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 9c8919733a..5323779e82 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -7990,12 +7990,25 @@ TEST (rpc, node_telemetry_all) { ASSERT_NO_ERROR (system.poll ()); - auto transaction = system.nodes.back ()->store.tx_begin_read (); - peers_stored = system.nodes.back ()->store.peer_count (transaction) != 0; + auto transaction = node1.store.tx_begin_read (); + peers_stored = node1.store.peer_count (transaction) != 0; } - boost::property_tree::ptree request; + // 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", "node_telemetry"); { test_response response (request, rpc.config.port, system.io_ctx); @@ -8138,3 +8151,96 @@ TEST (rpc, node_telemetry_self) 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); + node1.block_processor.flush (); + 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 cfe874160b..2098cceac3 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -41,8 +41,6 @@ add_library (secure buffer.hpp common.hpp common.cpp - epoch.hpp - epoch.cpp ledger.hpp ledger.cpp network_filter.hpp diff --git a/nano/secure/blockstore.cpp b/nano/secure/blockstore.cpp index 909dd2313e..1babdb2f54 100644 --- a/nano/secure/blockstore.cpp +++ b/nano/secure/blockstore.cpp @@ -1,162 +1,6 @@ #include #include -#include - -#include - -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; -} - -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, bool is_send, bool is_receive, bool is_epoch) : -type (type_a), -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) 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) - { - details.serialize (stream_a); - } -} - -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) - { - result = details.deserialize (stream_a); - } - } - 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), @@ -397,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) : diff --git a/nano/secure/blockstore.hpp b/nano/secure/blockstore.hpp index 66ac7e8d52..2ae13f6e3c 100644 --- a/nano/secure/blockstore.hpp +++ b/nano/secure/blockstore.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -16,45 +17,6 @@ namespace nano { -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); -}; - -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, bool is_send, bool is_receive, bool is_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::block_details details; -}; - // Move to versioning with a specific version if required for a future upgrade class state_block_w_sideband { @@ -311,33 +273,33 @@ class db_val return result; } -private: - // Common usage for versioning - template ::value || std::is_same::value>> - T as () const + explicit operator state_block_w_sideband () const { nano::bufferstream stream (reinterpret_cast (data ()), size ()); auto error (false); - T block_w_sideband; + nano::state_block_w_sideband 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); + error = block_w_sideband.sideband.deserialize (stream, nano::block_type::state); debug_assert (!error); return block_w_sideband; } -public: - explicit operator state_block_w_sideband () const - { - return as (); - } - explicit operator state_block_w_sideband_v14 () const { - return as (); + 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 @@ -441,19 +403,6 @@ class db_val class transaction; class block_store; -class block_w_sideband final -{ -public: - block_w_sideband (std::shared_ptr const & block_a, nano::block_sideband const & sideband_a) : - block (block_a), - sideband (sideband_a) - { - } - - std::shared_ptr block; - nano::block_sideband sideband; -}; - /** * Summation visitor for blocks, supporting amount and balance computations. These * computations are mutually dependant. The natural solution is to use mutual recursion @@ -714,10 +663,11 @@ class block_store public: virtual ~block_store () = default; 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 &, nano::block_sideband const &) = 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 &, nano::block_type) = 0; @@ -739,9 +689,9 @@ class block_store virtual size_t account_count (nano::transaction const &) = 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; @@ -753,7 +703,7 @@ class block_store 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; @@ -761,8 +711,7 @@ class block_store 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; - /* Returns true if nothing was deleted because it was not found, false otherwise */ - virtual bool unchecked_del (nano::write_transaction const &, nano::unchecked_key const &) = 0; + virtual void unchecked_del (nano::write_transaction const &, nano::unchecked_key const &) = 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; @@ -828,7 +777,7 @@ class block_store 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 6e837aacab..0e9bb13c3c 100644 --- a/nano/secure/blockstore_partial.hpp +++ b/nano/secure/blockstore_partial.hpp @@ -31,8 +31,8 @@ class block_store_partial : public block_store { auto hash_l (genesis_a.hash ()); debug_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, false, false, false); - block_put (transaction_a, hash_l, *genesis_a.open, sideband); + 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; @@ -44,9 +44,8 @@ class block_store_partial : public block_store 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)); + nano::uint128_t result (block_balance_calculated (block)); return result; } @@ -97,15 +96,14 @@ 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 { - debug_assert (block_a.type () == sideband_a.type); - debug_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); @@ -116,13 +114,12 @@ class block_store_partial : public block_store // 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); + auto block = block_get (transaction_a, hash_a); debug_assert (block != nullptr); - return sideband.height; + 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)); @@ -132,25 +129,37 @@ class block_store_partial : public block_store nano::bufferstream stream (reinterpret_cast (value.data ()), value.size ()); result = nano::deserialize_block (stream, type); debug_assert (result != nullptr); - if (sideband_a) + 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; - debug_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; } @@ -186,18 +195,17 @@ 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)); + auto block (block_get (transaction_a, hash_a)); nano::account result (block->account ()); if (result.is_zero ()) { - result = sideband.account; + result = block->sideband ().account; } 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 ()) @@ -205,7 +213,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 (); @@ -346,7 +354,7 @@ 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); } @@ -407,11 +415,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.details.epoch; + return block->sideband ().details.epoch; } return nano::epoch::epoch_0; @@ -434,8 +441,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 @@ -486,11 +493,10 @@ class block_store_partial : public block_store release_assert (success (status)); } - bool unchecked_del (nano::write_transaction const & transaction_a, nano::unchecked_key const & key_a) override + 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)); - return not_found (status); + release_assert (success (status)); } std::shared_ptr vote_get (nano::transaction const & transaction_a, nano::account const & account_a) override @@ -551,8 +557,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 @@ -717,12 +723,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); } @@ -842,7 +848,7 @@ class block_store_partial : public block_store auto hash (hash_a); while (result.is_zero ()) { - auto block (block_get (transaction_a, hash)); + auto block (block_get_no_sideband (transaction_a, hash)); debug_assert (block); result = block->account (); if (result.is_zero ()) diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 4b732b4ec8..f75a57b80d 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -135,6 +135,7 @@ 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 } diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 715ab3e19f..338237bccb 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -4,10 +4,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -419,6 +419,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; @@ -506,5 +508,29 @@ class ledger_cache std::atomic account_count{ 0 }; }; +/* 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 5f49b16095..1e552b5aa9 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -172,18 +172,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; @@ -230,7 +230,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 +253,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)); @@ -334,9 +334,8 @@ 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, is_send, is_receive, false); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), epoch, is_send, is_receive, false)); + ledger.store.block_put (transaction, hash, block_a); if (!info.head.is_zero ()) { @@ -371,7 +370,7 @@ void ledger_processor::state_block_impl (nano::state_block const & block_a) } } -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)); @@ -423,8 +422,8 @@ void ledger_processor::epoch_block_impl (nano::state_block const & block_a) 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, false, false, true); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), epoch, false, false, true)); + 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 ()) @@ -439,7 +438,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)); @@ -471,8 +470,8 @@ void ledger_processor::change_block (nano::change_block const & block_a) { debug_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, false, false, false); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); + 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); @@ -490,7 +489,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)); @@ -527,8 +526,8 @@ void ledger_processor::send_block (nano::send_block const & block_a) { auto amount (info.balance.number () - block_a.hashables.balance.number ()); ledger.cache.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, false /* unused */, false /* unused */, false /* unused */); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (account, 0, block_a.hashables.balance /* unused */, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); + 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 }); @@ -546,7 +545,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)); @@ -595,8 +594,8 @@ void ledger_processor::receive_block (nano::receive_block const & block_a) (void)error; debug_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, false /* unused */, false /* unused */, false /* unused */); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (account, 0, new_balance, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); + 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 ()); @@ -620,7 +619,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)); @@ -659,8 +658,8 @@ void ledger_processor::open_block (nano::open_block const & block_a) (void)error; debug_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, false /* unused */, false /* unused */, false /* unused */); - ledger.store.block_put (transaction, hash, block_a, sideband); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); + 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 ()); @@ -752,7 +751,7 @@ 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) { debug_assert (!nano::work_validate (block_a)); ledger_processor processor (*this, transaction_a, verification); @@ -808,12 +807,19 @@ 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 { bool result (false); - nano::block_hash previous (block_a.hashables.previous); - if (!previous.is_zero ()) + if (block_a.has_sideband ()) { - if (block_a.hashables.balance < balance (transaction_a, previous)) + result = block_a.sideband ().details.is_send; + } + else + { + 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; diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 60e7106069..00a460c58b 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -38,7 +38,7 @@ 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 &); diff --git a/nano/secure/network_filter.cpp b/nano/secure/network_filter.cpp index b6fc7b3f2b..f9e6b0374a 100644 --- a/nano/secure/network_filter.cpp +++ b/nano/secure/network_filter.cpp @@ -40,6 +40,19 @@ void nano::network_filter::clear (nano::uint128_t const & digest_a) } } +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)); @@ -48,12 +61,7 @@ void nano::network_filter::clear (uint8_t const * bytes_a, size_t count_a) template void nano::network_filter::clear (OBJECT const & object_a) { - std::vector bytes; - { - nano::vectorstream stream (bytes); - object_a->serialize (stream); - } - clear (bytes.data (), bytes.size ()); + clear (hash (object_a)); } void nano::network_filter::clear () @@ -62,6 +70,17 @@ void nano::network_filter::clear () 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 ()); @@ -77,3 +96,7 @@ nano::uint128_t nano::network_filter::hash (uint8_t const * bytes_a, size_t coun 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 index 60b99dbd08..0e42a2cce7 100644 --- a/nano/secure/network_filter.hpp +++ b/nano/secure/network_filter.hpp @@ -34,6 +34,11 @@ class network_filter final **/ 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. @@ -42,7 +47,7 @@ class network_filter final void clear (uint8_t const * bytes_a, size_t count_a); /** - * Serializes \p object_a and runs clears the resulting siphash digest. + * 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 @@ -51,6 +56,12 @@ class network_filter final /** 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>; diff --git a/nano/slow_test/node.cpp b/nano/slow_test/node.cpp index 08209b527f..d794c1261f 100644 --- a/nano/slow_test/node.cpp +++ b/nano/slow_test/node.cpp @@ -187,7 +187,9 @@ TEST (store, load) // ulimit -n increasing may be required TEST (node, fork_storm) { - nano::system system (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)); @@ -857,10 +859,8 @@ class data class shared_data { public: + nano::util::counted_completion write_completion{ 0 }; std::atomic done{ false }; - std::atomic count{ 0 }; - std::promise promise; - std::shared_future shared_future{ promise.get_future () }; }; template @@ -880,16 +880,10 @@ void callback_process (shared_data & shared_data_a, data & data, T & all_node_da data.awaiting_cache = true; data.orig_time = last_updated; } - if (--shared_data_a.count == 0 && std::all_of (all_node_data_a.begin (), all_node_data_a.end (), [](auto const & data) { return !data.keep_requesting_metrics; })) - { - shared_data_a.done = true; - shared_data_a.promise.set_value (); - } + shared_data_a.write_completion.increment (); }; } -namespace nano -{ TEST (node_telemetry, ongoing_requests) { nano::system system (2); @@ -899,20 +893,20 @@ TEST (node_telemetry, ongoing_requests) 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->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_client->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::out) != 1) + 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 + nano::telemetry_cache_cutoffs::test + node_client->telemetry.batch_request->alarm_cutoff + 1s)) + while (std::chrono::steady_clock::now () < (time + node_client->telemetry->cache_plus_buffer_cutoff_time () + 1s)) { ASSERT_NO_ERROR (system.poll ()); } @@ -924,70 +918,12 @@ TEST (node_telemetry, ongoing_requests) 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)); } -} - -TEST (node_telemetry, simultaneous_all_requests) -{ - const auto num_nodes = 4; - nano::system system (num_nodes); - - // Wait until peers are stored as they are done in the background - wait_peer_connections (system); - - std::vector threads; - const auto num_threads = 4; - - std::array all_data{}; - for (auto i = 0; i < num_nodes; ++i) - { - all_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 ([&all_data, &shared_data]() { - while (std::any_of (all_data.cbegin (), all_data.cend (), [](auto const & data) { return data.keep_requesting_metrics.load (); })) - { - for (auto & data : all_data) - { - // Keep calling requesting telemetry metrics 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.count; - data.node->telemetry.get_metrics_peers_async ([&shared_data, &data, &all_data](nano::telemetry_data_responses const & responses_a) { - callback_process (shared_data, data, all_data, *responses_a.telemetry_datas.begin ()->second.timestamp); - }); - } - std::this_thread::sleep_for (1ms); - } - } - - shared_data.shared_future.wait (); - ASSERT_EQ (shared_data.count, 0); - }); - } - - system.deadline_set (20s); - while (!shared_data.done) - { - ASSERT_NO_ERROR (system.poll ()); - } - - for (auto & thread : threads) - { - thread.join (); - } -} namespace nano { namespace transport { - TEST (node_telemetry, simultaneous_single_and_all_requests) + TEST (node_telemetry, simultaneous_requests) { const auto num_nodes = 4; nano::system system (num_nodes); @@ -997,66 +933,52 @@ namespace transport std::vector threads; const auto num_threads = 4; - std::array node_data_single{}; - std::array node_data_all{}; + std::array node_data{}; for (auto i = 0; i < num_nodes; ++i) { - node_data_single[i].node = system.nodes[i]; - node_data_all[i].node = system.nodes[i]; + node_data[i].node = system.nodes[i]; } - shared_data shared_data_single; - shared_data shared_data_all; + 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_single, &node_data_all, &shared_data_single, &shared_data_all]() { - auto func = [](auto & all_node_data_a, shared_data & shared_data_a, bool single_a) { - while (std::any_of (all_node_data_a.cbegin (), all_node_data_a.cend (), [](auto const & data) { return data.keep_requesting_metrics.load (); })) + 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) { - for (auto & data : all_node_data_a) + // 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) { - // 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_a.count; - - if (single_a) - { - // 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_a, &data, &all_node_data_a](nano::telemetry_data_response const & telemetry_data_response_a) { - callback_process (shared_data_a, data, all_node_data_a, *telemetry_data_response_a.telemetry_data.timestamp); - }); - } - else - { - data.node->telemetry.get_metrics_peers_async ([&shared_data_a, &data, &all_node_data_a](nano::telemetry_data_responses const & telemetry_data_responses_a) { - callback_process (shared_data_a, data, all_node_data_a, *telemetry_data_responses_a.telemetry_datas.begin ()->second.timestamp); - }); - } - } - std::this_thread::sleep_for (1ms); + 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_a.shared_future.wait (); - ASSERT_EQ (shared_data_a.count, 0); - }; - - func (node_data_single, shared_data_single, true); - func (node_data_all, shared_data_all, false); + shared_data.write_completion.await_count_for (20s); + shared_data.done = true; }); } system.deadline_set (30s); - while (!shared_data_all.done || !shared_data_single.done) + 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 (); diff --git a/util/build_prep/bootstrap_boost.sh b/util/build_prep/bootstrap_boost.sh index 47033144bf..acec3e3010 100755 --- a/util/build_prep/bootstrap_boost.sh +++ b/util/build_prep/bootstrap_boost.sh @@ -75,17 +75,17 @@ 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_URL=https://sourceforge.net/projects/boost/files/boost/1.67.0/${BOOST_BASENAME}.tar.bz2/download 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' ;; *)