diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 264870bba9..77ff7d4efc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,7 +56,7 @@ jobs: - name: Fetch Deps run: ci/actions/linux/install_deps.sh - name: Run Tests - run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:clang /bin/bash -c "cd /workspace && RELEASE=0 ASAN=0 TSAN=0 ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" + run: docker run -v ${PWD}:/workspace nanocurrency/nano-env:clang /bin/bash -c "cd /workspace && ./ci/build-travis.sh /usr/lib/x86_64-linux-gnu/cmake/Qt5 ${PWD}" windows_test: runs-on: windows-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a338faf89..9507694c8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ execute_process( ) option (CI_BUILD false) +set (CI_TEST 0 CACHE STRING "") set (CPACK_PACKAGE_VERSION_MAJOR "21") set (CPACK_PACKAGE_VERSION_MINOR "0") @@ -37,6 +38,7 @@ set (NANO_ROCKSDB OFF CACHE BOOL "") set (NANO_POW_SERVER OFF CACHE BOOL "") set (NANO_WARN_TO_ERR OFF CACHE BOOL "") set (NANO_TIMED_LOCKS 0 CACHE STRING "") +set (NANO_FUZZER_TEST OFF CACHE BOOL "") option (NANO_STACKTRACE_BACKTRACE "Use BOOST_STACKTRACE_USE_BACKTRACE in stacktraces, for POSIX" OFF) if (NANO_STACKTRACE_BACKTRACE) @@ -109,6 +111,11 @@ else () add_definitions(-DED25519_NO_INLINE_ASM) endif() + if (NANO_FUZZER_TEST) + add_compile_options (-fsanitize=fuzzer-no-link -fno-omit-frame-pointer) + add_definitions (-DNANO_FUZZER_TEST) + endif () + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86(_64)?)$") if (NANO_SIMD_OPTIMIZATIONS OR RAIBLOCKS_SIMD_OPTIMIZATIONS OR ENABLE_AVX2) add_compile_options(-msse4) @@ -174,6 +181,9 @@ else () set (PLATFORM_LINK_FLAGS "${PLATFORM_LINK_FLAGS} -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/tsan_clang_blacklist") endif() endif() + if (NANO_FUZZER_TEST) + set (PLATFORM_LINK_FLAGS "${PLATFORM_LINK_FLAGS} -fsanitize=fuzzer-no-link") + endif () endif () SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINK_FLAGS}" ) @@ -351,6 +361,14 @@ add_subdirectory(nano/nano_node) add_subdirectory(nano/rpc) add_subdirectory(nano/nano_rpc) +if (NANO_FUZZER_TEST) + if (NOT WIN32) + add_subdirectory (nano/fuzzer_test) + else () + message (WARNING "Fuzzing is not supported on Windows") + endif () +endif () + if (NANO_TEST OR RAIBLOCKS_TEST) if(WIN32) if(MSVC_VERSION) diff --git a/ci/actions/windows/build.ps1 b/ci/actions/windows/build.ps1 index c532c730f5..e7a13cf76a 100644 --- a/ci/actions/windows/build.ps1 +++ b/ci/actions/windows/build.ps1 @@ -25,7 +25,7 @@ if (${env:artifact} -eq 1) { } $env:NETWORK_CFG = "test" $env:NANO_TEST = "-DNANO_TEST=ON" - $env:CI = "-DCI_BUILD=OFF" + $env:CI = '-DCI_TEST="1"' $env:RUN = "test" } @@ -39,7 +39,7 @@ if (${LastExitCode} -ne 0) { if (${env:RUN} -eq "artifact") { $p = Get-Location - Invoke-WebRequest -Uri https://aka.ms/vs/15/release/vc_redist.x64.exe -OutFile "$p\vc_redist.x64.exe" + Invoke-WebRequest -Uri https://aka.ms/vs/16/release/vc_redist.x64.exe -OutFile "$p\vc_redist.x64.exe" } & ..\ci\actions\windows\build.bat diff --git a/ci/build-travis.sh b/ci/build-travis.sh index 6698cc1e8a..e8da49f10d 100755 --- a/ci/build-travis.sh +++ b/ci/build-travis.sh @@ -48,7 +48,6 @@ else ROCKSDB="" fi - cmake \ -G'Unix Makefiles' \ -DACTIVE_NETWORK=nano_test_network \ @@ -61,6 +60,7 @@ cmake \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DBOOST_ROOT=/tmp/boost/ \ -DQt5_DIR=${qt_dir} \ + -DCI_TEST="1" \ ${SANITIZERS} \ .. diff --git a/docker/ci/Dockerfile-clang-6 b/docker/ci/Dockerfile-clang-6 new file mode 100644 index 0000000000..c292050cdb --- /dev/null +++ b/docker/ci/Dockerfile-clang-6 @@ -0,0 +1,22 @@ +FROM nanocurrency/nano-env:base + +RUN apt-get update && apt-get install -yqq software-properties-common && \ + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main" && \ + apt-get update -qq && apt-get install -yqq \ + clang-6.0 lldb-6.0 libfuzzer-6.0-dev git + +ADD util/build_prep/fetch_rocksdb.sh fetch_rocksdb.sh +RUN ./fetch_rocksdb.sh + +ENV CXX=/usr/bin/clang++ +ENV CC=/usr/bin/clang +RUN ln -s /usr/bin/clang-6.0 /usr/bin/clang +RUN ln -s /usr/bin/clang++-6.0 /usr/bin/clang++ +RUN update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 +RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100 +ENV BOOST_ROOT=/tmp/boost + +ADD util/build_prep/bootstrap_boost.sh bootstrap_boost.sh + +RUN ./bootstrap_boost.sh -m -c -B 1.70 diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 52bd342f3b..a1dd3ffcf6 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -19,11 +19,12 @@ add_executable (core_test ledger.cpp locks.cpp logger.cpp - network.cpp - node.cpp message.cpp message_parser.cpp memory_pool.cpp + network.cpp + node.cpp + node_telemetry.cpp processor_service.cpp peer_container.cpp request_aggregator.cpp @@ -43,5 +44,7 @@ target_compile_definitions(core_test PRIVATE -DTAG_VERSION_STRING=${TAG_VERSION_STRING} -DGIT_COMMIT_HASH=${GIT_COMMIT_HASH} - -DBOOST_PROCESS_SUPPORTED=${BOOST_PROCESS_SUPPORTED}) + -DBOOST_PROCESS_SUPPORTED=${BOOST_PROCESS_SUPPORTED} + -DCI=${CI_TEST}) + target_link_libraries (core_test node secure gtest libminiupnpc-static Boost::log_setup Boost::log Boost::boost) diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp index a0307fa5f9..eca7ad2782 100644 --- a/nano/core_test/bootstrap.cpp +++ b/nano/core_test/bootstrap.cpp @@ -189,13 +189,13 @@ TEST (bootstrap_processor, process_one) nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; node_config.enable_voting = false; - auto node0 = system.add_node (node_config); + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 = system.add_node (node_config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 100)); node_config.peering_port = nano::get_available_port (); - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_rep_crawler = true; auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), system.alarm, node_config, system.work, node_flags)); nano::block_hash hash1 (node0->latest (nano::test_genesis_key.pub)); @@ -214,22 +214,27 @@ TEST (bootstrap_processor, process_one) TEST (bootstrap_processor, process_two) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash hash1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash1 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 50)); - nano::block_hash hash2 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash2 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 50)); - nano::block_hash hash3 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash hash3 (node0->latest (nano::test_genesis_key.pub)); ASSERT_NE (hash1, hash2); ASSERT_NE (hash1, hash3); ASSERT_NE (hash2, hash3); auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); - ASSERT_NE (node1->latest (nano::test_genesis_key.pub), system.nodes[0]->latest (nano::test_genesis_key.pub)); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); + ASSERT_NE (node1->latest (nano::test_genesis_key.pub), node0->latest (nano::test_genesis_key.pub)); system.deadline_set (10s); - while (node1->latest (nano::test_genesis_key.pub) != system.nodes[0]->latest (nano::test_genesis_key.pub)) + while (node1->latest (nano::test_genesis_key.pub) != node0->latest (nano::test_genesis_key.pub)) { ASSERT_NO_ERROR (system.poll ()); } @@ -239,10 +244,14 @@ TEST (bootstrap_processor, process_two) // Bootstrap can pull universal blocks TEST (bootstrap_processor, process_state) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node0 (system.nodes[0]); auto block1 (std::make_shared (nano::test_genesis_key.pub, node0->latest (nano::test_genesis_key.pub), nano::test_genesis_key.pub, nano::genesis_amount - 100, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); auto block2 (std::make_shared (nano::test_genesis_key.pub, block1->hash (), nano::test_genesis_key.pub, nano::genesis_amount, block1->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node0->work_generate_blocking (*block1); @@ -265,45 +274,57 @@ TEST (bootstrap_processor, process_state) TEST (bootstrap_processor, process_new) { - nano::system system (2); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node1 (system.add_node (config, node_flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, node_flags)); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node1->config.receive_minimum.number ())); system.deadline_set (10s); - while (system.nodes[0]->balance (key2.pub).is_zero ()) + while (node1->balance (key2.pub).is_zero ()) { ASSERT_NO_ERROR (system.poll ()); } - nano::uint128_t balance1 (system.nodes[0]->balance (nano::test_genesis_key.pub)); - nano::uint128_t balance2 (system.nodes[0]->balance (key2.pub)); - auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); - ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + nano::uint128_t balance1 (node1->balance (nano::test_genesis_key.pub)); + nano::uint128_t balance2 (node1->balance (key2.pub)); + auto node3 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + ASSERT_FALSE (node3->init_error ()); + node3->bootstrap_initiator.bootstrap (node1->network.endpoint ()); system.deadline_set (10s); - while (node1->balance (key2.pub) != balance2) + while (node3->balance (key2.pub) != balance2) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (balance1, node1->balance (nano::test_genesis_key.pub)); - node1->stop (); + ASSERT_EQ (balance1, node3->balance (nano::test_genesis_key.pub)); + node3->stop (); } TEST (bootstrap_processor, pull_diamond) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::keypair key; - auto send1 (std::make_shared (system.nodes[0]->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (system.nodes[0]->latest (nano::test_genesis_key.pub)))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*send1).code); + auto send1 (std::make_shared (node0->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (node0->latest (nano::test_genesis_key.pub)))); + ASSERT_EQ (nano::process_result::progress, node0->process (*send1).code); auto open (std::make_shared (send1->hash (), 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*open).code); + ASSERT_EQ (nano::process_result::progress, node0->process (*open).code); auto send2 (std::make_shared (open->hash (), nano::test_genesis_key.pub, std::numeric_limits::max () - 100, key.prv, key.pub, *system.work.generate (open->hash ()))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*send2).code); + ASSERT_EQ (nano::process_result::progress, node0->process (*send2).code); auto receive (std::make_shared (send1->hash (), send2->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (*receive).code); + ASSERT_EQ (nano::process_result::progress, node0->process (*receive).code); auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); while (node1->balance (nano::test_genesis_key.pub) != 100) { @@ -313,11 +334,16 @@ TEST (bootstrap_processor, pull_diamond) node1->stop (); } -TEST (bootstrap_processor, pull_requeue_network_error) +TEST (bootstrap_processor, DISABLED_pull_requeue_network_error) { - nano::system system (2); - auto node1 = system.nodes[0]; - auto node2 = system.nodes[1]; + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node1 (system.add_node (config, node_flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); @@ -456,14 +482,17 @@ TEST (bootstrap_processor, frontiers_confirmed) TEST (bootstrap_processor, push_diamond) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system.add_node (config)); nano::keypair key; auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node1->init_error ()); auto wallet1 (node1->wallets.create (100)); wallet1->insert_adhoc (nano::test_genesis_key.prv); wallet1->insert_adhoc (key.prv); - auto send1 (std::make_shared (system.nodes[0]->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (system.nodes[0]->latest (nano::test_genesis_key.pub)))); + auto send1 (std::make_shared (node0->latest (nano::test_genesis_key.pub), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (node0->latest (nano::test_genesis_key.pub)))); ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); auto open (std::make_shared (send1->hash (), 1, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); @@ -471,19 +500,22 @@ TEST (bootstrap_processor, push_diamond) ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); auto receive (std::make_shared (send1->hash (), send2->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); ASSERT_EQ (nano::process_result::progress, node1->process (*receive).code); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) != 100) + while (node0->balance (nano::test_genesis_key.pub) != 100) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (100, system.nodes[0]->balance (nano::test_genesis_key.pub)); + ASSERT_EQ (100, node0->balance (nano::test_genesis_key.pub)); node1->stop (); } TEST (bootstrap_processor, push_one) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system.add_node (config)); nano::keypair key1; auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); auto wallet (node1->wallets.create (nano::random_wallet_id ())); @@ -492,9 +524,9 @@ TEST (bootstrap_processor, push_one) nano::uint128_t balance1 (node1->balance (nano::test_genesis_key.pub)); ASSERT_NE (nullptr, wallet->send_action (nano::test_genesis_key.pub, key1.pub, 100)); ASSERT_NE (balance1, node1->balance (nano::test_genesis_key.pub)); - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); system.deadline_set (10s); - while (system.nodes[0]->balance (nano::test_genesis_key.pub) == balance1) + while (node0->balance (nano::test_genesis_key.pub) == balance1) { ASSERT_NO_ERROR (system.poll ()); } @@ -503,24 +535,29 @@ TEST (bootstrap_processor, push_one) TEST (bootstrap_processor, lazy_hash) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.flush (); // Start lazy bootstrap with last block in chain known auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->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 ()); @@ -538,8 +575,12 @@ TEST (bootstrap_processor, lazy_hash) TEST (bootstrap_processor, lazy_hash_bootstrap_id) { - nano::system system (1); - auto node0 (system.nodes[0]); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; @@ -574,30 +615,35 @@ TEST (bootstrap_processor, lazy_hash_bootstrap_id) TEST (bootstrap_processor, lazy_max_pull_count) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + auto node0 (system.add_node (config, node_flags)); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); - auto change1 (std::make_shared (key2.pub, receive2->hash (), key1.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (receive2->hash ()))); - auto change2 (std::make_shared (key2.pub, change1->hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (change1->hash ()))); - auto change3 (std::make_shared (key2.pub, change2->hash (), key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (change2->hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); + auto change1 (std::make_shared (key2.pub, receive2->hash (), key1.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (receive2->hash ()))); + auto change2 (std::make_shared (key2.pub, change1->hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (change1->hash ()))); + auto change3 (std::make_shared (key2.pub, change2->hash (), key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, *node0->work_generate_blocking (change2->hash ()))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.add (change1); - system.nodes[0]->block_processor.add (change2); - system.nodes[0]->block_processor.add (change3); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.add (change1); + node0->block_processor.add (change2); + node0->block_processor.add (change3); + node0->block_processor.flush (); // Start lazy bootstrap with last block in chain known auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); node1->bootstrap_initiator.bootstrap_lazy (change3->hash ()); // Check processed blocks system.deadline_set (10s); @@ -614,9 +660,12 @@ TEST (bootstrap_processor, lazy_max_pull_count) TEST (bootstrap_processor, lazy_unclear_state_link) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key; // Generating test chain @@ -649,24 +698,25 @@ TEST (bootstrap_processor, lazy_unclear_state_link) TEST (bootstrap_processor, lazy_unclear_state_link_not_existing) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key, key2; // Generating test chain auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); ASSERT_EQ (nano::process_result::progress, node1->process (*send1).code); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); auto open (std::make_shared (send1->hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub))); ASSERT_EQ (nano::process_result::progress, node1->process (*open).code); - auto send3 (std::make_shared (key.pub, open->hash (), key.pub, 0, key2.pub, key.prv, key.pub, *system.work.generate (open->hash ()))); // It is not possible to define this block send/receive status based on previous block (legacy open) - ASSERT_EQ (nano::process_result::progress, node1->process (*send3).code); + auto send2 (std::make_shared (key.pub, open->hash (), key.pub, 0, key2.pub, key.prv, key.pub, *system.work.generate (open->hash ()))); // It is not possible to define this block send/receive status based on previous block (legacy open) + ASSERT_EQ (nano::process_result::progress, node1->process (*send2).code); // Start lazy bootstrap with last block in chain known auto node2 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); node2->network.udp_channels.insert (node1->network.endpoint (), node1->network_params.protocol.protocol_version); - node2->bootstrap_initiator.bootstrap_lazy (send3->hash ()); + node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); // Check processed blocks system.deadline_set (15s); while (node2->bootstrap_initiator.in_progress ()) @@ -675,18 +725,20 @@ TEST (bootstrap_processor, lazy_unclear_state_link_not_existing) } node2->block_processor.flush (); ASSERT_TRUE (node2->ledger.block_exists (send1->hash ())); - ASSERT_TRUE (node2->ledger.block_exists (send2->hash ())); ASSERT_TRUE (node2->ledger.block_exists (open->hash ())); - ASSERT_TRUE (node2->ledger.block_exists (send3->hash ())); + ASSERT_TRUE (node2->ledger.block_exists (send2->hash ())); ASSERT_EQ (1, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in)); } TEST (bootstrap_processor, lazy_destinations) { nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (nano::node_config (nano::get_available_port (), system.logging), node_flags); + auto node1 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1, key2; // Generating test chain @@ -717,24 +769,30 @@ TEST (bootstrap_processor, lazy_destinations) TEST (bootstrap_processor, wallet_lazy_frontier) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_legacy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); - auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *system.nodes[0]->work_generate_blocking (key2.pub))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); + auto receive2 (std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, send2->hash (), key2.prv, key2.pub, *node0->work_generate_blocking (key2.pub))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.add (receive2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.add (receive2); + node0->block_processor.flush (); // Start wallet lazy bootstrap auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); auto wallet (node1->wallets.create (nano::random_wallet_id ())); ASSERT_NE (nullptr, wallet); wallet->insert_adhoc (key2.prv); @@ -755,22 +813,28 @@ TEST (bootstrap_processor, wallet_lazy_frontier) TEST (bootstrap_processor, wallet_lazy_pending) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_legacy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::genesis genesis; nano::keypair key1; nano::keypair key2; // Generating test chain - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.nodes[0]->work_generate_blocking (genesis.hash ()))); - auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (key1.pub))); - auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *system.nodes[0]->work_generate_blocking (receive1->hash ()))); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node0->work_generate_blocking (genesis.hash ()))); + auto receive1 (std::make_shared (key1.pub, 0, key1.pub, nano::Gxrb_ratio, send1->hash (), key1.prv, key1.pub, *node0->work_generate_blocking (key1.pub))); + auto send2 (std::make_shared (key1.pub, receive1->hash (), key1.pub, 0, key2.pub, key1.prv, key1.pub, *node0->work_generate_blocking (receive1->hash ()))); // Processing test chain - system.nodes[0]->block_processor.add (send1); - system.nodes[0]->block_processor.add (receive1); - system.nodes[0]->block_processor.add (send2); - system.nodes[0]->block_processor.flush (); + node0->block_processor.add (send1); + node0->block_processor.add (receive1); + node0->block_processor.add (send2); + node0->block_processor.flush (); // Start wallet lazy bootstrap auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); - node1->network.udp_channels.insert (system.nodes[0]->network.endpoint (), node1->network_params.protocol.protocol_version); + node1->network.udp_channels.insert (node0->network.endpoint (), node1->network_params.protocol.protocol_version); auto wallet (node1->wallets.create (nano::random_wallet_id ())); ASSERT_NE (nullptr, wallet); wallet->insert_adhoc (key2.prv); @@ -904,9 +968,14 @@ TEST (frontier_req, time_cutoff) TEST (bulk, genesis) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node1 = system.add_node (config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node1 = system.nodes[0]; auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node2->init_error ()); nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); @@ -928,9 +997,14 @@ TEST (bulk, genesis) TEST (bulk, offline_send) { - nano::system system (1); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node1 = system.add_node (config, node_flags); system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto node1 = system.nodes[0]; auto node2 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); ASSERT_FALSE (node2->init_error ()); node2->start (); diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index b9ffb56497..8ec09d5b5b 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -742,11 +742,10 @@ TEST (votes, check_signature) } auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); vote1->signature.bytes[0] ^= 1; - auto transaction (node1.store.tx_begin_read ()); - ASSERT_EQ (nano::vote_code::invalid, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::invalid, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); vote1->signature.bytes[0] ^= 1; - ASSERT_EQ (nano::vote_code::vote, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); - ASSERT_EQ (nano::vote_code::replay, node1.vote_processor.vote_blocking (transaction, vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::vote, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); + ASSERT_EQ (nano::vote_code::replay, node1.vote_processor.vote_blocking (vote1, std::make_shared (node1.network.udp_channels, nano::endpoint (boost::asio::ip::address_v6 (), 0), node1.network_params.protocol.protocol_version))); } TEST (votes, add_one) @@ -878,13 +877,13 @@ TEST (votes, add_old) votes1 = node1.active.roots.find (send1->qualified_root ())->election; } auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - node1.vote_processor.vote_blocking (transaction, vote1, channel); + node1.vote_processor.vote_blocking (vote1, channel); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); - node1.vote_processor.vote_blocking (transaction, vote2, channel); + node1.vote_processor.vote_blocking (vote2, channel); ASSERT_EQ (2, votes1->last_votes_size ()); ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); @@ -919,12 +918,12 @@ TEST (votes, add_old_different_account) ASSERT_EQ (1, votes2->last_votes_size ()); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - auto vote_result1 (node1.vote_processor.vote_blocking (transaction, vote1, channel)); + auto vote_result1 (node1.vote_processor.vote_blocking (vote1, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result1); ASSERT_EQ (2, votes1->last_votes.size ()); ASSERT_EQ (1, votes2->last_votes.size ()); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); - auto vote_result2 (node1.vote_processor.vote_blocking (transaction, vote2, channel)); + auto vote_result2 (node1.vote_processor.vote_blocking (vote2, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result2); ASSERT_EQ (2, votes1->last_votes.size ()); ASSERT_EQ (2, votes2->last_votes.size ()); @@ -957,12 +956,12 @@ TEST (votes, add_cooldown) } auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - node1.vote_processor.vote_blocking (transaction, vote1, channel); + node1.vote_processor.vote_blocking (vote1, channel); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send2)); - node1.vote_processor.vote_blocking (transaction, vote2, channel); + node1.vote_processor.vote_blocking (vote2, channel); ASSERT_EQ (2, votes1->last_votes.size ()); ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub)); ASSERT_EQ (send1->hash (), votes1->last_votes[nano::test_genesis_key.pub].hash); diff --git a/nano/core_test/message_parser.cpp b/nano/core_test/message_parser.cpp index a377ef75f5..f67b2fd6e7 100644 --- a/nano/core_test/message_parser.cpp +++ b/nano/core_test/message_parser.cpp @@ -25,33 +25,37 @@ class test_visitor : public nano::message_visitor } void bulk_pull (nano::bulk_pull const &) override { - ++bulk_pull_count; + ASSERT_FALSE (true); } void bulk_pull_account (nano::bulk_pull_account const &) override { - ++bulk_pull_account_count; + ASSERT_FALSE (true); } void bulk_push (nano::bulk_push const &) override { - ++bulk_push_count; + ASSERT_FALSE (true); } void frontier_req (nano::frontier_req const &) override { - ++frontier_req_count; + ASSERT_FALSE (true); } void node_id_handshake (nano::node_id_handshake const &) override { - ++node_id_handshake_count; + ASSERT_FALSE (true); } + void telemetry_req (nano::telemetry_req const &) override + { + ASSERT_FALSE (true); + } + void telemetry_ack (nano::telemetry_ack const &) override + { + ASSERT_FALSE (true); + } + uint64_t keepalive_count{ 0 }; uint64_t publish_count{ 0 }; uint64_t confirm_req_count{ 0 }; uint64_t confirm_ack_count{ 0 }; - uint64_t bulk_pull_count{ 0 }; - uint64_t bulk_pull_account_count{ 0 }; - uint64_t bulk_push_count{ 0 }; - uint64_t frontier_req_count{ 0 }; - uint64_t node_id_handshake_count{ 0 }; }; } diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 872700583e..ae909967dd 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -229,8 +229,13 @@ TEST (node, node_receive_quorum) TEST (node, auto_bootstrap) { - nano::system system (1); - auto node0 (system.nodes[0]); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); @@ -284,8 +289,13 @@ TEST (node, auto_bootstrap) TEST (node, auto_bootstrap_reverse) { - nano::system system (1); - auto node0 (system.nodes[0]); + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto node0 = system.add_node (config, node_flags); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); system.wallet (0)->insert_adhoc (key2.prv); @@ -1403,12 +1413,19 @@ TEST (node, fork_multi_flip) // This could happen if a fork wasn't resolved before the process previously shut down TEST (node, fork_bootstrap_flip) { - nano::system system0 (1); - nano::system system1 (1); - auto & node1 (*system0.nodes[0]); - auto & node2 (*system1.nodes[0]); + nano::system system0; + nano::system system1; + nano::node_config config0 (nano::get_available_port (), system0.logging); + config0.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + auto & node1 (*system0.add_node (config0, node_flags)); + nano::node_config config1 (nano::get_available_port (), system1.logging); + config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node2 (*system1.add_node (config1, node_flags)); system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (system0.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::block_hash latest (node1.latest (nano::test_genesis_key.pub)); nano::keypair key1; auto send1 (std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system0.work.generate (latest))); nano::keypair key2; @@ -1858,10 +1875,14 @@ TEST (node, DISABLED_bootstrap_no_publish) // Check that an outgoing bootstrap request can push blocks TEST (node, bootstrap_bulk_push) { - nano::system system0 (1); - nano::system system1 (1); - auto node0 (system0.nodes[0]); - auto node1 (system1.nodes[0]); + nano::system system0; + nano::system system1; + nano::node_config config0 (nano::get_available_port (), system0.logging); + config0.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node0 (system0.add_node (config0)); + nano::node_config config1 (nano::get_available_port (), system1.logging); + config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node1 (system1.add_node (config1)); nano::keypair key0; // node0 knows about send0 but node1 doesn't. nano::send_block send0 (node0->latest (nano::test_genesis_key.pub), key0.pub, 500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); @@ -2296,34 +2317,31 @@ TEST (node, send_callback) // This helps representatives continue from their last sequence number if their node is reinitialized and the old sequence number is lost TEST (node, vote_replay) { - nano::system system (2); + nano::system system (1); auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); nano::keypair key; - auto open (std::make_shared (0, 1, key.pub, key.prv, key.pub, 0)); - node1.work_generate_blocking (*open); + nano::genesis genesis; for (auto i (0); i < 11000; ++i) { - auto transaction (node2.store.tx_begin_read ()); - auto vote (node2.store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, open)); + auto transaction (node1.store.tx_begin_read ()); + auto vote (node1.store.vote_generate (transaction, nano::test_genesis_key.pub, nano::test_genesis_key.prv, genesis.open)); } + auto node2 = system.add_node (); { - auto transaction (node1.store.tx_begin_read ()); - nano::lock_guard lock (node1.store.get_cache_mutex ()); - auto vote (node1.store.vote_current (transaction, nano::test_genesis_key.pub)); + auto transaction (node2->store.tx_begin_read ()); + nano::lock_guard lock (node2->store.get_cache_mutex ()); + auto vote (node2->store.vote_current (transaction, nano::test_genesis_key.pub)); ASSERT_EQ (nullptr, vote); } - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); - ASSERT_NE (nullptr, block); + system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv); auto done (false); system.deadline_set (20s); while (!done) { auto ec = system.poll (); - auto transaction (node1.store.tx_begin_read ()); - nano::lock_guard lock (node1.store.get_cache_mutex ()); - auto vote (node1.store.vote_current (transaction, nano::test_genesis_key.pub)); + auto transaction (node2->store.tx_begin_read ()); + nano::lock_guard lock (node2->store.get_cache_mutex ()); + auto vote (node2->store.vote_current (transaction, nano::test_genesis_key.pub)); done = vote && (vote->sequence >= 10000); ASSERT_NO_ERROR (ec); } @@ -2965,7 +2983,12 @@ TEST (node, epoch_conflict_confirm) } } +// Test is unstable on github actions for windows, disable if CI detected and windows +#if (defined(_WIN32) && CI) +TEST (node, DISABLED_fork_invalid_block_signature) +#else TEST (node, fork_invalid_block_signature) +#endif { nano::system system (2); auto & node1 (*system.nodes[0]); @@ -2998,41 +3021,66 @@ TEST (node, fork_invalid_block_signature) ASSERT_EQ (node1.block (send2->hash ())->block_signature (), send2->block_signature ()); } -TEST (node, fork_invalid_block_signature_vote_by_hash) +TEST (node, fork_election_invalid_block_signature) { nano::system system (1); auto & node1 (*system.nodes[0]); - nano::keypair key2; nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (genesis.hash (), key2.pub, std::numeric_limits::max () - node1.config.receive_minimum.number () * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2_corrupt (std::make_shared (*send2)); - send2_corrupt->signature = nano::signature (123); - node1.process_active (send1); + nano::block_builder builder; + std::shared_ptr send1 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .build (); + std::shared_ptr send2 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .build (); + std::shared_ptr send3 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (nano::test_genesis_key.pub) + .work (*system.work.generate (genesis.hash ())) + .sign (nano::test_genesis_key.prv, 0) // Invalid signature + .build (); + auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); + node1.network.process_message (nano::publish (send1), channel1); system.deadline_set (5s); - while (!node1.block (send1->hash ())) - { - ASSERT_NO_ERROR (system.poll ()); - } - node1.active.publish (send2_corrupt); - ASSERT_NO_ERROR (system.poll ()); - node1.active.publish (send2); - std::vector vote_blocks; - vote_blocks.push_back (send2->hash ()); - auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - { - auto transaction (node1.store.tx_begin_read ()); - node1.vote_processor.vote_blocking (transaction, vote, std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); - } - while (node1.block (send1->hash ())) + std::shared_ptr election; + while (election == nullptr) { ASSERT_NO_ERROR (system.poll ()); + nano::lock_guard lock (node1.active.mutex); + auto existing = node1.active.blocks.find (send1->hash ()); + if (existing != node1.active.blocks.end ()) + { + election = existing->second; + } } - while (!node1.block (send2->hash ())) + nano::unique_lock lock (node1.active.mutex); + ASSERT_EQ (1, election->blocks.size ()); + lock.unlock (); + node1.network.process_message (nano::publish (send3), channel1); + node1.network.process_message (nano::publish (send2), channel1); + lock.lock (); + while (election->blocks.size () == 1) { + lock.unlock (); ASSERT_NO_ERROR (system.poll ()); + lock.lock (); } - ASSERT_EQ (node1.block (send2->hash ())->block_signature (), send2->block_signature ()); + ASSERT_EQ (election->blocks[send2->hash ()]->block_signature (), send2->block_signature ()); } TEST (node, block_processor_signatures) @@ -3189,10 +3237,7 @@ TEST (node, confirm_back) std::vector vote_blocks; vote_blocks.push_back (send2->hash ()); auto vote (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, vote_blocks)); - { - auto transaction (node.store.tx_begin_read ()); - node.vote_processor.vote_blocking (transaction, vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); - } + node.vote_processor.vote_blocking (vote, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); system.deadline_set (10s); while (!node.active.empty ()) { @@ -3383,7 +3428,12 @@ TEST (node, dont_write_lock_node) finished_promise.set_value (); } +// Test is unstable on github actions for windows, disable if CI detected +#if (defined(_WIN32) && CI) +TEST (node, DISABLED_bidirectional_tcp) +#else TEST (node, bidirectional_tcp) +#endif { nano::system system; nano::node_flags node_flags; @@ -3437,7 +3487,7 @@ TEST (node, bidirectional_tcp) } // Test block confirmation from node 2 confirmed = false; - system.deadline_set (10s); + system.deadline_set (20s); while (!confirmed) { auto transaction1 (node1->store.tx_begin_read ()); diff --git a/nano/core_test/node_telemetry.cpp b/nano/core_test/node_telemetry.cpp new file mode 100644 index 0000000000..1c06daf543 --- /dev/null +++ b/nano/core_test/node_telemetry.cpp @@ -0,0 +1,753 @@ +#include +#include +#include + +#include + +#include + +using namespace std::chrono_literals; + +namespace +{ +void wait_peer_connections (nano::system & system_a); +void compare_default_test_result_data (nano::telemetry_data & telemetry_data_a, nano::node const & node_server_a); +} + +TEST (node_telemetry, consolidate_data) +{ + nano::telemetry_data data; + data.account_count = 2; + data.block_count = 1; + data.cemented_count = 1; + data.vendor_version = 20; + data.protocol_version_number = 12; + data.peer_count = 2; + data.bandwidth_cap = 100; + data.unchecked_count = 3; + data.uptime = 6; + data.genesis_block = nano::block_hash (3); + + nano::telemetry_data data1; + data1.account_count = 5; + data1.block_count = 7; + data1.cemented_count = 4; + data1.vendor_version = 10; + data1.protocol_version_number = 11; + data1.peer_count = 5; + data1.bandwidth_cap = 0; + data1.unchecked_count = 1; + data1.uptime = 10; + data1.genesis_block = nano::block_hash (4); + + nano::telemetry_data data2; + data2.account_count = 3; + data2.block_count = 3; + data2.cemented_count = 2; + data2.vendor_version = 20; + data2.protocol_version_number = 11; + data2.peer_count = 4; + data2.bandwidth_cap = 0; + data2.unchecked_count = 2; + data2.uptime = 3; + data2.genesis_block = nano::block_hash (4); + + std::vector all_data{ data, data1, data2 }; + + auto consolidated_telemetry_data = nano::telemetry_data::consolidate (all_data); + ASSERT_EQ (consolidated_telemetry_data.account_count, 3); + ASSERT_EQ (consolidated_telemetry_data.block_count, 3); + ASSERT_EQ (consolidated_telemetry_data.cemented_count, 2); + ASSERT_EQ (consolidated_telemetry_data.vendor_version, 20); + ASSERT_EQ (consolidated_telemetry_data.protocol_version_number, 11); + ASSERT_EQ (consolidated_telemetry_data.peer_count, 3); + ASSERT_EQ (consolidated_telemetry_data.bandwidth_cap, 0); + ASSERT_EQ (consolidated_telemetry_data.unchecked_count, 2); + ASSERT_EQ (consolidated_telemetry_data.uptime, 6); + ASSERT_EQ (consolidated_telemetry_data.genesis_block, nano::block_hash (4)); + + // Modify the metrics which may be either the mode or averages to ensure all are tested. + all_data[2].bandwidth_cap = 53; + all_data[2].protocol_version_number = 13; + all_data[2].vendor_version = 13; + all_data[2].genesis_block = nano::block_hash (3); + + auto consolidated_telemetry_data1 = nano::telemetry_data::consolidate (all_data); + ASSERT_TRUE (consolidated_telemetry_data1.vendor_version == 10 || consolidated_telemetry_data1.vendor_version == 13 || consolidated_telemetry_data1.vendor_version == 20); + ASSERT_TRUE (consolidated_telemetry_data1.protocol_version_number == 11 || consolidated_telemetry_data1.protocol_version_number == 12 || consolidated_telemetry_data1.protocol_version_number == 13); + ASSERT_EQ (consolidated_telemetry_data1.bandwidth_cap, 51); + ASSERT_EQ (consolidated_telemetry_data1.genesis_block, nano::block_hash (3)); + + // Test equality operator + ASSERT_FALSE (consolidated_telemetry_data == consolidated_telemetry_data1); + ASSERT_EQ (consolidated_telemetry_data, consolidated_telemetry_data); +} + +TEST (node_telemetry, no_peers) +{ + nano::system system (1); + + std::atomic done{ false }; + system.nodes[0]->telemetry.get_metrics_random_peers_async ([&done](nano::telemetry_data_responses const & responses_a) { + ASSERT_TRUE (responses_a.data.empty ()); + ASSERT_FALSE (responses_a.all_received); + ASSERT_FALSE (responses_a.is_cached); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +namespace nano +{ +TEST (node_telemetry, basic) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + wait_peer_connections (system); + + // Request telemetry metrics + std::vector all_telemetry_data; + { + std::atomic done{ false }; + node_client->telemetry.get_metrics_random_peers_async ([&done, &all_telemetry_data](nano::telemetry_data_responses const & responses_a) { + ASSERT_FALSE (responses_a.is_cached); + ASSERT_TRUE (responses_a.all_received); + all_telemetry_data = responses_a.data; + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + // Check the metrics are correct + ASSERT_EQ (all_telemetry_data.size (), 1); + auto & telemetry_data = all_telemetry_data.front (); + 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_random_peers_async ([&done, &telemetry_data](nano::telemetry_data_responses const & responses_a) { + ASSERT_EQ (telemetry_data, responses_a.data.front ()); + ASSERT_TRUE (responses_a.is_cached); + ASSERT_TRUE (responses_a.all_received); + 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_impl::cache_cutoff); + + std::atomic done{ false }; + node_client->telemetry.get_metrics_random_peers_async ([&done, &telemetry_data](nano::telemetry_data_responses const & responses_a) { + ASSERT_FALSE (responses_a.is_cached); + ASSERT_TRUE (responses_a.all_received); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + 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; + 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 get afterwards that there are no duplicates + node_config.bandwidth_limit = 100000 + i; + system.add_node (node_config); + } + + wait_peer_connections (system); + + // Give all nodes a non-default number of blocks + nano::keypair key; + nano::genesis genesis; + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Mxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ())); + for (auto node : system.nodes) + { + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + + // This is the node which will request metrics from all other nodes + auto node_client = system.nodes.front (); + + std::atomic done{ false }; + std::vector all_telemetry_data; + node_client->telemetry.get_metrics_random_peers_async ([&done, &all_telemetry_data](nano::telemetry_data_responses const & responses_a) { + ASSERT_FALSE (responses_a.is_cached); + ASSERT_TRUE (responses_a.all_received); + all_telemetry_data = responses_a.data; + done = true; + }); + + system.deadline_set (20s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Check the metrics + nano::network_params params; + for (auto & data : all_telemetry_data) + { + ASSERT_EQ (data.unchecked_count, 0); + ASSERT_EQ (data.cemented_count, 1); + ASSERT_LE (data.peer_count, 9); + ASSERT_EQ (data.account_count, 1); + ASSERT_TRUE (data.block_count == 2); + ASSERT_EQ (data.protocol_version_number, params.protocol.telemetry_protocol_version_min); + ASSERT_GE (data.bandwidth_cap, 100000); + ASSERT_LT (data.bandwidth_cap, 100000 + system.nodes.size ()); + ASSERT_EQ (data.vendor_version, nano::get_major_node_version ()); + ASSERT_LT (data.uptime, 100); + ASSERT_EQ (data.genesis_block, genesis.hash ()); + } + + // We gave some nodes different bandwidth caps, confirm they are not all the time + auto all_bandwidth_limits_same = std::all_of (all_telemetry_data.begin () + 1, all_telemetry_data.end (), [bandwidth_cap = all_telemetry_data[0].bandwidth_cap](auto & telemetry) { + return telemetry.bandwidth_cap == bandwidth_cap; + }); + ASSERT_FALSE (all_bandwidth_limits_same); +} + +TEST (node_telemetry, receive_from_non_listening_channel) +{ + nano::system system; + auto node = system.add_node (); + nano::telemetry_ack message (nano::telemetry_data{}); + node->network.process_message (message, node->network.udp_channels.create (node->network.endpoint ())); + // We have not sent a telemetry_req message to this endpoint, so shouldn't count telemetry_ack received from it. + ASSERT_EQ (node->telemetry.telemetry_data_size (), 0); +} + +TEST (node_telemetry, over_udp) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_tcp_realtime = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + wait_peer_connections (system); + + std::atomic done{ false }; + std::vector all_telemetry_data; + node_client->telemetry.get_metrics_random_peers_async ([&done, &all_telemetry_data](nano::telemetry_data_responses const & responses_a) { + ASSERT_FALSE (responses_a.is_cached); + ASSERT_TRUE (responses_a.all_received); + all_telemetry_data = responses_a.data; + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (all_telemetry_data.size (), 1); + compare_default_test_result_data (all_telemetry_data.front (), *node_server); + + // Check channels are indeed udp + ASSERT_EQ (1, node_client->network.size ()); + auto list1 (node_client->network.list (2)); + ASSERT_EQ (node_server->network.endpoint (), list1[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list1[0]->get_type ()); + ASSERT_EQ (1, node_server->network.size ()); + auto list2 (node_server->network.list (2)); + ASSERT_EQ (node_client->network.endpoint (), list2[0]->get_endpoint ()); + ASSERT_EQ (nano::transport::transport_type::udp, list2[0]->get_type ()); +} + +TEST (node_telemetry, simultaneous_random_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::atomic done{ false }; + class Data + { + public: + std::atomic awaiting_cache{ false }; + std::atomic keep_requesting_metrics{ true }; + std::shared_ptr node; + }; + + std::array all_data{}; + for (auto i = 0; i < num_nodes; ++i) + { + all_data[i].node = system.nodes[i]; + } + + std::atomic count{ 0 }; + std::promise promise; + std::shared_future shared_future (promise.get_future ()); + + // 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, &done, &count, &promise, &shared_future]() { + 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 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) + { + ++count; + + data.node->telemetry.get_metrics_random_peers_async ([&promise, &done, &data, &all_data, &count](nano::telemetry_data_responses const & responses_a) { + if (data.awaiting_cache && !responses_a.is_cached) + { + data.keep_requesting_metrics = false; + } + if (responses_a.is_cached) + { + data.awaiting_cache = true; + } + if (--count == 0 && std::all_of (all_data.begin (), all_data.end (), [](auto const & data) { return !data.keep_requesting_metrics; })) + { + done = true; + promise.set_value (); + } + }); + } + std::this_thread::sleep_for (1ms); + } + } + + ASSERT_EQ (count, 0); + shared_future.wait (); + }); + } + + system.deadline_set (20s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + for (auto & thread : threads) + { + thread.join (); + } +} + +namespace nano +{ +TEST (node_telemetry, single_request) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + 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](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.is_cached); + ASSERT_FALSE (response_a.error); + telemetry_data = response_a.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.data); + ASSERT_TRUE (response_a.is_cached); + 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_impl::cache_cutoff); + + std::atomic done{ false }; + node_client->telemetry.get_metrics_single_peer_async (channel, [&done, &telemetry_data](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.is_cached); + 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) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + std::atomic done{ false }; + node_client->telemetry.get_metrics_single_peer_async (nullptr, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_TRUE (response_a.error); + done = true; + }); + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (node_telemetry, simultaneous_single_and_random_requests) +{ + const auto num_nodes = 4; + nano::system system (num_nodes); + + wait_peer_connections (system); + + std::vector threads; + const auto num_threads = 4; + + class data + { + public: + std::atomic awaiting_cache{ false }; + std::atomic keep_requesting_metrics{ true }; + std::shared_ptr node; + }; + + std::array node_data_single{}; + std::array node_data_random{}; + for (auto i = 0; i < num_nodes; ++i) + { + node_data_single[i].node = system.nodes[i]; + node_data_random[i].node = system.nodes[i]; + } + + class shared_data + { + public: + std::atomic done{ false }; + std::atomic count{ 0 }; + std::promise promise; + std::shared_future shared_future{ promise.get_future () }; + }; + + shared_data shared_data_single; + shared_data shared_data_random; + + // 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_random, &shared_data_single, &shared_data_random]() { + auto func = [](auto & all_node_data_a, shared_data & shared_data_a) { + while (std::any_of (all_node_data_a.cbegin (), all_node_data_a.cend (), [](auto const & data) { return data.keep_requesting_metrics.load (); })) + { + 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) + { + ++shared_data_a.count; + + data.node->telemetry.get_metrics_random_peers_async ([& shared_data = shared_data_a, &data, &all_node_data = all_node_data_a](nano::telemetry_data_responses const & responses_a) { + if (data.awaiting_cache && !responses_a.is_cached) + { + data.keep_requesting_metrics = false; + } + if (responses_a.is_cached) + { + data.awaiting_cache = true; + } + if (--shared_data.count == 0 && std::all_of (all_node_data.begin (), all_node_data.end (), [](auto const & data) { return !data.keep_requesting_metrics; })) + { + shared_data.done = true; + shared_data.promise.set_value (); + } + }); + } + std::this_thread::sleep_for (1ms); + } + } + + ASSERT_EQ (shared_data_a.count, 0); + shared_data_a.shared_future.wait (); + }; + + func (node_data_single, shared_data_single); + func (node_data_random, shared_data_random); + }); + } + + system.deadline_set (20s); + while (!shared_data_single.done || !shared_data_random.done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + for (auto & thread : threads) + { + thread.join (); + } +} + +TEST (node_telemetry, blocking_single_and_random) +{ + nano::system system (2); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + wait_peer_connections (system); + + // Request telemetry metrics + std::atomic done{ false }; + std::function call_system_poll; + std::promise promise; + call_system_poll = [&call_system_poll, &worker = node_client->worker, &done, &system, &promise]() { + if (!done) + { + ASSERT_NO_ERROR (system.poll ()); + worker.push_task (call_system_poll); + } + else + { + promise.set_value (); + } + }; + + // Keep pushing system.polls in another thread (worker), because we will be blocking this thread and unable to do so. + system.deadline_set (10s); + node_client->worker.push_task (call_system_poll); + + // Blocking version of get_random_metrics_async + auto telemetry_data_responses = node_client->telemetry.get_metrics_random_peers (); + ASSERT_FALSE (telemetry_data_responses.is_cached); + ASSERT_TRUE (telemetry_data_responses.all_received); + compare_default_test_result_data (telemetry_data_responses.data.front (), *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 ())); + ASSERT_FALSE (telemetry_data_response.is_cached); + ASSERT_FALSE (telemetry_data_response.error); + compare_default_test_result_data (telemetry_data_response.data, *node_server); + + 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 }; + node_client->telemetry.get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + ASSERT_FALSE (response_a.is_cached); + done = true; + }); + + ASSERT_EQ (1, node_client->telemetry.single_requests.size ()); + auto last_updated = node_client->telemetry.single_requests.begin ()->second.last_updated; + + system.deadline_set (10s); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + + done = false; + // Make another request to keep + system.deadline_set (10s); + node_client->telemetry.get_metrics_single_peer_async (channel, [&done](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + ASSERT_TRUE (response_a.is_cached); + done = true; + }); + + ASSERT_LT (last_updated, node_client->telemetry.single_requests.begin ()->second.last_updated); + + 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](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + ASSERT_FALSE (response_a.is_cached); + 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](nano::telemetry_data_response const & response_a) { + ASSERT_FALSE (response_a.error); + ASSERT_TRUE (response_a.is_cached); + 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); + + auto node_client = system.nodes.front (); + auto node_server = system.nodes.back (); + + wait_peer_connections (system); + + // Try and request metrics from a node which is turned off but a channel is not closed yet + auto channel = node_client->network.find_channel (node_server->network.endpoint ()); + node_server->stop (); + ASSERT_TRUE (channel); + + std::atomic done{ false }; + node_client->telemetry.get_metrics_random_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) { + ASSERT_TRUE (response_a.error); + 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); + }); + } +} + +void compare_default_test_result_data (nano::telemetry_data & telemetry_data_a, nano::node const & node_server_a) +{ + ASSERT_EQ (telemetry_data_a.block_count, 1); + ASSERT_EQ (telemetry_data_a.cemented_count, 1); + ASSERT_EQ (telemetry_data_a.bandwidth_cap, node_server_a.config.bandwidth_limit); + ASSERT_EQ (telemetry_data_a.peer_count, 1); + ASSERT_EQ (telemetry_data_a.protocol_version_number, node_server_a.network_params.protocol.telemetry_protocol_version_min); + ASSERT_EQ (telemetry_data_a.unchecked_count, 0); + ASSERT_EQ (telemetry_data_a.account_count, 1); + ASSERT_EQ (telemetry_data_a.vendor_version, nano::get_major_node_version ()); + ASSERT_LT (telemetry_data_a.uptime, 100); + ASSERT_EQ (telemetry_data_a.genesis_block, nano::genesis ().hash ()); +} +} diff --git a/nano/core_test/request_aggregator.cpp b/nano/core_test/request_aggregator.cpp index 9192f08060..819dd04354 100644 --- a/nano/core_test/request_aggregator.cpp +++ b/nano/core_test/request_aggregator.cpp @@ -26,15 +26,15 @@ TEST (request_aggregator, one) { ASSERT_NO_ERROR (system.poll ()); } - // Not yet in the ledger, should be ignored - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); + // Not yet in the ledger + ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); node.aggregator.add (channel, request); ASSERT_EQ (1, node.aggregator.size ()); // In the ledger but no vote generated yet // Generated votes are created after the pool is removed from the aggregator, so a simple check on empty () is not enough system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) == 0) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0) { ASSERT_NO_ERROR (system.poll ()); } @@ -47,10 +47,11 @@ TEST (request_aggregator, one) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (3, node.stats.count (nano::stat::type::requests, nano::stat::detail::all)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached)); + ASSERT_EQ (3, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (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)); } @@ -78,15 +79,18 @@ TEST (request_aggregator, one_update) // In the ledger but no vote generated yet // Generated votes are created after the pool is removed from the aggregator, so a simple check on empty () is not enough system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) == 0) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0) { ASSERT_NO_ERROR (system.poll ()); } ASSERT_TRUE (node.aggregator.empty ()); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::all)); - ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); - ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + 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)); } @@ -112,12 +116,11 @@ TEST (request_aggregator, two) // One vote should be generated for both blocks // Generated votes are created after the pool is removed from the aggregator, so a simple check on empty () is not enough system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) == 0) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0) { ASSERT_NO_ERROR (system.poll ()); } ASSERT_TRUE (node.aggregator.empty ()); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); // The same request should now send the cached vote node.aggregator.add (channel, request); ASSERT_EQ (1, node.aggregator.size ()); @@ -126,10 +129,13 @@ TEST (request_aggregator, two) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::all)); - ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); - ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + 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)); // Make sure the cached vote is for both hashes auto vote1 (node.votes_cache.find (send1->hash ())); @@ -144,9 +150,11 @@ TEST (request_aggregator, two_endpoints) nano::system system; nano::node_config node_config (nano::get_available_port (), system.logging); node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node1 (*system.add_node (node_config)); + nano::node_flags node_flags; + node_flags.disable_rep_crawler = true; + auto & node1 (*system.add_node (node_config, node_flags)); node_config.peering_port = nano::get_available_port (); - auto & node2 (*system.add_node (node_config)); + auto & node2 (*system.add_node (node_config, node_flags)); nano::genesis genesis; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node1.work_generate_blocking (genesis.hash ()))); @@ -166,10 +174,13 @@ TEST (request_aggregator, two_endpoints) { ASSERT_NO_ERROR (system.poll ()); } - ASSERT_EQ (2, node1.stats.count (nano::stat::type::requests, nano::stat::detail::all)); - ASSERT_EQ (0, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); - ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); - ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached)); + ASSERT_EQ (2, node1.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_unknown)); + ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); + ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes)); + ASSERT_EQ (1, node1.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached_votes)); } TEST (request_aggregator, split) @@ -208,25 +219,19 @@ TEST (request_aggregator, split) // In the ledger but no vote generated yet // Generated votes are created after the pool is removed from the aggregator, so a simple check on empty () is not enough system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) < 2) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) < 2) { ASSERT_NO_ERROR (system.poll ()); } ASSERT_TRUE (node.aggregator.empty ()); // Two votes were sent, the first one for 12 hashes and the second one for 1 hash - ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::all)); - ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_ignored)); - ASSERT_EQ (2, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated)); - ASSERT_EQ (0, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_cached)); + ASSERT_EQ (1, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); + ASSERT_EQ (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); + ASSERT_EQ (13, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); + 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)); - auto transaction (node.store.tx_begin_read ()); - auto pre_last_hash (node.store.block_get (transaction, previous)->previous ()); - auto vote1 (node.votes_cache.find (pre_last_hash)); - ASSERT_EQ (1, vote1.size ()); - ASSERT_EQ (max_vbh, vote1[0]->blocks.size ()); - auto vote2 (node.votes_cache.find (previous)); - ASSERT_EQ (1, vote2.size ()); - ASSERT_EQ (1, vote2[0]->blocks.size ()); } TEST (request_aggregator, channel_lifetime) @@ -248,7 +253,7 @@ TEST (request_aggregator, channel_lifetime) } ASSERT_EQ (1, node.aggregator.size ()); system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) == 0) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0) { ASSERT_NO_ERROR (system.poll ()); } @@ -280,8 +285,56 @@ TEST (request_aggregator, channel_update) // channel1 is not being held anymore ASSERT_EQ (nullptr, channel1_w.lock ()); system.deadline_set (3s); - while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated) == 0) + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (request_aggregator, channel_max_queue) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + node_config.max_queued_requests = 1; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped) < 1) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (request_aggregator, unique) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node (*system.add_node (node_config)); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node.work_generate_blocking (genesis.hash ()))); + ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code); + std::vector> request; + request.emplace_back (send1->hash (), send1->root ()); + auto channel (node.network.udp_channels.create (node.network.endpoint ())); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + node.aggregator.add (channel, request); + system.deadline_set (3s); + while (node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes) < 1) { ASSERT_NO_ERROR (system.poll ()); } + ASSERT_EQ (1, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes)); } diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index e403d0b454..eba2640449 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -180,6 +180,7 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.vote_minimum, defaults.node.vote_minimum); ASSERT_EQ (conf.node.work_peers, defaults.node.work_peers); ASSERT_EQ (conf.node.work_threads, defaults.node.work_threads); + ASSERT_EQ (conf.node.max_queued_requests, defaults.node.max_queued_requests); ASSERT_EQ (conf.node.logging.bulk_pull_logging_value, defaults.node.logging.bulk_pull_logging_value); ASSERT_EQ (conf.node.logging.flush, defaults.node.logging.flush); @@ -198,6 +199,7 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.logging.network_publish_logging_value, defaults.node.logging.network_publish_logging_value); ASSERT_EQ (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); ASSERT_EQ (conf.node.logging.node_lifetime_tracing_value, defaults.node.logging.node_lifetime_tracing_value); + ASSERT_EQ (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); ASSERT_EQ (conf.node.logging.rotation_size, defaults.node.logging.rotation_size); ASSERT_EQ (conf.node.logging.single_line_record_value, defaults.node.logging.single_line_record_value); ASSERT_EQ (conf.node.logging.timing_logging_value, defaults.node.logging.timing_logging_value); @@ -412,6 +414,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) work_threads = 999 work_watcher_period = 999 max_work_generate_multiplier = 1.0 + max_queued_requests = 999 frontiers_confirmation = "always" [node.diagnostics.txn_tracking] enable = true @@ -451,6 +454,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) network_keepalive = true network_message = true network_node_id_handshake = true + network_telemetry_logging = true network_packet = true network_publish = true network_timeout = true @@ -566,6 +570,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.vote_minimum, defaults.node.vote_minimum); ASSERT_NE (conf.node.work_peers, defaults.node.work_peers); ASSERT_NE (conf.node.work_threads, defaults.node.work_threads); + ASSERT_NE (conf.node.max_queued_requests, defaults.node.max_queued_requests); ASSERT_NE (conf.node.logging.bulk_pull_logging_value, defaults.node.logging.bulk_pull_logging_value); ASSERT_NE (conf.node.logging.flush, defaults.node.logging.flush); @@ -580,6 +585,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.logging.network_keepalive_logging_value, defaults.node.logging.network_keepalive_logging_value); ASSERT_NE (conf.node.logging.network_message_logging_value, defaults.node.logging.network_message_logging_value); ASSERT_NE (conf.node.logging.network_node_id_handshake_logging_value, defaults.node.logging.network_node_id_handshake_logging_value); + ASSERT_NE (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); ASSERT_NE (conf.node.logging.network_packet_logging_value, defaults.node.logging.network_packet_logging_value); ASSERT_NE (conf.node.logging.network_publish_logging_value, defaults.node.logging.network_publish_logging_value); ASSERT_NE (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index abe3c5ff27..0630af8b6a 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -832,7 +832,7 @@ TEST (websocket, bootstrap) } ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::bootstrap)); - // Start bootsrap attempt + // Start bootstrap attempt node1->bootstrap_initiator.bootstrap (true, "123abc"); ASSERT_NE (nullptr, node1->bootstrap_initiator.current_attempt ()); @@ -865,7 +865,7 @@ TEST (websocket, bootstrap) } } -TEST (websocket, bootstrap_excited) +TEST (websocket, bootstrap_exited) { nano::system system; nano::node_config config (nano::get_available_port (), system.logging); @@ -877,17 +877,13 @@ TEST (websocket, bootstrap_excited) // Start bootstrap, exit after subscription std::atomic bootstrap_started{ false }; - std::atomic subscribed{ false }; - std::thread bootstrap_thread ([&system, node1, &bootstrap_started, &subscribed]() { + nano::util::counted_completion subscribed_completion (1); + std::thread bootstrap_thread ([node1, &bootstrap_started, &subscribed_completion]() { node1->bootstrap_initiator.bootstrap (true, "123abc"); auto attempt (node1->bootstrap_initiator.current_attempt ()); ASSERT_NE (nullptr, attempt); bootstrap_started = true; - system.deadline_set (5s); - while (!subscribed) - { - ASSERT_NO_ERROR (system.poll ()); - } + ASSERT_FALSE (subscribed_completion.await_count_for (5s)); }); // Wait for bootstrap start @@ -914,7 +910,7 @@ TEST (websocket, bootstrap_excited) ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::bootstrap)); // Wait for the bootstrap notification - subscribed = true; + subscribed_completion.increment (); bootstrap_thread.join (); system.deadline_set (5s); while (client_future.wait_for (std::chrono::seconds (0)) != std::future_status::ready) @@ -958,4 +954,4 @@ TEST (websocket, ws_keepalive) ASSERT_NO_ERROR (system.poll ()); } subscription_thread.join (); -} \ No newline at end of file +} diff --git a/nano/fuzzer_test/CMakeLists.txt b/nano/fuzzer_test/CMakeLists.txt new file mode 100644 index 0000000000..0e02876e05 --- /dev/null +++ b/nano/fuzzer_test/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(fuzz_buffer fuzz_buffer.cpp) +target_compile_options(fuzz_buffer PUBLIC -fsanitize=fuzzer) +target_link_libraries(fuzz_buffer PRIVATE -fsanitize=fuzzer node) diff --git a/nano/fuzzer_test/fuzz_buffer.cpp b/nano/fuzzer_test/fuzz_buffer.cpp new file mode 100644 index 0000000000..0c139e3e4e --- /dev/null +++ b/nano/fuzzer_test/fuzz_buffer.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nano +{ +void force_nano_test_network (); +} +namespace +{ +std::shared_ptr system0; +std::shared_ptr node0; + +class fuzz_visitor : public nano::message_visitor +{ +public: + virtual void keepalive (nano::keepalive const &) override + { + } + virtual void publish (nano::publish const &) override + { + } + virtual void confirm_req (nano::confirm_req const &) override + { + } + virtual void confirm_ack (nano::confirm_ack const &) override + { + } + virtual void bulk_pull (nano::bulk_pull const &) override + { + } + virtual void bulk_pull_account (nano::bulk_pull_account const &) override + { + } + virtual void bulk_push (nano::bulk_push const &) override + { + } + virtual void frontier_req (nano::frontier_req const &) override + { + } + virtual void node_id_handshake (nano::node_id_handshake const &) override + { + } +}; +} + +/** Fuzz live message parsing. This covers parsing and block/vote uniquing. */ +void fuzz_message_parser (const uint8_t * Data, size_t Size) +{ + static bool initialized = false; + if (!initialized) + { + nano::force_nano_test_network (); + initialized = true; + system0 = std::make_shared (1); + node0 = system0->nodes[0]; + } + + fuzz_visitor visitor; + nano::message_parser parser (node0->block_uniquer, node0->vote_uniquer, visitor, node0->work); + parser.deserialize_buffer (Data, Size); +} + +/** Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput (const uint8_t * Data, size_t Size) +{ + fuzz_message_parser (Data, Size); + return 0; +} diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index faef2b19a7..6d25e5fb69 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -78,6 +78,8 @@ if (NANO_STACKTRACE_BACKTRACE) endif () target_compile_definitions(nano_lib + PRIVATE + -DMAJOR_VERSION_STRING=${CPACK_PACKAGE_VERSION_MAJOR} PUBLIC -DACTIVE_NETWORK=${ACTIVE_NETWORK} ) diff --git a/nano/lib/blocks.cpp b/nano/lib/blocks.cpp index fb07a09521..fe4ccbf6c4 100644 --- a/nano/lib/blocks.cpp +++ b/nano/lib/blocks.cpp @@ -1313,7 +1313,9 @@ std::shared_ptr nano::deserialize_block (nano::stream & stream_a, n break; } default: +#ifndef NANO_FUZZER_TEST assert (false); +#endif break; } if (uniquer_a != nullptr) diff --git a/nano/lib/config.cpp b/nano/lib/config.cpp index bd6dc1f8bd..7fcc7c448b 100644 --- a/nano/lib/config.cpp +++ b/nano/lib/config.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -8,6 +9,11 @@ namespace nano { const char * network_constants::active_network_err_msg = "Invalid network. Valid values are live, beta and test."; +uint8_t get_major_node_version () +{ + return boost::numeric_cast (boost::lexical_cast (NANO_MAJOR_VERSION_STRING)); +} + void force_nano_test_network () { nano::network_constants::set_active_network (nano::nano_networks::nano_test_network); diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index 59c7dedd00..9ef0e5e96a 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -20,6 +20,7 @@ namespace filesystem * Returns build version information */ const char * const NANO_VERSION_STRING = xstr (TAG_VERSION_STRING); +const char * const NANO_MAJOR_VERSION_STRING = xstr (MAJOR_VERSION_STRING); const char * const BUILD_INFO = xstr (GIT_COMMIT_HASH BOOST_COMPILER) " \"BOOST " xstr (BOOST_VERSION) "\" BUILT " xstr (__DATE__); @@ -36,6 +37,8 @@ const bool is_sanitizer_build = false; namespace nano { +uint8_t get_major_node_version (); + /** * Network variants with different genesis blocks and network parameters * @warning Enum values are used in integral comparisons; do not change. diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 6f0296bb24..2ff739fbf7 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -200,6 +200,10 @@ std::string nano::error_rpc_messages::message (int ev) const return "Account has non-zero balance"; case nano::error_rpc::payment_unable_create_account: return "Unable to create transaction account"; + case nano::error_rpc::peer_not_found: + return "Peer not found"; + case nano::error_rpc::requires_port_and_address: + return "Both port and address required"; case nano::error_rpc::rpc_control_disabled: return "RPC control is disabled"; case nano::error_rpc::sign_hash_disabled: diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index be710b2e0f..32535c02d6 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -112,6 +112,8 @@ enum class error_rpc invalid_timestamp, payment_account_balance, payment_unable_create_account, + peer_not_found, + requires_port_and_address, rpc_control_disabled, sign_hash_disabled, source_not_found diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index 64fe6d7784..32953fcbfc 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -438,6 +438,9 @@ std::string nano::stat::type_to_string (uint32_t key) case nano::stat::type::drop: res = "drop"; break; + case nano::stat::type::aggregator: + res = "aggregator"; + break; case nano::stat::type::requests: res = "requests"; break; @@ -508,6 +511,15 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::fork: res = "fork"; break; + case nano::stat::detail::old: + res = "old"; + break; + case nano::stat::detail::gap_previous: + res = "gap_previous"; + break; + case nano::stat::detail::gap_source: + res = "gap_source"; + break; case nano::stat::detail::frontier_confirmation_failed: res = "frontier_confirmation_failed"; break; @@ -556,6 +568,12 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::send: res = "send"; break; + case nano::stat::detail::telemetry_req: + res = "telemetry_req"; + break; + case nano::stat::detail::telemetry_ack: + res = "telemetry_ack"; + break; case nano::stat::detail::state_block: res = "state_block"; break; @@ -634,6 +652,12 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::invalid_node_id_handshake_message: res = "invalid_node_id_handshake_message"; break; + case nano::stat::detail::invalid_telemetry_req_message: + res = "invalid_telemetry_req_message"; + break; + case nano::stat::detail::invalid_telemetry_ack_message: + res = "invalid_telemetry_ack_message"; + break; case nano::stat::detail::outdated_version: res = "outdated_version"; break; @@ -643,14 +667,26 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::blocks_confirmed: res = "blocks_confirmed"; break; - case nano::stat::detail::requests_cached: - res = "requests_votes_cached"; + case nano::stat::detail::aggregator_accepted: + res = "aggregator_accepted"; + break; + case nano::stat::detail::aggregator_dropped: + res = "aggregator_dropped"; + break; + case nano::stat::detail::requests_cached_hashes: + res = "requests_cached_hashes"; + break; + case nano::stat::detail::requests_generated_hashes: + res = "requests_generated_hashes"; + break; + case nano::stat::detail::requests_cached_votes: + res = "requests_cached_votes"; break; - case nano::stat::detail::requests_generated: - res = "requests_votes_generated"; + case nano::stat::detail::requests_generated_votes: + res = "requests_generated_votes"; break; - case nano::stat::detail::requests_ignored: - res = "requests_votes_ignored"; + case nano::stat::detail::requests_unknown: + res = "requests_unknown"; break; } return res; diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index d5f7c5ffb6..86836b4cf4 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -198,6 +198,7 @@ class stat final observer, confirmation_height, drop, + aggregator, requests }; @@ -225,6 +226,9 @@ class stat final state_block, epoch_block, fork, + old, + gap_previous, + gap_source, // message specific keepalive, @@ -233,6 +237,8 @@ class stat final confirm_req, confirm_ack, node_id_handshake, + telemetry_req, + telemetry_ack, // bootstrap, callback initiate, @@ -278,6 +284,8 @@ class stat final invalid_confirm_req_message, invalid_confirm_ack_message, invalid_node_id_handshake_message, + invalid_telemetry_req_message, + invalid_telemetry_ack_message, outdated_version, // tcp @@ -295,10 +303,16 @@ class stat final blocks_confirmed, invalid_block, + // [request] aggregator + aggregator_accepted, + aggregator_dropped, + // requests - requests_cached, - requests_generated, - requests_ignored + requests_cached_hashes, + requests_generated_hashes, + requests_cached_votes, + requests_generated_votes, + requests_unknown }; /** Direction of the stat. If the direction is irrelevant, use in */ diff --git a/nano/lib/work.cpp b/nano/lib/work.cpp index 700fe461ae..f7b55bd7fd 100644 --- a/nano/lib/work.cpp +++ b/nano/lib/work.cpp @@ -22,6 +22,7 @@ bool nano::work_validate (nano::block const & block_a, uint64_t * difficulty_a) return work_validate (block_a.root (), block_a.block_work (), difficulty_a); } +#ifndef NANO_FUZZER_TEST uint64_t nano::work_value (nano::root const & root_a, uint64_t work_a) { uint64_t result; @@ -32,6 +33,18 @@ uint64_t nano::work_value (nano::root const & root_a, uint64_t work_a) blake2b_final (&hash, reinterpret_cast (&result), sizeof (result)); return result; } +#else +uint64_t nano::work_value (nano::root const & root_a, uint64_t work_a) +{ + static nano::network_constants network_constants; + if (!network_constants.is_test_network ()) + { + assert (false); + std::exit (1); + } + return network_constants.publish_threshold + 1; +} +#endif nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function (nano::root const &, uint64_t, std::atomic &)> opencl_a) : ticket (0), diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 776adc80cf..4c1e035a75 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -76,6 +76,8 @@ add_library (node logging.cpp network.hpp network.cpp + telemetry.hpp + telemetry.cpp nodeconfig.hpp nodeconfig.cpp node_observers.hpp diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 2b9db6eac8..fa08451fc3 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -420,6 +420,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } node.gap_cache.add (hash); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_previous); break; } case nano::process_result::gap_source: @@ -443,6 +444,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } node.gap_cache.add (hash); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); break; } case nano::process_result::old: @@ -456,6 +458,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction queue_unchecked (transaction_a, hash); } node.active.update_difficulty (info_a.block, transaction_a); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::old); break; } case nano::process_result::bad_signature: @@ -486,7 +489,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction case nano::process_result::fork: { node.process_fork (transaction_a, info_a.block); - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::fork, nano::stat::dir::in); + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::fork); if (node.config.logging.ledger_logging ()) { node.logger.try_log (boost::str (boost::format ("Fork for: %1% root: %2%") % hash.to_string () % info_a.block->root ().to_string ())); diff --git a/nano/node/bootstrap/bootstrap.cpp b/nano/node/bootstrap/bootstrap.cpp index 1e1d39c3e5..f12f9d6bbf 100644 --- a/nano/node/bootstrap/bootstrap.cpp +++ b/nano/node/bootstrap/bootstrap.cpp @@ -884,6 +884,7 @@ void nano::bootstrap_attempt::lazy_pull_flush () 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) @@ -896,6 +897,12 @@ void nano::bootstrap_attempt::lazy_pull_flush () ++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 (); + } } } } @@ -907,6 +914,7 @@ bool nano::bootstrap_attempt::lazy_finished () 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;) @@ -921,6 +929,12 @@ bool nano::bootstrap_attempt::lazy_finished () 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 ()) @@ -1201,6 +1215,7 @@ void nano::bootstrap_attempt::lazy_block_state_backlog_check (std::shared_ptrstore.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;) @@ -1223,6 +1238,12 @@ void nano::bootstrap_attempt::lazy_backlog_cleanup () 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 (); + } } } diff --git a/nano/node/bootstrap/bootstrap.hpp b/nano/node/bootstrap/bootstrap.hpp index 0ab8319445..e43779281b 100644 --- a/nano/node/bootstrap/bootstrap.hpp +++ b/nano/node/bootstrap/bootstrap.hpp @@ -164,6 +164,8 @@ class bootstrap_attempt final : public std::enable_shared_from_this 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 { diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.cpp b/nano/node/bootstrap/bootstrap_bulk_pull.cpp index af887bf7cb..58e60bf064 100644 --- a/nano/node/bootstrap/bootstrap_bulk_pull.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_pull.cpp @@ -366,8 +366,7 @@ void nano::bulk_pull_account_client::receive_pending () { if (!pending.is_zero ()) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); - if (!this_l->connection->node->store.block_exists (transaction, pending)) + if (!this_l->connection->node->ledger.block_exists (pending)) { this_l->connection->attempt->lazy_start (pending); } @@ -552,8 +551,7 @@ std::shared_ptr nano::bulk_pull_server::get_next () if (send_current) { - auto transaction (connection->node->store.tx_begin_read ()); - result = connection->node->store.block_get (transaction, current); + result = connection->node->block (current); if (result != nullptr && set_current_to_end == false) { auto previous (result->previous ()); diff --git a/nano/node/bootstrap/bootstrap_bulk_push.cpp b/nano/node/bootstrap/bootstrap_bulk_push.cpp index 95eb49b6e9..a739eae036 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.cpp @@ -20,10 +20,9 @@ void nano::bulk_push_client::start () auto this_l (shared_from_this ()); connection->channel->send ( message, [this_l](boost::system::error_code const & ec, size_t size_a) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); if (!ec) { - this_l->push (transaction); + this_l->push (); } else { @@ -36,7 +35,7 @@ void nano::bulk_push_client::start () false); // is bootstrap traffic is_droppable false } -void nano::bulk_push_client::push (nano::transaction const & transaction_a) +void nano::bulk_push_client::push () { std::shared_ptr block; bool finished (false); @@ -57,7 +56,7 @@ void nano::bulk_push_client::push (nano::transaction const & transaction_a) } if (!finished) { - block = connection->node->store.block_get (transaction_a, current_target.first); + block = connection->node->block (current_target.first); if (block == nullptr) { current_target.first = nano::block_hash (0); @@ -108,8 +107,7 @@ void nano::bulk_push_client::push_block (nano::block const & block_a) connection->channel->send_buffer (nano::shared_const_buffer (std::move (buffer)), nano::stat::detail::all, [this_l](boost::system::error_code const & ec, size_t size_a) { if (!ec) { - auto transaction (this_l->connection->node->store.tx_begin_read ()); - this_l->push (transaction); + this_l->push (); } else { diff --git a/nano/node/bootstrap/bootstrap_bulk_push.hpp b/nano/node/bootstrap/bootstrap_bulk_push.hpp index 114a0235d3..b84eaac61a 100644 --- a/nano/node/bootstrap/bootstrap_bulk_push.hpp +++ b/nano/node/bootstrap/bootstrap_bulk_push.hpp @@ -6,7 +6,6 @@ namespace nano { -class transaction; class bootstrap_client; class bulk_push_client final : public std::enable_shared_from_this { @@ -14,7 +13,7 @@ class bulk_push_client final : public std::enable_shared_from_this const &); ~bulk_push_client (); void start (); - void push (nano::transaction const &); + void push (); void push_block (nano::block const &); void send_finished (); std::shared_ptr connection; diff --git a/nano/node/bootstrap/bootstrap_frontier.cpp b/nano/node/bootstrap/bootstrap_frontier.cpp index ad51fed1d2..70918e87fe 100644 --- a/nano/node/bootstrap/bootstrap_frontier.cpp +++ b/nano/node/bootstrap/bootstrap_frontier.cpp @@ -42,8 +42,7 @@ current (0), count (0), bulk_push_cost (0) { - auto transaction (connection->node->store.tx_begin_read ()); - next (transaction); + next (); } nano::frontier_req_client::~frontier_req_client () @@ -123,14 +122,13 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con { connection->node->logger.always_log (boost::str (boost::format ("Received %1% frontiers from %2%") % std::to_string (count) % connection->channel->to_string ())); } - auto transaction (connection->node->store.tx_begin_read ()); if (!account.is_zero ()) { while (!current.is_zero () && current < account) { // We know about an account they don't. unsynced (frontier, 0); - next (transaction); + next (); } if (!current.is_zero ()) { @@ -142,7 +140,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con } else { - if (connection->node->store.block_exists (transaction, latest)) + if (connection->node->ledger.block_exists (latest)) { // We know about a block they don't. unsynced (frontier, latest); @@ -155,7 +153,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con bulk_push_cost += 5; } } - next (transaction); + next (); } else { @@ -175,7 +173,7 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con { // We know about an account they don't. unsynced (frontier, 0); - next (transaction); + next (); } if (connection->node->config.logging.bulk_pull_logging ()) { @@ -202,13 +200,14 @@ void nano::frontier_req_client::received_frontier (boost::system::error_code con } } -void nano::frontier_req_client::next (nano::transaction const & transaction_a) +void nano::frontier_req_client::next () { // Filling accounts deque to prevent often read transactions if (accounts.empty ()) { size_t max_size (128); - for (auto i (connection->node->store.latest_begin (transaction_a, current.number () + 1)), n (connection->node->store.latest_end ()); i != n && accounts.size () != max_size; ++i) + auto transaction (connection->node->store.tx_begin_read ()); + for (auto i (connection->node->store.latest_begin (transaction, current.number () + 1)), n (connection->node->store.latest_end ()); i != n && accounts.size () != max_size; ++i) { nano::account_info const & info (i->second); nano::account const & account (i->first); diff --git a/nano/node/bootstrap/bootstrap_frontier.hpp b/nano/node/bootstrap/bootstrap_frontier.hpp index b6b788d0cb..ad3c4962b5 100644 --- a/nano/node/bootstrap/bootstrap_frontier.hpp +++ b/nano/node/bootstrap/bootstrap_frontier.hpp @@ -7,7 +7,6 @@ namespace nano { -class transaction; class bootstrap_client; class frontier_req_client final : public std::enable_shared_from_this { @@ -18,7 +17,7 @@ class frontier_req_client final : public std::enable_shared_from_this connection; nano::account current; nano::block_hash frontier; diff --git a/nano/node/bootstrap/bootstrap_server.cpp b/nano/node/bootstrap/bootstrap_server.cpp index aa9e3281bd..dd005ee7f1 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -237,6 +237,23 @@ void nano::bootstrap_server::receive_header_action (boost::system::error_code co }); break; } + case nano::message_type::telemetry_req: + { + node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::telemetry_req, nano::stat::dir::in); + if (is_realtime_connection ()) + { + add_request (std::make_unique (header)); + } + receive (); + break; + } + case nano::message_type::telemetry_ack: + { + socket->async_read (receive_buffer, header.payload_length_bytes (), [this_l, header](boost::system::error_code const & ec, size_t size_a) { + this_l->receive_telemetry_ack_action (ec, size_a, header); + }); + break; + } default: { if (node->config.logging.network_logging ()) @@ -356,6 +373,31 @@ void nano::bootstrap_server::receive_keepalive_action (boost::system::error_code } } +void nano::bootstrap_server::receive_telemetry_ack_action (boost::system::error_code const & ec, size_t size_a, nano::message_header const & header_a) +{ + if (!ec) + { + auto error (false); + nano::bufferstream stream (receive_buffer->data (), size_a); + auto request (std::make_unique (error, stream, header_a)); + if (!error) + { + if (is_realtime_connection ()) + { + add_request (std::unique_ptr (request.release ())); + } + receive (); + } + } + else + { + if (node->config.logging.network_telemetry_logging ()) + { + node->logger.try_log (boost::str (boost::format ("Error receiving telemetry ack: %1%") % ec.message ())); + } + } +} + void nano::bootstrap_server::receive_publish_action (boost::system::error_code const & ec, size_t size_a, nano::message_header const & header_a) { if (!ec) @@ -523,7 +565,6 @@ class request_response_visitor : public nano::message_visitor connection (connection_a) { } - virtual ~request_response_visitor () = default; void keepalive (nano::keepalive const & message_a) override { connection->finish_request_async (); @@ -576,6 +617,22 @@ class request_response_visitor : public nano::message_visitor auto response (std::make_shared (connection, std::unique_ptr (static_cast (connection->requests.front ().release ())))); response->send_next (); } + void telemetry_req (nano::telemetry_req const & message_a) override + { + connection->finish_request_async (); + auto connection_l (connection->shared_from_this ()); + connection->node->background ([connection_l, message_a]() { + connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, connection_l->remote_node_id, connection_l->socket, connection_l->type); + }); + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + connection->finish_request_async (); + auto connection_l (connection->shared_from_this ()); + connection->node->background ([connection_l, message_a]() { + connection_l->node->network.tcp_channels.process_message (message_a, connection_l->remote_endpoint, connection_l->remote_node_id, connection_l->socket, connection_l->type); + }); + } void node_id_handshake (nano::node_id_handshake const & message_a) override { if (connection->node->config.logging.network_node_id_handshake_logging ()) diff --git a/nano/node/bootstrap/bootstrap_server.hpp b/nano/node/bootstrap/bootstrap_server.hpp index 61b705fee1..63dfd9b0f3 100644 --- a/nano/node/bootstrap/bootstrap_server.hpp +++ b/nano/node/bootstrap/bootstrap_server.hpp @@ -57,6 +57,7 @@ class bootstrap_server final : public std::enable_shared_from_this); void finish_request (); void finish_request_async (); diff --git a/nano/node/common.cpp b/nano/node/common.cpp index c5184a547c..6b9d225861 100644 --- a/nano/node/common.cpp +++ b/nano/node/common.cpp @@ -12,6 +12,7 @@ std::bitset<16> constexpr nano::message_header::block_type_mask; std::bitset<16> constexpr nano::message_header::count_mask; + namespace { nano::protocol_constants const & get_protocol_constants () @@ -197,8 +198,9 @@ size_t nano::message_header::payload_length_bytes () const return nano::bulk_pull::size + (bulk_pull_is_count_present () ? nano::bulk_pull::extended_parameters_size : 0); } case nano::message_type::bulk_push: + case nano::message_type::telemetry_req: { - // bulk_push doesn't have a payload + // These don't have a payload return 0; } case nano::message_type::frontier_req: @@ -229,6 +231,10 @@ size_t nano::message_header::payload_length_bytes () const { return nano::node_id_handshake::size (*this); } + case nano::message_type::telemetry_ack: + { + return nano::telemetry_ack::size (*this); + } default: { assert (false); @@ -280,6 +286,14 @@ std::string nano::message_parser::status_string () { return "invalid_node_id_handshake_message"; } + case nano::message_parser::parse_status::invalid_telemetry_req_message: + { + return "invalid_telemetry_req_message"; + } + case nano::message_parser::parse_status::invalid_telemetry_ack_message: + { + return "invalid_telemetry_ack_message"; + } case nano::message_parser::parse_status::outdated_version: { return "outdated_version"; @@ -353,6 +367,16 @@ void nano::message_parser::deserialize_buffer (uint8_t const * buffer_a, size_t deserialize_node_id_handshake (stream, header); break; } + case nano::message_type::telemetry_req: + { + deserialize_telemetry_req (stream, header); + break; + } + case nano::message_type::telemetry_ack: + { + deserialize_telemetry_ack (stream, header); + break; + } default: { status = parse_status::invalid_message_type; @@ -467,6 +491,34 @@ void nano::message_parser::deserialize_node_id_handshake (nano::stream & stream_ } } +void nano::message_parser::deserialize_telemetry_req (nano::stream & stream_a, nano::message_header const & header_a) +{ + nano::telemetry_req incoming (header_a); + if (at_end (stream_a)) + { + visitor.telemetry_req (incoming); + } + else + { + status = parse_status::invalid_telemetry_req_message; + } +} + +void nano::message_parser::deserialize_telemetry_ack (nano::stream & stream_a, nano::message_header const & header_a) +{ + bool error_l (false); + nano::telemetry_ack incoming (error_l, stream_a, header_a); + // Intentionally not checking if at the end of stream, because these messages support backwards/forwards compatibility + if (!error_l) + { + visitor.telemetry_ack (incoming); + } + else + { + status = parse_status::invalid_telemetry_ack_message; + } +} + bool nano::message_parser::at_end (nano::stream & stream_a) { uint8_t junk; @@ -993,6 +1045,234 @@ void nano::bulk_push::visit (nano::message_visitor & visitor_a) const visitor_a.bulk_push (*this); } +nano::telemetry_req::telemetry_req () : +message (nano::message_type::telemetry_req) +{ +} + +nano::telemetry_req::telemetry_req (nano::message_header const & header_a) : +message (header_a) +{ +} + +bool nano::telemetry_req::deserialize (nano::stream & stream_a) +{ + assert (header.type == nano::message_type::telemetry_req); + return false; +} + +void nano::telemetry_req::serialize (nano::stream & stream_a) const +{ + header.serialize (stream_a); +} + +void nano::telemetry_req::visit (nano::message_visitor & visitor_a) const +{ + visitor_a.telemetry_req (*this); +} + +nano::telemetry_ack::telemetry_ack (bool & error_a, nano::stream & stream_a, nano::message_header const & message_header) : +message (message_header) +{ + if (!error_a) + { + error_a = deserialize (stream_a); + } +} + +nano::telemetry_ack::telemetry_ack (nano::telemetry_data const & telemetry_data_a) : +message (nano::message_type::telemetry_ack), +data (telemetry_data_a) +{ + header.extensions = telemetry_data::size; +} + +void nano::telemetry_ack::serialize (nano::stream & stream_a) const +{ + header.serialize (stream_a); + write (stream_a, data.block_count); + write (stream_a, data.cemented_count); + write (stream_a, data.unchecked_count); + write (stream_a, data.account_count); + write (stream_a, data.bandwidth_cap); + write (stream_a, data.peer_count); + write (stream_a, data.protocol_version_number); + write (stream_a, data.vendor_version); + write (stream_a, data.uptime); + write (stream_a, data.genesis_block.bytes); +} + +bool nano::telemetry_ack::deserialize (nano::stream & stream_a) +{ + auto error (false); + try + { + read (stream_a, data.block_count); + read (stream_a, data.cemented_count); + read (stream_a, data.unchecked_count); + read (stream_a, data.account_count); + read (stream_a, data.bandwidth_cap); + read (stream_a, data.peer_count); + read (stream_a, data.protocol_version_number); + read (stream_a, data.vendor_version); + read (stream_a, data.uptime); + read (stream_a, data.genesis_block.bytes); + } + catch (std::runtime_error const &) + { + error = true; + } + + return error; +} + +void nano::telemetry_ack::visit (nano::message_visitor & visitor_a) const +{ + visitor_a.telemetry_ack (*this); +} + +uint16_t nano::telemetry_ack::size (nano::message_header const & message_header_a) +{ + return static_cast (message_header_a.extensions.to_ulong ()); +} + +nano::telemetry_data nano::telemetry_data::consolidate (std::vector const & telemetry_data_responses_a) +{ + if (telemetry_data_responses_a.empty ()) + { + return {}; + } + else if (telemetry_data_responses_a.size () == 1) + { + // Only 1 element in the collection, so just return it. + return telemetry_data_responses_a.front (); + } + + nano::uint128_t account_sum{ 0 }; + nano::uint128_t block_sum{ 0 }; + nano::uint128_t cemented_sum{ 0 }; + nano::uint128_t peer_sum{ 0 }; + nano::uint128_t unchecked_sum{ 0 }; + nano::uint128_t uptime_sum{ 0 }; + nano::uint128_t bandwidth_sum{ 0 }; + + std::unordered_map protocol_versions; + std::unordered_map vendor_versions; + std::unordered_map bandwidth_caps; + std::unordered_map genesis_blocks; + + nano::uint128_t account_average{ 0 }; + + for (auto const & telemetry_data : telemetry_data_responses_a) + { + account_sum += telemetry_data.account_count; + block_sum += telemetry_data.block_count; + cemented_sum += telemetry_data.cemented_count; + ++vendor_versions[telemetry_data.vendor_version]; + ++protocol_versions[telemetry_data.protocol_version_number]; + peer_sum += telemetry_data.peer_count; + + // 0 has a special meaning (unlimited), don't include it in the average as it will be heavily skewed + if (telemetry_data.bandwidth_cap != 0) + { + bandwidth_sum += telemetry_data.bandwidth_cap; + } + ++bandwidth_caps[telemetry_data.bandwidth_cap]; + unchecked_sum += telemetry_data.unchecked_count; + uptime_sum += telemetry_data.uptime; + ++genesis_blocks[telemetry_data.genesis_block]; + } + + nano::telemetry_data consolidated_data; + auto size = telemetry_data_responses_a.size (); + consolidated_data.account_count = boost::numeric_cast (account_sum / size); + consolidated_data.block_count = boost::numeric_cast (block_sum / size); + consolidated_data.cemented_count = boost::numeric_cast (cemented_sum / size); + consolidated_data.peer_count = boost::numeric_cast (peer_sum / size); + consolidated_data.uptime = boost::numeric_cast (uptime_sum / size); + consolidated_data.unchecked_count = boost::numeric_cast (unchecked_sum / size); + + auto set_mode_or_average = [](auto const & collection, auto & var, auto const & sum, size_t size) { + auto max = std::max_element (collection.begin (), collection.end (), [](auto const & lhs, auto const & rhs) { + return lhs.second < rhs.second; + }); + if (max->second > 1) + { + var = max->first; + } + else + { + var = (sum / size).template convert_to> (); + } + }; + + auto set_mode = [](auto const & collection, auto & var, size_t size) { + auto max = std::max_element (collection.begin (), collection.end (), [](auto const & lhs, auto const & rhs) { + return lhs.second < rhs.second; + }); + if (max->second > 1) + { + var = max->first; + } + else + { + // Just pick the first one + var = collection.begin ()->first; + } + }; + + // Use the mode of protocol version, vendor version and bandwidth cap if there is 2 or more using it + set_mode_or_average (bandwidth_caps, consolidated_data.bandwidth_cap, bandwidth_sum, size); + set_mode (protocol_versions, consolidated_data.protocol_version_number, size); + set_mode (vendor_versions, consolidated_data.vendor_version, size); + set_mode (genesis_blocks, consolidated_data.genesis_block, size); + + return consolidated_data; +} + +nano::error nano::telemetry_data::serialize_json (nano::jsonconfig & json) const +{ + json.put ("block_count", block_count); + json.put ("cemented_count", cemented_count); + json.put ("unchecked_count", unchecked_count); + json.put ("account_count", account_count); + json.put ("bandwidth_cap", bandwidth_cap); + json.put ("peer_count", peer_count); + json.put ("protocol_version_number", protocol_version_number); + json.put ("vendor_version", vendor_version); + json.put ("uptime", uptime); + json.put ("genesis_block", genesis_block.to_string ()); + return json.get_error (); +} + +nano::error nano::telemetry_data::deserialize_json (nano::jsonconfig & json) +{ + json.get ("block_count", block_count); + json.get ("cemented_count", cemented_count); + json.get ("unchecked_count", unchecked_count); + json.get ("account_count", account_count); + json.get ("bandwidth_cap", bandwidth_cap); + json.get ("peer_count", peer_count); + json.get ("protocol_version_number", protocol_version_number); + json.get ("vendor_version", vendor_version); + json.get ("uptime", uptime); + std::string genesis_block_l; + json.get ("genesis_block", genesis_block_l); + if (!json.get_error ()) + { + if (genesis_block.decode_hex (genesis_block_l)) + { + json.get_error ().set ("Could not deserialize genesis block"); + } + } + return json.get_error (); +} + +bool nano::telemetry_data::operator== (nano::telemetry_data const & data_a) const +{ + return (block_count == data_a.block_count && cemented_count == data_a.cemented_count && unchecked_count == data_a.unchecked_count && account_count == data_a.account_count && bandwidth_cap == data_a.bandwidth_cap && uptime == data_a.uptime && peer_count == data_a.peer_count && protocol_version_number == data_a.protocol_version_number && vendor_version == data_a.vendor_version && genesis_block == data_a.genesis_block); +} + nano::node_id_handshake::node_id_handshake (bool & error_a, nano::stream & stream_a, nano::message_header const & header_a) : message (header_a), query (boost::none), diff --git a/nano/node/common.hpp b/nano/node/common.hpp index e3f4b48f56..6021803fb6 100644 --- a/nano/node/common.hpp +++ b/nano/node/common.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -169,8 +170,11 @@ enum class message_type : uint8_t frontier_req = 0x8, /* deleted 0x9 */ node_id_handshake = 0x0a, - bulk_pull_account = 0x0b + bulk_pull_account = 0x0b, + telemetry_req = 0x0c, + telemetry_ack = 0x0d }; + enum class bulk_pull_account_flags : uint8_t { pending_hash_and_amount = 0x0, @@ -206,8 +210,8 @@ class message_header final /** Size of the payload in bytes. For some messages, the payload size is based on header flags. */ size_t payload_length_bytes () const; - static std::bitset<16> constexpr block_type_mask = std::bitset<16> (0x0f00); - static std::bitset<16> constexpr count_mask = std::bitset<16> (0xf000); + static std::bitset<16> constexpr block_type_mask{ 0x0f00 }; + static std::bitset<16> constexpr count_mask{ 0xf000 }; }; class message { @@ -237,6 +241,8 @@ class message_parser final invalid_confirm_req_message, invalid_confirm_ack_message, invalid_node_id_handshake_message, + invalid_telemetry_req_message, + invalid_telemetry_ack_message, outdated_version, invalid_magic, invalid_network @@ -248,6 +254,8 @@ class message_parser final 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::block_uniquer & block_uniquer; nano::vote_uniquer & vote_uniquer; @@ -321,6 +329,49 @@ class frontier_req final : public message uint32_t count; static size_t constexpr size = sizeof (start) + sizeof (age) + sizeof (count); }; + +class telemetry_data +{ +public: + uint64_t block_count{ 0 }; + uint64_t cemented_count{ 0 }; + uint64_t unchecked_count{ 0 }; + uint64_t account_count{ 0 }; + uint64_t bandwidth_cap{ 0 }; + uint64_t uptime{ 0 }; + uint32_t peer_count{ 0 }; + uint8_t protocol_version_number{ 0 }; + uint8_t vendor_version{ 0 }; + nano::block_hash genesis_block{ 0 }; + + static nano::telemetry_data consolidate (std::vector const & telemetry_data_responses); + nano::error serialize_json (nano::jsonconfig & json) const; + nano::error deserialize_json (nano::jsonconfig & json); + bool operator== (nano::telemetry_data const &) const; + + static auto constexpr size = sizeof (block_count) + sizeof (cemented_count) + sizeof (unchecked_count) + sizeof (account_count) + sizeof (bandwidth_cap) + sizeof (peer_count) + sizeof (protocol_version_number) + sizeof (vendor_version) + sizeof (uptime) + sizeof (genesis_block); +}; +class telemetry_req final : public message +{ +public: + telemetry_req (); + explicit telemetry_req (nano::message_header const &); + void serialize (nano::stream &) const override; + bool deserialize (nano::stream &); + void visit (nano::message_visitor &) const override; +}; +class telemetry_ack final : public message +{ +public: + telemetry_ack (bool &, nano::stream &, nano::message_header const &); + explicit telemetry_ack (telemetry_data const &); + void serialize (nano::stream &) const override; + void visit (nano::message_visitor &) const override; + bool deserialize (nano::stream &); + static uint16_t size (nano::message_header const &); + nano::telemetry_data data; +}; + class bulk_pull final : public message { public: @@ -387,6 +438,8 @@ class message_visitor virtual void bulk_push (nano::bulk_push const &) = 0; virtual void frontier_req (nano::frontier_req const &) = 0; virtual void node_id_handshake (nano::node_id_handshake const &) = 0; + virtual void telemetry_req (nano::telemetry_req const &) = 0; + virtual void telemetry_ack (nano::telemetry_ack const &) = 0; virtual ~message_visitor (); }; diff --git a/nano/node/confirmation_solicitor.cpp b/nano/node/confirmation_solicitor.cpp index ae1baf5b3b..34caaf2c9d 100644 --- a/nano/node/confirmation_solicitor.cpp +++ b/nano/node/confirmation_solicitor.cpp @@ -6,6 +6,7 @@ using namespace std::chrono_literals; nano::confirmation_solicitor::confirmation_solicitor (nano::network & network_a, nano::network_constants const & params_a) : max_confirm_req_batches (params_a.is_test_network () ? 1 : 20), max_block_broadcasts (params_a.is_test_network () ? 4 : 30), +max_election_requests (30), network (network_a) { } @@ -35,20 +36,21 @@ bool nano::confirmation_solicitor::add (nano::election const & election_a) { assert (prepared); auto const max_channel_requests (max_confirm_req_batches * nano::network::confirm_req_hashes_max); - bool result = true; - for (auto const & rep : representatives) + unsigned count = 0; + for (auto i (representatives.begin ()), n (representatives.end ()); i != n && count < max_election_requests; ++i) { + auto rep (*i); if (election_a.last_votes.find (rep.account) == election_a.last_votes.end ()) { auto & request_queue (requests[rep.channel]); if (request_queue.size () < max_channel_requests) { request_queue.emplace_back (election_a.status.winner->hash (), election_a.status.winner->root ()); - result = false; + ++count; } } } - return result; + return count == 0; } void nano::confirmation_solicitor::flush () diff --git a/nano/node/confirmation_solicitor.hpp b/nano/node/confirmation_solicitor.hpp index ae5c9a2797..a9fd21d896 100644 --- a/nano/node/confirmation_solicitor.hpp +++ b/nano/node/confirmation_solicitor.hpp @@ -22,15 +22,17 @@ class confirmation_solicitor final bool add (nano::election const &); /** Dispatch bundled requests to each channel*/ void flush (); - /** The maximum amount of confirmation requests (batches) to be sent to each channel */ + /** Maximum amount of confirmation requests (batches) to be sent to each channel */ size_t const max_confirm_req_batches; - /** The global maximum amount of block broadcasts */ + /** Global maximum amount of block broadcasts */ size_t const max_block_broadcasts; + /** Maximum amount of requests to be sent per election */ + size_t const max_election_requests; private: nano::network & network; - int rebroadcasted{ 0 }; + unsigned rebroadcasted{ 0 }; std::vector representatives; using vector_root_hashes = std::vector>; std::unordered_map, vector_root_hashes> requests; diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 4a36c4e8df..028b579edc 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -211,21 +211,16 @@ bool nano::election::publish (std::shared_ptr block_a) } if (!result) { - auto transaction (node.store.tx_begin_read ()); - result = node.validate_block_by_previous (transaction, block_a); - if (!result) + if (blocks.find (block_a->hash ()) == blocks.end ()) { - if (blocks.find (block_a->hash ()) == blocks.end ()) - { - blocks.emplace (block_a->hash (), block_a); - insert_inactive_votes_cache (block_a->hash ()); - confirm_if_quorum (); - node.network.flood_block (block_a, false); - } - else - { - result = true; - } + blocks.emplace (std::make_pair (block_a->hash (), block_a)); + insert_inactive_votes_cache (block_a->hash ()); + confirm_if_quorum (); + node.network.flood_block (block_a, false); + } + else + { + result = true; } } return result; diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 27bc9bce8a..4a0150d087 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -833,7 +834,7 @@ void nano::json_handler::accounts_pending () if (!ec) { boost::property_tree::ptree peers_l; - for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))), n (node.store.pending_end ()); i != n && nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) { nano::pending_key const & key (i->first); if (block_confirmed (node, transaction, key.hash, include_active, include_only_confirmed)) @@ -2912,7 +2913,7 @@ void nano::json_handler::pending () { boost::property_tree::ptree peers_l; auto transaction (node.store.tx_begin_read ()); - for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))), n (node.store.pending_end ()); i != n && nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) { nano::pending_key const & key (i->first); if (block_confirmed (node, transaction, key.hash, include_active, include_only_confirmed)) @@ -3894,6 +3895,130 @@ void nano::json_handler::stop () } } +void nano::json_handler::telemetry () +{ + auto rpc_l (shared_from_this ()); + + auto address_text (request.get_optional ("address")); + auto port_text (request.get_optional ("port")); + + if (address_text.is_initialized () || port_text.is_initialized ()) + { + // Check both are specified + std::shared_ptr channel; + if (address_text.is_initialized () && port_text.is_initialized ()) + { + uint16_t port; + if (!nano::parse_port (*port_text, port)) + { + boost::system::error_code address_ec; + auto address (boost::asio::ip::make_address_v6 (*address_text, address_ec)); + if (!address_ec) + { + nano::endpoint endpoint (address, port); + channel = node.network.find_channel (endpoint); + if (!channel) + { + ec = nano::error_rpc::peer_not_found; + } + } + else + { + ec = nano::error_common::invalid_ip_address; + } + } + else + { + ec = nano::error_common::invalid_port; + } + } + else + { + ec = nano::error_rpc::requires_port_and_address; + } + + if (!ec) + { + assert (channel); + node.telemetry.get_metrics_single_peer_async (channel, [rpc_l](auto const & single_telemetry_metric_a) { + if (!single_telemetry_metric_a.error) + { + nano::jsonconfig config_l; + auto err = single_telemetry_metric_a.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 ()); + rpc_l->response_l.put ("cached", single_telemetry_metric_a.is_cached); + } + else + { + rpc_l->ec = nano::error_rpc::generic; + } + } + else + { + rpc_l->ec = nano::error_rpc::generic; + } + + rpc_l->response_errors (); + }); + } + else + { + response_errors (); + } + } + else + { + // By default, consolidated (average or mode) telemetry metrics are returned, + // setting "raw" to true returns metrics from all nodes requested. + auto raw = request.get_optional ("raw"); + auto output_raw = raw.value_or (false); + node.telemetry.get_metrics_random_peers_async ([rpc_l, output_raw](auto const & batched_telemetry_metrics_a) { + if (output_raw) + { + boost::property_tree::ptree metrics; + for (auto & telemetry_metrics : batched_telemetry_metrics_a.data) + { + nano::jsonconfig config_l; + auto err = telemetry_metrics.serialize_json (config_l); + if (!err) + { + metrics.push_back (std::make_pair ("", config_l.get_tree ())); + } + else + { + rpc_l->ec = nano::error_rpc::generic; + } + } + + rpc_l->response_l.put_child ("metrics", metrics); + } + else + { + nano::jsonconfig config_l; + auto average_telemetry_metrics = nano::telemetry_data::consolidate (batched_telemetry_metrics_a.data); + auto err = average_telemetry_metrics.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; + } + } + + rpc_l->response_l.put ("cached", batched_telemetry_metrics_a.is_cached); + rpc_l->response_errors (); + }); + } +} + void nano::json_handler::unchecked () { const bool json_block_l = request.get ("json_block", false); @@ -4548,7 +4673,7 @@ void nano::json_handler::wallet_pending () { nano::account const & account (i->first); boost::property_tree::ptree peers_l; - for (auto ii (node.store.pending_begin (block_transaction, nano::pending_key (account, 0))); nano::pending_key (ii->first).account == account && peers_l.size () < count; ++ii) + for (auto ii (node.store.pending_begin (block_transaction, nano::pending_key (account, 0))), nn (node.store.pending_end ()); ii != nn && nano::pending_key (ii->first).account == account && peers_l.size () < count; ++ii) { nano::pending_key key (ii->first); if (block_confirmed (node, block_transaction, key.hash, include_active, include_only_confirmed)) @@ -5034,6 +5159,7 @@ ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map () no_arg_funcs.emplace ("stats", &nano::json_handler::stats); no_arg_funcs.emplace ("stats_clear", &nano::json_handler::stats_clear); no_arg_funcs.emplace ("stop", &nano::json_handler::stop); + no_arg_funcs.emplace ("node_telemetry", &nano::json_handler::telemetry); no_arg_funcs.emplace ("unchecked", &nano::json_handler::unchecked); no_arg_funcs.emplace ("unchecked_clear", &nano::json_handler::unchecked_clear); no_arg_funcs.emplace ("unchecked_get", &nano::json_handler::unchecked_get); diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp index c9ee197c0d..ff8face8ee 100644 --- a/nano/node/json_handler.hpp +++ b/nano/node/json_handler.hpp @@ -97,6 +97,7 @@ class json_handler : public std::enable_shared_from_this void stats (); void stats_clear (); void stop (); + void telemetry (); void unchecked (); void unchecked_clear (); void unchecked_get (); diff --git a/nano/node/logging.cpp b/nano/node/logging.cpp index fb28b99ad1..17f756f4c9 100644 --- a/nano/node/logging.cpp +++ b/nano/node/logging.cpp @@ -94,6 +94,7 @@ nano::error nano::logging::serialize_toml (nano::tomlconfig & toml) const toml.put ("network_packet", network_packet_logging_value, "Log network packet activity.\ntype:bool"); toml.put ("network_keepalive", network_keepalive_logging_value, "Log keepalive related messages.\ntype:bool"); toml.put ("network_node_id_handshake", network_node_id_handshake_logging_value, "Log node-id handshake related messages.\ntype:bool"); + toml.put ("network_telemetry", network_telemetry_logging_value, "Log telemetry related messages.\ntype:bool"); toml.put ("node_lifetime_tracing", node_lifetime_tracing_value, "Log node startup and shutdown messages.\ntype:bool"); toml.put ("insufficient_work", insufficient_work_logging_value, "Log if insufficient work is detected.\ntype:bool"); toml.put ("log_ipc", log_ipc_value, "Log IPC related activity.\ntype:bool"); @@ -124,6 +125,7 @@ nano::error nano::logging::deserialize_toml (nano::tomlconfig & toml) toml.get ("network_packet", network_packet_logging_value); toml.get ("network_keepalive", network_keepalive_logging_value); toml.get ("network_node_id_handshake", network_node_id_handshake_logging_value); + toml.get ("network_telemetry_logging", network_telemetry_logging_value); toml.get ("node_lifetime_tracing", node_lifetime_tracing_value); toml.get ("insufficient_work", insufficient_work_logging_value); toml.get ("log_ipc", log_ipc_value); @@ -157,6 +159,7 @@ nano::error nano::logging::serialize_json (nano::jsonconfig & json) const json.put ("network_packet", network_packet_logging_value); json.put ("network_keepalive", network_keepalive_logging_value); json.put ("network_node_id_handshake", network_node_id_handshake_logging_value); + json.put ("network_telemetry_logging", network_telemetry_logging_value); json.put ("node_lifetime_tracing", node_lifetime_tracing_value); json.put ("insufficient_work", insufficient_work_logging_value); json.put ("log_ipc", log_ipc_value); @@ -309,6 +312,11 @@ bool nano::logging::network_node_id_handshake_logging () const return network_logging () && network_node_id_handshake_logging_value; } +bool nano::logging::network_telemetry_logging () const +{ + return network_logging () && network_telemetry_logging_value; +} + bool nano::logging::node_lifetime_tracing () const { return node_lifetime_tracing_value; diff --git a/nano/node/logging.hpp b/nano/node/logging.hpp index 2da20c4fcc..61dcec5e0f 100644 --- a/nano/node/logging.hpp +++ b/nano/node/logging.hpp @@ -52,6 +52,7 @@ class logging final bool network_packet_logging () const; bool network_keepalive_logging () const; bool network_node_id_handshake_logging () const; + bool network_telemetry_logging () const; bool node_lifetime_tracing () const; bool insufficient_work_logging () const; bool upnp_details_logging () const; @@ -75,6 +76,7 @@ class logging final bool network_packet_logging_value{ false }; bool network_keepalive_logging_value{ false }; bool network_node_id_handshake_logging_value{ false }; + bool network_telemetry_logging_value{ false }; bool node_lifetime_tracing_value{ false }; bool insufficient_work_logging_value{ true }; bool log_ipc_value{ true }; diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 334c8ac39c..ffbe20839b 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -439,7 +440,6 @@ class network_message_visitor : public nano::message_visitor { node.stats.inc (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::in); } - node.active.publish (message_a.block); } void confirm_req (nano::confirm_req const & message_a) override { @@ -498,7 +498,6 @@ class network_message_visitor : public nano::message_visitor { node.stats.inc (nano::stat::type::drop, nano::stat::detail::confirm_ack, nano::stat::dir::in); } - node.active.publish (block); } } node.vote_processor.vote (message_a.vote, channel); @@ -523,6 +522,42 @@ class network_message_visitor : public nano::message_visitor { node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } + void telemetry_req (nano::telemetry_req const & message_a) override + { + if (node.config.logging.network_telemetry_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Telemetry_req message from %1%") % channel->to_string ())); + } + node.stats.inc (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in); + + nano::telemetry_data telemetry_data; + telemetry_data.block_count = node.ledger.cache.block_count; + telemetry_data.cemented_count = node.ledger.cache.cemented_count; + telemetry_data.bandwidth_cap = node.config.bandwidth_limit; + telemetry_data.protocol_version_number = node.network_params.protocol.protocol_version; + telemetry_data.vendor_version = nano::get_major_node_version (); + telemetry_data.uptime = std::chrono::duration_cast (std::chrono::steady_clock::now () - node.startup_time).count (); + telemetry_data.unchecked_count = node.ledger.cache.unchecked_count; + telemetry_data.genesis_block = nano::genesis ().hash (); + telemetry_data.peer_count = node.network.size (); + + { + auto transaction = node.store.tx_begin_read (); + telemetry_data.account_count = node.store.account_count (transaction); + } + + nano::telemetry_ack telemetry_ack (telemetry_data); + channel->send (telemetry_ack); + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + if (node.config.logging.network_telemetry_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Received telemetry_ack message from %1%") % channel->to_string ())); + } + node.stats.inc (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in); + node.telemetry.add (message_a.data, channel->get_endpoint ()); + } nano::node & node; std::shared_ptr channel; }; @@ -623,10 +658,10 @@ size_t nano::network::fanout (float scale) const return static_cast (std::ceil (scale * size_sqrt ())); } -std::unordered_set> nano::network::random_set (size_t count_a) const +std::unordered_set> nano::network::random_set (size_t count_a, uint8_t min_version_a) const { - std::unordered_set> result (tcp_channels.random_set (count_a)); - std::unordered_set> udp_random (udp_channels.random_set (count_a)); + std::unordered_set> result (tcp_channels.random_set (count_a, min_version_a)); + std::unordered_set> udp_random (udp_channels.random_set (count_a, min_version_a)); for (auto i (udp_random.begin ()), n (udp_random.end ()); i != n && result.size () < count_a * 1.5; ++i) { result.insert (*i); diff --git a/nano/node/network.hpp b/nano/node/network.hpp index e321e05b1d..9156ab8fce 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -137,7 +137,7 @@ class network final // Desired fanout for a given scale size_t fanout (float scale = 1.0f) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + std::unordered_set> random_set (size_t, uint8_t = 0) const; // Get the next peer for attempting a tcp bootstrap connection nano::tcp_endpoint bootstrap_peer (bool = false); nano::endpoint endpoint (); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index eacbcf5537..9f8c798d98 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -125,23 +126,26 @@ gap_cache (*this), ledger (store, stats, flags_a.generate_cache), checker (config.signature_checker_threads), network (*this, config.peering_port), +telemetry (network, alarm, worker), bootstrap_initiator (*this), bootstrap (config.peering_port, *this), application_path (application_path_a), port_mapping (*this), -vote_processor (checker, active, store, observers, stats, config, logger, online_reps, ledger, network_params), +vote_processor (checker, active, observers, stats, config, logger, online_reps, ledger, network_params), rep_crawler (*this), warmed_up (0), block_processor (*this, write_database_queue), +// clang-format off block_processor_thread ([this]() { nano::thread_role::set (nano::thread_role::name::block_processing); this->block_processor.process_blocks (); }), +// clang-format on online_reps (ledger, network_params, config.online_weight_minimum.number ()), votes_cache (wallets), vote_uniquer (block_uniquer), active (*this), -aggregator (stats, network_params.network, votes_cache, store, wallets), +aggregator (network_params.network, config, stats, votes_cache, store, wallets), confirmation_height_processor (pending_confirmation_height, ledger, active, write_database_queue, config.conf_height_processor_batch_min_time, logger), payment_observer_processor (observers.blocks), wallets (wallets_store.init_error (), *this), @@ -537,6 +541,7 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha auto root (block_a->root ()); if (!store.block_exists (transaction_a, block_a->type (), block_a->hash ()) && store.root_exists (transaction_a, block_a->root ())) { + active.publish (block_a); std::shared_ptr ledger_block (ledger.forked_block (transaction_a, *block_a)); if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ())) { @@ -579,6 +584,7 @@ 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")); 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")); @@ -625,6 +631,7 @@ nano::process_return nano::node::process_local (std::shared_ptr blo void nano::node::start () { + long_inactivity_cleanup (); network.start (); add_initial_peers (); if (!flags.disable_legacy_bootstrap) @@ -691,6 +698,7 @@ void nano::node::stop () confirmation_height_processor.stop (); active.stop (); network.stop (); + telemetry.stop (); if (websocket_server) { websocket_server->stop (); @@ -768,6 +776,31 @@ nano::uint128_t nano::node::minimum_principal_weight (nano::uint128_t const & on return online_stake / network_params.network.principal_weight_factor; } +void nano::node::long_inactivity_cleanup () +{ + bool perform_cleanup = false; + auto transaction (store.tx_begin_write ()); + if (store.online_weight_count (transaction) > 0) + { + auto i (store.online_weight_begin (transaction)); + auto sample (store.online_weight_begin (transaction)); + auto n (store.online_weight_end ()); + while (++i != n) + { + ++sample; + } + assert (sample != n); + auto const one_week_ago = (std::chrono::system_clock::now () - std::chrono::hours (7 * 24)).time_since_epoch ().count (); + perform_cleanup = sample->first < one_week_ago; + } + if (perform_cleanup) + { + store.online_weight_clear (transaction); + store.peer_clear (transaction); + logger.always_log ("Removed records of peers and online weight after a long period of inactivity"); + } +} + void nano::node::ongoing_rep_calculation () { auto now (std::chrono::steady_clock::now ()); @@ -1262,55 +1295,6 @@ std::shared_ptr nano::node::shared () return shared_from_this (); } -bool nano::node::validate_block_by_previous (nano::transaction const & transaction, std::shared_ptr block_a) -{ - bool result (false); - nano::root account; - if (!block_a->previous ().is_zero ()) - { - if (store.block_exists (transaction, block_a->previous ())) - { - account = ledger.account (transaction, block_a->previous ()); - } - else - { - result = true; - } - } - else - { - account = block_a->root (); - } - if (!result && block_a->type () == nano::block_type::state) - { - std::shared_ptr block_l (std::static_pointer_cast (block_a)); - nano::amount prev_balance (0); - if (!block_l->hashables.previous.is_zero ()) - { - if (store.block_exists (transaction, block_l->hashables.previous)) - { - prev_balance = ledger.balance (transaction, block_l->hashables.previous); - } - else - { - result = true; - } - } - if (!result) - { - if (block_l->hashables.balance == prev_balance && ledger.is_epoch_link (block_l->hashables.link)) - { - account = ledger.epoch_signer (block_l->link ()); - } - } - } - if (!result && (account.is_zero () || nano::validate_message (account, block_a->hash (), block_a->block_signature ()))) - { - result = true; - } - return result; -} - int nano::node::store_version () { auto transaction (store.tx_begin_read ()); diff --git a/nano/node/node.hpp b/nano/node/node.hpp index b8985a5d75..41e674f4ae 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -47,7 +48,7 @@ namespace websocket } class node; - +class telemetry; class work_pool; class block_arrival_info final { @@ -138,7 +139,6 @@ class node final : public std::enable_shared_from_this void block_confirm (std::shared_ptr); bool block_confirmed_or_being_confirmed (nano::transaction const &, nano::block_hash const &); void process_fork (nano::transaction const &, std::shared_ptr); - bool validate_block_by_previous (nano::transaction const &, std::shared_ptr); void do_rpc_callback (boost::asio::ip::tcp::resolver::iterator i_a, std::string const &, uint16_t, std::shared_ptr, std::shared_ptr, std::shared_ptr); nano::uint128_t delta () const; void ongoing_online_weight_calculation (); @@ -166,6 +166,7 @@ class node final : public std::enable_shared_from_this nano::ledger ledger; nano::signature_checker checker; nano::network network; + nano::telemetry telemetry; nano::bootstrap_initiator bootstrap_initiator; nano::bootstrap_listener bootstrap; boost::filesystem::path application_path; @@ -194,6 +195,9 @@ class node final : public std::enable_shared_from_this std::atomic stopped{ false }; static double constexpr price_max = 16.0; static double constexpr free_cutoff = 1024.0; + +private: + void long_inactivity_cleanup (); }; std::unique_ptr collect_container_info (node & node, const std::string & name); diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 175e930328..45fb429557 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -101,6 +101,7 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const toml.put ("work_watcher_period", work_watcher_period.count (), "Time between checks for confirmation and re-generating higher difficulty work if unconfirmed, for blocks in the work watcher.\ntype:seconds"); toml.put ("max_work_generate_multiplier", max_work_generate_multiplier, "Maximum allowed difficulty multiplier for work generation.\ntype:double,[1..]"); toml.put ("frontiers_confirmation", serialize_frontiers_confirmation (frontiers_confirmation), "Mode controlling frontier confirmation rate.\ntype:string,{auto,always,disabled}"); + toml.put ("max_queued_requests", max_queued_requests, "Limit for number of queued confirmation requests for one channel, after which new requests are dropped until the queue drops below this value.\ntype:uint32"); auto work_peers_l (toml.create_array ("work_peers", "A list of \"address:port\" entries to identify work peers.")); for (auto i (work_peers.begin ()), n (work_peers.end ()); i != n; ++i) @@ -333,6 +334,8 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) toml.get ("max_work_generate_multiplier", max_work_generate_multiplier); max_work_generate_difficulty = nano::difficulty::from_multiplier (max_work_generate_multiplier, network.publish_threshold); + toml.get ("max_queued_requests", max_queued_requests); + if (toml.has_key ("frontiers_confirmation")) { auto frontiers_confirmation_l (toml.get ("frontiers_confirmation")); diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index 03e4d441f6..a7d3d3c020 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -93,6 +93,7 @@ class node_config std::chrono::seconds work_watcher_period{ std::chrono::seconds (5) }; double max_work_generate_multiplier{ 64. }; uint64_t max_work_generate_difficulty{ nano::network_constants::publish_full_threshold }; + uint32_t max_queued_requests{ 512 }; nano::rocksdb_config rocksdb_config; nano::frontiers_confirmation_mode frontiers_confirmation{ nano::frontiers_confirmation_mode::automatic }; std::string serialize_frontiers_confirmation (nano::frontiers_confirmation_mode) const; diff --git a/nano/node/repcrawler.cpp b/nano/node/repcrawler.cpp index c55e265aaa..8e30473bdc 100644 --- a/nano/node/repcrawler.cpp +++ b/nano/node/repcrawler.cpp @@ -38,6 +38,7 @@ void nano::rep_crawler::validate () nano::lock_guard lock (active_mutex); responses_l.swap (responses); } + auto transaction (node.store.tx_begin_read ()); auto minimum = node.minimum_principal_weight (); for (auto const & i : responses_l) { @@ -85,6 +86,15 @@ void nano::rep_crawler::validate () } } } + // This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them + // Only do this if the sequence number is significantly different to account for network reordering + // Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase + auto max_vote (node.store.vote_max (transaction, vote)); + if (max_vote->sequence > vote->sequence + 10000) + { + nano::confirm_ack confirm (max_vote); + channel->send (confirm); // this is non essential traffic as it will be resolicited if not received + } } } diff --git a/nano/node/request_aggregator.cpp b/nano/node/request_aggregator.cpp index fcb5525c0a..092e4a3698 100644 --- a/nano/node/request_aggregator.cpp +++ b/nano/node/request_aggregator.cpp @@ -2,15 +2,18 @@ #include #include #include +#include #include #include #include #include #include -nano::request_aggregator::request_aggregator (nano::stat & stats_a, nano::network_constants const & network_constants_a, nano::votes_cache & cache_a, nano::block_store & store_a, nano::wallets & wallets_a) : +nano::request_aggregator::request_aggregator (nano::network_constants const & network_constants_a, nano::node_config const & config_a, nano::stat & stats_a, nano::votes_cache & cache_a, nano::block_store & store_a, nano::wallets & wallets_a) : max_delay (network_constants_a.is_test_network () ? 50 : 300), small_delay (network_constants_a.is_test_network () ? 10 : 50), +max_channel_requests (config_a.max_queued_requests), +max_consecutive_requests (network_constants_a.is_test_network () ? 1 : 10), stats (stats_a), votes_cache (cache_a), store (store_a), @@ -24,26 +27,37 @@ thread ([this]() { run (); }) void nano::request_aggregator::add (std::shared_ptr & channel_a, std::vector> const & hashes_roots_a) { assert (wallets.rep_counts ().voting > 0); + bool error = true; auto const endpoint (nano::transport::map_endpoint_to_v6 (channel_a->get_endpoint ())); nano::unique_lock lock (mutex); - auto & requests_by_endpoint (requests.get ()); - auto existing (requests_by_endpoint.find (endpoint)); - if (existing == requests_by_endpoint.end ()) + // Protecting from ever-increasing memory usage when request are consumed slower than generated + // Reject request if the oldest request has not yet been processed after its deadline + a modest margin + if (requests.empty () || (requests.get ().begin ()->deadline + 2 * this->max_delay > std::chrono::steady_clock::now ())) { - existing = requests_by_endpoint.emplace (channel_a).first; - } - requests_by_endpoint.modify (existing, [&hashes_roots_a, &channel_a, this](channel_pool & pool_a) { - // This extends the lifetime of the channel, which is acceptable up to max_delay - pool_a.channel = channel_a; - auto new_deadline (std::min (pool_a.start + this->max_delay, std::chrono::steady_clock::now () + this->small_delay)); - pool_a.deadline = new_deadline; - pool_a.hashes_roots.insert (pool_a.hashes_roots.begin (), hashes_roots_a.begin (), hashes_roots_a.end ()); - }); - if (requests.size () == 1) - { - lock.unlock (); - condition.notify_all (); + auto & requests_by_endpoint (requests.get ()); + auto existing (requests_by_endpoint.find (endpoint)); + if (existing == requests_by_endpoint.end ()) + { + existing = requests_by_endpoint.emplace (channel_a).first; + } + requests_by_endpoint.modify (existing, [&hashes_roots_a, &channel_a, &error, this](channel_pool & pool_a) { + // This extends the lifetime of the channel, which is acceptable up to max_delay + pool_a.channel = channel_a; + if (pool_a.hashes_roots.size () + hashes_roots_a.size () <= this->max_channel_requests) + { + error = false; + auto new_deadline (std::min (pool_a.start + this->max_delay, std::chrono::steady_clock::now () + this->small_delay)); + pool_a.deadline = new_deadline; + pool_a.hashes_roots.insert (pool_a.hashes_roots.begin (), hashes_roots_a.begin (), hashes_roots_a.end ()); + } + }); + if (requests.size () == 1) + { + lock.unlock (); + condition.notify_all (); + } } + stats.inc (nano::stat::type::aggregator, !error ? nano::stat::detail::aggregator_accepted : nano::stat::detail::aggregator_dropped); } void nano::request_aggregator::run () @@ -54,6 +68,7 @@ void nano::request_aggregator::run () lock.unlock (); condition.notify_all (); lock.lock (); + unsigned consecutive_requests = 0; while (!stopped) { if (!requests.empty ()) @@ -75,17 +90,26 @@ void nano::request_aggregator::run () lock.unlock (); // Generate votes for the remaining hashes generate (transaction, std::move (remaining), channel); + consecutive_requests = 0; + lock.lock (); + } + else if (++consecutive_requests == max_consecutive_requests) + { + lock.unlock (); + consecutive_requests = 0; 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 (); }); } } @@ -117,6 +141,17 @@ bool nano::request_aggregator::empty () 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; + std::sort (pool_a.hashes_roots.begin (), pool_a.hashes_roots.end (), [](pair const & pair1, pair const & pair2) { + return pair1.first < pair2.first; + }); + pool_a.hashes_roots.erase (std::unique (pool_a.hashes_roots.begin (), pool_a.hashes_roots.end (), [](pair const & pair1, pair const & pair2) { + return pair1.first == pair2.first; + }), + pool_a.hashes_roots.end ()); + + size_t cached_hashes = 0; std::vector to_generate; std::vector> cached_votes; for (auto const & hash_root : pool_a.hashes_roots) @@ -124,6 +159,7 @@ std::vector nano::request_aggregator::aggregate (nano::transac auto find_votes (votes_cache.find (hash_root.first)); if (!find_votes.empty ()) { + ++cached_hashes; cached_votes.insert (cached_votes.end (), find_votes.begin (), find_votes.end ()); } else if (!hash_root.first.is_zero () && store.block_exists (transaction_a, hash_root.first)) @@ -162,7 +198,7 @@ std::vector nano::request_aggregator::aggregate (nano::transac } else { - stats.inc (nano::stat::type::requests, nano::stat::detail::requests_ignored); + stats.inc (nano::stat::type::requests, nano::stat::detail::requests_unknown, stat::dir::in); } } } @@ -174,11 +210,12 @@ std::vector nano::request_aggregator::aggregate (nano::transac nano::confirm_ack confirm (vote); pool_a.channel->send (confirm); } - stats.add (nano::stat::type::requests, nano::stat::detail::requests_cached, stat::dir::in, cached_votes.size ()); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_cached_hashes, stat::dir::in, cached_hashes); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_cached_votes, stat::dir::in, cached_votes.size ()); return to_generate; } -void nano::request_aggregator::generate (nano::transaction const & transaction_a, std::vector const hashes_a, std::shared_ptr & channel_a) const +void nano::request_aggregator::generate (nano::transaction const & transaction_a, std::vector hashes_a, std::shared_ptr & channel_a) const { size_t generated_l = 0; auto i (hashes_a.begin ()); @@ -198,7 +235,8 @@ void nano::request_aggregator::generate (nano::transaction const & transaction_a this->votes_cache.add (vote); }); } - stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated, stat::dir::in, generated_l); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes, stat::dir::in, hashes_a.size ()); + stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_votes, stat::dir::in, generated_l); } std::unique_ptr nano::collect_container_info (nano::request_aggregator & aggregator, const std::string & name) diff --git a/nano/node/request_aggregator.hpp b/nano/node/request_aggregator.hpp index ceee0f7815..f1b5dee8d1 100644 --- a/nano/node/request_aggregator.hpp +++ b/nano/node/request_aggregator.hpp @@ -21,6 +21,7 @@ class votes_cache; class block_store; class wallets; class stat; +class node_config; /** * Pools together confirmation requests, separately for each endpoint. * Requests are added from network messages, and aggregated to minimize bandwidth and vote generation. Example: @@ -57,7 +58,7 @@ class request_aggregator final public: request_aggregator () = delete; - request_aggregator (nano::stat &, nano::network_constants const &, nano::votes_cache &, nano::block_store &, nano::wallets &); + request_aggregator (nano::network_constants const &, nano::node_config const & config, nano::stat & stats_a, nano::votes_cache &, nano::block_store &, nano::wallets &); /** Add a new request by \p channel_a for hashes \p hashes_roots_a */ void add (std::shared_ptr & channel_a, std::vector> const & hashes_roots_a); @@ -68,13 +69,16 @@ class request_aggregator final const std::chrono::milliseconds max_delay; const std::chrono::milliseconds small_delay; + const size_t max_channel_requests; 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; /** 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 const hashes_a, std::shared_ptr & channel_a) const; + 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; diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp new file mode 100644 index 0000000000..f0875fd21c --- /dev/null +++ b/nano/node/telemetry.cpp @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +std::chrono::milliseconds constexpr nano::telemetry_impl::cache_cutoff; + +nano::telemetry::telemetry (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a) : +network (network_a), +alarm (alarm_a), +worker (worker_a), +batch_request (std::make_shared (network, alarm, worker)) +{ +} + +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) +{ + nano::lock_guard guard (mutex); + if (!stopped) + { + batch_request->add (telemetry_data_a, endpoint_a); + + for (auto & request : single_requests) + { + request.second.impl->add (telemetry_data_a, endpoint_a); + } + } +} + +void nano::telemetry::get_metrics_random_peers_async (std::function const & callback_a) +{ + // These peers will only be used if there isn't an already ongoing batch telemetry request round + auto random_peers = network.random_set (network.size_sqrt (), network_params.protocol.telemetry_protocol_version_min); + nano::lock_guard guard (mutex); + if (!stopped && !random_peers.empty ()) + { + batch_request->get_metrics_async (random_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{ {}, false, all_received }); + } +} + +nano::telemetry_data_responses nano::telemetry::get_metrics_random_peers () +{ + std::promise promise; + get_metrics_random_peers_async ([&promise](telemetry_data_responses const & telemetry_data_responses_a) { + promise.set_value (telemetry_data_responses_a); + }); + + return promise.get_future ().get (); +} + +// 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) +{ + // This class is just + class ongoing_func_wrapper + { + public: + std::function ongoing_func; + }; + + auto wrapper = std::make_shared (); + // Keep calling ongoing_func while the peer is still being called + const auto & last_updated = single_request_data_a.last_updated; + wrapper->ongoing_func = [this, telemetry_impl_w = std::weak_ptr (single_request_data_a.impl), &last_updated, &endpoint_a, wrapper]() { + if (auto telemetry_impl = telemetry_impl_w.lock ()) + { + nano::lock_guard guard (this->mutex); + if (std::chrono::steady_clock::now () - telemetry_impl->cache_cutoff > last_updated && telemetry_impl->callbacks.empty ()) + { + this->single_requests.erase (endpoint_a); + } + else + { + // Request is still active, so call again + this->alarm.add (std::chrono::steady_clock::now () + telemetry_impl->cache_cutoff, wrapper->ongoing_func); + } + } + }; + + alarm.add (std::chrono::steady_clock::now () + single_request_data_a.impl->cache_cutoff, wrapper->ongoing_func); +} + +void nano::telemetry::update_cleanup_data (nano::endpoint const & endpoint_a, nano::telemetry::single_request_data & single_request_data_a, bool is_new_a) +{ + auto telemetry_impl = single_request_data_a.impl; + 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 (); + } +} + +void nano::telemetry::get_metrics_single_peer_async (std::shared_ptr const & channel_a, std::function const & callback_a) +{ + auto invoke_callback_with_error = [&callback_a]() { + auto const is_cached = false; + auto const error = true; + callback_a ({ nano::telemetry_data (), is_cached, error }); + }; + + nano::lock_guard guard (mutex); + if (!stopped) + { + if (channel_a && (channel_a->get_network_version () >= network_params.protocol.telemetry_protocol_version_min)) + { + auto pair = single_requests.emplace (channel_a->get_endpoint (), single_request_data{ std::make_shared (network, alarm, worker), std::chrono::steady_clock::now () }); + update_cleanup_data (pair.first->first, pair.first->second, pair.second); + + pair.first->second.impl->get_metrics_async ({ channel_a }, [callback_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) + { + assert (telemetry_data_responses_a.data.size () == 1); + callback_a ({ telemetry_data_responses_a.data.front (), telemetry_data_responses_a.is_cached, error }); + } + else + { + callback_a ({ nano::telemetry_data (), telemetry_data_responses_a.is_cached, error }); + } + }); + } + else + { + invoke_callback_with_error (); + } + } + else + { + invoke_callback_with_error (); + } +} + +nano::telemetry_data_response nano::telemetry::get_metrics_single_peer (std::shared_ptr const & channel_a) +{ + std::promise promise; + get_metrics_single_peer_async (channel_a, [&promise](telemetry_data_response const & single_metric_data_a) { + promise.set_value (single_metric_data_a); + }); + + return promise.get_future ().get (); +} + +size_t nano::telemetry::telemetry_data_size () +{ + 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 (); + }); + + if (batch_request) + { + total += batch_request->telemetry_data_size (); + } + return total; +} + +nano::telemetry_impl::telemetry_impl (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a) : +network (network_a), +alarm (alarm_a), +worker (worker_a) +{ +} + +void nano::telemetry_impl::flush_callbacks (nano::unique_lock & lk_a, bool cached_a) +{ + // Invoke all callbacks, it's possible that during the mutex unlock other callbacks were added, + // so check again and invoke those too + assert (lk_a.owns_lock ()); + invoking = true; + while (!callbacks.empty ()) + { + lk_a.unlock (); + invoke_callbacks (cached_a); + lk_a.lock (); + } + invoking = false; +} + +void nano::telemetry_impl::get_metrics_async (std::unordered_set> const & channels_a, std::function const & callback_a) +{ + { + assert (!channels_a.empty ()); + 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 (std::chrono::steady_clock::now () < (last_time + cache_cutoff)) + { + // 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 (auto this_l = this_w.lock ()) + { + nano::unique_lock lk (this_l->mutex); + const auto is_cached = true; + this_l->flush_callbacks (lk, is_cached); + } + }); + return; + } + + all_received = true; + 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 (); + }); + } + + fire_request_messages (channels_a); +} + +void nano::telemetry_impl::add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_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; + } + + current_telemetry_data_responses.push_back (telemetry_data_a); + channel_processed (lk, endpoint_a); +} + +void nano::telemetry_impl::invoke_callbacks (bool cached_a) +{ + decltype (callbacks) callbacks_l; + + { + // Copy callbacks so that they can be called outside of holding the lock + nano::lock_guard guard (mutex); + callbacks_l = callbacks; + current_telemetry_data_responses.clear (); + callbacks.clear (); + } + for (auto & callback : callbacks_l) + { + callback ({ cached_telemetry_data, cached_a, all_received }); + } +} + +void nano::telemetry_impl::channel_processed (nano::unique_lock & lk_a, nano::endpoint const & endpoint_a) +{ + assert (lk_a.owns_lock ()); + auto num_removed = required_responses.erase (endpoint_a); + if (num_removed > 0 && required_responses.empty ()) + { + assert (lk_a.owns_lock ()); + cached_telemetry_data = current_telemetry_data_responses; + + last_time = std::chrono::steady_clock::now (); + auto const is_cached = false; + flush_callbacks (lk_a, is_cached); + } +} + +void nano::telemetry_impl::fire_request_messages (std::unordered_set> 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) + { + assert (channel->get_network_version () >= network_params.protocol.telemetry_protocol_version_min); + + std::weak_ptr this_w (shared_from_this ()); + 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->all_received = false; + this_l->channel_processed (lk, endpoint); + } + } + }); + + // 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 () + cache_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->all_received = false; + this_l->channel_processed (lk, endpoint); + } + } + }); + } +} + +size_t nano::telemetry_impl::telemetry_data_size () +{ + nano::lock_guard guard (mutex); + return current_telemetry_data_responses.size (); +} + +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) + { + composite->add_component (collect_container_info (*telemetry.batch_request, "batch_request")); + } + composite->add_component (std::make_unique (container_info{ "single_requests", single_requests_count, sizeof (decltype (telemetry.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 (); + } + + 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; +} diff --git a/nano/node/telemetry.hpp b/nano/node/telemetry.hpp new file mode 100644 index 0000000000..72d4b1bdf9 --- /dev/null +++ b/nano/node/telemetry.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace nano +{ +class network; +class alarm; +class worker; +class telemetry; +namespace transport +{ + class channel; +} + +/* + * Holds a response from a telemetry request + */ +class telemetry_data_response +{ +public: + nano::telemetry_data data; + bool is_cached; + bool error; +}; + +/* + * Holds many responses from telemetry requests + */ +class telemetry_data_responses +{ +public: + std::vector data; + bool is_cached; + bool all_received; +}; + +/* + * 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 +{ +public: + telemetry_impl (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a); + +private: + // Class only available to the telemetry class + void get_metrics_async (std::unordered_set> const & channels_a, std::function const & callback_a); + void add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a); + size_t telemetry_data_size (); + + nano::network_params network_params; + // Anything older than this requires requesting metrics from other nodes + static std::chrono::milliseconds constexpr cache_cutoff{ 3000 }; + + // 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::vector current_telemetry_data_responses; + /* The metrics for the last request round */ + std::vector cached_telemetry_data; + std::unordered_set required_responses; + uint64_t round{ 0 }; + /* Currently executing callbacks */ + bool invoking{ false }; + + std::atomic all_received{ true }; + + nano::network & network; + nano::alarm & alarm; + nano::worker & worker; + + void invoke_callbacks (bool cached_a); + void channel_processed (nano::unique_lock & lk_a, nano::endpoint const & endpoint_a); + void flush_callbacks (nano::unique_lock & lk_a, bool cached_a); + void fire_request_messages (std::unordered_set> 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; +}; + +std::unique_ptr collect_container_info (telemetry_impl & telemetry_impl, const std::string & name); + +class telemetry +{ +public: + telemetry (nano::network & network_a, nano::alarm & alarm_a, nano::worker & worker_a); + + /* + * Add telemetry metrics received from this endpoint. + * Should this be unsolicited, it will not be added. + */ + void add (nano::telemetry_data const & telemetry_data_a, nano::endpoint const & endpoint_a); + + /* + * Collects metrics from square root number of peers and invokes the callback when complete. + */ + void get_metrics_random_peers_async (std::function const & callback_a); + + /* + * A blocking version of get_metrics_random_peers_async (). + */ + telemetry_data_responses get_metrics_random_peers (); + + /* + * This makes a telemetry request to the specific channel + */ + void get_metrics_single_peer_async (std::shared_ptr const &, std::function const & callback_a); + + /* + * A blocking version of get_metrics_single_peer_async + */ + telemetry_data_response get_metrics_single_peer (std::shared_ptr const &); + + /* + * Return the number of node metrics collected + */ + size_t telemetry_data_size (); + + /* + * Stop the telemetry processor + */ + void stop (); + +private: + nano::network & network; + nano::alarm & alarm; + nano::worker & worker; + + 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 () }; + }; + + 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; + 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); + + friend class node_telemetry_multiple_single_request_clearing_Test; + friend std::unique_ptr collect_container_info (telemetry &, const std::string &); +}; + +std::unique_ptr collect_container_info (telemetry & telemetry, const std::string & name); +} \ No newline at end of file diff --git a/nano/node/transport/tcp.cpp b/nano/node/transport/tcp.cpp index 2be9588f30..27e51a000c 100644 --- a/nano/node/transport/tcp.cpp +++ b/nano/node/transport/tcp.cpp @@ -152,7 +152,7 @@ std::shared_ptr nano::transport::tcp_channels::fin return result; } -std::unordered_set> nano::transport::tcp_channels::random_set (size_t count_a) const +std::unordered_set> nano::transport::tcp_channels::random_set (size_t count_a, uint8_t min_version) const { std::unordered_set> result; result.reserve (count_a); @@ -167,7 +167,12 @@ std::unordered_set> nano::transport::t for (auto i (0); i < random_cutoff && result.size () < count_a; ++i) { auto index (nano::random_pool::generate_word32 (0, static_cast (peers_size - 1))); - result.insert (channels.get ()[index].channel); + + auto channel = channels.get ()[index].channel; + if (channel->get_network_version () >= min_version && !channel->server) + { + result.insert (channel); + } } } return result; diff --git a/nano/node/transport/tcp.hpp b/nano/node/transport/tcp.hpp index a6efe8ce9d..50cbf798ad 100644 --- a/nano/node/transport/tcp.hpp +++ b/nano/node/transport/tcp.hpp @@ -84,7 +84,7 @@ namespace transport size_t size () const; std::shared_ptr find_channel (nano::tcp_endpoint const &) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + std::unordered_set> random_set (size_t, uint8_t = 0) const; bool store_all (bool = true); std::shared_ptr find_node_id (nano::account const &); // Get the next peer for attempting a tcp connection diff --git a/nano/node/transport/transport.cpp b/nano/node/transport/transport.cpp index 37f1731a30..490a7d6bdc 100644 --- a/nano/node/transport/transport.cpp +++ b/nano/node/transport/transport.cpp @@ -47,6 +47,14 @@ class callback_visitor : public nano::message_visitor { result = nano::stat::detail::node_id_handshake; } + void telemetry_req (nano::telemetry_req const & message_a) override + { + result = nano::stat::detail::telemetry_req; + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + result = nano::stat::detail::telemetry_ack; + } nano::stat::detail result; }; } diff --git a/nano/node/transport/udp.cpp b/nano/node/transport/udp.cpp index 801dc48401..20b382b747 100644 --- a/nano/node/transport/udp.cpp +++ b/nano/node/transport/udp.cpp @@ -147,7 +147,7 @@ std::shared_ptr nano::transport::udp_channels::cha return result; } -std::unordered_set> nano::transport::udp_channels::random_set (size_t count_a) const +std::unordered_set> nano::transport::udp_channels::random_set (size_t count_a, uint8_t min_version) const { std::unordered_set> result; result.reserve (count_a); @@ -162,7 +162,11 @@ std::unordered_set> nano::transport::u for (auto i (0); i < random_cutoff && result.size () < count_a; ++i) { auto index (nano::random_pool::generate_word32 (0, static_cast (peers_size - 1))); - result.insert (channels.get ()[index].channel); + auto channel = channels.get ()[index].channel; + if (channel->get_network_version () >= min_version) + { + result.insert (channel); + } } } return result; @@ -428,6 +432,14 @@ class udp_message_visitor : public nano::message_visitor { assert (false); } + void telemetry_req (nano::telemetry_req const & message_a) override + { + message (message_a); + } + void telemetry_ack (nano::telemetry_ack const & message_a) override + { + message (message_a); + } void node_id_handshake (nano::node_id_handshake const & message_a) override { if (node.config.logging.network_node_id_handshake_logging ()) @@ -552,6 +564,12 @@ void nano::transport::udp_channels::receive_action (nano::message_buffer * data_ case nano::message_parser::parse_status::invalid_node_id_handshake_message: node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_node_id_handshake_message); break; + case nano::message_parser::parse_status::invalid_telemetry_req_message: + node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_telemetry_req_message); + break; + case nano::message_parser::parse_status::invalid_telemetry_ack_message: + node.stats.inc (nano::stat::type::udp, nano::stat::detail::invalid_telemetry_ack_message); + break; case nano::message_parser::parse_status::outdated_version: node.stats.inc (nano::stat::type::udp, nano::stat::detail::outdated_version); break; diff --git a/nano/node/transport/udp.hpp b/nano/node/transport/udp.hpp index 67a315396e..9945546d0c 100644 --- a/nano/node/transport/udp.hpp +++ b/nano/node/transport/udp.hpp @@ -69,7 +69,7 @@ namespace transport size_t size () const; std::shared_ptr channel (nano::endpoint const &) const; void random_fill (std::array &) const; - std::unordered_set> random_set (size_t) const; + std::unordered_set> random_set (size_t, uint8_t = 0) const; bool store_all (bool = true); std::shared_ptr find_node_id (nano::account const &); void clean_node_id (nano::account const &); diff --git a/nano/node/vote_processor.cpp b/nano/node/vote_processor.cpp index 6403f8ec0a..b77766e98b 100644 --- a/nano/node/vote_processor.cpp +++ b/nano/node/vote_processor.cpp @@ -15,10 +15,9 @@ #include -nano::vote_processor::vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::block_store & store_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a) : +nano::vote_processor::vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a) : checker (checker_a), active (active_a), -store (store_a), observers (observers_a), stats (stats_a), config (config_a), @@ -54,7 +53,7 @@ void nano::vote_processor::process_loop () { if (!votes.empty ()) { - std::deque, std::shared_ptr>> votes_l; + decltype (votes) votes_l; votes_l.swap (votes); log_this_iteration = false; @@ -70,20 +69,6 @@ void nano::vote_processor::process_loop () is_active = true; lock.unlock (); verify_votes (votes_l); - { - auto transaction (store.tx_begin_read ()); - uint64_t count (1); - for (auto & i : votes_l) - { - vote_blocking (transaction, i.first, i.second, true); - // Free active_transactions mutex each 100 processed votes - if (count % 100 == 0) - { - transaction.refresh (); - } - count++; - } - } lock.lock (); is_active = false; @@ -155,7 +140,7 @@ void nano::vote_processor::vote (std::shared_ptr vote_a, std::shared } } -void nano::vote_processor::verify_votes (std::deque, std::shared_ptr>> & votes_a) +void nano::vote_processor::verify_votes (decltype (votes) const & votes_a) { auto size (votes_a.size ()); std::vector messages; @@ -178,37 +163,26 @@ void nano::vote_processor::verify_votes (std::deque result; auto i (0); for (auto const & vote : votes_a) { assert (verifications[i] == 1 || verifications[i] == 0); if (verifications[i] == 1) { - result.push_back (vote); + vote_blocking (vote.first, vote.second, true); } ++i; } - votes_a.swap (result); } // node.active.mutex lock required -nano::vote_code nano::vote_processor::vote_blocking (nano::transaction const & transaction_a, std::shared_ptr vote_a, std::shared_ptr channel_a, bool validated) +nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr vote_a, std::shared_ptr channel_a, bool validated) { auto result (nano::vote_code::invalid); if (validated || !vote_a->validate ()) { result = active.vote (vote_a); observers.vote.notify (vote_a, channel_a, result); - // This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them - // Only do this if the sequence number is significantly different to account for network reordering - // Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase - auto max_vote (store.vote_max (transaction_a, vote_a)); - if (max_vote->sequence > vote_a->sequence + 10000) - { - nano::confirm_ack confirm (max_vote); - channel_a->send (confirm); // this is non essential traffic as it will be resolicited if not received - } } std::string status; switch (result) diff --git a/nano/node/vote_processor.hpp b/nano/node/vote_processor.hpp index cea715fe49..0a8e3ef2cc 100644 --- a/nano/node/vote_processor.hpp +++ b/nano/node/vote_processor.hpp @@ -32,11 +32,11 @@ namespace transport class vote_processor final { public: - explicit vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::block_store & store_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a); + explicit vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::node_observers & observers_a, nano::stat & stats_a, nano::node_config & config_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::ledger & ledger_a, nano::network_params & network_params_a); void vote (std::shared_ptr, std::shared_ptr); /** Note: node.active.mutex lock is required */ - nano::vote_code vote_blocking (nano::transaction const &, std::shared_ptr, std::shared_ptr, bool = false); - void verify_votes (std::deque, std::shared_ptr>> &); + nano::vote_code vote_blocking (std::shared_ptr, std::shared_ptr, bool = false); + void verify_votes (std::deque, std::shared_ptr>> const &); void flush (); void calculate_weights (); void stop (); @@ -46,7 +46,6 @@ class vote_processor final nano::signature_checker & checker; nano::active_transactions & active; - nano::block_store & store; nano::node_observers & observers; nano::stat & stats; nano::node_config & config; diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index c20570e3ff..3e3dc9ae5d 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1253,7 +1253,7 @@ bool nano::wallet::search_pending () // Don't search pending for watch-only accounts if (!nano::wallet_value (i->second).key.is_zero ()) { - for (auto j (wallets.node.store.pending_begin (block_transaction, nano::pending_key (account, 0))); nano::pending_key (j->first).account == account; ++j) + for (auto j (wallets.node.store.pending_begin (block_transaction, nano::pending_key (account, 0))), k (wallets.node.store.pending_end ()); j != k && nano::pending_key (j->first).account == account; ++j) { nano::pending_key key (j->first); auto hash (key.hash); @@ -1318,7 +1318,7 @@ uint32_t nano::wallet::deterministic_check (nano::transaction const & transactio else { // Check if there are pending blocks for account - for (auto ii (wallets.node.store.pending_begin (block_transaction, nano::pending_key (pair.pub, 0))); nano::pending_key (ii->first).account == pair.pub; ++ii) + for (auto ii (wallets.node.store.pending_begin (block_transaction, nano::pending_key (pair.pub, 0))), nn (wallets.node.store.pending_end ()); ii != nn && nano::pending_key (ii->first).account == pair.pub; ++ii) { index = i; n = i + 64 + (i / 64); diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index d81ff87851..c5e2f3361b 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -2672,6 +2672,45 @@ TEST (rpc, pending) check_block_response_count (0); } +TEST (rpc, pending_burn) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::account burn (0); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, burn, 100)); + scoped_io_thread_name_change scoped_thread_name_io; + system.deadline_set (5s); + while (node->active.active (*block1)) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "pending"); + request.put ("account", burn.to_account ()); + request.put ("count", "100"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & blocks_node (response.json.get_child ("blocks")); + ASSERT_EQ (1, blocks_node.size ()); + nano::block_hash hash (blocks_node.begin ()->second.get ("")); + ASSERT_EQ (block1->hash (), hash); + } +} + TEST (rpc, search_pending) { nano::system system; @@ -7792,3 +7831,201 @@ TEST (rpc, receive_work_disabled) ASSERT_EQ (std::error_code (nano::error_common::disabled_work_generation).message (), response.json.get ("error")); } } + +namespace +{ +void compare_default_test_result_data (test_response & response, nano::node const & node_server_a) +{ + ASSERT_EQ (200, response.status); + ASSERT_FALSE (response.json.get ("cached")); + ASSERT_EQ (1, response.json.get ("block_count")); + ASSERT_EQ (1, response.json.get ("cemented_count")); + ASSERT_EQ (0, response.json.get ("unchecked_count")); + ASSERT_EQ (1, response.json.get ("account_count")); + ASSERT_EQ (node_server_a.config.bandwidth_limit, response.json.get ("bandwidth_cap")); + ASSERT_EQ (1, response.json.get ("peer_count")); + ASSERT_EQ (node_server_a.network_params.protocol.protocol_version, response.json.get ("protocol_version_number")); + ASSERT_EQ (nano::get_major_node_version (), response.json.get ("vendor_version")); + ASSERT_GE (100, response.json.get ("uptime")); + ASSERT_EQ (nano::genesis ().hash ().to_string (), response.json.get ("genesis_block")); +} +} + +TEST (rpc, node_telemetry_single) +{ + nano::system system (1); + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + // Wait until peers are stored as they are done in the background + auto peers_stored = false; + while (!peers_stored) + { + ASSERT_NO_ERROR (system.poll ()); + + auto transaction = system.nodes.back ()->store.tx_begin_read (); + peers_stored = system.nodes.back ()->store.peer_count (transaction) != 0; + } + + // Missing port + boost::property_tree::ptree request; + auto node = system.nodes.front (); + request.put ("action", "node_telemetry"); + request.put ("address", "not_a_valid_address"); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_rpc::requires_port_and_address).message (), response.json.get ("error")); + } + + // Missing address + request.erase ("address"); + request.put ("port", 65); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_rpc::requires_port_and_address).message (), response.json.get ("error")); + } + + // Try with invalid address + request.put ("address", "not_a_valid_address"); + request.put ("port", 65); + + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_common::invalid_ip_address).message (), response.json.get ("error")); + } + + // Then invalid port + request.put ("address", (boost::format ("%1%") % node->network.endpoint ().address ()).str ()); + request.put ("port", "invalid port"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_common::invalid_port).message (), response.json.get ("error")); + } + + // Use correctly formed address and port + request.put ("port", node->network.endpoint ().port ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + compare_default_test_result_data (response, *node); + } +} + +TEST (rpc, node_telemetry_random) +{ + nano::system system (1); + auto & node1 = *add_ipc_enabled_node (system); + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); + nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); + rpc.start (); + + // Wait until peers are stored as they are done in the background + auto peers_stored = false; + while (!peers_stored) + { + ASSERT_NO_ERROR (system.poll ()); + + auto transaction = system.nodes.back ()->store.tx_begin_read (); + peers_stored = system.nodes.back ()->store.peer_count (transaction) != 0; + } + + boost::property_tree::ptree request; + auto node = system.nodes.front (); + request.put ("action", "node_telemetry"); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + ASSERT_FALSE (response.json.get ("cached")); + ASSERT_EQ (1, response.json.get ("block_count")); + ASSERT_EQ (1, response.json.get ("cemented_count")); + ASSERT_EQ (0, response.json.get ("unchecked_count")); + ASSERT_EQ (1, response.json.get ("account_count")); + ASSERT_EQ (node->config.bandwidth_limit, response.json.get ("bandwidth_cap")); + ASSERT_EQ (1, response.json.get ("peer_count")); + ASSERT_EQ (node->network_params.protocol.protocol_version, response.json.get ("protocol_version_number")); + ASSERT_EQ (nano::get_major_node_version (), response.json.get ("vendor_version")); + ASSERT_GE (100, response.json.get ("uptime")); + ASSERT_EQ (nano::genesis ().hash ().to_string (), response.json.get ("genesis_block")); + } + + request.put ("raw", "true"); + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + + // This may fail if the response has taken longer than the cache cutoff time. + ASSERT_TRUE (response.json.get ("cached")); + + auto & all_metrics = response.json.get_child ("metrics"); + std::vector> raw_metrics_json_l; + for (auto & metrics_pair : all_metrics) + { + auto & metrics = metrics_pair.second; + raw_metrics_json_l.emplace_back (metrics.get ("block_count"), metrics.get ("cemented_count"), metrics.get ("unchecked_count"), metrics.get ("account_count"), metrics.get ("bandwidth_cap"), metrics.get ("peer_count"), metrics.get ("protocol_version_number"), metrics.get ("vendor_version"), metrics.get ("uptime"), metrics.get ("genesis_block")); + } + + ASSERT_EQ (1, raw_metrics_json_l.size ()); + auto const & metrics = raw_metrics_json_l.front (); + ASSERT_EQ (1, std::get<0> (metrics)); + ASSERT_EQ (1, std::get<1> (metrics)); + ASSERT_EQ (0, std::get<2> (metrics)); + ASSERT_EQ (1, std::get<3> (metrics)); + ASSERT_EQ (node->config.bandwidth_limit, std::get<4> (metrics)); + ASSERT_EQ (1, std::get<5> (metrics)); + ASSERT_EQ (node->network_params.protocol.protocol_version, std::get<6> (metrics)); + ASSERT_EQ (nano::get_major_node_version (), std::get<7> (metrics)); + ASSERT_GE (100, std::get<8> (metrics)); + ASSERT_EQ (nano::genesis ().hash ().to_string (), std::get<9> (metrics)); +} diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 6e5d70c8a0..9adb292112 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -161,7 +161,6 @@ nano::keypair const & nano::test_genesis_key (test_constants.test_genesis_key); nano::account const & nano::nano_test_account (test_constants.nano_test_account); std::string const & nano::nano_test_genesis (test_constants.nano_test_genesis); nano::account const & nano::genesis_account (test_constants.genesis_account); -std::string const & nano::genesis_block (test_constants.genesis_block); nano::uint128_t const & nano::genesis_amount (test_constants.genesis_amount); nano::account const & nano::burn_account (test_constants.burn_account); diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 3cf07ac11e..6fe99d6980 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -347,7 +347,7 @@ class protocol_constants protocol_constants (nano::nano_networks network_a); /** Current protocol version */ - uint8_t protocol_version = 0x11; + uint8_t protocol_version = 0x12; /** Minimum accepted protocol version */ uint8_t protocol_version_min = 0x10; @@ -360,6 +360,9 @@ class protocol_constants /** Do not start TCP realtime network connections to nodes older than this version */ uint8_t tcp_realtime_protocol_version_min = 0x11; + + /** Do not request telemetry metrics to nodes older than this version */ + uint8_t telemetry_protocol_version_min = 0x12; }; /** Genesis keys and ledger constants for network variants */