diff --git a/.github/workflows/develop_branch_dockers_deploy.yml b/.github/workflows/develop_branch_dockers_deploy.yml deleted file mode 100644 index 18ce83de7a..0000000000 --- a/.github/workflows/develop_branch_dockers_deploy.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Develop Branch Dockers Deploy - -on: - push: - branches: - - develop - -jobs: - linux_job: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - with: - submodules: "recursive" - - name: Fetch Deps - run: ci/actions/linux/install_deps.sh - - name: Deploy Docker (ghcr.io) - run: ci/actions/linux/ghcr-deploy-env.sh - env: - DOCKER_REGISTRY: ghcr.io - DOCKER_USER: ${{ github.repository_owner }} - DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - name: Check if secrets.DOCKER_PASSWORD exists - run: echo "DOCKER_PASSWORD_EXISTS=${{ secrets.DOCKER_PASSWORD != '' }}" >> $GITHUB_ENV - - name: Deploy Docker (nanocurrency/nano-env) - if: env.DOCKER_PASSWORD_EXISTS == 'true' - run: ci/actions/linux/docker-deploy-env.sh - env: - DOCKER_HUB: ${{ secrets.DOCKER_HUB }} - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b93fac654..296491d1fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,8 @@ endif() if(MSVC) add_definitions(/MP) + add_definitions( + -D_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) # Suppress iterator warning endif() set(CPACK_PACKAGE_VENDOR "Nano Currency") @@ -114,8 +116,8 @@ set(NANO_FUZZER_TEST OFF CACHE BOOL "") set(NANO_ASIO_HANDLER_TRACKING - 0 - CACHE STRING "") + OFF + CACHE BOOL "") set(NANO_ROCKSDB_TOOLS OFF CACHE BOOL "") @@ -151,9 +153,8 @@ if(${NANO_TIMED_LOCKS} GREATER 0) endif() endif() -if(${NANO_ASIO_HANDLER_TRACKING} GREATER 0) - add_definitions(-DNANO_ASIO_HANDLER_TRACKING=${NANO_ASIO_HANDLER_TRACKING} - -DBOOST_ASIO_ENABLE_HANDLER_TRACKING) +if(NANO_ASIO_HANDLER_TRACKING) + add_definitions(-DBOOST_ASIO_ENABLE_HANDLER_TRACKING) endif() option(NANO_SIMD_OPTIMIZATIONS diff --git a/README.md b/README.md index 8d09eb63f9..a6355a2640 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,11 @@ -[![Live Artifacts](https://github.com/nanocurrency/nano-node/workflows/Live/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ALive) -[![Beta Artifacts](https://github.com/nanocurrency/nano-node/workflows/Beta/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ABeta) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nanocurrency/nano-node)](https://github.com/nanocurrency/nano-node/releases/latest) [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/nanocurrency/nano-node?color=darkblue&label=beta)](https://github.com/nanocurrency/nano-node/tags) +[![Nano CT Status](https://raw.githubusercontent.com/gr0vity-dev/nano-node-builder/main/status_latest.svg)](https://ct.bnano.info) [![Coverage Status](https://coveralls.io/repos/github/nanocurrency/nano-node/badge.svg?branch=develop)](https://coveralls.io/github/nanocurrency/nano-node?branch=develop) -[![Tests](https://github.com/nanocurrency/nano-node/workflows/Tests/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3ATests) -[![RelWithDebug Tests](https://github.com/nanocurrency/nano-node/workflows/Release%20Tests/badge.svg)](https://github.com/nanocurrency/nano-node/actions?query=workflow%3A%22Release+Tests%22) [![Discord](https://img.shields.io/badge/discord-join%20chat-orange.svg)](https://chat.nano.org) -[![Nano CT Status](https://raw.githubusercontent.com/gr0vity-dev/nano-node-builder/main/status_latest.svg)](https://ct.bnano.info) --- diff --git a/SECURITY.md b/SECURITY.md index 74e6407f2a..1db434e63b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,10 +21,5 @@ To report security issues in the Nano protocol, please send an email to security | GitHub Username | Email | GPG Pubkey | |-----------------------|--------|-----------------| | [clemahieu](https://github.com/clemahieu) | clemahieu { at } gmail.com | [clemahieu.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/clemahieu.asc) | -| [argakiig](https://github.com/argakiig) | russel { at } nano.org | [argakiig.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/argakiig.asc) | -| [sergiysw](https://github.com/sergiysw) | sergiysw { at } gmail.com | [sergiysw.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/sergiysw.asc) | -| [zhyatt](https://github.com/zhyatt) | zach { at } nano.org | [zhyatt.asc](https://github.com/nanocurrency/nano-node/blob/develop/etc/gpg/zhyatt.asc) | For details on how to send a GPG encrypted email, see the tutorial here: https://www.linode.com/docs/security/encryption/gpg-keys-to-send-encrypted-messages/. - -For general support and other non-sensitive inquiries, please visit https://forum.nano.org. diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 2db941e69c..28f6f553a8 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -4,14 +4,15 @@ add_executable( fakes/websocket_client.hpp fakes/work_peer.hpp active_elections.cpp + assert.cpp async.cpp backlog.cpp block.cpp block_store.cpp - blockprocessor.cpp + block_processor.cpp bootstrap.cpp - bootstrap_ascending.cpp bootstrap_server.cpp + bucketing.cpp cli.cpp confirmation_solicitor.cpp confirming_set.cpp @@ -26,6 +27,7 @@ add_executable( ipc.cpp ledger.cpp ledger_confirm.cpp + ledger_priority.cpp locks.cpp logging.cpp message.cpp @@ -37,10 +39,14 @@ add_executable( node.cpp numbers.cpp object_stream.cpp + observer_set.cpp + online_reps.cpp optimistic_scheduler.cpp processing_queue.cpp processor_service.cpp + random.cpp random_pool.cpp + rate_limiting.cpp rep_crawler.cpp receivable.cpp peer_history.cpp @@ -52,6 +58,7 @@ add_executable( signal_manager.cpp socket.cpp system.cpp + tcp_listener.cpp telemetry.cpp thread_pool.cpp throttle.cpp diff --git a/nano/core_test/active_elections.cpp b/nano/core_test/active_elections.cpp index f592ea133c..ab0bb87f53 100644 --- a/nano/core_test/active_elections.cpp +++ b/nano/core_test/active_elections.cpp @@ -3,14 +3,17 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -121,8 +124,9 @@ TEST (active_elections, confirm_frontier) nano::node_flags node_flags; node_flags.disable_request_loop = true; node_flags.disable_ongoing_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - auto & node1 = *system.add_node (node_flags); + nano::node_config node_config; + node_config.bootstrap.enable = false; + auto & node1 = *system.add_node (node_config, node_flags); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); // we cannot use the same block instance on 2 different nodes, so make a copy @@ -134,10 +138,11 @@ TEST (active_elections, confirm_frontier) // The rep crawler would otherwise request confirmations in order to find representatives nano::node_flags node_flags2; node_flags2.disable_ongoing_bootstrap = true; - node_flags2.disable_ascending_bootstrap = true; node_flags2.disable_rep_crawler = true; + nano::node_config node_config2; + node_config2.bootstrap.enable = false; // start node2 later so that we do not get the gossip traffic - auto & node2 = *system.add_node (node_flags2); + auto & node2 = *system.add_node (node_config2, node_flags2); // Add representative to disabled rep crawler auto peers (node2.network.random_set (1)); @@ -166,7 +171,7 @@ TEST (active_elections, DISABLED_keep_local) // Bound to 2, won't drop wallet created transactions, but good to test dropping remote node_config.active_elections.size = 2; // Disable frontier confirmation to allow the test to finish before - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); auto & wallet (*system.wallet (0)); @@ -326,7 +331,7 @@ TEST (inactive_votes_cache, existing_vote) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); nano::block_hash latest (node.latest (nano::dev::genesis_key.pub)); nano::keypair key; @@ -380,7 +385,7 @@ TEST (inactive_votes_cache, multiple_votes) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); nano::keypair key1; nano::block_builder builder; @@ -433,9 +438,9 @@ TEST (inactive_votes_cache, election_start) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; - node_config.priority_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.backlog_scan.enable = false; + node_config.priority_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; auto & node = *system.add_node (node_config); nano::block_hash latest (node.latest (nano::dev::genesis_key.pub)); nano::keypair key1, key2; @@ -510,6 +515,7 @@ TEST (inactive_votes_cache, election_start) node.vote_processor.vote (vote2, std::make_shared (node, node)); // Only election for send1 should start, other blocks are missing dependencies and don't have enough final weight ASSERT_TIMELY_EQ (5s, 1, node.active.size ()); + ASSERT_TRUE (node.vote_router.contains (send1->hash ())); ASSERT_TRUE (node.vote_router.active (send1->hash ())); // Confirm elections with weight quorum @@ -517,7 +523,8 @@ TEST (inactive_votes_cache, election_start) node.vote_processor.vote (vote0, std::make_shared (node, node)); ASSERT_TIMELY_EQ (5s, 0, node.active.size ()); ASSERT_TIMELY_EQ (5s, 5, node.ledger.cemented_count ()); - ASSERT_TRUE (nano::test::confirmed (node, { send1, send2, open1, open2 })); + // Confirmation on disk may lag behind cemented_count cache + ASSERT_TIMELY (5s, nano::test::confirmed (node, { send1, send2, open1, open2 })); // A late block arrival also checks the inactive votes cache ASSERT_TRUE (node.active.empty ()); @@ -539,7 +546,7 @@ TEST (active_elections, vote_replays) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.enable_voting = false; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); nano::keypair key; nano::state_block_builder builder; @@ -695,7 +702,7 @@ TEST (active_elections, republish_winner) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); node_config.peering_port = system.get_available_port (); auto & node2 = *system.add_node (node_config); @@ -761,7 +768,7 @@ TEST (active_elections, fork_filter_cleanup) nano::test::system system{}; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); nano::keypair key{}; @@ -842,7 +849,7 @@ TEST (active_elections, fork_replacement_tally) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 (*system.add_node (node_config)); size_t const reps_count = 20; @@ -896,6 +903,7 @@ TEST (active_elections, fork_replacement_tally) .build (); // Forks without votes + std::shared_ptr election; for (auto i (0); i < reps_count; i++) { auto fork = builder.make_block () @@ -908,10 +916,12 @@ TEST (active_elections, fork_replacement_tally) .work (*system.work.generate (latest)) .build (); node1.process_active (fork); + + // Assert election exists and is the same for each fork + ASSERT_TIMELY (1s, election = node1.active.election (fork->qualified_root ())); } // Check overflow of blocks - std::shared_ptr election; ASSERT_TIMELY (5s, election = node1.active.election (send_last->qualified_root ())); ASSERT_TIMELY_EQ (5s, max_blocks, election->blocks ().size ()); @@ -961,7 +971,7 @@ TEST (active_elections, fork_replacement_tally) node_config.peering_port = system.get_available_port (); auto & node2 (*system.add_node (node_config)); node1.network.filter.clear (); - node2.network.flood_block (send_last); + node2.network.flood_block (send_last, nano::transport::traffic_type::test); ASSERT_TIMELY (3s, node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) > 0); // Correct block without votes is ignored @@ -975,7 +985,7 @@ TEST (active_elections, fork_replacement_tally) // ensure vote arrives before the block ASSERT_TIMELY_EQ (5s, 1, node1.vote_cache.find (send_last->hash ()).size ()); node1.network.filter.clear (); - node2.network.flood_block (send_last); + node2.network.flood_block (send_last, nano::transport::traffic_type::test); ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) > 1); // the send_last block should replace one of the existing block of the election because it has higher vote weight @@ -999,7 +1009,7 @@ TEST (active_elections, confirmation_consistency) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); for (unsigned i = 0; i < 10; ++i) @@ -1091,7 +1101,7 @@ TEST (active_elections, activate_account_chain) nano::test::system system; nano::node_flags flags; nano::node_config config = system.default_config (); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config, flags); nano::keypair key; @@ -1183,7 +1193,7 @@ TEST (active_elections, activate_inactive) nano::test::system system; nano::node_flags flags; nano::node_config config = system.default_config (); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config, flags); nano::keypair key; @@ -1333,8 +1343,8 @@ TEST (active_elections, limit_vote_hinted_elections) nano::test::system system; nano::node_config config = system.default_config (); const int aec_limit = 10; - config.backlog_population.enable = false; - config.optimistic_scheduler.enabled = false; + config.backlog_scan.enable = false; + config.optimistic_scheduler.enable = false; config.active_elections.size = aec_limit; config.active_elections.hinted_limit_percentage = 10; // Should give us a limit of 1 hinted election auto & node = *system.add_node (config); @@ -1442,10 +1452,10 @@ TEST (active_elections, broadcast_block_on_activation) nano::node_config config1 = system.default_config (); // Deactivates elections on both nodes. config1.active_elections.size = 0; - config1.bootstrap_ascending.enable = false; + config1.bootstrap.enable = false; nano::node_config config2 = system.default_config (); config2.active_elections.size = 0; - config2.bootstrap_ascending.enable = false; + config2.bootstrap.enable = false; nano::node_flags flags; // Disables bootstrap listener to make sure the block won't be shared by this channel. flags.disable_bootstrap_listener = true; diff --git a/nano/core_test/assert.cpp b/nano/core_test/assert.cpp new file mode 100644 index 0000000000..32a40a3000 --- /dev/null +++ b/nano/core_test/assert.cpp @@ -0,0 +1,15 @@ +#include + +#include + +TEST (assert_DeathTest, debug_assert) +{ + debug_assert (true); + ASSERT_DEATH (debug_assert (false), ".*Assertion \\(false\\) failed.*"); +} + +TEST (assert_DeathTest, release_assert) +{ + release_assert (true); + ASSERT_DEATH (release_assert (false), ".*Assertion \\(false\\) failed.*"); +} \ No newline at end of file diff --git a/nano/core_test/backlog.cpp b/nano/core_test/backlog.cpp index 21a2e80ebc..7e1cf5da1c 100644 --- a/nano/core_test/backlog.cpp +++ b/nano/core_test/backlog.cpp @@ -22,10 +22,12 @@ TEST (backlog, population) nano::test::system system{}; auto & node = *system.add_node (); - node.backlog.activate_callback.add ([&] (nano::secure::transaction const & transaction, nano::account const & account) { + node.backlog_scan.batch_activated.add ([&] (auto const & batch) { nano::lock_guard lock{ mutex }; - - activated.insert (account); + for (auto const & info : batch) + { + activated.insert (info.account); + } }); auto blocks = nano::test::setup_independent_blocks (system, node, 256); diff --git a/nano/core_test/block.cpp b/nano/core_test/block.cpp index f5dd614b3d..fe90835fea 100644 --- a/nano/core_test/block.cpp +++ b/nano/core_test/block.cpp @@ -1,6 +1,9 @@ +#include #include #include -#include +#include +#include +#include #include #include diff --git a/nano/core_test/blockprocessor.cpp b/nano/core_test/block_processor.cpp similarity index 100% rename from nano/core_test/blockprocessor.cpp rename to nano/core_test/block_processor.cpp diff --git a/nano/core_test/block_store.cpp b/nano/core_test/block_store.cpp index 491741708f..c8033103e5 100644 --- a/nano/core_test/block_store.cpp +++ b/nano/core_test/block_store.cpp @@ -1,11 +1,13 @@ #include +#include #include +#include #include #include #include #include #include -#include +#include #include #include #include @@ -902,21 +904,6 @@ TEST (block_store, cemented_count_cache) ASSERT_EQ (1, ledger.cemented_count ()); } -TEST (block_store, block_random) -{ - nano::logger logger; - auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants); - { - nano::ledger_cache ledger_cache{ store->rep_weight }; - auto transaction (store->tx_begin_write ()); - store->initialize (transaction, ledger_cache, nano::dev::constants); - } - auto transaction (store->tx_begin_read ()); - auto block (store->block.random (transaction)); - ASSERT_NE (nullptr, block); - ASSERT_EQ (*block, *nano::dev::genesis); -} - TEST (block_store, pruned_random) { nano::logger logger; @@ -1629,11 +1616,8 @@ TEST (block_store, final_vote) ASSERT_EQ (store->final_vote.count (transaction), 0); store->final_vote.put (transaction, qualified_root, nano::block_hash (2)); ASSERT_EQ (store->final_vote.count (transaction), 1); - // Clearing with incorrect root shouldn't remove - store->final_vote.clear (transaction, qualified_root.previous ()); - ASSERT_EQ (store->final_vote.count (transaction), 1); // Clearing with correct root should remove - store->final_vote.clear (transaction, qualified_root.root ()); + store->final_vote.del (transaction, qualified_root); ASSERT_EQ (store->final_vote.count (transaction), 0); } } diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp index 3d1fc18b4c..ccebd97cd4 100644 --- a/nano/core_test/bootstrap.cpp +++ b/nano/core_test/bootstrap.cpp @@ -1,2115 +1,574 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include -#include #include #include #include -using namespace std::chrono_literals; - -// If the account doesn't exist, current == end so there's no iteration -TEST (bulk_pull, no_address) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = 1; - req->end = 2; - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (request->current, request->request->end); - ASSERT_TRUE (request->current.is_zero ()); -} +#include -TEST (bulk_pull, genesis_to_end) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end.clear (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (system.nodes[0]->latest (nano::dev::genesis_key.pub), request->current); - ASSERT_EQ (request->request->end, request->request->end); -} - -// If we can't find the end block, send everything -TEST (bulk_pull, no_end) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end = 1; - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (system.nodes[0]->latest (nano::dev::genesis_key.pub), request->current); - ASSERT_TRUE (request->request->end.is_zero ()); -} - -TEST (bulk_pull, end_not_owned) -{ - nano::test::system system (1); - nano::keypair key2; - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, 100)); - nano::block_hash latest (system.nodes[0]->latest (nano::dev::genesis_key.pub)); - nano::block_builder builder; - auto open = builder - .open () - .source (0) - .representative (1) - .account (2) - .sign (nano::keypair ().prv, 4) - .work (5) - .build (); - open->hashables.account = key2.pub; - open->hashables.representative = key2.pub; - open->hashables.source = latest; - open->refresh (); - open->signature = nano::sign_message (key2.prv, key2.pub, open->hash ()); - system.nodes[0]->work_generate_blocking (*open); - ASSERT_EQ (nano::block_status::progress, system.nodes[0]->process (open)); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = key2.pub; - req->end = nano::dev::genesis->hash (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (request->current, request->request->end); -} +using namespace std::chrono_literals; -TEST (bulk_pull, none) +namespace { - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end = nano::dev::genesis->hash (); - auto request (std::make_shared (connection, std::move (req))); - auto block (request->get_next ()); - ASSERT_EQ (nullptr, block); -} - -TEST (bulk_pull, get_next_on_open) +nano::block_hash random_hash () { - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end.clear (); - auto request (std::make_shared (connection, std::move (req))); - auto block (request->get_next ()); - ASSERT_NE (nullptr, block); - ASSERT_TRUE (block->previous ().is_zero ()); - ASSERT_EQ (request->current, request->request->end); + nano::block_hash random_hash; + nano::random_pool::generate_block (random_hash.bytes.data (), random_hash.bytes.size ()); + return random_hash; } - -/** - Tests that the ascending flag is respected in the bulk_pull message when given a known block hash - */ -TEST (bulk_pull, ascending_one_hash) -{ - nano::test::system system{ 1 }; - auto & node = *system.nodes[0]; - nano::state_block_builder builder; - auto block1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 100) - .link (nano::dev::genesis_key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node.work_generate_blocking (*block1); - ASSERT_EQ (nano::block_status::progress, node.process (block1)); - auto socket = std::make_shared (node, nano::transport::socket_endpoint::server); - auto connection = std::make_shared (socket, system.nodes[0]); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis->hash (); - req->end.clear (); - req->header.flag_set (nano::message_header::bulk_pull_ascending_flag); - auto request = std::make_shared (connection, std::move (req)); - auto block_out1 = request->get_next (); - ASSERT_NE (nullptr, block_out1); - ASSERT_EQ (block_out1->hash (), block1->hash ()); - ASSERT_EQ (nullptr, request->get_next ()); } -/** - Tests that the ascending flag is respected in the bulk_pull message when given an account number +/* + * account_sets */ -TEST (bulk_pull, ascending_two_account) -{ - nano::test::system system{ 1 }; - auto & node = *system.nodes[0]; - nano::state_block_builder builder; - auto block1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 100) - .link (nano::dev::genesis_key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node.work_generate_blocking (*block1); - ASSERT_EQ (nano::block_status::progress, node.process (block1)); - auto socket = std::make_shared (node, nano::transport::socket_endpoint::server); - auto connection = std::make_shared (socket, system.nodes[0]); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end.clear (); - req->header.flag_set (nano::message_header::bulk_pull_ascending_flag); - auto request = std::make_shared (connection, std::move (req)); - auto block_out1 = request->get_next (); - ASSERT_NE (nullptr, block_out1); - ASSERT_EQ (block_out1->hash (), nano::dev::genesis->hash ()); - auto block_out2 = request->get_next (); - ASSERT_NE (nullptr, block_out2); - ASSERT_EQ (block_out2->hash (), block1->hash ()); - ASSERT_EQ (nullptr, request->get_next ()); -} -/** - Tests that the `end' value is respected in the bulk_pull message when the ascending flag is used. - */ -TEST (bulk_pull, ascending_end) +TEST (account_sets, construction) { - nano::test::system system{ 1 }; - auto & node = *system.nodes[0]; - nano::state_block_builder builder; - auto block1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 100) - .link (nano::dev::genesis_key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node.work_generate_blocking (*block1); - ASSERT_EQ (nano::block_status::progress, node.process (block1)); - auto socket = std::make_shared (node, nano::transport::socket_endpoint::server); - auto connection = std::make_shared (socket, system.nodes[0]); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub; - req->end = block1->hash (); - req->header.flag_set (nano::message_header::bulk_pull_ascending_flag); - auto request = std::make_shared (connection, std::move (req)); - auto block_out1 = request->get_next (); - ASSERT_NE (nullptr, block_out1); - ASSERT_EQ (block_out1->hash (), nano::dev::genesis->hash ()); - ASSERT_EQ (nullptr, request->get_next ()); + nano::test::system system; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; } -TEST (bulk_pull, by_block) +TEST (account_sets, empty_blocked) { - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis->hash (); - req->end.clear (); - auto request (std::make_shared (connection, std::move (req))); - auto block (request->get_next ()); - ASSERT_NE (nullptr, block); - ASSERT_EQ (block->hash (), nano::dev::genesis->hash ()); - - block = request->get_next (); - ASSERT_EQ (nullptr, block); -} + nano::test::system system; -TEST (bulk_pull, by_block_single) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis->hash (); - req->end = nano::dev::genesis->hash (); - auto request (std::make_shared (connection, std::move (req))); - auto block (request->get_next ()); - ASSERT_NE (nullptr, block); - ASSERT_EQ (block->hash (), nano::dev::genesis->hash ()); - - block = request->get_next (); - ASSERT_EQ (nullptr, block); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + ASSERT_FALSE (sets.blocked (account)); } -TEST (bulk_pull, count_limit) +TEST (account_sets, block) { - nano::test::system system (1); - auto node0 (system.nodes[0]); - - nano::block_builder builder; - auto send1 = builder - .send () - .previous (node0->latest (nano::dev::genesis_key.pub)) - .destination (nano::dev::genesis_key.pub) - .balance (1) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (node0->latest (nano::dev::genesis_key.pub))) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (send1)); - auto receive1 = builder - .receive () - .previous (send1->hash ()) - .source (send1->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (receive1)); - - auto connection (std::make_shared (std::make_shared (*node0, nano::transport::socket_endpoint::server), node0)); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = receive1->hash (); - req->set_count_present (true); - req->count = 2; - auto request (std::make_shared (connection, std::move (req))); - - ASSERT_EQ (request->max_count, 2); - ASSERT_EQ (request->sent_count, 0); - - auto block (request->get_next ()); - ASSERT_NE (nullptr, block); - ASSERT_EQ (receive1->hash (), block->hash ()); - - block = request->get_next (); - ASSERT_EQ (send1->hash (), block->hash ()); + nano::test::system system; - block = request->get_next (); - ASSERT_EQ (nullptr, block); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_up (account); + sets.block (account, random_hash ()); + ASSERT_TRUE (sets.blocked (account)); } -TEST (bootstrap_processor, process_none) +TEST (account_sets, unblock) { - nano::test::system system (1); - auto node0 = system.nodes[0]; - auto node1 = system.make_disconnected_node (); - - std::atomic done = false; - node0->observers.socket_connected.add ([&] (nano::transport::tcp_socket & socket) { - done = true; - }); + nano::test::system system; - node1->bootstrap_initiator.bootstrap (system.nodes[0]->network.endpoint (), false); - ASSERT_TIMELY (5s, done); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + auto hash = random_hash (); + sets.priority_up (account); + sets.block (account, hash); + ASSERT_TRUE (sets.blocked (account)); + sets.unblock (account, hash); + ASSERT_FALSE (sets.blocked (account)); } -// Bootstrap can pull one basic block -TEST (bootstrap_processor, process_one) +TEST (account_sets, priority_base) { nano::test::system system; - nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; - node_config.enable_voting = false; - 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::dev::genesis_key.prv); - auto send (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::genesis_key.pub, 100)); - ASSERT_NE (nullptr, send); - ASSERT_TIMELY (5s, node0->latest (nano::dev::genesis_key.pub) != nano::dev::genesis->hash ()); - - node_flags.disable_rep_crawler = true; - node_config.peering_port = system.get_available_port (); - auto node1 = system.make_disconnected_node (node_config, node_flags); - ASSERT_NE (node0->latest (nano::dev::genesis_key.pub), node1->latest (nano::dev::genesis_key.pub)); - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (10s, node1->latest (nano::dev::genesis_key.pub), node0->latest (nano::dev::genesis_key.pub)); -} -TEST (bootstrap_processor, process_two) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - 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::dev::genesis_key.prv); - ASSERT_TRUE (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::genesis_key.pub, 50)); - ASSERT_TRUE (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::genesis_key.pub, 50)); - ASSERT_TIMELY_EQ (5s, nano::test::account_info (*node0, nano::dev::genesis_key.pub).block_count, 3); - - // create a node manually to avoid making automatic network connections - auto node1 = system.make_disconnected_node (); - ASSERT_NE (node1->latest (nano::dev::genesis_key.pub), node0->latest (nano::dev::genesis_key.pub)); // nodes should be out of sync here - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); // bootstrap triggered - ASSERT_TIMELY_EQ (5s, node1->latest (nano::dev::genesis_key.pub), node0->latest (nano::dev::genesis_key.pub)); // nodes should sync up + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + ASSERT_EQ (0.0, sets.priority (account)); } -// Bootstrap can pull universal blocks -TEST (bootstrap_processor, process_state) +TEST (account_sets, priority_blocked) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::state_block_builder builder; - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - auto block1 = builder - .account (nano::dev::genesis_key.pub) - .previous (node0->latest (nano::dev::genesis_key.pub)) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 100) - .link (nano::dev::genesis_key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - auto block2 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (block1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount) - .link (block1->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - - node0->work_generate_blocking (*block1); - node0->work_generate_blocking (*block2); - ASSERT_EQ (nano::block_status::progress, node0->process (block1)); - ASSERT_EQ (nano::block_status::progress, node0->process (block2)); - ASSERT_TIMELY_EQ (5s, nano::test::account_info (*node0, nano::dev::genesis_key.pub).block_count, 3); - - auto node1 = system.make_disconnected_node (std::nullopt, node_flags); - ASSERT_EQ (node0->latest (nano::dev::genesis_key.pub), block2->hash ()); - ASSERT_NE (node1->latest (nano::dev::genesis_key.pub), block2->hash ()); - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node1->latest (nano::dev::genesis_key.pub), block2->hash ()); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.block (account, random_hash ()); + ASSERT_EQ (0.0, sets.priority (account)); } -TEST (bootstrap_processor, process_new) +TEST (account_sets, priority_unblock) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - nano::keypair key2; - - auto node1 = system.add_node (config, node_flags); - config.peering_port = system.get_available_port (); - auto node2 = system.add_node (config, node_flags); - - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - system.wallet (1)->insert_adhoc (key2.prv); - - // send amount raw from genesis to key2, the wallet will autoreceive - auto amount = node1->config.receive_minimum.number (); - auto send = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, amount); - ASSERT_NE (nullptr, send); - ASSERT_TIMELY (5s, !node1->balance (key2.pub).is_zero ()); - - // wait for the receive block on node2 - std::shared_ptr receive; - ASSERT_TIMELY (5s, receive = node2->block (node2->latest (key2.pub))); - - // All blocks should be propagated & confirmed - ASSERT_TIMELY (5s, nano::test::confirmed (*node1, { send, receive })); - ASSERT_TIMELY (5s, nano::test::confirmed (*node2, { send, receive })); - ASSERT_TIMELY (5s, node1->active.empty ()); - ASSERT_TIMELY (5s, node2->active.empty ()); - - // create a node manually to avoid making automatic network connections - auto node3 = system.make_disconnected_node (); - node3->bootstrap_initiator.bootstrap (node1->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node3->balance (key2.pub), amount); -} -TEST (bootstrap_processor, pull_diamond) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::keypair key; - nano::block_builder builder; - auto send1 = builder - .send () - .previous (node0->latest (nano::dev::genesis_key.pub)) - .destination (key.pub) - .balance (0) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (node0->latest (nano::dev::genesis_key.pub))) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (send1)); - auto open = builder - .open () - .source (send1->hash ()) - .representative (1) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (open)); - auto send2 = builder - .send () - .previous (open->hash ()) - .destination (nano::dev::genesis_key.pub) - .balance (std::numeric_limits::max () - 100) - .sign (key.prv, key.pub) - .work (*system.work.generate (open->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (send2)); - auto receive = builder - .receive () - .previous (send1->hash ()) - .source (send2->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (receive)); - - auto node1 = system.make_disconnected_node (); - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node1->balance (nano::dev::genesis_key.pub), 100); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_up (account); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_initial); + auto hash = random_hash (); + sets.block (account, hash); + ASSERT_EQ (0.0, sets.priority (account)); + sets.unblock (account, hash); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_initial); } -TEST (bootstrap_processor, DISABLED_pull_requeue_network_error) +TEST (account_sets, priority_up_down) { - // Bootstrap attempt stopped before requeue & then cannot be found in attempts list nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node1 (system.add_node (config, node_flags)); - config.peering_port = system.get_available_port (); - auto node2 (system.add_node (config, node_flags)); - nano::keypair key1; - - nano::state_block_builder builder; - - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - node1->bootstrap_initiator.bootstrap (node2->network.endpoint ()); - auto attempt (node1->bootstrap_initiator.current_attempt ()); - ASSERT_NE (nullptr, attempt); - ASSERT_TIMELY (2s, attempt->frontiers_received); - // Add non-existing pull & stop remote peer - { - nano::unique_lock lock{ node1->bootstrap_initiator.connections->mutex }; - ASSERT_FALSE (attempt->stopped); - ++attempt->pulling; - node1->bootstrap_initiator.connections->pulls.emplace_back (nano::dev::genesis_key.pub, send1->hash (), nano::dev::genesis->hash (), attempt->incremental_id); - node1->bootstrap_initiator.connections->request_pull (lock); - } - ASSERT_TIMELY (5s, attempt == nullptr || attempt->requeued_pulls == 1); - ASSERT_EQ (0, node1->stats.count (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in)); // Requeue is not increasing failed attempts + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_up (account); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_initial); + sets.priority_down (account); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_initial / nano::bootstrap::account_sets::priority_divide); } -TEST (bootstrap_processor, push_diamond) +TEST (account_sets, priority_down_empty) { nano::test::system system; - nano::keypair key; - - auto node1 = system.make_disconnected_node (); - auto wallet1 (node1->wallets.create (100)); - wallet1->insert_adhoc (nano::dev::genesis_key.prv); - wallet1->insert_adhoc (key.prv); - - nano::block_builder builder; - // send all balance from genesis to key - auto send1 = builder - .send () - .previous (nano::dev::genesis->hash ()) - .destination (key.pub) - .balance (0) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - - // open key account receiving all balance of genesis - auto open = builder - .open () - .source (send1->hash ()) - .representative (1) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - - // send from key to genesis 100 raw - auto send2 = builder - .send () - .previous (open->hash ()) - .destination (nano::dev::genesis_key.pub) - .balance (std::numeric_limits::max () - 100) - .sign (key.prv, key.pub) - .work (*system.work.generate (open->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - - // receive the 100 raw on genesis - auto receive = builder - .receive () - .previous (send1->hash ()) - .source (send2->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (receive)); - - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags flags; - flags.disable_ongoing_bootstrap = true; - flags.disable_ascending_bootstrap = true; - auto node2 = system.add_node (config, flags); - node1->bootstrap_initiator.bootstrap (node2->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node2->balance (nano::dev::genesis_key.pub), 100); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_down (account); + ASSERT_EQ (0.0, sets.priority (account)); } -TEST (bootstrap_processor, push_diamond_pruning) +TEST (account_sets, priority_down_saturate) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags0; - node_flags0.disable_ascending_bootstrap = true; - node_flags0.disable_ongoing_bootstrap = true; - auto node0 (system.add_node (config, node_flags0)); - nano::keypair key; - - config.enable_voting = false; // Remove after allowing pruned voting - nano::node_flags node_flags; - node_flags.enable_pruning = true; - config.peering_port = system.get_available_port (); - auto node1 = system.make_disconnected_node (config, node_flags); - - nano::block_builder builder; - - // send all balance from genesis to key - auto send1 = builder - .send () - .previous (nano::dev::genesis->hash ()) - .destination (key.pub) - .balance (0) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - - // receive all balance on key - auto open = builder - .open () - .source (send1->hash ()) - .representative (1) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - - // 1st bootstrap - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node0->balance (key.pub), nano::dev::constants.genesis_amount); - ASSERT_TIMELY_EQ (5s, node1->balance (key.pub), nano::dev::constants.genesis_amount); - - // Process more blocks & prune old - - // send 100 raw from key to genesis - auto send2 = builder - .send () - .previous (open->hash ()) - .destination (nano::dev::genesis_key.pub) - .balance (std::numeric_limits::max () - 100) - .sign (key.prv, key.pub) - .work (*system.work.generate (open->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - - // receive the 100 raw from key on genesis - auto receive = builder - .receive () - .previous (send1->hash ()) - .source (send2->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (receive)); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_up (account); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_initial); + for (int n = 0; n < 1000; ++n) { - auto transaction = node1->ledger.tx_begin_write (); - node1->ledger.confirm (transaction, open->hash ()); - ASSERT_EQ (1, node1->ledger.pruning_action (transaction, send1->hash (), 2)); - ASSERT_EQ (1, node1->ledger.pruning_action (transaction, open->hash (), 1)); - ASSERT_TRUE (node1->ledger.any.block_exists (transaction, nano::dev::genesis->hash ())); - ASSERT_FALSE (node1->ledger.any.block_exists (transaction, send1->hash ())); - ASSERT_TRUE (node1->store.pruned.exists (transaction, send1->hash ())); - ASSERT_FALSE (node1->ledger.any.block_exists (transaction, open->hash ())); - ASSERT_TRUE (node1->store.pruned.exists (transaction, open->hash ())); - ASSERT_TRUE (node1->ledger.any.block_exists (transaction, send2->hash ())); - ASSERT_TRUE (node1->ledger.any.block_exists (transaction, receive->hash ())); - ASSERT_EQ (2, node1->ledger.pruned_count ()); - ASSERT_EQ (5, node1->ledger.block_count ()); + sets.priority_down (account); } - - // 2nd bootstrap - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node0->balance (nano::dev::genesis_key.pub), 100); - ASSERT_TIMELY_EQ (5s, node1->balance (nano::dev::genesis_key.pub), 100); + ASSERT_FALSE (sets.prioritized (account)); } -TEST (bootstrap_processor, push_one) +TEST (account_sets, priority_set) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - auto node0 (system.add_node (config)); - nano::keypair key1; - auto node1 = system.make_disconnected_node (); - auto wallet = node1->wallets.create (nano::random_wallet_id ()); - ASSERT_NE (nullptr, wallet); - wallet->insert_adhoc (nano::dev::genesis_key.prv); - - // send 100 raw from genesis to key1 - nano::uint128_t genesis_balance = node1->balance (nano::dev::genesis_key.pub); - auto send = wallet->send_action (nano::dev::genesis_key.pub, key1.pub, 100); - ASSERT_NE (nullptr, send); - ASSERT_TIMELY_EQ (5s, genesis_balance - 100, node1->balance (nano::dev::genesis_key.pub)); - - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node0->balance (nano::dev::genesis_key.pub), genesis_balance - 100); -} - -TEST (bootstrap_processor, lazy_hash) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::keypair key1; - nano::keypair key2; - // Generating test chain - - nano::state_block_builder builder; - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); - auto receive2 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (key2.pub)) - .build (); - - // Processing test chain - node0->block_processor.add (send1); - node0->block_processor.add (receive1); - node0->block_processor.add (send2); - node0->block_processor.add (receive2); - ASSERT_TIMELY (5s, nano::test::exists (*node0, { send1, receive1, send2, receive2 })); - - // Start lazy bootstrap with last block in chain known - auto node1 = system.make_disconnected_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - node1->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true); - { - auto lazy_attempt (node1->bootstrap_initiator.current_lazy_attempt ()); - ASSERT_NE (nullptr, lazy_attempt); - ASSERT_EQ (receive2->hash ().to_string (), lazy_attempt->id); - } - // Check processed blocks - ASSERT_TIMELY (10s, node1->balance (key2.pub) != 0); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + sets.priority_set (account, 10.0); + ASSERT_EQ (sets.priority (account), 10.0); } -TEST (bootstrap_processor, lazy_hash_bootstrap_id) +// Ensure priority value is bounded +TEST (account_sets, saturate_priority) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::keypair key1; - nano::keypair key2; - // Generating test chain - - nano::state_block_builder builder; - - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); - auto receive2 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (key2.pub)) - .build (); - // Processing test chain - node0->block_processor.add (send1); - node0->block_processor.add (receive1); - node0->block_processor.add (send2); - node0->block_processor.add (receive2); - ASSERT_TIMELY (5s, nano::test::exists (*node0, { send1, receive1, send2, receive2 })); - - // Start lazy bootstrap with last block in chain known - auto node1 = system.make_disconnected_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - node1->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true, "123456"); + nano::account account{ 1 }; + nano::account_sets_config config; + nano::bootstrap::account_sets sets{ config, system.stats }; + for (int n = 0; n < 1000; ++n) { - auto lazy_attempt (node1->bootstrap_initiator.current_lazy_attempt ()); - ASSERT_NE (nullptr, lazy_attempt); - ASSERT_EQ ("123456", lazy_attempt->id); + sets.priority_up (account); } - // Check processed blocks - ASSERT_TIMELY (10s, node1->balance (key2.pub) != 0); + ASSERT_EQ (sets.priority (account), nano::bootstrap::account_sets::priority_max); } -TEST (bootstrap_processor, lazy_hash_pruning) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - config.enable_voting = false; // Remove after allowing pruned voting - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.enable_pruning = true; - auto node0 = system.add_node (config, node_flags); - - nano::state_block_builder builder; - - // send Knano_ratio raw from genesis to genesis - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (nano::dev::genesis_key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - - // receive send1 - auto receive1 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (send1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount) - .link (send1->hash ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (send1->hash ())) - .build (); - - // change rep of genesis account to be key1 - nano::keypair key1; - auto change1 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (nano::dev::constants.genesis_amount) - .link (0) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); - - // change rep of genesis account to be rep2 - nano::keypair key2; - auto change2 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (change1->hash ()) - .representative (key2.pub) - .balance (nano::dev::constants.genesis_amount) - .link (0) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (change1->hash ())) - .build (); - - // send Knano_ratio from genesis to key1 and genesis rep back to genesis account - auto send2 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (change2->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (change2->hash ())) - .build (); - - // receive send2 and rep of key1 to be itself - auto receive2 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - - // send all available balance from key1 to key2 - auto send3 = builder - .make_block () - .account (key1.pub) - .previous (receive2->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive2->hash ())) - .build (); - - // receive send3 on key2, set rep of key2 to be itself - auto receive3 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send3->hash ()) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (key2.pub)) - .build (); - - std::vector> blocks = { send1, receive1, change1, change2, send2, receive2, send3, receive3 }; - ASSERT_TRUE (nano::test::process (*node0, blocks)); - nano::test::confirm (node0->ledger, blocks); - - config.peering_port = system.get_available_port (); - auto node1 = system.make_disconnected_node (config, node_flags); - - // Processing chain to prune for node1 - node1->process_active (send1); - node1->process_active (receive1); - node1->process_active (change1); - node1->process_active (change2); - ASSERT_TIMELY (5s, nano::test::exists (*node1, { send1, receive1, change1, change2 })); - - // Confirm last block to prune previous - nano::test::confirm (node1->ledger, { send1, receive1, change1, change2 }); - ASSERT_EQ (5, node1->ledger.block_count ()); - ASSERT_EQ (5, node1->ledger.cemented_count ()); - - // Pruning action - node1->ledger_pruning (2, false); - ASSERT_EQ (9, node0->ledger.block_count ()); - ASSERT_EQ (0, node0->ledger.pruned_count ()); - ASSERT_EQ (5, node1->ledger.block_count ()); - ASSERT_EQ (3, node1->ledger.pruned_count ()); - - // Start lazy bootstrap with last block in chain known - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - node1->bootstrap_initiator.bootstrap_lazy (receive3->hash (), true); - - // Check processed blocks - ASSERT_TIMELY_EQ (5s, node1->ledger.block_count (), 9); - ASSERT_TIMELY (5s, node1->balance (key2.pub) != 0); - ASSERT_TIMELY (5s, !node1->bootstrap_initiator.in_progress ()); -} +/* + * bootstrap + */ -TEST (bootstrap_processor, lazy_max_pull_count) +/** + * Tests the base case for returning + */ +TEST (bootstrap, account_base) { - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::keypair key1; - nano::keypair key2; - // Generating test chain - + nano::node_flags flags; + nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; + auto & node0 = *system.nodes[0]; nano::state_block_builder builder; - - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); - auto receive2 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (key2.pub)) - .build (); - auto change1 = builder - .make_block () - .account (key2.pub) - .previous (receive2->hash ()) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (0) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (receive2->hash ())) - .build (); - auto change2 = builder - .make_block () - .account (key2.pub) - .previous (change1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::Knano_ratio) - .link (0) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (change1->hash ())) - .build (); - auto change3 = builder - .make_block () - .account (key2.pub) - .previous (change2->hash ()) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (0) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (change2->hash ())) - .build (); - // Processing test chain - node0->block_processor.add (send1); - node0->block_processor.add (receive1); - node0->block_processor.add (send2); - node0->block_processor.add (receive2); - node0->block_processor.add (change1); - node0->block_processor.add (change2); - node0->block_processor.add (change3); - ASSERT_TIMELY (5s, nano::test::exists (*node0, { send1, receive1, send2, receive2, change1, change2, change3 })); - - // Start lazy bootstrap with last block in chain known - auto node1 = system.make_disconnected_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - node1->bootstrap_initiator.bootstrap_lazy (change3->hash ()); - // Check processed blocks - ASSERT_TIMELY (10s, node1->block (change3->hash ())); -} - -TEST (bootstrap_processor, lazy_unclear_state_link) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - auto node1 = system.add_node (config, node_flags); - nano::keypair key; - - nano::block_builder builder; - - auto send1 = builder - .state () + auto send1 = builder.make_block () .account (nano::dev::genesis_key.pub) .previous (nano::dev::genesis->hash ()) .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key.pub) + .link (0) + .balance (nano::dev::constants.genesis_amount - 1) .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) .work (*system.work.generate (nano::dev::genesis->hash ())) .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - auto send2 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (send1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio) - .link (key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - auto open = builder - .open () - .source (send1->hash ()) - .representative (key.pub) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - auto receive = builder - .state () - .account (key.pub) - .previous (open->hash ()) - .representative (key.pub) - .balance (2 * nano::Knano_ratio) - .link (send2->hash ()) - .sign (key.prv, key.pub) - .work (*system.work.generate (open->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (receive)); - - ASSERT_TIMELY (5s, nano::test::exists (*node1, { send1, send2, open, receive })); - - // Start lazy bootstrap with last block in chain known - auto node2 = system.make_disconnected_node (std::nullopt, node_flags); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - node2->bootstrap_initiator.bootstrap_lazy (receive->hash ()); - ASSERT_TIMELY (5s, nano::test::exists (*node2, { send1, send2, open, receive })); - ASSERT_EQ (0, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in)); -} - -TEST (bootstrap_processor, lazy_unclear_state_link_not_existing) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - auto node1 = system.add_node (config, node_flags); - nano::keypair key, key2; - // Generating test chain - - nano::block_builder builder; - - auto send1 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - auto open = builder - .open () - .source (send1->hash ()) - .representative (key.pub) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - auto send2 = builder - .state () - .account (key.pub) - .previous (open->hash ()) - .representative (key.pub) - .balance (0) - .link (key2.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (open->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - - // Start lazy bootstrap with last block in chain known - auto node2 = system.make_disconnected_node (std::nullopt, node_flags); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); - // Check processed blocks - ASSERT_TIMELY (15s, !node2->bootstrap_initiator.in_progress ()); - ASSERT_TIMELY (15s, nano::test::block_or_pruned_all_exists (*node2, { send1, open, send2 })); - ASSERT_EQ (1, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in)); + ASSERT_EQ (nano::block_status::progress, node0.process (send1)); + auto & node1 = *system.add_node (flags); + ASSERT_TIMELY (5s, node1.block (send1->hash ()) != nullptr); } -TEST (bootstrap_processor, lazy_destinations) +/** + * Tests that bootstrap will return multiple new blocks in-order + */ +TEST (bootstrap, account_inductive) { - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - auto node1 = system.add_node (config, node_flags); - nano::keypair key1, key2; - - nano::block_builder builder; - - // send Knano_ratio raw from genesis to key1 - auto send1 = builder - .state () + nano::node_flags flags; + nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; + auto & node0 = *system.nodes[0]; + nano::state_block_builder builder; + auto send1 = builder.make_block () .account (nano::dev::genesis_key.pub) .previous (nano::dev::genesis->hash ()) .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) + .link (0) + .balance (nano::dev::constants.genesis_amount - 1) .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) .work (*system.work.generate (nano::dev::genesis->hash ())) .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - - // send Knano_ratio raw from genesis to key2 - auto send2 = builder - .state () + auto send2 = builder.make_block () .account (nano::dev::genesis_key.pub) .previous (send1->hash ()) .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio) - .link (key2.pub) + .link (0) + .balance (nano::dev::constants.genesis_amount - 2) .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) .work (*system.work.generate (send1->hash ())) .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - - // receive send1 on key1 - auto open = builder - .open () - .source (send1->hash ()) - .representative (key1.pub) - .account (key1.pub) - .sign (key1.prv, key1.pub) - .work (*system.work.generate (key1.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - - // receive send2 on key2 - auto state_open = builder - .state () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*system.work.generate (key2.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (state_open)); - - // Start lazy bootstrap with last block in sender chain - auto node2 = system.make_disconnected_node (std::nullopt, node_flags); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); - - // Check processed blocks - ASSERT_TIMELY (5s, !node2->bootstrap_initiator.in_progress ()); - ASSERT_TIMELY (5s, node2->block_or_pruned_exists (send1->hash ())); - ASSERT_TIMELY (5s, node2->block_or_pruned_exists (send2->hash ())); - ASSERT_FALSE (node2->block_or_pruned_exists (open->hash ())); - ASSERT_FALSE (node2->block_or_pruned_exists (state_open->hash ())); + // std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl; + // std::cerr << "Send1: " << send1->hash ().to_string () << std::endl; + // std::cerr << "Send2: " << send2->hash ().to_string () << std::endl; + ASSERT_EQ (nano::block_status::progress, node0.process (send1)); + ASSERT_EQ (nano::block_status::progress, node0.process (send2)); + auto & node1 = *system.add_node (flags); + ASSERT_TIMELY (50s, node1.block (send2->hash ()) != nullptr); } -TEST (bootstrap_processor, lazy_pruning_missing_block) +/** + * Tests that bootstrap will return multiple new blocks in-order + */ +TEST (bootstrap, trace_base) { - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - config.enable_voting = false; // Remove after allowing pruned voting - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - node_flags.enable_pruning = true; - auto node1 = system.add_node (config, node_flags); - nano::keypair key1, key2; - - nano::block_builder builder; - - // send from genesis to key1 - auto send1 = builder - .state () + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; + auto & node0 = *system.nodes[0]; + nano::keypair key; + nano::state_block_builder builder; + auto send1 = builder.make_block () .account (nano::dev::genesis_key.pub) .previous (nano::dev::genesis->hash ()) .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) + .link (key.pub) + .balance (nano::dev::constants.genesis_amount - 1) .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) .work (*system.work.generate (nano::dev::genesis->hash ())) .build (); - - // send from genesis to key2 - auto send2 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (send1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio) - .link (key2.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - - // open account key1 - auto open = builder - .open () - .source (send1->hash ()) - .representative (key1.pub) - .account (key1.pub) - .sign (key1.prv, key1.pub) - .work (*system.work.generate (key1.pub)) - .build (); - - // open account key2 - auto state_open = builder - .state () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*system.work.generate (key2.pub)) - .build (); - - // add the blocks without starting elections because elections publish blocks - // and the publishing would interefere with the testing - std::vector> const blocks{ send1, send2, open, state_open }; - ASSERT_TRUE (nano::test::process (*node1, blocks)); - ASSERT_TIMELY (5s, nano::test::exists (*node1, blocks)); - nano::test::confirm (node1->ledger, blocks); - ASSERT_TIMELY (5s, nano::test::confirmed (*node1, blocks)); - ASSERT_EQ (5, node1->ledger.block_count ()); - ASSERT_EQ (5, node1->ledger.cemented_count ()); - - // Pruning action, send1 should get pruned - ASSERT_EQ (0, node1->ledger.pruned_count ()); - node1->ledger_pruning (2, false); - ASSERT_EQ (1, node1->ledger.pruned_count ()); - ASSERT_EQ (5, node1->ledger.block_count ()); - ASSERT_TRUE (node1->ledger.store.pruned.exists (node1->ledger.store.tx_begin_read (), send1->hash ())); - ASSERT_TRUE (nano::test::exists (*node1, { send2, open, state_open })); - - // Start lazy bootstrap with last block in sender chain - config.peering_port = system.get_available_port (); - auto node2 = system.make_disconnected_node (config, node_flags); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - node2->bootstrap_initiator.bootstrap_lazy (send2->hash ()); - - // Check processed blocks - auto lazy_attempt (node2->bootstrap_initiator.current_lazy_attempt ()); - ASSERT_NE (nullptr, lazy_attempt); - ASSERT_TIMELY (5s, lazy_attempt->stopped || lazy_attempt->requeued_pulls >= 4); - - // Some blocks cannot be retrieved from pruned node - ASSERT_EQ (1, node2->ledger.block_count ()); - ASSERT_TRUE (nano::test::block_or_pruned_none_exists (*node2, { send1, send2, open, state_open })); - { - auto transaction (node2->store.tx_begin_read ()); - ASSERT_TRUE (node2->unchecked.exists (nano::unchecked_key (send2->root ().as_block_hash (), send2->hash ()))); - } - - // Insert missing block - node2->process_active (send1); - ASSERT_TIMELY_EQ (5s, 3, node2->ledger.block_count ()); - ASSERT_TIMELY (5s, nano::test::exists (*node2, { send1, send2 })); - ASSERT_TRUE (nano::test::block_or_pruned_none_exists (*node2, { open, state_open })); -} - -TEST (bootstrap_processor, lazy_cancel) + auto receive1 = builder.make_block () + .account (key.pub) + .previous (0) + .representative (nano::dev::genesis_key.pub) + .link (send1->hash ()) + .balance (1) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + // std::cerr << "Genesis key: " << nano::dev::genesis_key.pub.to_account () << std::endl; + // std::cerr << "Key: " << key.pub.to_account () << std::endl; + // std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl; + // std::cerr << "send1: " << send1->hash ().to_string () << std::endl; + // std::cerr << "receive1: " << receive1->hash ().to_string () << std::endl; + auto & node1 = *system.add_node (); + // std::cerr << "--------------- Start ---------------\n"; + ASSERT_EQ (nano::block_status::progress, node0.process (send1)); + ASSERT_EQ (nano::block_status::progress, node0.process (receive1)); + ASSERT_EQ (node1.ledger.any.receivable_end (), node1.ledger.any.receivable_upper_bound (node1.ledger.tx_begin_read (), key.pub, 0)); + // std::cerr << "node0: " << node0.network.endpoint () << std::endl; + // std::cerr << "node1: " << node1.network.endpoint () << std::endl; + ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr); +} + +/* + * Tests that bootstrap will prioritize existing accounts with outdated frontiers + */ +TEST (bootstrap, frontier_scan) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node0 (system.add_node (config, node_flags)); - nano::keypair key1; - // Generating test chain - - auto send1 = nano::state_block_builder () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - // Start lazy bootstrap with last block in chain known - auto node1 = system.make_disconnected_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - node1->bootstrap_initiator.bootstrap_lazy (send1->hash (), true); // Start "confirmed" block bootstrap + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap.enable_scan = false; + config.bootstrap.enable_dependency_walker = false; + // Disable election activation + config.backlog_scan.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 updates) + std::vector> sends; + std::vector> opens; + std::vector> updates; { - auto lazy_attempt (node1->bootstrap_initiator.current_lazy_attempt ()); - ASSERT_NE (nullptr, lazy_attempt); - ASSERT_EQ (send1->hash ().to_string (), lazy_attempt->id); - } - // Cancel failing lazy bootstrap - ASSERT_TIMELY (10s, !node1->bootstrap_initiator.in_progress ()); -} - -TEST (bootstrap_processor, wallet_lazy_frontier) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - auto node0 = system.add_node (config, node_flags); - nano::keypair key1; - nano::keypair key2; - // Generating test chain - - nano::state_block_builder builder; + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); - auto receive2 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*node0->work_generate_blocking (key2.pub)) - .build (); + size_t const count = 10; - // Processing test chain - node0->block_processor.add (send1); - node0->block_processor.add (receive1); - node0->block_processor.add (send2); - node0->block_processor.add (receive2); - ASSERT_TIMELY (5s, nano::test::exists (*node0, { send1, receive1, send2, receive2 })); - - // Start wallet lazy bootstrap - auto node1 = system.make_disconnected_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - auto wallet (node1->wallets.create (nano::random_wallet_id ())); - ASSERT_NE (nullptr, wallet); - wallet->insert_adhoc (key2.prv); - node1->bootstrap_wallet (); - { - auto wallet_attempt (node1->bootstrap_initiator.current_wallet_attempt ()); - ASSERT_NE (nullptr, wallet_attempt); - ASSERT_EQ (key2.pub.to_account (), wallet_attempt->id); + for (int n = 0; n < count; ++n) + { + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto update = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (0) + .balance (1) + .link (0) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + sends.push_back (send); + opens.push_back (open); + updates.push_back (update); + } } - // Check processed blocks - ASSERT_TIMELY (10s, node1->block_or_pruned_exists (receive2->hash ())); -} -TEST (bootstrap_processor, wallet_lazy_pending) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_legacy_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - auto node0 = system.add_node (config, node_flags); - nano::keypair key1; - nano::keypair key2; - // Generating test chain + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); - nano::state_block_builder builder; + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, updates)); - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node0->work_generate_blocking (receive1->hash ())) - .build (); + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); - // Processing test chain - node0->block_processor.add (send1); - node0->block_processor.add (receive1); - node0->block_processor.add (send2); - nano::test::exists (*node0, { send1, receive1, send2 }); - - // Start wallet lazy bootstrap - auto node1 = system.add_node (); - nano::test::establish_tcp (system, *node1, node0->network.endpoint ()); - auto wallet (node1->wallets.create (nano::random_wallet_id ())); - ASSERT_NE (nullptr, wallet); - wallet->insert_adhoc (key2.prv); - node1->bootstrap_wallet (); - // Check processed blocks - ASSERT_TIMELY (10s, node1->block_or_pruned_exists (send2->hash ())); + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (updates.begin (), updates.end (), [&node1] (auto const & block) { + return node1.bootstrap.prioritized (block->account ()); + })); } -TEST (bootstrap_processor, multiple_attempts) +/* + * Tests that bootstrap will prioritize not yet existing accounts with pending blocks + */ +TEST (bootstrap, frontier_scan_pending) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - auto node1 = system.add_node (config, node_flags); - nano::keypair key1; - nano::keypair key2; - // Generating test chain - - nano::state_block_builder builder; - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node1->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (key1.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (*node1->work_generate_blocking (key1.pub)) - .build (); - auto send2 = builder - .make_block () - .account (key1.pub) - .previous (receive1->hash ()) - .representative (key1.pub) - .balance (0) - .link (key2.pub) - .sign (key1.prv, key1.pub) - .work (*node1->work_generate_blocking (receive1->hash ())) - .build (); - auto receive2 = builder - .make_block () - .account (key2.pub) - .previous (0) - .representative (key2.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key2.prv, key2.pub) - .work (*node1->work_generate_blocking (key2.pub)) - .build (); + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap.enable_scan = false; + config.bootstrap.enable_dependency_walker = false; + // Disable election activation + config.backlog_scan.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); - // Processing test chain - node1->block_processor.add (send1); - node1->block_processor.add (receive1); - node1->block_processor.add (send2); - node1->block_processor.add (receive2); - nano::test::exists (*node1, { send1, receive1, send2, receive2 }); - - // Start 2 concurrent bootstrap attempts - nano::node_config node_config = system.default_config (); - node_config.bootstrap_initiator_threads = 3; - - auto node2 = system.make_disconnected_node (node_config); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - node2->bootstrap_initiator.bootstrap_lazy (receive2->hash (), true); - node2->bootstrap_initiator.bootstrap (); - auto lazy_attempt (node2->bootstrap_initiator.current_lazy_attempt ()); - auto legacy_attempt (node2->bootstrap_initiator.current_attempt ()); - ASSERT_TIMELY (5s, lazy_attempt->started && legacy_attempt->started); - // Check processed blocks - ASSERT_TIMELY (10s, node2->balance (key2.pub) != 0); - // Check attempts finish - ASSERT_TIMELY_EQ (5s, node2->bootstrap_initiator.attempts.size (), 0); -} + size_t const count = 10; -TEST (frontier_req_response, DISABLED_destruction) -{ - { - std::shared_ptr hold; // Destructing tcp acceptor on non-existent io_context + for (int n = 0; n < count; ++n) { - nano::test::system system (1); - auto connection (std::make_shared (nullptr, system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = std::numeric_limitsage)>::max (); - req->count = std::numeric_limitscount)>::max (); - hold = std::make_shared (connection, std::move (req)); + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); } } - ASSERT_TRUE (true); -} - -TEST (frontier_req, begin) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = std::numeric_limitsage)>::max (); - req->count = std::numeric_limitscount)>::max (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (nano::dev::genesis_key.pub, request->current); - ASSERT_EQ (nano::dev::genesis->hash (), request->frontier); -} - -TEST (frontier_req, end) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start = nano::dev::genesis_key.pub.number () + 1; - req->age = std::numeric_limitsage)>::max (); - req->count = std::numeric_limitscount)>::max (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_TRUE (request->current.is_zero ()); -} -TEST (frontier_req, count) -{ - nano::test::system system (1); - auto node1 = system.nodes[0]; - // Public key FB93... after genesis in accounts table - nano::keypair key1 ("ED5AE0A6505B14B67435C29FD9FEEBC26F597D147BC92F6D795FFAD7AFD3D967"); - nano::state_block_builder builder; + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node1->work_generate_blocking (*send1); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - auto receive1 = builder - .make_block () - .account (key1.pub) - .previous (0) - .representative (nano::dev::genesis_key.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key1.prv, key1.pub) - .work (0) - .build (); - node1->work_generate_blocking (*receive1); - ASSERT_EQ (nano::block_status::progress, node1->process (receive1)); - - auto connection (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = std::numeric_limitsage)>::max (); - req->count = 1; - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (nano::dev::genesis_key.pub, request->current); - ASSERT_EQ (send1->hash (), request->frontier); -} + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, opens)); -TEST (frontier_req, time_bound) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = 1; - req->count = std::numeric_limitscount)>::max (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (nano::dev::genesis_key.pub, request->current); - // Wait 2 seconds until age of account will be > 1 seconds - std::this_thread::sleep_for (std::chrono::milliseconds (2100)); - auto req2 (std::make_unique (nano::dev::network_params.network)); - req2->start.clear (); - req2->age = 1; - req2->count = std::numeric_limitscount)>::max (); - auto connection2 (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto request2 (std::make_shared (connection, std::move (req2))); - ASSERT_TRUE (request2->current.is_zero ()); -} + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); -TEST (frontier_req, time_cutoff) -{ - nano::test::system system (1); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = 3; - req->count = std::numeric_limitscount)>::max (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (nano::dev::genesis_key.pub, request->current); - ASSERT_EQ (nano::dev::genesis->hash (), request->frontier); - // Wait 4 seconds until age of account will be > 3 seconds - std::this_thread::sleep_for (std::chrono::milliseconds (4100)); - auto req2 (std::make_unique (nano::dev::network_params.network)); - req2->start.clear (); - req2->age = 3; - req2->count = std::numeric_limitscount)>::max (); - auto connection2 (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - auto request2 (std::make_shared (connection, std::move (req2))); - ASSERT_TRUE (request2->frontier.is_zero ()); + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (opens.begin (), opens.end (), [&node1] (auto const & block) { + return node1.bootstrap.prioritized (block->account ()); + })); } -TEST (frontier_req, confirmed_frontier) -{ - nano::test::system system (1); - auto node1 = system.nodes[0]; - nano::keypair key_before_genesis; - // Public key before genesis in accounts table - while (key_before_genesis.pub.number () >= nano::dev::genesis_key.pub.number ()) - { - key_before_genesis = nano::keypair (); - } - nano::keypair key_after_genesis; - // Public key after genesis in accounts table - while (key_after_genesis.pub.number () <= nano::dev::genesis_key.pub.number ()) - { - key_after_genesis = nano::keypair (); - } - nano::state_block_builder builder; - - auto send1 = builder - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key_before_genesis.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node1->work_generate_blocking (*send1); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - auto send2 = builder - .make_block () - .account (nano::dev::genesis_key.pub) - .previous (send1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio) - .link (key_after_genesis.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (0) - .build (); - node1->work_generate_blocking (*send2); - ASSERT_EQ (nano::block_status::progress, node1->process (send2)); - auto receive1 = builder - .make_block () - .account (key_before_genesis.pub) - .previous (0) - .representative (nano::dev::genesis_key.pub) - .balance (nano::Knano_ratio) - .link (send1->hash ()) - .sign (key_before_genesis.prv, key_before_genesis.pub) - .work (0) - .build (); - node1->work_generate_blocking (*receive1); - ASSERT_EQ (nano::block_status::progress, node1->process (receive1)); - auto receive2 = builder - .make_block () - .account (key_after_genesis.pub) - .previous (0) - .representative (nano::dev::genesis_key.pub) - .balance (nano::Knano_ratio) - .link (send2->hash ()) - .sign (key_after_genesis.prv, key_after_genesis.pub) - .work (0) - .build (); - node1->work_generate_blocking (*receive2); - ASSERT_EQ (nano::block_status::progress, node1->process (receive2)); - - // Request for all accounts (confirmed only) - auto connection (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req = std::make_unique (nano::dev::network_params.network); - req->start.clear (); - req->age = std::numeric_limitsage)>::max (); - req->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req->header.frontier_req_is_only_confirmed_present ()); - req->header.flag_set (nano::message_header::frontier_req_only_confirmed); - ASSERT_TRUE (req->header.frontier_req_is_only_confirmed_present ()); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_EQ (nano::dev::genesis_key.pub, request->current); - ASSERT_EQ (nano::dev::genesis->hash (), request->frontier); - - // Request starting with account before genesis (confirmed only) - auto connection2 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req2 = std::make_unique (nano::dev::network_params.network); - req2->start = key_before_genesis.pub; - req2->age = std::numeric_limitsage)>::max (); - req2->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req2->header.frontier_req_is_only_confirmed_present ()); - req2->header.flag_set (nano::message_header::frontier_req_only_confirmed); - ASSERT_TRUE (req2->header.frontier_req_is_only_confirmed_present ()); - auto request2 (std::make_shared (connection2, std::move (req2))); - ASSERT_EQ (nano::dev::genesis_key.pub, request2->current); - ASSERT_EQ (nano::dev::genesis->hash (), request2->frontier); - - // Request starting with account after genesis (confirmed only) - auto connection3 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req3 = std::make_unique (nano::dev::network_params.network); - req3->start = key_after_genesis.pub; - req3->age = std::numeric_limitsage)>::max (); - req3->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req3->header.frontier_req_is_only_confirmed_present ()); - req3->header.flag_set (nano::message_header::frontier_req_only_confirmed); - ASSERT_TRUE (req3->header.frontier_req_is_only_confirmed_present ()); - auto request3 (std::make_shared (connection3, std::move (req3))); - ASSERT_TRUE (request3->current.is_zero ()); - ASSERT_TRUE (request3->frontier.is_zero ()); - - // Request for all accounts (unconfirmed blocks) - auto connection4 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req4 = std::make_unique (nano::dev::network_params.network); - req4->start.clear (); - req4->age = std::numeric_limitsage)>::max (); - req4->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req4->header.frontier_req_is_only_confirmed_present ()); - auto request4 (std::make_shared (connection4, std::move (req4))); - ASSERT_EQ (key_before_genesis.pub, request4->current); - ASSERT_EQ (receive1->hash (), request4->frontier); - - // Request starting with account after genesis (unconfirmed blocks) - auto connection5 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req5 = std::make_unique (nano::dev::network_params.network); - req5->start = key_after_genesis.pub; - req5->age = std::numeric_limitsage)>::max (); - req5->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req5->header.frontier_req_is_only_confirmed_present ()); - auto request5 (std::make_shared (connection5, std::move (req5))); - ASSERT_EQ (key_after_genesis.pub, request5->current); - ASSERT_EQ (receive2->hash (), request5->frontier); - - // Confirm account before genesis (confirmed only) - nano::test::confirm (node1->ledger, receive1); - auto connection6 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req6 = std::make_unique (nano::dev::network_params.network); - req6->start = key_before_genesis.pub; - req6->age = std::numeric_limitsage)>::max (); - req6->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req6->header.frontier_req_is_only_confirmed_present ()); - req6->header.flag_set (nano::message_header::frontier_req_only_confirmed); - ASSERT_TRUE (req6->header.frontier_req_is_only_confirmed_present ()); - auto request6 (std::make_shared (connection6, std::move (req6))); - ASSERT_EQ (key_before_genesis.pub, request6->current); - ASSERT_EQ (receive1->hash (), request6->frontier); - - // Confirm account after genesis (confirmed only) - nano::test::confirm (node1->ledger, receive2); - auto connection7 (std::make_shared (std::make_shared (*node1, nano::transport::socket_endpoint::server), node1)); - auto req7 = std::make_unique (nano::dev::network_params.network); - req7->start = key_after_genesis.pub; - req7->age = std::numeric_limitsage)>::max (); - req7->count = std::numeric_limitscount)>::max (); - ASSERT_FALSE (req7->header.frontier_req_is_only_confirmed_present ()); - req7->header.flag_set (nano::message_header::frontier_req_only_confirmed); - ASSERT_TRUE (req7->header.frontier_req_is_only_confirmed_present ()); - auto request7 (std::make_shared (connection7, std::move (req7))); - ASSERT_EQ (key_after_genesis.pub, request7->current); - ASSERT_EQ (receive2->hash (), request7->frontier); -} - -TEST (bulk, genesis) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - 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::dev::genesis_key.prv); - - auto node2 = system.make_disconnected_node (); - nano::block_hash latest1 (node1->latest (nano::dev::genesis_key.pub)); - nano::block_hash latest2 (node2->latest (nano::dev::genesis_key.pub)); - ASSERT_EQ (latest1, latest2); - nano::keypair key2; - auto send (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, 100)); - ASSERT_NE (nullptr, send); - nano::block_hash latest3 (node1->latest (nano::dev::genesis_key.pub)); - ASSERT_NE (latest1, latest3); - - node2->bootstrap_initiator.bootstrap (node1->network.endpoint (), false); - ASSERT_TIMELY_EQ (10s, node2->latest (nano::dev::genesis_key.pub), node1->latest (nano::dev::genesis_key.pub)); - ASSERT_EQ (node2->latest (nano::dev::genesis_key.pub), node1->latest (nano::dev::genesis_key.pub)); -} - -TEST (bulk, offline_send) +/* + * Bootstrap should not attempt to prioritize accounts that can't be immediately connected to the ledger (no pending blocks, no existing frontier) + */ +TEST (bootstrap, frontier_scan_cannot_prioritize) { nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - 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::dev::genesis_key.prv); - const auto amount = node1->config.receive_minimum.number (); - auto node2 = system.make_disconnected_node (); - nano::keypair key2; - auto wallet (node2->wallets.create (nano::random_wallet_id ())); - wallet->insert_adhoc (key2.prv); - - // send amount from genesis to key2, it will be autoreceived - auto send1 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, amount); - ASSERT_NE (nullptr, send1); - - // Wait to finish election background tasks - ASSERT_TIMELY (5s, node1->active.empty ()); - ASSERT_TIMELY (5s, node1->block_confirmed (send1->hash ())); - ASSERT_EQ (std::numeric_limits::max () - amount, node1->balance (nano::dev::genesis_key.pub)); - - // Initiate bootstrap - node2->bootstrap_initiator.bootstrap (node1->network.endpoint ()); - - // Nodes should find each other after bootstrap initiation - ASSERT_TIMELY (5s, !node1->network.empty ()); - ASSERT_TIMELY (5s, !node2->network.empty ()); - - // Send block arrival via bootstrap - ASSERT_TIMELY_EQ (5s, node2->balance (nano::dev::genesis_key.pub), std::numeric_limits::max () - amount); - // Receiving send block - ASSERT_TIMELY_EQ (5s, node2->balance (key2.pub), amount); -} -TEST (bulk, genesis_pruning) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.backlog_population.enable = false; - config.enable_voting = false; // Remove after allowing pruned voting - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_lazy_bootstrap = true; - node_flags.disable_ongoing_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - node_flags.enable_pruning = true; - - auto node1 = system.add_node (config, node_flags); - auto blocks = nano::test::setup_chain (system, *node1, 3); - auto send1 = blocks[0]; - auto send2 = blocks[1]; - auto send3 = blocks[2]; - - ASSERT_EQ (4, node1->ledger.block_count ()); - node1->ledger_pruning (2, false); - ASSERT_EQ (2, node1->ledger.pruned_count ()); - ASSERT_EQ (4, node1->ledger.block_count ()); - ASSERT_TRUE (node1->ledger.store.pruned.exists (node1->ledger.store.tx_begin_read (), send1->hash ())); - ASSERT_FALSE (nano::test::exists (*node1, { send1 })); - ASSERT_TRUE (node1->ledger.store.pruned.exists (node1->ledger.store.tx_begin_read (), send2->hash ())); - ASSERT_FALSE (nano::test::exists (*node1, { send2 })); - ASSERT_TRUE (nano::test::exists (*node1, { send3 })); - - // Bootstrap with missing blocks for node2 - node_flags.enable_pruning = false; - auto node2 = system.make_disconnected_node (std::nullopt, node_flags); - node2->bootstrap_initiator.bootstrap (node1->network.endpoint (), false); - node2->network.merge_peer (node1->network.endpoint ()); - ASSERT_TIMELY (5s, node2->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out) >= 1); - ASSERT_TIMELY (5s, !node2->bootstrap_initiator.in_progress ()); - - // node2 still missing blocks - ASSERT_EQ (1, node2->ledger.block_count ()); + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap.enable_scan = false; + config.bootstrap.enable_dependency_walker = false; + // Disable election activation + config.backlog_scan.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + std::vector> sends2; + std::vector> opens2; { - auto transaction (node2->store.tx_begin_write ()); - node2->unchecked.clear (); - } + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); - // Insert pruned blocks - node2->process_active (send1); - node2->process_active (send2); - ASSERT_TIMELY_EQ (5s, 3, node2->ledger.block_count ()); + size_t const count = 10; - // New bootstrap to sync up everything - ASSERT_TIMELY_EQ (5s, node2->bootstrap_initiator.connections->connections_count, 0); - node2->bootstrap_initiator.bootstrap (node1->network.endpoint (), false); - ASSERT_TIMELY_EQ (5s, node2->latest (nano::dev::genesis_key.pub), node1->latest (nano::dev::genesis_key.pub)); -} - -TEST (bulk_pull_account, basics) -{ - nano::test::system system (1); - system.nodes[0]->config.receive_minimum = 20; - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - system.wallet (0)->insert_adhoc (key1.prv); - auto send1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 25)); - auto send2 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 10)); - auto send3 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 2)); - ASSERT_TIMELY_EQ (5s, system.nodes[0]->balance (key1.pub), 25); - auto connection (std::make_shared (std::make_shared (*system.nodes[0], nano::transport::socket_endpoint::server), system.nodes[0])); - - { - auto req = std::make_unique (nano::dev::network_params.network); - req->account = key1.pub; - req->minimum_amount = 5; - req->flags = nano::bulk_pull_account_flags (); - auto request (std::make_shared (connection, std::move (req))); - ASSERT_FALSE (request->invalid_request); - ASSERT_FALSE (request->pending_include_address); - ASSERT_FALSE (request->pending_address_only); - ASSERT_EQ (request->current_key.account, key1.pub); - ASSERT_EQ (request->current_key.hash, 0); - auto block_data (request->get_next ()); - ASSERT_EQ (send2->hash (), block_data.first.get ()->hash); - ASSERT_EQ (nano::uint128_union (10), block_data.second.get ()->amount); - ASSERT_EQ (nano::dev::genesis_key.pub, block_data.second.get ()->source); - ASSERT_EQ (nullptr, request->get_next ().first.get ()); - } - - { - auto req = std::make_unique (nano::dev::network_params.network); - req->account = key1.pub; - req->minimum_amount = 0; - req->flags = nano::bulk_pull_account_flags::pending_address_only; - auto request (std::make_shared (connection, std::move (req))); - ASSERT_TRUE (request->pending_address_only); - auto block_data (request->get_next ()); - ASSERT_NE (nullptr, block_data.first.get ()); - ASSERT_NE (nullptr, block_data.second.get ()); - ASSERT_EQ (nano::dev::genesis_key.pub, block_data.second.get ()->source); - block_data = request->get_next (); - ASSERT_EQ (nullptr, block_data.first.get ()); - ASSERT_EQ (nullptr, block_data.second.get ()); + for (int n = 0; n < count; ++n) + { + nano::keypair key, key2; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto send2 = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (key.pub) + .balance (0) + .link (key2.pub) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + auto open2 = builder + .state () + .account (key2.pub) + .previous (0) + .representative (key2.pub) + .balance (1) + .link (send2->hash ()) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); + sends2.push_back (send2); + opens2.push_back (open2); + } } -} -TEST (block_deserializer, construction) -{ - auto deserializer = std::make_shared (); -} + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, sends2)); + ASSERT_TRUE (nano::test::process (node0, opens2)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should not detect the accounts + ASSERT_ALWAYS (1s, std::none_of (opens2.begin (), opens2.end (), [&node1] (auto const & block) { + return node1.bootstrap.prioritized (block->account ()); + })); +} \ No newline at end of file diff --git a/nano/core_test/bootstrap_ascending.cpp b/nano/core_test/bootstrap_ascending.cpp deleted file mode 100644 index 4595d08123..0000000000 --- a/nano/core_test/bootstrap_ascending.cpp +++ /dev/null @@ -1,486 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -using namespace std::chrono_literals; - -namespace -{ -nano::block_hash random_hash () -{ - nano::block_hash random_hash; - nano::random_pool::generate_block (random_hash.bytes.data (), random_hash.bytes.size ()); - return random_hash; -} -} - -TEST (account_sets, construction) -{ - nano::test::system system; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; -} - -TEST (account_sets, empty_blocked) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - ASSERT_FALSE (sets.blocked (account)); -} - -TEST (account_sets, block) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - sets.block (account, random_hash ()); - ASSERT_TRUE (sets.blocked (account)); -} - -TEST (account_sets, unblock) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - auto hash = random_hash (); - sets.block (account, hash); - sets.unblock (account, hash); - ASSERT_FALSE (sets.blocked (account)); -} - -TEST (account_sets, priority_base) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - ASSERT_EQ (0.0, sets.priority (account)); -} - -TEST (account_sets, priority_blocked) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - sets.block (account, random_hash ()); - ASSERT_EQ (0.0, sets.priority (account)); -} - -// When account is unblocked, check that it retains it former priority -TEST (account_sets, priority_unblock_keep) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - sets.priority_up (account); - sets.priority_up (account); - ASSERT_EQ (sets.priority (account), nano::bootstrap_ascending::account_sets::priority_initial + nano::bootstrap_ascending::account_sets::priority_increase); - auto hash = random_hash (); - sets.block (account, hash); - ASSERT_EQ (0.0, sets.priority (account)); - sets.unblock (account, hash); - ASSERT_EQ (sets.priority (account), nano::bootstrap_ascending::account_sets::priority_initial + nano::bootstrap_ascending::account_sets::priority_increase); -} - -TEST (account_sets, priority_up_down) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - sets.priority_up (account); - ASSERT_EQ (sets.priority (account), nano::bootstrap_ascending::account_sets::priority_initial); - sets.priority_down (account); - ASSERT_EQ (sets.priority (account), nano::bootstrap_ascending::account_sets::priority_initial / nano::bootstrap_ascending::account_sets::priority_divide); -} - -TEST (account_sets, priority_down_sat) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - sets.priority_down (account); - ASSERT_EQ (0.0, sets.priority (account)); -} - -// Ensure priority value is bounded -TEST (account_sets, saturate_priority) -{ - nano::test::system system; - - nano::account account{ 1 }; - auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants); - ASSERT_FALSE (store->init_error ()); - nano::account_sets_config config; - nano::bootstrap_ascending::account_sets sets{ config, system.stats }; - for (int n = 0; n < 1000; ++n) - { - sets.priority_up (account); - } - ASSERT_EQ (sets.priority (account), nano::bootstrap_ascending::account_sets::priority_max); -} - -/** - * Tests the base case for returning - */ -TEST (bootstrap_ascending, account_base) -{ - nano::node_flags flags; - nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; - auto & node0 = *system.nodes[0]; - nano::state_block_builder builder; - auto send1 = builder.make_block () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .link (0) - .balance (nano::dev::constants.genesis_amount - 1) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node0.process (send1)); - auto & node1 = *system.add_node (flags); - ASSERT_TIMELY (5s, node1.block (send1->hash ()) != nullptr); -} - -/** - * Tests that bootstrap_ascending will return multiple new blocks in-order - */ -TEST (bootstrap_ascending, account_inductive) -{ - nano::node_flags flags; - nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; - auto & node0 = *system.nodes[0]; - nano::state_block_builder builder; - auto send1 = builder.make_block () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .link (0) - .balance (nano::dev::constants.genesis_amount - 1) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - auto send2 = builder.make_block () - .account (nano::dev::genesis_key.pub) - .previous (send1->hash ()) - .representative (nano::dev::genesis_key.pub) - .link (0) - .balance (nano::dev::constants.genesis_amount - 2) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send1->hash ())) - .build (); - // std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl; - // std::cerr << "Send1: " << send1->hash ().to_string () << std::endl; - // std::cerr << "Send2: " << send2->hash ().to_string () << std::endl; - ASSERT_EQ (nano::block_status::progress, node0.process (send1)); - ASSERT_EQ (nano::block_status::progress, node0.process (send2)); - auto & node1 = *system.add_node (flags); - ASSERT_TIMELY (50s, node1.block (send2->hash ()) != nullptr); -} - -/** - * Tests that bootstrap_ascending will return multiple new blocks in-order - */ -TEST (bootstrap_ascending, trace_base) -{ - nano::node_flags flags; - flags.disable_legacy_bootstrap = true; - nano::test::system system{ 1, nano::transport::transport_type::tcp, flags }; - auto & node0 = *system.nodes[0]; - nano::keypair key; - nano::state_block_builder builder; - auto send1 = builder.make_block () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .link (key.pub) - .balance (nano::dev::constants.genesis_amount - 1) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - auto receive1 = builder.make_block () - .account (key.pub) - .previous (0) - .representative (nano::dev::genesis_key.pub) - .link (send1->hash ()) - .balance (1) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - // std::cerr << "Genesis key: " << nano::dev::genesis_key.pub.to_account () << std::endl; - // std::cerr << "Key: " << key.pub.to_account () << std::endl; - // std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl; - // std::cerr << "send1: " << send1->hash ().to_string () << std::endl; - // std::cerr << "receive1: " << receive1->hash ().to_string () << std::endl; - auto & node1 = *system.add_node (); - // std::cerr << "--------------- Start ---------------\n"; - ASSERT_EQ (nano::block_status::progress, node0.process (send1)); - ASSERT_EQ (nano::block_status::progress, node0.process (receive1)); - ASSERT_EQ (node1.ledger.any.receivable_end (), node1.ledger.any.receivable_upper_bound (node1.ledger.tx_begin_read (), key.pub, 0)); - // std::cerr << "node0: " << node0.network.endpoint () << std::endl; - // std::cerr << "node1: " << node1.network.endpoint () << std::endl; - ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr); -} - -TEST (bootstrap_ascending, pending_database_scanner) -{ - nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits::max () }; - - // Prepare pending sends from genesis - // 1 account with 1 pending - // 1 account with 21 pendings - // 2 accounts with 1 pending each - std::deque> blocks; - nano::keypair key1, key2, key3, key4; - { - nano::state_block_builder builder; - - auto source = nano::dev::genesis_key; - auto latest = nano::dev::genesis->hash (); - auto balance = nano::dev::genesis->balance ().number (); - - // 1 account with 1 pending - { - auto send = builder.make_block () - .account (source.pub) - .previous (latest) - .representative (source.pub) - .link (key1.pub) - .balance (balance - 1) - .sign (source.prv, source.pub) - .work (*pool.generate (latest)) - .build (); - latest = send->hash (); - balance = send->balance_field ().value ().number (); - blocks.push_back (send); - } - // 1 account with 21 pendings - for (int i = 0; i < 21; ++i) - { - auto send = builder.make_block () - .account (source.pub) - .previous (latest) - .representative (source.pub) - .link (key2.pub) - .balance (balance - 1) - .sign (source.prv, source.pub) - .work (*pool.generate (latest)) - .build (); - latest = send->hash (); - balance = send->balance_field ().value ().number (); - blocks.push_back (send); - } - // 2 accounts with 1 pending each - { - auto send = builder.make_block () - .account (source.pub) - .previous (latest) - .representative (source.pub) - .link (key3.pub) - .balance (balance - 1) - .sign (source.prv, source.pub) - .work (*pool.generate (latest)) - .build (); - latest = send->hash (); - balance = send->balance_field ().value ().number (); - blocks.push_back (send); - } - { - auto send = builder.make_block () - .account (source.pub) - .previous (latest) - .representative (source.pub) - .link (key4.pub) - .balance (balance - 1) - .sign (source.prv, source.pub) - .work (*pool.generate (latest)) - .build (); - latest = send->hash (); - balance = send->balance_field ().value ().number (); - blocks.push_back (send); - } - } - - nano::test::ledger_context ctx{ std::move (blocks) }; - - // Single batch - { - nano::bootstrap_ascending::pending_database_iterator scanner{ ctx.ledger () }; - auto transaction = ctx.store ().tx_begin_read (); - auto accounts = scanner.next_batch (transaction, 256); - - // Check that account set contains all keys - ASSERT_EQ (accounts.size (), 4); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key1.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key2.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key3.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key4.pub) != accounts.end ()); - - ASSERT_EQ (scanner.completed, 1); - } - // Multi batch - { - nano::bootstrap_ascending::pending_database_iterator scanner{ ctx.ledger () }; - auto transaction = ctx.store ().tx_begin_read (); - - // Request accounts in multiple batches - auto accounts1 = scanner.next_batch (transaction, 2); - auto accounts2 = scanner.next_batch (transaction, 1); - auto accounts3 = scanner.next_batch (transaction, 1); - - ASSERT_EQ (accounts1.size (), 2); - ASSERT_EQ (accounts2.size (), 1); - ASSERT_EQ (accounts3.size (), 1); - - std::deque accounts; - accounts.insert (accounts.end (), accounts1.begin (), accounts1.end ()); - accounts.insert (accounts.end (), accounts2.begin (), accounts2.end ()); - accounts.insert (accounts.end (), accounts3.begin (), accounts3.end ()); - - // Check that account set contains all keys - ASSERT_EQ (accounts.size (), 4); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key1.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key2.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key3.pub) != accounts.end ()); - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key4.pub) != accounts.end ()); - - ASSERT_EQ (scanner.completed, 1); - } -} - -TEST (bootstrap_ascending, account_database_scanner) -{ - nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits::max () }; - - size_t const count = 4; - - // Prepare some accounts - std::deque> blocks; - std::deque keys; - { - nano::state_block_builder builder; - - auto source = nano::dev::genesis_key; - auto latest = nano::dev::genesis->hash (); - auto balance = nano::dev::genesis->balance ().number (); - - for (int i = 0; i < count; ++i) - { - nano::keypair key; - auto send = builder.make_block () - .account (source.pub) - .previous (latest) - .representative (source.pub) - .link (key.pub) - .balance (balance - 1) - .sign (source.prv, source.pub) - .work (*pool.generate (latest)) - .build (); - auto open = builder.make_block () - .account (key.pub) - .previous (0) - .representative (key.pub) - .link (send->hash ()) - .balance (1) - .sign (key.prv, key.pub) - .work (*pool.generate (key.pub)) - .build (); - latest = send->hash (); - balance = send->balance_field ().value ().number (); - blocks.push_back (send); - blocks.push_back (open); - keys.push_back (key); - } - } - - nano::test::ledger_context ctx{ std::move (blocks) }; - - // Single batch - { - nano::bootstrap_ascending::account_database_iterator scanner{ ctx.ledger () }; - auto transaction = ctx.store ().tx_begin_read (); - auto accounts = scanner.next_batch (transaction, 256); - - // Check that account set contains all keys - ASSERT_EQ (accounts.size (), keys.size () + 1); // +1 for genesis - for (auto const & key : keys) - { - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key.pub) != accounts.end ()); - } - ASSERT_EQ (scanner.completed, 1); - } - // Multi batch - { - nano::bootstrap_ascending::account_database_iterator scanner{ ctx.ledger () }; - auto transaction = ctx.store ().tx_begin_read (); - - // Request accounts in multiple batches - auto accounts1 = scanner.next_batch (transaction, 2); - auto accounts2 = scanner.next_batch (transaction, 2); - auto accounts3 = scanner.next_batch (transaction, 1); - - ASSERT_EQ (accounts1.size (), 2); - ASSERT_EQ (accounts2.size (), 2); - ASSERT_EQ (accounts3.size (), 1); - - std::deque accounts; - accounts.insert (accounts.end (), accounts1.begin (), accounts1.end ()); - accounts.insert (accounts.end (), accounts2.begin (), accounts2.end ()); - accounts.insert (accounts.end (), accounts3.begin (), accounts3.end ()); - - // Check that account set contains all keys - ASSERT_EQ (accounts.size (), keys.size () + 1); // +1 for genesis - for (auto const & key : keys) - { - ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key.pub) != accounts.end ()); - } - ASSERT_EQ (scanner.completed, 1); - } -} \ No newline at end of file diff --git a/nano/core_test/bootstrap_server.cpp b/nano/core_test/bootstrap_server.cpp index 659ecd46ba..45e493807a 100644 --- a/nano/core_test/bootstrap_server.cpp +++ b/nano/core_test/bootstrap_server.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/nano/core_test/bucketing.cpp b/nano/core_test/bucketing.cpp new file mode 100644 index 0000000000..94a6b41587 --- /dev/null +++ b/nano/core_test/bucketing.cpp @@ -0,0 +1,55 @@ +#include + +#include + +#include + +TEST (bucketing, construction) +{ + nano::bucketing bucketing; + ASSERT_EQ (63, bucketing.size ()); +} + +TEST (bucketing, zero_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (0, bucketing.bucket_index (0)); +} + +TEST (bucketing, raw_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (0, bucketing.bucket_index (nano::raw_ratio)); +} + +TEST (bucketing, nano_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (14, bucketing.bucket_index (nano::nano_ratio)); +} + +TEST (bucketing, Knano_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (49, bucketing.bucket_index (nano::Knano_ratio)); +} + +TEST (bucketing, max_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (62, bucketing.bucket_index (std::numeric_limits::max ())); +} + +TEST (bucketing, indices) +{ + nano::bucketing bucketing; + auto indices = bucketing.bucket_indices (); + ASSERT_EQ (63, indices.size ()); + ASSERT_EQ (indices.size (), bucketing.size ()); + + // Check that the indices are in ascending order + ASSERT_TRUE (std::adjacent_find (indices.begin (), indices.end (), [] (auto const & lhs, auto const & rhs) { + return lhs >= rhs; + }) + == indices.end ()); +} \ No newline at end of file diff --git a/nano/core_test/confirming_set.cpp b/nano/core_test/confirming_set.cpp index 596f65444c..c49b4f44b3 100644 --- a/nano/core_test/confirming_set.cpp +++ b/nano/core_test/confirming_set.cpp @@ -1,9 +1,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -16,45 +18,70 @@ using namespace std::chrono_literals; +namespace +{ +struct confirming_set_context +{ + nano::logger & logger; + nano::stats & stats; + nano::ledger & ledger; + + nano::unchecked_map unchecked; + nano::block_processor block_processor; + nano::confirming_set confirming_set; + + explicit confirming_set_context (nano::test::ledger_context & ledger_context, nano::node_config node_config = {}) : + logger{ ledger_context.logger () }, + stats{ ledger_context.stats () }, + ledger{ ledger_context.ledger () }, + unchecked{ 0, stats, false }, + block_processor{ node_config, ledger, unchecked, stats, logger }, + confirming_set{ node_config.confirming_set, ledger, block_processor, stats, logger } + { + } +}; +} + TEST (confirming_set, construction) { - auto ctx = nano::test::ledger_empty (); - nano::confirming_set_config config{}; - nano::confirming_set confirming_set{ config, ctx.ledger (), ctx.stats (), ctx.logger () }; + auto ledger_ctx = nano::test::ledger_empty (); + confirming_set_context ctx{ ledger_ctx }; } TEST (confirming_set, add_exists) { - auto ctx = nano::test::ledger_send_receive (); - nano::confirming_set_config config{}; - nano::confirming_set confirming_set{ config, ctx.ledger (), ctx.stats (), ctx.logger () }; - auto send = ctx.blocks ()[0]; + auto ledger_ctx = nano::test::ledger_send_receive (); + confirming_set_context ctx{ ledger_ctx }; + nano::confirming_set & confirming_set = ctx.confirming_set; + auto send = ledger_ctx.blocks ()[0]; confirming_set.add (send->hash ()); ASSERT_TRUE (confirming_set.contains (send->hash ())); } TEST (confirming_set, process_one) { - auto ctx = nano::test::ledger_send_receive (); - nano::confirming_set_config config{}; - nano::confirming_set confirming_set{ config, ctx.ledger (), ctx.stats (), ctx.logger () }; + auto ledger_ctx = nano::test::ledger_send_receive (); + confirming_set_context ctx{ ledger_ctx }; + nano::confirming_set & confirming_set = ctx.confirming_set; std::atomic count = 0; std::mutex mutex; std::condition_variable condition; confirming_set.cemented_observers.add ([&] (auto const &) { ++count; condition.notify_all (); }); - confirming_set.add (ctx.blocks ()[0]->hash ()); + confirming_set.add (ledger_ctx.blocks ()[0]->hash ()); nano::test::start_stop_guard guard{ confirming_set }; std::unique_lock lock{ mutex }; ASSERT_TRUE (condition.wait_for (lock, 5s, [&] () { return count == 1; })); - ASSERT_EQ (1, ctx.stats ().count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (2, ctx.ledger ().cemented_count ()); + ASSERT_EQ (1, ctx.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (2, ctx.ledger.cemented_count ()); } TEST (confirming_set, process_multiple) { + nano::test::system system; + auto & node = *system.add_node (); auto ctx = nano::test::ledger_send_receive (); nano::confirming_set_config config{}; - nano::confirming_set confirming_set{ config, ctx.ledger (), ctx.stats (), ctx.logger () }; + nano::confirming_set confirming_set{ config, ctx.ledger (), node.block_processor, ctx.stats (), ctx.logger () }; std::atomic count = 0; std::mutex mutex; std::condition_variable condition; @@ -73,7 +100,7 @@ TEST (confirmation_callback, observer_callbacks) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -117,11 +144,10 @@ TEST (confirmation_callback, observer_callbacks) TEST (confirmation_callback, confirmed_history) { nano::test::system system; - nano::node_flags node_flags; - node_flags.disable_ascending_bootstrap = true; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; - auto node = system.add_node (node_config, node_flags); + node_config.backlog_scan.enable = false; + node_config.bootstrap.enable = false; + auto node = system.add_node (node_config); nano::block_hash latest (node->latest (nano::dev::genesis_key.pub)); @@ -191,7 +217,7 @@ TEST (confirmation_callback, dependent_election) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); nano::block_hash latest (node->latest (nano::dev::genesis_key.pub)); diff --git a/nano/core_test/conflicts.cpp b/nano/core_test/conflicts.cpp index 43c1938158..f9469f3bf0 100644 --- a/nano/core_test/conflicts.cpp +++ b/nano/core_test/conflicts.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/nano/core_test/difficulty.cpp b/nano/core_test/difficulty.cpp index 2e65afacbf..77fbc418d8 100644 --- a/nano/core_test/difficulty.cpp +++ b/nano/core_test/difficulty.cpp @@ -1,8 +1,10 @@ +#include #include #include #include #include #include +#include #include #include diff --git a/nano/core_test/distributed_work.cpp b/nano/core_test/distributed_work.cpp index 6eb8437464..f5e040aa9a 100644 --- a/nano/core_test/distributed_work.cpp +++ b/nano/core_test/distributed_work.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp index 200f84ffbb..d329852211 100644 --- a/nano/core_test/election.cpp +++ b/nano/core_test/election.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ TEST (election, quorum_minimum_flip_success) nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = nano::dev::constants.genesis_amount; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); auto const latest_hash = nano::dev::genesis->hash (); @@ -86,7 +87,7 @@ TEST (election, quorum_minimum_flip_fail) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = nano::dev::constants.genesis_amount; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); nano::state_block_builder builder; @@ -137,7 +138,7 @@ TEST (election, quorum_minimum_confirm_success) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = nano::dev::constants.genesis_amount; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); nano::keypair key1; nano::block_builder builder; @@ -167,7 +168,7 @@ TEST (election, quorum_minimum_confirm_fail) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = nano::dev::constants.genesis_amount; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); nano::block_builder builder; @@ -205,7 +206,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks) nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -259,11 +260,9 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks) node1.rep_crawler.force_process (vote2, channel); ASSERT_FALSE (election->confirmed ()); - { - nano::lock_guard guard (node1.online_reps.mutex); - // Modify online_m for online_reps to more than is available, this checks that voting below updates it to current online reps. - node1.online_reps.online_m = node_config.online_weight_minimum.number () + 20; - } + + // Modify online_m for online_reps to more than is available, this checks that voting below updates it to current online reps. + node1.online_reps.force_online_weight (node_config.online_weight_minimum.number () + 20); ASSERT_EQ (nano::vote_code::vote, node1.vote_router.vote (vote2).at (send1->hash ())); ASSERT_TIMELY (5s, election->confirmed ()); ASSERT_NE (nullptr, node1.block (send1->hash ())); diff --git a/nano/core_test/election_scheduler.cpp b/nano/core_test/election_scheduler.cpp index 14f840d4fa..21af2c6a7e 100644 --- a/nano/core_test/election_scheduler.cpp +++ b/nano/core_test/election_scheduler.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -157,6 +158,57 @@ TEST (election_scheduler, activate_one_flush) ASSERT_TIMELY (5s, node.active.election (send1->qualified_root ())); } +/* + * Tests that an optimistic election can be transitioned to a priority election. + * + * The test: + * 1. Creates a chain of 2 blocks with an optimistic election for the second block + * 2. Confirms the first block in the chain + * 3. Attempts to start a priority election for the second block + * 4. Verifies that the existing optimistic election is transitioned to priority + * 5. Verifies a new vote is broadcast after the transition + */ +TEST (election_scheduler, transition_optimistic_to_priority) +{ + nano::test::system system; + nano::node_config config = system.default_config (); + config.optimistic_scheduler.gap_threshold = 1; + config.enable_voting = true; + config.hinted_scheduler.enable = false; + config.network_params.network.vote_broadcast_interval = 15000ms; + auto & node = *system.add_node (config); + + // Add representative + const nano::uint128_t rep_weight = nano::Knano_ratio * 100; + nano::keypair rep = nano::test::setup_rep (system, node, rep_weight); + system.wallet (0)->insert_adhoc (rep.prv); + + // Create a chain of blocks - and trigger an optimistic election for the last block + const int howmany_blocks = 2; + auto chains = nano::test::setup_chains (system, node, /* single chain */ 1, howmany_blocks, nano::dev::genesis_key, /* do not confirm */ false); + auto & [account, blocks] = chains.front (); + + // Wait for optimistic election to start for last block + auto const & block = blocks.back (); + ASSERT_TIMELY (5s, node.vote_router.active (block->hash ())); + auto election = node.active.election (block->qualified_root ()); + ASSERT_EQ (election->behavior (), nano::election_behavior::optimistic); + ASSERT_TIMELY_EQ (1s, 1, election->current_status ().status.vote_broadcast_count); + + // Confirm first block to allow upgrading second block's election + nano::test::confirm (node.ledger, blocks.at (howmany_blocks - 1)); + + // Attempt to start priority election for second block + node.active.insert (block, nano::election_behavior::priority); + + // Verify priority transition + ASSERT_EQ (election->behavior (), nano::election_behavior::priority); + ASSERT_EQ (1, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::transition_priority)); + // Verify vote broadcast after transitioning + ASSERT_TIMELY_EQ (1s, 2, election->current_status ().status.vote_broadcast_count); + ASSERT_TRUE (node.active.active (*block)); +} + /** * Tests that the election scheduler and the active transactions container (AEC) * work in sync with regards to the node configuration value "active_elections.size". @@ -178,7 +230,7 @@ TEST (election_scheduler, no_vacancy) nano::node_config config = system.default_config (); config.active_elections.size = 1; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config); nano::state_block_builder builder{}; @@ -195,7 +247,7 @@ TEST (election_scheduler, no_vacancy) .work (*system.work.generate (nano::dev::genesis->hash ())) .build (); ASSERT_EQ (nano::block_status::progress, node.process (send)); - node.process_confirmed (send->hash ()); + node.confirming_set.add (send->hash ()); auto receive = builder.make_block () .account (key.pub) @@ -207,7 +259,7 @@ TEST (election_scheduler, no_vacancy) .work (*system.work.generate (key.pub)) .build (); ASSERT_EQ (nano::block_status::progress, node.process (receive)); - node.process_confirmed (receive->hash ()); + node.confirming_set.add (receive->hash ()); ASSERT_TIMELY (5s, nano::test::confirmed (node, { send, receive })); @@ -256,8 +308,7 @@ TEST (election_scheduler_bucket, construction) auto & node = *system.add_node (); nano::scheduler::priority_bucket_config bucket_config; - nano::scheduler::bucket bucket{ nano::Knano_ratio, bucket_config, node.active, node.stats }; - ASSERT_EQ (nano::Knano_ratio, bucket.minimum_balance); + nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; ASSERT_TRUE (bucket.empty ()); ASSERT_EQ (0, bucket.size ()); } @@ -269,7 +320,9 @@ TEST (election_scheduler_bucket, insert_one) nano::scheduler::priority_bucket_config bucket_config; nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; + ASSERT_FALSE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (1000, block0 ())); + ASSERT_TRUE (bucket.contains (block0 ()->hash ())); ASSERT_FALSE (bucket.empty ()); ASSERT_EQ (1, bucket.size ()); auto blocks = bucket.blocks (); @@ -320,10 +373,15 @@ TEST (election_scheduler_bucket, max_blocks) }; nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; ASSERT_TRUE (bucket.push (2000, block0 ())); + ASSERT_TRUE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (900, block1 ())); + ASSERT_TRUE (bucket.contains (block1 ()->hash ())); ASSERT_FALSE (bucket.push (3000, block2 ())); + ASSERT_FALSE (bucket.contains (block2 ()->hash ())); ASSERT_TRUE (bucket.push (1001, block3 ())); // Evicts 2000 + ASSERT_FALSE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (1000, block0 ())); // Evicts 1001 + ASSERT_FALSE (bucket.contains (block3 ()->hash ())); ASSERT_EQ (2, bucket.size ()); auto blocks = bucket.blocks (); ASSERT_EQ (2, blocks.size ()); diff --git a/nano/core_test/entry.cpp b/nano/core_test/entry.cpp index 9d08284dd0..929544a834 100644 --- a/nano/core_test/entry.cpp +++ b/nano/core_test/entry.cpp @@ -1,5 +1,6 @@ +#include #include -#include +#include #include #include diff --git a/nano/core_test/fair_queue.cpp b/nano/core_test/fair_queue.cpp index 50fd6c8fc2..83366b1cea 100644 --- a/nano/core_test/fair_queue.cpp +++ b/nano/core_test/fair_queue.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/nano/core_test/fakes/work_peer.hpp b/nano/core_test/fakes/work_peer.hpp index bff40fee21..0e9e80f90d 100644 --- a/nano/core_test/fakes/work_peer.hpp +++ b/nano/core_test/fakes/work_peer.hpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 0dd6e786db..74a72aec21 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -1017,7 +1018,7 @@ TEST (votes, add_existing) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = nano::dev::constants.genesis_amount; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 = *system.add_node (node_config); nano::keypair key1; nano::block_builder builder; @@ -4266,7 +4267,7 @@ TEST (ledger, unchecked_epoch_invalid) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 (*system.add_node (node_config)); nano::keypair destination; nano::block_builder builder; @@ -5350,7 +5351,7 @@ TEST (ledger, pruning_safe_functions) ASSERT_EQ (nano::dev::genesis_key.pub, ledger.any.block_account (transaction, send2->hash ()).value ()); } -TEST (ledger, hash_root_random) +TEST (ledger, random_blocks) { nano::logger logger; auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants); @@ -5393,23 +5394,46 @@ TEST (ledger, hash_root_random) ASSERT_TRUE (store->pruned.exists (transaction, send1->hash ())); ASSERT_TRUE (ledger.any.block_exists (transaction, nano::dev::genesis->hash ())); ASSERT_TRUE (ledger.any.block_exists (transaction, send2->hash ())); - // Test random block including pruned - bool done (false); - auto iteration (0); - while (!done) + // Prunned block will not be included in the random selection because it's not in the blocks set { - ++iteration; - auto root_hash (ledger.hash_root_random (transaction)); - done = (root_hash.first == send1->hash ()) && (root_hash.second.is_zero ()); - ASSERT_LE (iteration, 1000); + bool done = false; + size_t iteration = 0; + while (!done && iteration < 42) + { + ++iteration; + auto blocks = ledger.random_blocks (transaction, 10); + ASSERT_EQ (blocks.size (), 10); // Random blocks should repeat if the ledger is smaller than the requested count + auto first = blocks.front (); + done = (first->hash () == send1->hash ()); + } + ASSERT_FALSE (done); } - done = false; - while (!done) + // Genesis and send2 should be included in the random selection { - ++iteration; - auto root_hash (ledger.hash_root_random (transaction)); - done = (root_hash.first == send2->hash ()) && (root_hash.second == send2->root ().as_block_hash ()); - ASSERT_LE (iteration, 1000); + bool done = false; + size_t iteration = 0; + while (!done) + { + ++iteration; + auto blocks = ledger.random_blocks (transaction, 1); + ASSERT_EQ (blocks.size (), 1); + auto first = blocks.front (); + done = (first->hash () == send2->hash ()); + ASSERT_LE (iteration, 1000); + } + } + { + bool done = false; + size_t iteration = 0; + while (!done) + { + ++iteration; + auto blocks = ledger.random_blocks (transaction, 1); + ASSERT_EQ (blocks.size (), 1); + auto first = blocks.front (); + done = (first->hash () == nano::dev::genesis->hash ()); + ASSERT_LE (iteration, 1000); + } } } @@ -5481,8 +5505,12 @@ TEST (ledger, migrate_lmdb_to_rocksdb) ASSERT_FALSE (rocksdb_store.confirmation_height.get (rocksdb_transaction, nano::dev::genesis_key.pub, confirmation_height_info)); ASSERT_EQ (confirmation_height_info.height, 2); ASSERT_EQ (confirmation_height_info.frontier, send->hash ()); - ASSERT_EQ (rocksdb_store.final_vote.get (rocksdb_transaction, nano::root (send->previous ())).size (), 1); - ASSERT_EQ (rocksdb_store.final_vote.get (rocksdb_transaction, nano::root (send->previous ()))[0], nano::block_hash (2)); + ASSERT_TRUE (rocksdb_store.final_vote.get (rocksdb_transaction, send->qualified_root ()).has_value ()); + ASSERT_EQ (rocksdb_store.final_vote.get (rocksdb_transaction, send->qualified_root ()).value (), nano::block_hash (2)); + + // Retry migration while rocksdb folder is still present + auto error_on_retry = ledger.migrate_lmdb_to_rocksdb (path); + ASSERT_EQ (error_on_retry, true); } TEST (ledger, is_send_genesis) @@ -5826,7 +5854,7 @@ TEST (ledger_transaction, write_wait_order) WAIT (250ms); // Allow thread to start auto fut2 = std::async (std::launch::async, [&ctx, &acquired2, &latch2] { - auto tx = ctx.ledger ().tx_begin_write (nano::store::writer::blockprocessor); + auto tx = ctx.ledger ().tx_begin_write (nano::store::writer::block_processor); acquired2 = true; latch2.wait (); // Wait for the signal to drop tx }); @@ -5856,3 +5884,38 @@ TEST (ledger_transaction, write_wait_order) // Signal to continue and drop the third transaction latch3.count_down (); } + +TEST (ledger_transaction, multithreaded_interleaving) +{ + nano::test::system system; + + auto ctx = nano::test::ledger_empty (); + + int constexpr num_threads = 2; + int constexpr num_iterations = 10; + int constexpr num_blocks = 10; + + std::deque threads; + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back ([&] { + for (int n = 0; n < num_iterations; ++n) + { + auto tx = ctx.ledger ().tx_begin_write (nano::store::writer::testing); + for (unsigned k = 0; k < num_blocks; ++k) + { + ctx.store ().account.put (tx, nano::account{ k }, nano::account_info{}); + } + for (unsigned k = 0; k < num_blocks; ++k) + { + ctx.store ().account.del (tx, nano::account{ k }); + } + } + }); + } + + for (auto & thread : threads) + { + thread.join (); + } +} \ No newline at end of file diff --git a/nano/core_test/ledger_confirm.cpp b/nano/core_test/ledger_confirm.cpp index f9bfeff998..5663154cc2 100644 --- a/nano/core_test/ledger_confirm.cpp +++ b/nano/core_test/ledger_confirm.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ TEST (ledger_confirm, multiple_accounts) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); nano::keypair key1; nano::keypair key2; @@ -232,7 +233,7 @@ TEST (ledger_confirm, send_receive_between_2_accounts) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); nano::keypair key1; nano::block_hash latest (node->latest (nano::dev::genesis_key.pub)); @@ -361,7 +362,7 @@ TEST (ledger_confirm, send_receive_self) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); nano::block_hash latest (node->latest (nano::dev::genesis_key.pub)); @@ -449,7 +450,7 @@ TEST (ledger_confirm, all_block_types) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config, node_flags); nano::block_hash latest (node->latest (nano::dev::genesis_key.pub)); nano::keypair key1; diff --git a/nano/core_test/ledger_priority.cpp b/nano/core_test/ledger_priority.cpp new file mode 100644 index 0000000000..f1a156fb64 --- /dev/null +++ b/nano/core_test/ledger_priority.cpp @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Test priority of genesis block +TEST (ledger_priority, genesis_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & store = ctx.store (); + auto transaction = ledger.tx_begin_write (); + + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *nano::dev::genesis); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (0, priority_timestamp); +} + +// Test priority of legacy blocks +TEST (ledger_priority, legacy_blocks_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + // Create legacy send block + nano::keypair key1; + nano::block_builder builder; + auto send = builder + .send () + .previous (nano::dev::genesis->hash ()) + .destination (key1.pub) + .balance (100) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check send priority - should use max of current and previous balance + auto const [send_balance, send_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, send_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, send_timestamp); + + // Create legacy open/receive block + auto open = builder + .open () + .source (send->hash ()) + .representative (key1.pub) + .account (key1.pub) + .sign (key1.prv, key1.pub) + .work (*pool.generate (key1.pub)) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open)); + + // Check open priority - should use current balance + auto const [open_balance, open_timestamp] = ledger.block_priority (transaction, *open); + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, open_balance); + ASSERT_EQ (open->sideband ().timestamp, open_timestamp); +} + +// Test priority of a send state block +TEST (ledger_priority, send_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // Create state send block + auto send = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (100) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check priority + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, priority_timestamp); +} + +// Test priority of a full balance state send +TEST (ledger_priority, full_balance_send) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // Create state send block that sends full balance + auto send = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (0) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check priority - should use previous balance since current is 0 + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, priority_timestamp); +} + +// Test priority of state blocks with multiple operations +TEST (ledger_priority, sequential_blocks) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 100) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // State open + auto open = builder + .state () + .account (key1.pub) + .previous (0) + .representative (key1.pub) + .balance (100) + .link (send1->hash ()) + .sign (key1.prv, key1.pub) + .work (*pool.generate (key1.pub)) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open)); + + // Second state send + auto send2 = builder + .state () + .account (key1.pub) + .previous (open->hash ()) + .representative (key1.pub) + .balance (50) + .link (nano::dev::genesis_key.pub) + .sign (key1.prv, key1.pub) + .work (*pool.generate (open->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2)); + + // Check priorities follow the chain + auto const [priority_balance1, timestamp1] = ledger.block_priority (transaction, *send1); + auto const [priority_balance2, timestamp2] = ledger.block_priority (transaction, *open); + auto const [priority_balance3, timestamp3] = ledger.block_priority (transaction, *send2); + + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance1); + ASSERT_EQ (100, priority_balance2); + ASSERT_EQ (100, priority_balance3); // Max of current (50) and previous (100) + + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, timestamp1); // genesis account + // Opening account must have equal or greater timestamp than sending counterpart. + ASSERT_GE (timestamp2, send1->sideband ().timestamp); // key1 account + ASSERT_EQ (open->sideband ().timestamp, timestamp3); // key1 account +} + +// Test priority after rolling back state blocks +TEST (ledger_priority, block_rollback) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 100) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // Get priority before rollback + auto const [priority_before, timestamp_before] = ledger.block_priority (transaction, *send1); + + // Rollback the send + ASSERT_FALSE (ledger.rollback (transaction, send1->hash ())); + + // Create new state send with different amount + auto send2 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 200) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2)); + + // Check priority after rollback and new block + auto const [priority_after, timestamp_after] = ledger.block_priority (transaction, *send2); + + // Priorities should be the same since both use genesis as previous + ASSERT_EQ (priority_before, priority_after); + ASSERT_EQ (timestamp_before, timestamp_after); +} + +// Test priority with state block fork +TEST (ledger_priority, block_fork) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1, key2; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 100) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // Create two competing state sends + auto send2a = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (send1->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 200) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (send1->hash ())) + .build (); + + auto send2b = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (send1->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 150) + .link (key2.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (send1->hash ())) + .build (); + + // Process first fork + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2a)); + + // Try to process second fork + ASSERT_EQ (nano::block_status::fork, ledger.process (transaction, send2b)); + + // Get priority of first fork + auto const [priority_a, timestamp_a] = ledger.block_priority (transaction, *send2a); + + // Rollback first fork + ASSERT_FALSE (ledger.rollback (transaction, send2a->hash ())); + + // Process second fork + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2b)); + + // Check priority of second fork + auto const [priority_b, timestamp_b] = ledger.block_priority (transaction, *send2b); + + // Both should use send1's balance as previous + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, priority_a); + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, priority_b); + // Both should use send1's timestamp + ASSERT_EQ (send1->sideband ().timestamp, timestamp_a); + ASSERT_EQ (send1->sideband ().timestamp, timestamp_b); +} \ No newline at end of file diff --git a/nano/core_test/memory_pool.cpp b/nano/core_test/memory_pool.cpp index 6d5dd4ae90..087f848947 100644 --- a/nano/core_test/memory_pool.cpp +++ b/nano/core_test/memory_pool.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include diff --git a/nano/core_test/message.cpp b/nano/core_test/message.cpp index 60d2338218..12ff989afd 100644 --- a/nano/core_test/message.cpp +++ b/nano/core_test/message.cpp @@ -1,8 +1,9 @@ #include #include #include -#include +#include #include +#include #include #include diff --git a/nano/core_test/message_deserializer.cpp b/nano/core_test/message_deserializer.cpp index 44e9fa06df..7d65d6528a 100644 --- a/nano/core_test/message_deserializer.cpp +++ b/nano/core_test/message_deserializer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index 3ae42f764b..21d74fae43 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -117,7 +118,7 @@ TEST (network, last_contacted) ASSERT_EQ (0, node0->network.size ()); nano::node_config node1_config = system.default_config (); - node1_config.tcp_incoming_connections_max = 0; // Prevent ephemeral node1->node0 channel repacement with incoming connection + node1_config.tcp.max_inbound_connections = 0; // Prevent ephemeral node1->node0 channel repacement with incoming connection auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), node1_config, system.work)); node1->start (); system.nodes.push_back (node1); @@ -132,8 +133,8 @@ TEST (network, last_contacted) { // check that the endpoints are part of the same connection - std::shared_ptr sock0 = channel0->socket.lock (); - std::shared_ptr sock1 = channel1->socket.lock (); + std::shared_ptr sock0 = channel0->socket; + std::shared_ptr sock1 = channel1->socket; ASSERT_EQ (sock0->local_endpoint (), sock1->remote_endpoint ()); ASSERT_EQ (sock1->local_endpoint (), sock0->remote_endpoint ()); } @@ -194,7 +195,7 @@ TEST (network, send_discarded_publish) .build (); { auto transaction = node1.ledger.tx_begin_read (); - node1.network.flood_block (block); + node1.network.flood_block (block, nano::transport::traffic_type::test); ASSERT_EQ (nano::dev::genesis->hash (), node1.ledger.any.account_head (transaction, nano::dev::genesis_key.pub)); ASSERT_EQ (nano::dev::genesis->hash (), node2.latest (nano::dev::genesis_key.pub)); } @@ -220,7 +221,7 @@ TEST (network, send_invalid_publish) .build (); { auto transaction = node1.ledger.tx_begin_read (); - node1.network.flood_block (block); + node1.network.flood_block (block, nano::transport::traffic_type::test); ASSERT_EQ (nano::dev::genesis->hash (), node1.ledger.any.account_head (transaction, nano::dev::genesis_key.pub)); ASSERT_EQ (nano::dev::genesis->hash (), node2.latest (nano::dev::genesis_key.pub)); } @@ -265,8 +266,6 @@ TEST (network, send_valid_publish) nano::test::system system (2, type, node_flags); auto & node1 (*system.nodes[0]); auto & node2 (*system.nodes[1]); - node1.bootstrap_initiator.stop (); - node2.bootstrap_initiator.stop (); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::keypair key2; system.wallet (1)->insert_adhoc (key2.prv); @@ -307,7 +306,7 @@ TEST (network, send_insufficient_work) nano::publish publish1{ nano::dev::network_params.network, block1 }; auto tcp_channel (node1.network.tcp_channels.find_node_id (node2.get_node_id ())); ASSERT_NE (nullptr, tcp_channel); - tcp_channel->send (publish1, [] (boost::system::error_code const & ec, size_t size) {}); + tcp_channel->send (publish1, nano::transport::traffic_type::test); ASSERT_EQ (0, node1.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); ASSERT_TIMELY (10s, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work) != 0); ASSERT_EQ (1, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); @@ -321,7 +320,7 @@ TEST (network, send_insufficient_work) .work (system.work_generate_limited (block1->hash (), node1.network_params.work.epoch_2_receive, node1.network_params.work.epoch_1 - 1)) .build (); nano::publish publish2{ nano::dev::network_params.network, block2 }; - tcp_channel->send (publish2, [] (boost::system::error_code const & ec, size_t size) {}); + tcp_channel->send (publish2, nano::transport::traffic_type::test); ASSERT_TIMELY (10s, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work) != 1); ASSERT_EQ (2, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); // Legacy block work epoch_1 @@ -334,7 +333,7 @@ TEST (network, send_insufficient_work) .work (*system.work.generate (block2->hash (), node1.network_params.work.epoch_2)) .build (); nano::publish publish3{ nano::dev::network_params.network, block3 }; - tcp_channel->send (publish3, [] (boost::system::error_code const & ec, size_t size) {}); + tcp_channel->send (publish3, nano::transport::traffic_type::test); ASSERT_EQ (0, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in)); ASSERT_TIMELY (10s, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) != 0); ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in)); @@ -350,7 +349,7 @@ TEST (network, send_insufficient_work) .work (system.work_generate_limited (block1->hash (), node1.network_params.work.epoch_2_receive, node1.network_params.work.epoch_1 - 1)) .build (); nano::publish publish4{ nano::dev::network_params.network, block4 }; - tcp_channel->send (publish4, [] (boost::system::error_code const & ec, size_t size) {}); + tcp_channel->send (publish4, nano::transport::traffic_type::test); ASSERT_TIMELY (10s, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) != 0); ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in)); ASSERT_EQ (2, node2.stats.count (nano::stat::type::error, nano::stat::detail::insufficient_work)); @@ -548,90 +547,6 @@ TEST (network, endpoint_bad_fd) ASSERT_TIMELY_EQ (10s, system.nodes[0]->network.endpoint ().port (), 0); } -TEST (tcp_listener, tcp_node_id_handshake) -{ - nano::test::system system (1); - auto socket (std::make_shared (*system.nodes[0])); - auto bootstrap_endpoint (system.nodes[0]->tcp_listener.endpoint ()); - auto cookie (system.nodes[0]->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (bootstrap_endpoint))); - ASSERT_TRUE (cookie); - nano::node_id_handshake::query_payload query{ *cookie }; - nano::node_id_handshake node_id_handshake{ nano::dev::network_params.network, query }; - auto input (node_id_handshake.to_shared_const_buffer ()); - std::atomic write_done (false); - socket->async_connect (bootstrap_endpoint, [&input, socket, &write_done] (boost::system::error_code const & ec) { - ASSERT_FALSE (ec); - socket->async_write (input, [&input, &write_done] (boost::system::error_code const & ec, size_t size_a) { - ASSERT_FALSE (ec); - ASSERT_EQ (input.size (), size_a); - write_done = true; - }); - }); - - ASSERT_TIMELY (5s, write_done); - - nano::node_id_handshake::response_payload response_zero{ 0 }; - nano::node_id_handshake node_id_handshake_response{ nano::dev::network_params.network, std::nullopt, response_zero }; - auto output (node_id_handshake_response.to_bytes ()); - std::atomic done (false); - socket->async_read (output, output->size (), [&output, &done] (boost::system::error_code const & ec, size_t size_a) { - ASSERT_FALSE (ec); - ASSERT_EQ (output->size (), size_a); - done = true; - }); - ASSERT_TIMELY (5s, done); -} - -// Test disabled because it's failing intermittently. -// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3611 -// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3615 -TEST (tcp_listener, DISABLED_tcp_listener_timeout_empty) -{ - nano::test::system system (1); - auto node0 (system.nodes[0]); - auto socket (std::make_shared (*node0)); - std::atomic connected (false); - socket->async_connect (node0->tcp_listener.endpoint (), [&connected] (boost::system::error_code const & ec) { - ASSERT_FALSE (ec); - connected = true; - }); - ASSERT_TIMELY (5s, connected); - bool disconnected (false); - system.deadline_set (std::chrono::seconds (6)); - while (!disconnected) - { - disconnected = node0->tcp_listener.connection_count () == 0; - ASSERT_NO_ERROR (system.poll ()); - } -} - -TEST (tcp_listener, tcp_listener_timeout_node_id_handshake) -{ - nano::test::system system (1); - auto node0 (system.nodes[0]); - auto socket (std::make_shared (*node0)); - auto cookie (node0->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (node0->tcp_listener.endpoint ()))); - ASSERT_TRUE (cookie); - nano::node_id_handshake::query_payload query{ *cookie }; - nano::node_id_handshake node_id_handshake{ nano::dev::network_params.network, query }; - auto channel = std::make_shared (*node0, socket); - socket->async_connect (node0->tcp_listener.endpoint (), [&node_id_handshake, channel] (boost::system::error_code const & ec) { - ASSERT_FALSE (ec); - channel->send (node_id_handshake, [] (boost::system::error_code const & ec, size_t size_a) { - ASSERT_FALSE (ec); - }); - }); - ASSERT_TIMELY (5s, node0->stats.count (nano::stat::type::tcp_server, nano::stat::detail::node_id_handshake) != 0); - ASSERT_EQ (node0->tcp_listener.connection_count (), 1); - bool disconnected (false); - system.deadline_set (std::chrono::seconds (20)); - while (!disconnected) - { - disconnected = node0->tcp_listener.connection_count () == 0; - ASSERT_NO_ERROR (system.poll ()); - } -} - // Test disabled because it's failing repeatedly for Windows + LMDB. // PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3622 // Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3621 @@ -647,14 +562,14 @@ TEST (network, peer_max_tcp_attempts) node_config.network.max_peers_per_ip = 3; auto node = system.add_node (node_config, node_flags); - for (auto i (0); i < node_config.network.max_peers_per_ip; ++i) + for (auto i = 0; i < node_config.network.max_peers_per_ip; ++i) { - auto node2 (std::make_shared (system.io_ctx, system.get_available_port (), nano::unique_path (), system.work, node_flags)); - node2->start (); - system.nodes.push_back (node2); - - // Start TCP attempt - node->network.merge_peer (node2->network.endpoint ()); + // Disable reachout from temporary nodes to avoid mixing outbound and inbound connections + nano::node_config temp_config = system.default_config (); + temp_config.network.peer_reachout = {}; + temp_config.network.cached_peer_reachout = {}; + auto temp_node = system.make_disconnected_node (temp_config, node_flags); + ASSERT_TRUE (node->network.merge_peer (temp_node->network.endpoint ())); } ASSERT_TIMELY_EQ (15s, node->network.size (), node_config.network.max_peers_per_ip); @@ -717,9 +632,9 @@ TEST (network, duplicate_detection) ASSERT_NE (nullptr, tcp_channel); ASSERT_EQ (0, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish_message)); - tcp_channel->send (publish); + tcp_channel->send (publish, nano::transport::traffic_type::test); ASSERT_ALWAYS_EQ (100ms, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish_message), 0); - tcp_channel->send (publish); + tcp_channel->send (publish, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (2s, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_publish_message), 1); } @@ -766,9 +681,9 @@ TEST (network, duplicate_vote_detection) ASSERT_NE (nullptr, tcp_channel); ASSERT_EQ (0, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message)); - tcp_channel->send (message); + tcp_channel->send (message, nano::transport::traffic_type::test); ASSERT_ALWAYS_EQ (100ms, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 0); - tcp_channel->send (message); + tcp_channel->send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (2s, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 1); } @@ -796,12 +711,12 @@ TEST (network, duplicate_revert_vote) ASSERT_NE (nullptr, tcp_channel); // First vote should be processed - tcp_channel->send (message1); + tcp_channel->send (message1, nano::transport::traffic_type::test); ASSERT_ALWAYS_EQ (100ms, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 0); ASSERT_TIMELY (5s, node1.network.filter.check (bytes1.data (), bytes1.size ())); // Second vote should get dropped from processor queue - tcp_channel->send (message2); + tcp_channel->send (message2, nano::transport::traffic_type::test); ASSERT_ALWAYS_EQ (100ms, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 0); // And the filter should not have it WAIT (500ms); // Give the node time to process the vote @@ -826,9 +741,9 @@ TEST (network, expire_duplicate_filter) // Send a vote ASSERT_EQ (0, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message)); - tcp_channel->send (message); + tcp_channel->send (message, nano::transport::traffic_type::test); ASSERT_ALWAYS_EQ (100ms, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 0); - tcp_channel->send (message); + tcp_channel->send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (2s, node1.stats.count (nano::stat::type::filter, nano::stat::detail::duplicate_confirm_ack_message), 1); // The filter should expire the vote after some time @@ -837,7 +752,7 @@ TEST (network, expire_duplicate_filter) } // The test must be completed in less than 1 second -TEST (network, bandwidth_limiter_4_messages) +TEST (network, DISABLED_bandwidth_limiter_4_messages) { nano::test::system system; nano::publish message{ nano::dev::network_params.network, nano::dev::genesis }; @@ -852,22 +767,22 @@ TEST (network, bandwidth_limiter_4_messages) // Send droppable messages for (auto i = 0; i < message_limit; i += 2) // number of channels { - channel1.send (message); - channel2.send (message); + channel1.send (message, nano::transport::traffic_type::test); + channel2.send (message, nano::transport::traffic_type::test); } // Only sent messages below limit, so we don't expect any drops ASSERT_TIMELY_EQ (1s, 0, node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); // Send droppable message; drop stats should increase by one now - channel1.send (message); + channel1.send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (1s, 1, node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); // Send non-droppable message, i.e. drop stats should not increase - channel2.send (message, nullptr, nano::transport::buffer_drop_policy::no_limiter_drop); + channel2.send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (1s, 1, node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); } -TEST (network, bandwidth_limiter_2_messages) +TEST (network, DISABLED_bandwidth_limiter_2_messages) { nano::test::system system; nano::publish message{ nano::dev::network_params.network, nano::dev::genesis }; @@ -880,10 +795,10 @@ TEST (network, bandwidth_limiter_2_messages) nano::transport::inproc::channel channel1{ node, node }; nano::transport::inproc::channel channel2{ node, node }; // change the bandwidth settings, 2 packets will be dropped - channel1.send (message); - channel2.send (message); - channel1.send (message); - channel2.send (message); + channel1.send (message, nano::transport::traffic_type::test); + channel2.send (message, nano::transport::traffic_type::test); + channel1.send (message, nano::transport::traffic_type::test); + channel2.send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (1s, 2, node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); } @@ -900,10 +815,10 @@ TEST (network, bandwidth_limiter_with_burst) nano::transport::inproc::channel channel1{ node, node }; nano::transport::inproc::channel channel2{ node, node }; // change the bandwidth settings, no packet will be dropped - channel1.send (message); - channel2.send (message); - channel1.send (message); - channel2.send (message); + channel1.send (message, nano::transport::traffic_type::test); + channel2.send (message, nano::transport::traffic_type::test); + channel1.send (message, nano::transport::traffic_type::test); + channel2.send (message, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (1s, 0, node.stats.count (nano::stat::type::drop, nano::stat::detail::publish, nano::stat::dir::out)); } @@ -1047,7 +962,7 @@ TEST (network, filter_invalid_network_bytes) // send a keepalive, from node2 to node1, with the wrong network bytes nano::keepalive keepalive{ nano::dev::network_params.network }; const_cast (keepalive.header.network) = nano::networks::invalid; - channel->send (keepalive); + channel->send (keepalive, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (5s, 1, node1.stats.count (nano::stat::type::error, nano::stat::detail::invalid_network)); } @@ -1066,7 +981,7 @@ TEST (network, filter_invalid_version_using) // send a keepalive, from node2 to node1, with the wrong version_using nano::keepalive keepalive{ nano::dev::network_params.network }; const_cast (keepalive.header.version_using) = nano::dev::network_params.network.protocol_version_min - 1; - channel->send (keepalive); + channel->send (keepalive, nano::transport::traffic_type::test); ASSERT_TIMELY_EQ (5s, 1, node1.stats.count (nano::stat::type::error, nano::stat::detail::outdated_version)); } @@ -1153,8 +1068,8 @@ TEST (network, purge_dead_channel) auto & node1 = *system.add_node (flags); - node1.observers.socket_connected.add ([&] (nano::transport::tcp_socket & sock) { - system.logger.debug (nano::log::type::test, "Connected: {}", sock); + node1.observers.socket_connected.add ([&] (auto const & socket) { + system.logger.debug (nano::log::type::test, "Connected socket: {}", nano::streamed (socket)); }); auto & node2 = *system.add_node (flags); @@ -1204,8 +1119,8 @@ TEST (network, purge_dead_channel_remote) auto & node1 = *system.add_node (flags); auto & node2 = *system.add_node (flags); - node2.observers.socket_connected.add ([&] (nano::transport::tcp_socket & sock) { - system.logger.debug (nano::log::type::test, "Connected: {}", sock); + node2.observers.socket_connected.add ([&] (auto const & socket) { + system.logger.debug (nano::log::type::test, "Connected socket: {}", nano::streamed (socket)); }); ASSERT_EQ (node1.network.size (), 1); diff --git a/nano/core_test/network_filter.cpp b/nano/core_test/network_filter.cpp index 788d7c2715..8765b11180 100644 --- a/nano/core_test/network_filter.cpp +++ b/nano/core_test/network_filter.cpp @@ -1,7 +1,8 @@ #include #include #include -#include +#include +#include #include #include @@ -163,4 +164,4 @@ TEST (network_filter, expire) ASSERT_FALSE (filter.check (2)); // Entry with epoch 1 should be expired ASSERT_FALSE (filter.apply (2)); // Entry with epoch 1 should be replaced -} \ No newline at end of file +} diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 377d61ed54..697210c217 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -1,12 +1,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -19,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -261,7 +265,7 @@ TEST (node, auto_bootstrap) { nano::test::system system; nano::node_config config (system.get_available_port ()); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; nano::node_flags node_flags; node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_lazy_bootstrap = true; @@ -278,7 +282,6 @@ TEST (node, auto_bootstrap) system.nodes.push_back (node1); ASSERT_NE (nullptr, nano::test::establish_tcp (system, *node1, node0->network.endpoint ())); ASSERT_TIMELY_EQ (10s, node1->balance (key2.pub), node0->config.receive_minimum.number ()); - ASSERT_TIMELY (10s, !node1->bootstrap_initiator.in_progress ()); ASSERT_TRUE (node1->block_or_pruned_exists (send1->hash ())); // Wait block receive ASSERT_TIMELY_EQ (5s, node1->ledger.block_count (), 3); @@ -290,7 +293,7 @@ TEST (node, auto_bootstrap_reverse) { nano::test::system system; nano::node_config config (system.get_available_port ()); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; nano::node_flags node_flags; node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_lazy_bootstrap = true; @@ -307,27 +310,6 @@ TEST (node, auto_bootstrap_reverse) ASSERT_TIMELY_EQ (10s, node1->balance (key2.pub), node0->config.receive_minimum.number ()); } -TEST (node, auto_bootstrap_age) -{ - nano::test::system system; - nano::node_config config (system.get_available_port ()); - config.backlog_population.enable = false; - nano::node_flags node_flags; - node_flags.disable_bootstrap_bulk_push_client = true; - node_flags.disable_lazy_bootstrap = true; - node_flags.bootstrap_interval = 1; - auto node0 = system.add_node (config, node_flags); - auto node1 (std::make_shared (system.io_ctx, system.get_available_port (), nano::unique_path (), system.work, node_flags)); - ASSERT_FALSE (node1->init_error ()); - node1->start (); - system.nodes.push_back (node1); - ASSERT_NE (nullptr, nano::test::establish_tcp (system, *node1, node0->network.endpoint ())); - // 4 bootstraps with frontiers age - ASSERT_TIMELY (10s, node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out) >= 3); - // More attempts with frontiers age - ASSERT_GE (node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out), node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out)); -} - TEST (node, merge_peers) { nano::test::system system (1); @@ -384,7 +366,7 @@ TEST (node, search_receivable_confirmed) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -413,7 +395,7 @@ TEST (node, search_receivable_pruned) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node1 = system.add_node (node_config); nano::node_flags node_flags; node_flags.enable_pruning = true; @@ -492,7 +474,7 @@ TEST (node, confirm_locked) .sign (nano::keypair ().prv, 0) .work (0) .build (); - system.nodes[0]->network.flood_block (block); + system.nodes[0]->network.flood_block (block, nano::transport::traffic_type::test); } TEST (node_config, random_rep) @@ -541,13 +523,13 @@ TEST (node, fork_publish) .build (); node1.work_generate_blocking (*send2); node1.process_active (send1); + node1.process_active (send2); ASSERT_TIMELY_EQ (5s, 1, node1.active.size ()); + ASSERT_TIMELY (5s, node1.active.active (*send2)); auto election (node1.active.election (send1->qualified_root ())); ASSERT_NE (nullptr, election); // Wait until the genesis rep activated & makes vote ASSERT_TIMELY_EQ (1s, election->votes ().size (), 2); - node1.process_active (send2); - ASSERT_TIMELY (5s, node1.active.active (*send2)); auto votes1 (election->votes ()); auto existing1 (votes1.find (nano::dev::genesis_key.pub)); ASSERT_NE (votes1.end (), existing1); @@ -719,9 +701,10 @@ TEST (node, fork_multi_flip) nano::test::system system; nano::node_flags node_flags; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 (*system.add_node (node_config, node_flags, type)); node_config.peering_port = system.get_available_port (); + node_config.bootstrap.account_sets.cooldown = 100ms; // Reduce cooldown to speed up fork resolution auto & node2 (*system.add_node (node_config, node_flags, type)); ASSERT_EQ (1, node1.network.size ()); nano::keypair key1; @@ -756,9 +739,9 @@ TEST (node, fork_multi_flip) auto election = nano::test::start_election (system, node2, send2->hash ()); ASSERT_NE (nullptr, election); - ASSERT_TIMELY (5s, election->contains (send1->hash ())); + ASSERT_TIMELY (10s, election->contains (send1->hash ())); nano::test::confirm (node1.ledger, send1); - ASSERT_TIMELY (5s, node2.block_or_pruned_exists (send1->hash ())); + ASSERT_TIMELY (10s, node2.block_or_pruned_exists (send1->hash ())); ASSERT_TRUE (nano::test::block_or_pruned_none_exists (node2, { send2, send3 })); auto winner = *election->tally ().begin (); ASSERT_EQ (*send1, *winner.second); @@ -770,15 +753,16 @@ TEST (node, fork_multi_flip) TEST (node, fork_bootstrap_flip) { nano::test::system system; - nano::node_config config0{ system.get_available_port () }; - config0.backlog_population.enable = false; + nano::node_config config1{ system.get_available_port () }; + config1.backlog_scan.enable = false; nano::node_flags node_flags; node_flags.disable_bootstrap_bulk_push_client = true; node_flags.disable_lazy_bootstrap = true; - auto & node1 = *system.add_node (config0, node_flags); + auto & node1 = *system.add_node (config1, node_flags); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - nano::node_config config1 (system.get_available_port ()); - auto & node2 = *system.make_disconnected_node (config1, node_flags); + nano::node_config config2 (system.get_available_port ()); + config2.bootstrap.account_sets.cooldown = 100ms; // Reduce cooldown to speed up fork resolution + auto & node2 = *system.make_disconnected_node (config2, node_flags); nano::block_hash latest = node1.latest (nano::dev::genesis_key.pub); nano::keypair key1; nano::send_block_builder builder; @@ -801,8 +785,8 @@ TEST (node, fork_bootstrap_flip) ASSERT_EQ (nano::block_status::progress, node1.ledger.process (node1.ledger.tx_begin_write (), send1)); ASSERT_EQ (nano::block_status::progress, node2.ledger.process (node2.ledger.tx_begin_write (), send2)); nano::test::confirm (node1.ledger, send1); - ASSERT_TIMELY (1s, node1.ledger.any.block_exists (node1.ledger.tx_begin_read (), send1->hash ())); - ASSERT_TIMELY (1s, node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), send2->hash ())); + ASSERT_TIMELY (5s, node1.ledger.any.block_exists (node1.ledger.tx_begin_read (), send1->hash ())); + ASSERT_TIMELY (5s, node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), send2->hash ())); // Additionally add new peer to confirm & replace bootstrap block node2.network.merge_peer (node1.network.endpoint ()); @@ -1022,14 +1006,9 @@ TEST (node, fork_no_vote_quorum) ASSERT_FALSE (system.wallet (1)->store.fetch (transaction, key1, key3)); auto vote = std::make_shared (key1, key3, 0, 0, std::vector{ send2->hash () }); nano::confirm_ack confirm{ nano::dev::network_params.network, vote }; - std::vector buffer; - { - nano::vectorstream stream (buffer); - confirm.serialize (stream); - } auto channel = node2.network.find_node_id (node3.node_id.pub); ASSERT_NE (nullptr, channel); - channel->send_buffer (nano::shared_const_buffer (std::move (buffer))); + channel->send (confirm, nano::transport::traffic_type::test); ASSERT_TIMELY (10s, node3.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::in) >= 3); ASSERT_EQ (node1.latest (nano::dev::genesis_key.pub), send1->hash ()); ASSERT_EQ (node2.latest (nano::dev::genesis_key.pub), send1->hash ()); @@ -1107,7 +1086,6 @@ TEST (node, DISABLED_fork_stale) nano::test::system system2 (1); auto & node1 (*system1.nodes[0]); auto & node2 (*system2.nodes[0]); - node2.bootstrap_initiator.bootstrap (node1.network.endpoint (), false); auto channel = nano::test::establish_tcp (system1, node2, node1.network.endpoint ()); auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector ()); @@ -1162,7 +1140,6 @@ TEST (node, DISABLED_fork_stale) node1.process_active (send2); node2.process_active (send1); node2.process_active (send2); - node2.bootstrap_initiator.bootstrap (node1.network.endpoint (), false); while (node2.block (send1->hash ()) == nullptr) { system1.poll (); @@ -1179,7 +1156,7 @@ TEST (node, DISABLED_broadcast_elected) nano::node_flags node_flags; nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node0 = system.add_node (node_config, node_flags, type); node_config.peering_port = system.get_available_port (); auto node1 = system.add_node (node_config, node_flags, type); @@ -1304,10 +1281,18 @@ TEST (node, DISABLED_broadcast_elected) TEST (node, rep_self_vote) { nano::test::system system; - nano::node_config node_config (system.get_available_port ()); + + nano::node_flags node_flags; + node_flags.disable_request_loop = true; // Prevent automatic election cleanup + nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = std::numeric_limits::max (); - node_config.backlog_population.enable = false; - auto node0 = system.add_node (node_config); + // Disable automatic election activation + node_config.backlog_scan.enable = false; + node_config.priority_scheduler.enable = false; + node_config.hinted_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; + auto node0 = system.add_node (node_config, node_flags); + nano::keypair rep_big; nano::block_builder builder; auto fund_big = builder.send () @@ -1326,15 +1311,19 @@ TEST (node, rep_self_vote) .build (); ASSERT_EQ (nano::block_status::progress, node0->process (fund_big)); ASSERT_EQ (nano::block_status::progress, node0->process (open_big)); + // Confirm both blocks, allowing voting on the upcoming block node0->start_election (node0->block (open_big->hash ())); + std::shared_ptr election; ASSERT_TIMELY (5s, election = node0->active.election (open_big->qualified_root ())); election->force_confirm (); + // Insert representatives into the node to allow voting system.wallet (0)->insert_adhoc (rep_big.prv); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); ASSERT_EQ (system.wallet (0)->wallets.reps ().voting, 2); + auto block0 = builder.send () .previous (fund_big->hash ()) .destination (rep_big.pub) @@ -1343,12 +1332,14 @@ TEST (node, rep_self_vote) .work (*system.work.generate (fund_big->hash ())) .build (); ASSERT_EQ (nano::block_status::progress, node0->process (block0)); - auto & active = node0->active; - auto & scheduler = node0->scheduler; + auto election1 = nano::test::start_election (system, *node0, block0->hash ()); ASSERT_NE (nullptr, election1); + // Wait until representatives are activated & make vote ASSERT_TIMELY_EQ (1s, election1->votes ().size (), 3); + + // Election should receive votes from representatives hosted on the same node auto rep_votes (election1->votes ()); ASSERT_NE (rep_votes.end (), rep_votes.find (nano::dev::genesis_key.pub)); ASSERT_NE (rep_votes.end (), rep_votes.find (rep_big.pub)); @@ -1376,8 +1367,6 @@ TEST (node, DISABLED_bootstrap_no_publish) auto transaction = node0->ledger.tx_begin_write (); ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction, send0)); } - ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); ASSERT_TRUE (node1->active.empty ()); system1.deadline_set (10s); while (node1->block (send0->hash ()) == nullptr) @@ -1391,51 +1380,6 @@ TEST (node, DISABLED_bootstrap_no_publish) } } -// Check that an outgoing bootstrap request can push blocks -// Test disabled because it's failing intermittently. -// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3512 -// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3515 -TEST (node, DISABLED_bootstrap_bulk_push) -{ - nano::test::system system; - nano::test::system system0; - nano::test::system system1; - nano::node_config config0 (system.get_available_port ()); - config0.backlog_population.enable = false; - auto node0 (system0.add_node (config0)); - nano::node_config config1 (system.get_available_port ()); - config1.backlog_population.enable = false; - auto node1 (system1.add_node (config1)); - nano::keypair key0; - // node0 knows about send0 but node1 doesn't. - auto send0 = nano::send_block_builder () - .previous (nano::dev::genesis->hash ()) - .destination (key0.pub) - .balance (500) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node0->process (send0)); - - ASSERT_FALSE (node0->bootstrap_initiator.in_progress ()); - ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); - ASSERT_TRUE (node1->active.empty ()); - node0->bootstrap_initiator.bootstrap (node1->network.endpoint (), false); - system1.deadline_set (10s); - while (node1->block (send0->hash ()) == nullptr) - { - ASSERT_NO_ERROR (system0.poll ()); - ASSERT_NO_ERROR (system1.poll ()); - } - // since this uses bulk_push, the new block should be republished - system1.deadline_set (10s); - while (node1->active.empty ()) - { - ASSERT_NO_ERROR (system0.poll ()); - ASSERT_NO_ERROR (system1.poll ()); - } -} - // Bootstrapping a forked open block should succeed. TEST (node, bootstrap_fork_open) { @@ -1486,8 +1430,6 @@ TEST (node, bootstrap_fork_open) ASSERT_EQ (nano::block_status::progress, node1->process (open1)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); ASSERT_FALSE (node1->block_or_pruned_exists (open0->hash ())); - ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); - node1->bootstrap_initiator.bootstrap (node0->network.endpoint (), false); ASSERT_TIMELY (1s, node1->active.empty ()); ASSERT_TIMELY (10s, !node1->block_or_pruned_exists (open1->hash ()) && node1->block_or_pruned_exists (open0->hash ())); } @@ -1495,12 +1437,10 @@ TEST (node, bootstrap_fork_open) // Unconfirmed blocks from bootstrap should be confirmed TEST (node, bootstrap_confirm_frontiers) { - // create 2 separate systems, the 2 system do not interact with each other automatically - nano::test::system system0 (1); - nano::test::system system1 (1); - auto node0 = system0.nodes[0]; - auto node1 = system1.nodes[0]; - system0.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + nano::test::system system; + auto node0 = system.add_node (); + auto node1 = system.add_node (); + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::keypair key0; // create block to send 500 raw from genesis to key0 and save into node0 ledger without immediately triggering an election @@ -1512,25 +1452,7 @@ TEST (node, bootstrap_confirm_frontiers) .work (*node0->work_generate_blocking (nano::dev::genesis->hash ())) .build (); ASSERT_EQ (nano::block_status::progress, node0->process (send0)); - - // each system only has one node, so there should be no bootstrapping going on - ASSERT_FALSE (node0->bootstrap_initiator.in_progress ()); - ASSERT_FALSE (node1->bootstrap_initiator.in_progress ()); - ASSERT_TRUE (node1->active.empty ()); - - // create a bootstrap connection from node1 to node0 - // this also has the side effect of adding node0 to node1's list of peers, which will trigger realtime connections too - node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); - - // Wait until the block is confirmed on node1. Poll more than usual because we are polling - // on 2 different systems at once and in sequence and there might be strange timing effects. - system0.deadline_set (10s); - system1.deadline_set (10s); - while (!node1->ledger.confirmed.block_exists_or_pruned (node1->ledger.tx_begin_read (), send0->hash ())) - { - ASSERT_NO_ERROR (system0.poll (std::chrono::milliseconds (1))); - ASSERT_NO_ERROR (system1.poll (std::chrono::milliseconds (1))); - } + ASSERT_TIMELY (10s, node1->block_confirmed (send0->hash ())); } // Test that if we create a block that isn't confirmed, the bootstrapping processes sync the missing block. @@ -1644,102 +1566,6 @@ TEST (node, balance_observer) } } -TEST (node, bootstrap_connection_scaling) -{ - nano::test::system system (1); - auto & node1 (*system.nodes[0]); - ASSERT_EQ (34, node1.bootstrap_initiator.connections->target_connections (5000, 1)); - ASSERT_EQ (4, node1.bootstrap_initiator.connections->target_connections (0, 1)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 1)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 1)); - ASSERT_EQ (32, node1.bootstrap_initiator.connections->target_connections (5000, 0)); - ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (0, 0)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 0)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 0)); - ASSERT_EQ (36, node1.bootstrap_initiator.connections->target_connections (5000, 2)); - ASSERT_EQ (8, node1.bootstrap_initiator.connections->target_connections (0, 2)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 2)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (10000000000, 2)); - node1.config.bootstrap_connections = 128; - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (0, 1)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 1)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (0, 2)); - ASSERT_EQ (64, node1.bootstrap_initiator.connections->target_connections (50000, 2)); - node1.config.bootstrap_connections_max = 256; - ASSERT_EQ (128, node1.bootstrap_initiator.connections->target_connections (0, 1)); - ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (50000, 1)); - ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (0, 2)); - ASSERT_EQ (256, node1.bootstrap_initiator.connections->target_connections (50000, 2)); - node1.config.bootstrap_connections_max = 0; - ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (0, 1)); - ASSERT_EQ (1, node1.bootstrap_initiator.connections->target_connections (50000, 1)); -} - -TEST (node, online_reps) -{ - nano::test::system system (1); - auto & node1 (*system.nodes[0]); - // 1 sample of minimum weight - ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); - auto vote (std::make_shared ()); - ASSERT_EQ (0, node1.online_reps.online ()); - node1.online_reps.observe (nano::dev::genesis_key.pub); - ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.online ()); - // 1 minimum, 1 maximum - ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); - node1.online_reps.sample (); - ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.trended ()); - node1.online_reps.clear (); - // 2 minimum, 1 maximum - node1.online_reps.sample (); - ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); -} - -TEST (node, online_reps_rep_crawler) -{ - nano::test::system system; - nano::node_flags flags; - flags.disable_rep_crawler = true; - auto & node1 = *system.add_node (flags); - auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), 0, std::vector{ nano::dev::genesis->hash () }); - ASSERT_EQ (0, node1.online_reps.online ()); - // Without rep crawler - node1.vote_processor.vote_blocking (vote, std::make_shared (node1)); - ASSERT_EQ (0, node1.online_reps.online ()); - // After inserting to rep crawler - auto channel = std::make_shared (node1); - node1.rep_crawler.force_query (nano::dev::genesis->hash (), channel); - node1.vote_processor.vote_blocking (vote, channel); - ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.online ()); -} - -TEST (node, online_reps_election) -{ - nano::test::system system; - nano::node_flags flags; - flags.disable_rep_crawler = true; - auto & node1 = *system.add_node (flags); - // Start election - nano::keypair key; - nano::state_block_builder builder; - auto send1 = builder.make_block () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node1.work_generate_blocking (nano::dev::genesis->hash ())) - .build (); - node1.process_active (send1); - ASSERT_TIMELY_EQ (5s, 1, node1.active.size ()); - // Process vote for ongoing election - auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), 0, std::vector{ send1->hash () }); - ASSERT_EQ (0, node1.online_reps.online ()); - node1.vote_processor.vote_blocking (vote, std::make_shared (node1)); - ASSERT_EQ (nano::dev::constants.genesis_amount - nano::Knano_ratio, node1.online_reps.online ()); -} - TEST (node, block_confirm) { auto type = nano::transport::transport_type::tcp; @@ -1809,7 +1635,7 @@ TEST (node, DISABLED_local_votes_cache) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; node_config.receive_minimum = nano::dev::constants.genesis_amount; auto & node (*system.add_node (node_config)); nano::state_block_builder builder; @@ -1893,7 +1719,7 @@ TEST (node, DISABLED_local_votes_cache_batch) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); ASSERT_GE (node.network_params.voting.max_cache, 2); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -1967,7 +1793,7 @@ TEST (node, DISABLED_local_votes_cache_generate_new_vote) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -2021,7 +1847,7 @@ TEST (node, DISABLED_local_votes_cache_fork) node_flags.disable_legacy_bootstrap = true; node_flags.disable_wallet_bootstrap = true; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node1 (*system.add_node (node_config, node_flags)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); auto send1 = nano::state_block_builder () @@ -2270,13 +2096,20 @@ TEST (node, epoch_conflict_confirm) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node0 = *system.add_node (node_config); node_config.peering_port = system.get_available_port (); auto & node1 = *system.add_node (node_config); nano::keypair key; nano::keypair epoch_signer (nano::dev::genesis_key); nano::state_block_builder builder; + + // Node 1 is the voting node + // Send sends to an account we control: send -> open -> change + // Send2 sends to an account with public key of the open block + // Epoch open qualified root: (open, 0) on account with the same public key as the hash of the open block + // Epoch open and change have the same root! + auto send = builder.make_block () .account (nano::dev::genesis_key.pub) .previous (nano::dev::genesis->hash ()) @@ -2323,34 +2156,30 @@ TEST (node, epoch_conflict_confirm) .work (*system.work.generate (open->hash ())) .build (); - // Process initial blocks on node1 - ASSERT_TRUE (nano::test::process (node1, { send, send2, open })); + // Process initial blocks + ASSERT_TRUE (nano::test::process (node0, nano::test::clone ({ send, send2, open }))); + ASSERT_TRUE (nano::test::process (node1, nano::test::clone ({ send, send2, open }))); - // Confirm open block in node1 to allow generating votes - nano::test::confirm (node1.ledger, open); - - // Process initial blocks on node0 - ASSERT_TRUE (nano::test::process (node0, { send, send2, open })); - - // Process conflicting blocks on node 0 as blocks coming from live network - ASSERT_TRUE (nano::test::process_live (node0, { change, epoch_open })); + // Process conflicting blocks on nodes as blocks coming from live network + ASSERT_TRUE (nano::test::process_live (node0, nano::test::clone ({ change, epoch_open }))); + ASSERT_TRUE (nano::test::process_live (node1, nano::test::clone ({ change, epoch_open }))); // Ensure blocks were propagated to both nodes ASSERT_TIMELY (5s, nano::test::exists (node0, { change, epoch_open })); ASSERT_TIMELY (5s, nano::test::exists (node1, { change, epoch_open })); // Confirm initial blocks in node1 to allow generating votes later - ASSERT_TRUE (nano::test::start_elections (system, node1, { change, epoch_open, send2 }, true)); + nano::test::confirm (node1, { change, epoch_open, send2 }); ASSERT_TIMELY (5s, nano::test::confirmed (node1, { change, epoch_open, send2 })); - // Start elections for node0 for conflicting change and epoch_open blocks (those two blocks have the same root) + // Start elections on node0 for conflicting change and epoch_open blocks (these two blocks have the same root) ASSERT_TRUE (nano::test::activate (node0, { change, epoch_open })); ASSERT_TIMELY (5s, nano::test::active (node0, { change, epoch_open })); - // Make node1 a representative + // Make node1 a representative so it can vote for both blocks system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv); - // Ensure the elections for conflicting blocks have completed + // Ensure the elections for conflicting blocks have started ASSERT_TIMELY (5s, nano::test::active (node0, { change, epoch_open })); // Ensure both conflicting blocks were successfully processed and confirmed @@ -2575,7 +2404,7 @@ TEST (node, block_processor_reject_state) send1->signature.bytes[0] ^= 1; ASSERT_FALSE (node.block_or_pruned_exists (send1->hash ())); node.process_active (send1); - ASSERT_TIMELY_EQ (5s, 1, node.stats.count (nano::stat::type::blockprocessor_result, nano::stat::detail::bad_signature)); + ASSERT_TIMELY_EQ (5s, 1, node.stats.count (nano::stat::type::block_processor_result, nano::stat::detail::bad_signature)); ASSERT_FALSE (node.block_or_pruned_exists (send1->hash ())); auto send2 = builder.make_block () .account (nano::dev::genesis_key.pub) @@ -2764,13 +2593,6 @@ TEST (node, dont_write_lock_node) TEST (node, bidirectional_tcp) { -#ifdef _WIN32 - if (nano::rocksdb_config::using_rocksdb_in_tests ()) - { - // Don't test this in rocksdb mode - GTEST_SKIP (); - } -#endif nano::test::system system; nano::node_flags node_flags; // Disable bootstrap to start elections for new blocks @@ -2778,10 +2600,10 @@ TEST (node, bidirectional_tcp) node_flags.disable_lazy_bootstrap = true; node_flags.disable_wallet_bootstrap = true; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node1 = system.add_node (node_config, node_flags); node_config.peering_port = system.get_available_port (); - node_config.tcp_incoming_connections_max = 0; // Disable incoming TCP connections for node 2 + node_config.tcp.max_inbound_connections = 0; // Disable incoming TCP connections for node 2 auto node2 = system.add_node (node_config, node_flags); // Check network connections ASSERT_EQ (1, node1->network.size ()); @@ -2971,7 +2793,7 @@ TEST (node, rollback_gap_source) { nano::test::system system; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); nano::state_block_builder builder; nano::keypair key; @@ -3039,7 +2861,7 @@ TEST (node, dependency_graph) { nano::test::system system; nano::node_config config (system.get_available_port ()); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config); nano::state_block_builder builder; @@ -3237,10 +3059,10 @@ TEST (node, dependency_graph_frontier) { nano::test::system system; nano::node_config config (system.get_available_port ()); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node1 = *system.add_node (config); config.peering_port = system.get_available_port (); - config.backlog_population.enable = true; + config.backlog_scan.enable = true; auto & node2 = *system.add_node (config); nano::state_block_builder builder; @@ -3404,9 +3226,9 @@ TEST (node, deferred_dependent_elections) { nano::test::system system; nano::node_config node_config_1{ system.get_available_port () }; - node_config_1.backlog_population.enable = false; + node_config_1.backlog_scan.enable = false; nano::node_config node_config_2{ system.get_available_port () }; - node_config_2.backlog_population.enable = false; + node_config_2.backlog_scan.enable = false; nano::node_flags flags; flags.disable_request_loop = true; auto & node = *system.add_node (node_config_1, flags); @@ -3563,9 +3385,9 @@ TEST (node, pruning_automatic) ASSERT_TIMELY (5s, node1.block (send2->hash ()) != nullptr); // Force-confirm both blocks - node1.process_confirmed (send1->hash ()); + node1.confirming_set.add (send1->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ())); - node1.process_confirmed (send2->hash ()); + node1.confirming_set.add (send2->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ())); // Check pruning result @@ -3614,9 +3436,9 @@ TEST (node, DISABLED_pruning_age) node1.process_active (send2); // Force-confirm both blocks - node1.process_confirmed (send1->hash ()); + node1.confirming_set.add (send1->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ())); - node1.process_confirmed (send2->hash ()); + node1.confirming_set.add (send2->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ())); // Three blocks in total, nothing pruned yet @@ -3675,9 +3497,9 @@ TEST (node, DISABLED_pruning_depth) node1.process_active (send2); // Force-confirm both blocks - node1.process_confirmed (send1->hash ()); + node1.confirming_set.add (send1->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ())); - node1.process_confirmed (send2->hash ()); + node1.confirming_set.add (send2->hash ()); ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ())); // Three blocks in total, nothing pruned yet @@ -3760,9 +3582,9 @@ TEST (node, local_block_broadcast) // Disable active elections to prevent the block from being broadcasted by the election auto node_config = system.default_config (); - node_config.priority_scheduler.enabled = false; - node_config.hinted_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.priority_scheduler.enable = false; + node_config.hinted_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; node_config.local_block_broadcaster.rebroadcast_interval = 1s; auto & node1 = *system.add_node (node_config); auto & node2 = *system.make_disconnected_node (); @@ -3814,4 +3636,23 @@ TEST (node, container_info) // This should just execute, sanitizers will catch any problems ASSERT_NO_THROW (node1.container_info ()); ASSERT_NO_THROW (node2.container_info ()); -} \ No newline at end of file +} + +TEST (node, bounded_backlog) +{ + nano::test::system system; + + nano::node_config node_config; + node_config.max_backlog = 10; + node_config.backlog_scan.enable = false; + auto & node = *system.add_node (node_config); + + const int howmany_blocks = 64; + const int howmany_chains = 16; + + auto chains = nano::test::setup_chains (system, node, howmany_chains, howmany_blocks, nano::dev::genesis_key, /* do not confirm */ false); + + node.backlog_scan.trigger (); + + ASSERT_TIMELY_EQ (20s, node.ledger.block_count (), 11); // 10 + genesis +} diff --git a/nano/core_test/numbers.cpp b/nano/core_test/numbers.cpp index a30b2eb108..7b6e77015e 100644 --- a/nano/core_test/numbers.cpp +++ b/nano/core_test/numbers.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -647,4 +648,99 @@ TEST (uint512_union, hash) ASSERT_NE (h (x1), h (x2)); } } +} + +TEST (sat_math, add_sat) +{ + // Test uint128_t + { + nano::uint128_t max = std::numeric_limits::max (); + nano::uint128_t one = 1; + nano::uint128_t large_val = max - 100; + + // Normal addition + ASSERT_EQ (nano::add_sat (one, one), nano::uint128_t (2)); + + // Saturation at max + ASSERT_EQ (nano::add_sat (max, one), max); + ASSERT_EQ (nano::add_sat (large_val, nano::uint128_t (200)), max); + ASSERT_EQ (nano::add_sat (max, max), max); + } + // Test uint256_t + { + nano::uint256_t max = std::numeric_limits::max (); + nano::uint256_t one = 1; + nano::uint256_t large_val = max - 100; + + // Normal addition + ASSERT_EQ (nano::add_sat (one, one), nano::uint256_t (2)); + + // Saturation at max + ASSERT_EQ (nano::add_sat (max, one), max); + ASSERT_EQ (nano::add_sat (large_val, nano::uint256_t (200)), max); + ASSERT_EQ (nano::add_sat (max, max), max); + } + // Test uint512_t + { + nano::uint512_t max = std::numeric_limits::max (); + nano::uint512_t one = 1; + nano::uint512_t large_val = max - 100; + + // Normal addition + ASSERT_EQ (nano::add_sat (one, one), nano::uint512_t (2)); + + // Saturation at max + ASSERT_EQ (nano::add_sat (max, one), max); + ASSERT_EQ (nano::add_sat (large_val, nano::uint512_t (200)), max); + ASSERT_EQ (nano::add_sat (max, max), max); + } +} + +TEST (sat_math, sub_sat) +{ + // Test uint128_t + { + nano::uint128_t max = std::numeric_limits::max (); + nano::uint128_t min = std::numeric_limits::min (); + nano::uint128_t one = 1; + nano::uint128_t hundred (100); + + // Normal subtraction + ASSERT_EQ (nano::sub_sat (hundred, one), nano::uint128_t (99)); + + // Saturation at min + ASSERT_EQ (nano::sub_sat (min, one), min); + ASSERT_EQ (nano::sub_sat (hundred, nano::uint128_t (200)), min); + ASSERT_EQ (nano::sub_sat (min, max), min); + } + // Test uint256_t + { + nano::uint256_t max = std::numeric_limits::max (); + nano::uint256_t min = std::numeric_limits::min (); + nano::uint256_t one = 1; + nano::uint256_t hundred (100); + + // Normal subtraction + ASSERT_EQ (nano::sub_sat (hundred, one), nano::uint256_t (99)); + + // Saturation at min + ASSERT_EQ (nano::sub_sat (min, one), min); + ASSERT_EQ (nano::sub_sat (hundred, nano::uint256_t (200)), min); + ASSERT_EQ (nano::sub_sat (min, max), min); + } + // Test uint512_t + { + nano::uint512_t max = std::numeric_limits::max (); + nano::uint512_t min = std::numeric_limits::min (); + nano::uint512_t one = 1; + nano::uint512_t hundred (100); + + // Normal subtraction + ASSERT_EQ (nano::sub_sat (hundred, one), nano::uint512_t (99)); + + // Saturation at min + ASSERT_EQ (nano::sub_sat (min, one), min); + ASSERT_EQ (nano::sub_sat (hundred, nano::uint512_t (200)), min); + ASSERT_EQ (nano::sub_sat (min, max), min); + } } \ No newline at end of file diff --git a/nano/core_test/observer_set.cpp b/nano/core_test/observer_set.cpp new file mode 100644 index 0000000000..14c6d1301c --- /dev/null +++ b/nano/core_test/observer_set.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include + +#include +#include + +using namespace std::chrono_literals; + +TEST (observer_set, notify_one) +{ + nano::observer_set set; + int value{ 0 }; + set.add ([&value] (int v) { + value = v; + }); + set.notify (1); + ASSERT_EQ (1, value); +} + +TEST (observer_set, notify_multiple) +{ + nano::observer_set set; + int value{ 0 }; + set.add ([&value] (int v) { + value = v; + }); + set.add ([&value] (int v) { + value += v; + }); + set.notify (1); + ASSERT_EQ (2, value); +} + +TEST (observer_set, notify_empty) +{ + nano::observer_set set; + set.notify (1); +} + +TEST (observer_set, notify_multiple_types) +{ + nano::observer_set set; + int value{ 0 }; + std::string str; + set.add ([&value, &str] (int v, std::string s) { + value = v; + str = s; + }); + set.notify (1, "test"); + ASSERT_EQ (1, value); + ASSERT_EQ ("test", str); +} + +TEST (observer_set, empty_params) +{ + nano::observer_set<> set; + set.notify (); +} + +// Make sure there are no TSAN warnings +TEST (observer_set, parallel_notify) +{ + nano::observer_set set; + std::atomic value{ 0 }; + set.add ([&value] (int v) { + std::this_thread::sleep_for (100ms); + value = v; + }); + nano::timer timer{ nano::timer_state::started }; + std::vector threads; + for (int i = 0; i < 10; ++i) + { + threads.emplace_back ([&set] { + set.notify (1); + }); + } + for (auto & thread : threads) + { + thread.join (); + } + ASSERT_EQ (1, value); + // Notification should be done in parallel + ASSERT_LT (timer.since_start (), 300ms); +} + +namespace +{ +struct move_only +{ + move_only () = default; + move_only (move_only &&) = default; + move_only & operator= (move_only &&) = default; + move_only (move_only const &) = delete; + move_only & operator= (move_only const &) = delete; +}; + +struct copy_throw +{ + copy_throw () = default; + copy_throw (copy_throw &&) = default; + copy_throw & operator= (copy_throw &&) = default; + copy_throw (copy_throw const &) + { + throw std::runtime_error ("copy_throw"); + } + copy_throw & operator= (copy_throw const &) = delete; +}; +} + +// Ensure that parameters are not unnecessarily copied, this should compile +TEST (observer_set, move_only) +{ + nano::observer_set set; + set.add ([] (move_only const &) { + }); + move_only value; + set.notify (value); +} + +TEST (observer_set, copy_throw) +{ + nano::observer_set set; + set.add ([] (copy_throw const &) { + }); + copy_throw value; + ASSERT_NO_THROW (set.notify (value)); +} \ No newline at end of file diff --git a/nano/core_test/online_reps.cpp b/nano/core_test/online_reps.cpp new file mode 100644 index 0000000000..3bbaffabd2 --- /dev/null +++ b/nano/core_test/online_reps.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +#include + +TEST (online_reps, basic) +{ + nano::test::system system (1); + auto & node1 (*system.nodes[0]); + // 1 sample of minimum weight + ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); + auto vote (std::make_shared ()); + ASSERT_EQ (0, node1.online_reps.online ()); + node1.online_reps.observe (nano::dev::genesis_key.pub); + ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.online ()); + // 1 minimum, 1 maximum + ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); + node1.online_reps.force_sample (); + ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.trended ()); + node1.online_reps.clear (); + // 2 minimum, 1 maximum + node1.online_reps.force_sample (); + ASSERT_EQ (node1.config.online_weight_minimum, node1.online_reps.trended ()); +} + +TEST (online_reps, rep_crawler) +{ + nano::test::system system; + nano::node_flags flags; + flags.disable_rep_crawler = true; + auto & node1 = *system.add_node (flags); + auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), 0, std::vector{ nano::dev::genesis->hash () }); + ASSERT_EQ (0, node1.online_reps.online ()); + // Without rep crawler + node1.vote_processor.vote_blocking (vote, std::make_shared (node1)); + ASSERT_EQ (0, node1.online_reps.online ()); + // After inserting to rep crawler + auto channel = std::make_shared (node1); + node1.rep_crawler.force_query (nano::dev::genesis->hash (), channel); + node1.vote_processor.vote_blocking (vote, channel); + ASSERT_EQ (nano::dev::constants.genesis_amount, node1.online_reps.online ()); +} + +TEST (online_reps, election) +{ + nano::test::system system; + nano::node_flags flags; + flags.disable_rep_crawler = true; + auto & node1 = *system.add_node (flags); + // Start election + nano::keypair key; + nano::state_block_builder builder; + auto send1 = builder.make_block () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) + .link (key.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*node1.work_generate_blocking (nano::dev::genesis->hash ())) + .build (); + node1.process_active (send1); + ASSERT_TIMELY_EQ (5s, 1, node1.active.size ()); + // Process vote for ongoing election + auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), 0, std::vector{ send1->hash () }); + ASSERT_EQ (0, node1.online_reps.online ()); + node1.vote_processor.vote_blocking (vote, std::make_shared (node1)); + ASSERT_EQ (nano::dev::constants.genesis_amount - nano::Knano_ratio, node1.online_reps.online ()); +} \ No newline at end of file diff --git a/nano/core_test/optimistic_scheduler.cpp b/nano/core_test/optimistic_scheduler.cpp index 25d0771db3..fa2107d789 100644 --- a/nano/core_test/optimistic_scheduler.cpp +++ b/nano/core_test/optimistic_scheduler.cpp @@ -85,7 +85,7 @@ TEST (optimistic_scheduler, under_gap_threshold) { nano::test::system system{}; nano::node_config config = system.default_config (); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config); // Must be smaller than optimistic scheduler `gap_threshold` @@ -98,7 +98,7 @@ TEST (optimistic_scheduler, under_gap_threshold) nano::test::confirm (node.ledger, blocks.at (55)); // Manually trigger backlog scan - node.backlog.trigger (); + node.backlog_scan.trigger (); // Ensure unconfirmed account head block gets activated auto const & block = blocks.back (); diff --git a/nano/core_test/peer_container.cpp b/nano/core_test/peer_container.cpp index 44f7993b7a..7857f43fef 100644 --- a/nano/core_test/peer_container.cpp +++ b/nano/core_test/peer_container.cpp @@ -55,7 +55,7 @@ TEST (peer_container, reserved_ip_is_not_a_peer) // Test the TCP channel cleanup function works properly. It is used to remove peers that are not // exchanging messages after a while. -TEST (peer_container, tcp_channel_cleanup_works) +TEST (peer_container, DISABLED_tcp_channel_cleanup_works) { nano::test::system system; nano::node_config node_config = system.default_config (); @@ -90,6 +90,7 @@ TEST (peer_container, tcp_channel_cleanup_works) for (auto it = 0; node1.network.tcp_channels.size () > 1 && it < 10; ++it) { + // FIXME: This is racy and doesn't work reliably // we can't control everything the nodes are doing in background, so using the middle time as // the cutoff point. auto const channel1_last_packet_sent = channel1->get_last_packet_sent (); @@ -254,7 +255,7 @@ TEST (peer_container, depeer_on_outdated_version) nano::keepalive keepalive{ nano::dev::network_params.network }; const_cast (keepalive.header.version_using) = nano::dev::network_params.network.protocol_version_min - 1; ASSERT_TIMELY (5s, channel->alive ()); - channel->send (keepalive); + channel->send (keepalive, nano::transport::traffic_type::test); ASSERT_TIMELY (5s, !channel->alive ()); } diff --git a/nano/core_test/random.cpp b/nano/core_test/random.cpp new file mode 100644 index 0000000000..9217361551 --- /dev/null +++ b/nano/core_test/random.cpp @@ -0,0 +1,3 @@ +#include + +#include diff --git a/nano/core_test/rate_limiting.cpp b/nano/core_test/rate_limiting.cpp new file mode 100644 index 0000000000..0d547e41c0 --- /dev/null +++ b/nano/core_test/rate_limiting.cpp @@ -0,0 +1,113 @@ +#include +#include + +#include + +#include +#include + +using namespace std::chrono_literals; + +TEST (rate, basic) +{ + nano::rate::token_bucket bucket (10, 10); + + // Initial burst + ASSERT_TRUE (bucket.try_consume (10)); + ASSERT_FALSE (bucket.try_consume (10)); + + // With a fill rate of 10 tokens/sec, await 1/3 sec and get 3 tokens + std::this_thread::sleep_for (300ms); + ASSERT_TRUE (bucket.try_consume (3)); + ASSERT_FALSE (bucket.try_consume (10)); + + // Allow time for the bucket to completely refill and do a full burst + std::this_thread::sleep_for (1s); + ASSERT_TRUE (bucket.try_consume (10)); + ASSERT_EQ (bucket.largest_burst (), 10); +} + +TEST (rate, network) +{ + // For the purpose of the test, one token represents 1MB instead of one byte. + // Allow for 10 mb/s bursts (max bucket size), 5 mb/s long term rate + nano::rate::token_bucket bucket (10, 5); + + // Initial burst of 10 mb/s over two calls + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 5); + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 10); + ASSERT_FALSE (bucket.try_consume (5)); + + // After 200 ms, the 5 mb/s fillrate means we have 1 mb available + std::this_thread::sleep_for (200ms); + ASSERT_TRUE (bucket.try_consume (1)); + ASSERT_FALSE (bucket.try_consume (1)); +} + +TEST (rate, reset) +{ + nano::rate::token_bucket bucket (0, 0); + + // consume lots of tokens, buckets should be unlimited + ASSERT_TRUE (bucket.try_consume (1000000)); + ASSERT_TRUE (bucket.try_consume (1000000)); + + // set bucket to be limited + bucket.reset (1000, 1000); + ASSERT_FALSE (bucket.try_consume (1001)); + ASSERT_TRUE (bucket.try_consume (1000)); + ASSERT_FALSE (bucket.try_consume (1000)); + std::this_thread::sleep_for (2ms); + ASSERT_TRUE (bucket.try_consume (2)); + + // reduce the limit + bucket.reset (100, 100 * 1000); + ASSERT_FALSE (bucket.try_consume (101)); + ASSERT_TRUE (bucket.try_consume (100)); + std::this_thread::sleep_for (1ms); + ASSERT_TRUE (bucket.try_consume (100)); + + // increase the limit + bucket.reset (2000, 1); + ASSERT_FALSE (bucket.try_consume (2001)); + ASSERT_TRUE (bucket.try_consume (2000)); + + // back to unlimited + bucket.reset (0, 0); + ASSERT_TRUE (bucket.try_consume (1000000)); + ASSERT_TRUE (bucket.try_consume (1000000)); +} + +TEST (rate, unlimited) +{ + nano::rate::token_bucket bucket (0, 0); + ASSERT_TRUE (bucket.try_consume (5)); + ASSERT_EQ (bucket.largest_burst (), 5); + ASSERT_TRUE (bucket.try_consume (static_cast (1e9))); + ASSERT_EQ (bucket.largest_burst (), static_cast (1e9)); + + // With unlimited tokens, consuming always succeed + ASSERT_TRUE (bucket.try_consume (static_cast (1e9))); + ASSERT_EQ (bucket.largest_burst (), static_cast (1e9)); +} + +TEST (rate, busy_spin) +{ + // Bucket should refill at a rate of 1 token per second + nano::rate::token_bucket bucket (1, 1); + + // Run a very tight loop for 5 seconds + a bit of wiggle room + int counter = 0; + for (auto start = std::chrono::steady_clock::now (), now = start; now < start + 5500ms; now = std::chrono::steady_clock::now ()) + { + if (bucket.try_consume ()) + { + ++counter; + } + } + + // Bucket starts fully refilled, therefore we see 1 additional request + ASSERT_EQ (counter, 6); +} \ No newline at end of file diff --git a/nano/core_test/rep_crawler.cpp b/nano/core_test/rep_crawler.cpp index 1d52769df9..8a33786e67 100644 --- a/nano/core_test/rep_crawler.cpp +++ b/nano/core_test/rep_crawler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -127,53 +128,56 @@ TEST (rep_crawler, rep_remove) nano::keypair keys_rep2; // Principal representative 2 nano::block_builder builder; + auto const rep_weight = nano::test::minimum_principal_weight () * 2; + // Send enough nanos to Rep1 to make it a principal representative - std::shared_ptr send_to_rep1 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - searching_node.minimum_principal_weight () * 2) - .link (keys_rep1.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); + auto send_to_rep1 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - rep_weight) + .link (keys_rep1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (nano::dev::genesis->hash ())) + .build (); // Receive by Rep1 - std::shared_ptr receive_rep1 = builder - .state () - .account (keys_rep1.pub) - .previous (0) - .representative (keys_rep1.pub) - .balance (searching_node.minimum_principal_weight () * 2) - .link (send_to_rep1->hash ()) - .sign (keys_rep1.prv, keys_rep1.pub) - .work (*system.work.generate (keys_rep1.pub)) - .build (); + auto receive_rep1 = builder + .state () + .account (keys_rep1.pub) + .previous (0) + .representative (keys_rep1.pub) + .balance (rep_weight) + .link (send_to_rep1->hash ()) + .sign (keys_rep1.prv, keys_rep1.pub) + .work (*system.work.generate (keys_rep1.pub)) + .build (); // Send enough nanos to Rep2 to make it a principal representative - std::shared_ptr send_to_rep2 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (send_to_rep1->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - searching_node.minimum_principal_weight () * 4) - .link (keys_rep2.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (send_to_rep1->hash ())) - .build (); + auto send_to_rep2 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (send_to_rep1->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - rep_weight * 2) + .link (keys_rep2.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (send_to_rep1->hash ())) + .build (); // Receive by Rep2 - std::shared_ptr receive_rep2 = builder - .state () - .account (keys_rep2.pub) - .previous (0) - .representative (keys_rep2.pub) - .balance (searching_node.minimum_principal_weight () * 2) - .link (send_to_rep2->hash ()) - .sign (keys_rep2.prv, keys_rep2.pub) - .work (*system.work.generate (keys_rep2.pub)) - .build (); + auto receive_rep2 = builder + .state () + .account (keys_rep2.pub) + .previous (0) + .representative (keys_rep2.pub) + .balance (rep_weight) + .link (send_to_rep2->hash ()) + .sign (keys_rep2.prv, keys_rep2.pub) + .work (*system.work.generate (keys_rep2.pub)) + .build (); + { auto transaction = searching_node.ledger.tx_begin_write (); ASSERT_EQ (nano::block_status::progress, searching_node.ledger.process (transaction, send_to_rep1)); @@ -187,11 +191,12 @@ TEST (rep_crawler, rep_remove) // Ensure Rep1 is found by the rep_crawler after receiving a vote from it auto vote_rep1 = std::make_shared (keys_rep1.pub, keys_rep1.prv, 0, 0, std::vector{ nano::dev::genesis->hash () }); + ASSERT_LE (searching_node.minimum_principal_weight (), rep_weight); searching_node.rep_crawler.force_process (vote_rep1, channel_rep1); ASSERT_TIMELY_EQ (5s, searching_node.rep_crawler.representative_count (), 1); auto reps (searching_node.rep_crawler.representatives (1)); ASSERT_EQ (1, reps.size ()); - ASSERT_EQ (searching_node.minimum_principal_weight () * 2, searching_node.ledger.weight (reps[0].account)); + ASSERT_LE (searching_node.minimum_principal_weight (), searching_node.ledger.weight (reps[0].account)); ASSERT_EQ (keys_rep1.pub, reps[0].account); ASSERT_EQ (channel_rep1, reps[0].channel); @@ -322,9 +327,9 @@ TEST (rep_crawler, ignore_rebroadcasted) auto tick = [&] () { nano::confirm_ack msg{ nano::dev::network_params.network, vote, /* rebroadcasted */ true }; - channel2to1->send (msg, nullptr, nano::transport::buffer_drop_policy::no_socket_drop); + channel2to1->send (msg, nano::transport::traffic_type::test); return false; }; ASSERT_NEVER (1s, tick () || node1.rep_crawler.representative_count () > 0); -} \ No newline at end of file +} diff --git a/nano/core_test/request_aggregator.cpp b/nano/core_test/request_aggregator.cpp index 2f85074e40..0e28586de8 100644 --- a/nano/core_test/request_aggregator.cpp +++ b/nano/core_test/request_aggregator.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,7 @@ TEST (request_aggregator, one) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::block_builder builder; @@ -37,8 +38,7 @@ TEST (request_aggregator, one) std::vector> request{ { send1->hash (), send1->root () } }; - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + auto dummy_channel = nano::test::fake_channel (node); // Not yet in the ledger node.aggregator.request (request, dummy_channel); @@ -70,7 +70,7 @@ TEST (request_aggregator, one_update) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::keypair key1; @@ -136,7 +136,7 @@ TEST (request_aggregator, two) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::keypair key1; @@ -178,8 +178,9 @@ TEST (request_aggregator, two) std::vector> request; request.emplace_back (send2->hash (), send2->root ()); request.emplace_back (receive1->hash (), receive1->root ()); - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + + auto dummy_channel = nano::test::fake_channel (node); + // Process both blocks node.aggregator.request (request, dummy_channel); // One vote should be generated for both blocks @@ -207,7 +208,7 @@ TEST (request_aggregator, two_endpoints) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; nano::node_flags node_flags; node_flags.disable_rep_crawler = true; auto & node1 (*system.add_node (node_config, node_flags)); @@ -265,7 +266,7 @@ TEST (request_aggregator, split) size_t max_vbh = nano::network::confirm_ack_hashes_max; nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); std::vector> request; @@ -297,8 +298,9 @@ TEST (request_aggregator, split) } ASSERT_TIMELY_EQ (5s, max_vbh + 2, node.ledger.cemented_count ()); ASSERT_EQ (max_vbh + 1, request.size ()); - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + + auto dummy_channel = nano::test::fake_channel (node); + node.aggregator.request (request, dummy_channel); // In the ledger but no vote generated yet ASSERT_TIMELY_EQ (3s, 2, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes)); @@ -318,7 +320,7 @@ TEST (request_aggregator, channel_max_queue) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; node_config.request_aggregator.max_queue = 0; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -336,8 +338,9 @@ TEST (request_aggregator, channel_max_queue) ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), send1)); std::vector> request; request.emplace_back (send1->hash (), send1->root ()); - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + + auto dummy_channel = nano::test::fake_channel (node); + node.aggregator.request (request, dummy_channel); node.aggregator.request (request, dummy_channel); ASSERT_LT (0, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_dropped)); @@ -348,7 +351,7 @@ TEST (request_aggregator, DISABLED_unique) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node (*system.add_node (node_config)); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); nano::block_builder builder; @@ -365,8 +368,9 @@ TEST (request_aggregator, DISABLED_unique) ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), send1)); std::vector> request; request.emplace_back (send1->hash (), send1->root ()); - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + + auto dummy_channel = nano::test::fake_channel (node); + node.aggregator.request (request, dummy_channel); node.aggregator.request (request, dummy_channel); node.aggregator.request (request, dummy_channel); @@ -409,8 +413,8 @@ TEST (request_aggregator, cannot_vote) // Incorrect hash, correct root request.emplace_back (1, send2->root ()); - auto client = std::make_shared (node); - std::shared_ptr dummy_channel = std::make_shared (node, client); + auto dummy_channel = nano::test::fake_channel (node); + node.aggregator.request (request, dummy_channel); ASSERT_TIMELY (3s, node.aggregator.empty ()); ASSERT_EQ (1, node.stats.count (nano::stat::type::aggregator, nano::stat::detail::aggregator_accepted)); diff --git a/nano/core_test/socket.cpp b/nano/core_test/socket.cpp index fc19e26863..d0c213a066 100644 --- a/nano/core_test/socket.cpp +++ b/nano/core_test/socket.cpp @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -19,116 +20,7 @@ using namespace std::chrono_literals; -TEST (socket, max_connections) -{ - nano::test::system system; - - nano::node_flags node_flags; - nano::node_config node_config = system.default_config (); - node_config.tcp.max_inbound_connections = 2; - auto node = system.add_node (node_config, node_flags); - - // client side connection tracking - std::atomic connection_attempts = 0; - auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { - ASSERT_EQ (ec_a.value (), 0); - ++connection_attempts; - }; - - // start 3 clients, 2 will persist but 1 will be dropped - auto client1 = std::make_shared (*node); - client1->async_connect (node->network.endpoint (), connect_handler); - - auto client2 = std::make_shared (*node); - client2->async_connect (node->network.endpoint (), connect_handler); - - auto client3 = std::make_shared (*node); - client3->async_connect (node->network.endpoint (), connect_handler); - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 2); - ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 2); - ASSERT_TIMELY_EQ (5s, connection_attempts, 3); - - // create space for one socket and fill the connections table again - { - auto sockets1 = node->tcp_listener.sockets (); - ASSERT_EQ (sockets1.size (), 2); - sockets1[0]->close (); - } - ASSERT_TIMELY_EQ (10s, node->tcp_listener.sockets ().size (), 1); - - auto client4 = std::make_shared (*node); - client4->async_connect (node->network.endpoint (), connect_handler); - - auto client5 = std::make_shared (*node); - client5->async_connect (node->network.endpoint (), connect_handler); - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 3); - ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 3); - ASSERT_TIMELY_EQ (5s, connection_attempts, 5); - - // close all existing sockets and fill the connections table again - { - auto sockets2 = node->tcp_listener.sockets (); - ASSERT_EQ (sockets2.size (), 2); - sockets2[0]->close (); - sockets2[1]->close (); - } - ASSERT_TIMELY_EQ (10s, node->tcp_listener.sockets ().size (), 0); - - auto client6 = std::make_shared (*node); - client6->async_connect (node->network.endpoint (), connect_handler); - - auto client7 = std::make_shared (*node); - client7->async_connect (node->network.endpoint (), connect_handler); - - auto client8 = std::make_shared (*node); - client8->async_connect (node->network.endpoint (), connect_handler); - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 5); - ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 5); - ASSERT_TIMELY_EQ (5s, connection_attempts, 8); // connections initiated by the client -} - -TEST (socket, max_connections_per_ip) -{ - nano::test::system system; - - nano::node_flags node_flags; - nano::node_config node_config = system.default_config (); - node_config.network.max_peers_per_ip = 3; - auto node = system.add_node (node_config, node_flags); - ASSERT_FALSE (node->flags.disable_max_peers_per_ip); - - auto server_port = system.get_available_port (); - - const auto max_ip_connections = node->config.network.max_peers_per_ip; - ASSERT_GE (max_ip_connections, 1); - - // client side connection tracking - std::atomic connection_attempts = 0; - auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { - ASSERT_EQ (ec_a.value (), 0); - ++connection_attempts; - }; - - // start n clients, n-1 will persist but 1 will be dropped, where n == max_ip_connections - std::vector> client_list; - client_list.reserve (max_ip_connections + 1); - - for (auto idx = 0; idx < max_ip_connections + 1; ++idx) - { - auto client = std::make_shared (*node); - client->async_connect (node->network.endpoint (), connect_handler); - client_list.push_back (client); - } - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_ip_connections); - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_ip), 1); - ASSERT_TIMELY_EQ (5s, connection_attempts, max_ip_connections + 1); -} - -TEST (socket, limited_subnet_address) +TEST (socket_functions, limited_subnet_address) { auto address = boost::asio::ip::make_address ("a41d:b7b2:8298:cf45:672e:bd1a:e7fb:f713"); auto network = nano::transport::socket_functions::get_ipv6_subnet_address (address.to_v6 (), 32); // network prefix = 32. @@ -136,21 +28,21 @@ TEST (socket, limited_subnet_address) ASSERT_EQ ("a41d:b7b2::/32", network.canonical ().to_string ()); } -TEST (socket, first_ipv6_subnet_address) +TEST (socket_functions, first_ipv6_subnet_address) { auto address = boost::asio::ip::make_address ("a41d:b7b2:8298:cf45:672e:bd1a:e7fb:f713"); auto first_address = nano::transport::socket_functions::first_ipv6_subnet_address (address.to_v6 (), 32); // network prefix = 32. ASSERT_EQ ("a41d:b7b2::", first_address.to_string ()); } -TEST (socket, last_ipv6_subnet_address) +TEST (socket_functions, last_ipv6_subnet_address) { auto address = boost::asio::ip::make_address ("a41d:b7b2:8298:cf45:672e:bd1a:e7fb:f713"); auto last_address = nano::transport::socket_functions::last_ipv6_subnet_address (address.to_v6 (), 32); // network prefix = 32. ASSERT_EQ ("a41d:b7b2:ffff:ffff:ffff:ffff:ffff:ffff", last_address.to_string ()); } -TEST (socket, count_subnetwork_connections) +TEST (socket_functions, count_subnetwork_connections) { nano::test::system system; auto node = system.add_node (); @@ -184,87 +76,6 @@ TEST (socket, count_subnetwork_connections) ASSERT_EQ (4, nano::transport::socket_functions::count_subnetwork_connections (connections_per_address, address1.to_v6 (), 32)); } -TEST (socket, max_connections_per_subnetwork) -{ - nano::test::system system; - - nano::node_flags node_flags; - // disabling IP limit because it will be used the same IP address to check they come from the same subnetwork. - node_flags.disable_max_peers_per_ip = true; - node_flags.disable_max_peers_per_subnetwork = false; - nano::node_config node_config = system.default_config (); - node_config.network.max_peers_per_subnetwork = 3; - auto node = system.add_node (node_config, node_flags); - - ASSERT_TRUE (node->flags.disable_max_peers_per_ip); - ASSERT_FALSE (node->flags.disable_max_peers_per_subnetwork); - - const auto max_subnetwork_connections = node->config.network.max_peers_per_subnetwork; - ASSERT_GE (max_subnetwork_connections, 1); - - // client side connection tracking - std::atomic connection_attempts = 0; - auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { - ASSERT_EQ (ec_a.value (), 0); - ++connection_attempts; - }; - - // start n clients, n-1 will persist but 1 will be dropped, where n == max_subnetwork_connections - std::vector> client_list; - client_list.reserve (max_subnetwork_connections + 1); - - for (auto idx = 0; idx < max_subnetwork_connections + 1; ++idx) - { - auto client = std::make_shared (*node); - client->async_connect (node->network.endpoint (), connect_handler); - client_list.push_back (client); - } - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_subnetwork_connections); - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_subnetwork), 1); - ASSERT_TIMELY_EQ (5s, connection_attempts, max_subnetwork_connections + 1); -} - -TEST (socket, disabled_max_peers_per_ip) -{ - nano::test::system system; - - nano::node_flags node_flags; - node_flags.disable_max_peers_per_ip = true; - nano::node_config node_config = system.default_config (); - node_config.network.max_peers_per_ip = 3; - auto node = system.add_node (node_config, node_flags); - - ASSERT_TRUE (node->flags.disable_max_peers_per_ip); - - auto server_port = system.get_available_port (); - - const auto max_ip_connections = node->config.network.max_peers_per_ip; - ASSERT_GE (max_ip_connections, 1); - - // client side connection tracking - std::atomic connection_attempts = 0; - auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { - ASSERT_EQ (ec_a.value (), 0); - ++connection_attempts; - }; - - // start n clients, n-1 will persist but 1 will be dropped, where n == max_ip_connections - std::vector> client_list; - client_list.reserve (max_ip_connections + 1); - - for (auto idx = 0; idx < max_ip_connections + 1; ++idx) - { - auto client = std::make_shared (*node); - client->async_connect (node->network.endpoint (), connect_handler); - client_list.push_back (client); - } - - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_ip_connections + 1); - ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_ip), 0); - ASSERT_TIMELY_EQ (5s, connection_attempts, max_ip_connections + 1); -} - TEST (socket, disconnection_of_silent_connections) { nano::test::system system; @@ -316,9 +127,10 @@ TEST (socket, drop_policy) nano::inactive_node inactivenode (nano::unique_path (), node_flags); auto node = inactivenode.node; - std::vector> connections; + std::atomic completed_writes{ 0 }; + std::atomic failed_writes{ 0 }; - auto func = [&] (size_t total_message_count, nano::transport::buffer_drop_policy drop_policy) { + auto func = [&] (size_t total_message_count) { boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v6::loopback (), system.get_available_port ()); boost::asio::ip::tcp::acceptor acceptor (node->io_ctx); acceptor.open (endpoint.protocol ()); @@ -331,38 +143,39 @@ TEST (socket, drop_policy) }); auto client = std::make_shared (*node); - auto channel = std::make_shared (*node, client); - std::atomic completed_writes{ 0 }; + completed_writes = 0; + failed_writes = 0; client->async_connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::loopback (), acceptor.local_endpoint ().port ()), - [&channel, total_message_count, node, &completed_writes, &drop_policy, client] (boost::system::error_code const & ec_a) mutable { + [&] (boost::system::error_code const & ec_a) mutable { for (int i = 0; i < total_message_count; i++) { std::vector buff (1); - channel->send_buffer ( - nano::shared_const_buffer (std::move (buff)), [&completed_writes, client] (boost::system::error_code const & ec, size_t size_a) mutable { - client.reset (); - ++completed_writes; - }, - drop_policy); + client->async_write (nano::shared_const_buffer (std::move (buff)), [&] (boost::system::error_code const & ec, size_t size_a) { + if (!ec) + { + ++completed_writes; + } + else + { + ++failed_writes; + } + }); } }); - ASSERT_TIMELY_EQ (5s, completed_writes, total_message_count); + ASSERT_TIMELY_EQ (5s, completed_writes + failed_writes, total_message_count); ASSERT_EQ (1, client.use_count ()); }; // We're going to write twice the queue size + 1, and the server isn't reading // The total number of drops should thus be 1 (the socket allows doubling the queue size for no_socket_drop) - func (nano::transport::tcp_socket::default_max_queue_size * 2 + 1, nano::transport::buffer_drop_policy::no_socket_drop); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out)); - ASSERT_EQ (0, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out)); - - func (nano::transport::tcp_socket::default_max_queue_size + 1, nano::transport::buffer_drop_policy::limiter); - // The stats are accumulated from before - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out)); + func (nano::transport::tcp_socket::default_queue_size * 2 + 1); + ASSERT_EQ (1, failed_writes); + + func (nano::transport::tcp_socket::default_queue_size + 1); + ASSERT_EQ (0, failed_writes); } // This is abusing the socket class, it's interfering with the normal node lifetimes and as a result deadlocks @@ -493,7 +306,7 @@ TEST (socket_timeout, connect) // create one node and set timeout to 1 second nano::test::system system (1); std::shared_ptr node = system.nodes[0]; - node->config.tcp_io_timeout = std::chrono::seconds (1); + node->config.tcp_io_timeout = 1s; // try to connect to an IP address that most likely does not exist and will not reply // we want the tcp stack to not receive a negative reply, we want it to see silence and to keep trying @@ -505,22 +318,13 @@ TEST (socket_timeout, connect) std::atomic done = false; boost::system::error_code ec; socket->async_connect (endpoint, [&ec, &done] (boost::system::error_code const & ec_a) { - if (ec_a) - { - ec = ec_a; - done = true; - } + ec = ec_a; + done = true; }); - // check that the callback was called and we got an error + // Sometimes the connect will be aborted but there will be no error, just check that the callback was called due to the timeout ASSERT_TIMELY_EQ (6s, done, true); - ASSERT_TRUE (ec); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_connect_error, nano::stat::dir::in)); - - // check that the socket was closed due to tcp_io_timeout timeout - // NOTE: this assert is not guaranteed to be always true, it is only likely that it will be true, we can also get "No route to host" - // if this test is run repeatedly or in parallel then it is guaranteed to fail due to "No route to host" instead of timeout - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); + ASSERT_TRUE (socket->has_timed_out ()); } TEST (socket_timeout, read) @@ -571,6 +375,8 @@ TEST (socket_timeout, read) TEST (socket_timeout, write) { + std::atomic done = false; + // create one node and set timeout to 1 second nano::test::system system (1); std::shared_ptr node = system.nodes[0]; @@ -592,19 +398,17 @@ TEST (socket_timeout, write) // create a client socket and send lots of data to fill the socket queue on the local and remote side // eventually, the all tcp queues should fill up and async_write will not be able to progress // and the timeout should kick in and close the socket, which will cause the async_write to return an error - auto socket = std::make_shared (*node, nano::transport::socket_endpoint::client, 1024 * 64); // socket with a max queue size much larger than OS buffers - std::atomic done = false; - boost::system::error_code ec; - socket->async_connect (acceptor.local_endpoint (), [&socket, &ec, &done] (boost::system::error_code const & ec_a) { + auto socket = std::make_shared (*node, nano::transport::socket_endpoint::client, 1024 * 1024); // socket with a max queue size much larger than OS buffers + + socket->async_connect (acceptor.local_endpoint (), [&socket, &done] (boost::system::error_code const & ec_a) { EXPECT_FALSE (ec_a); auto buffer = std::make_shared> (128 * 1024); for (auto i = 0; i < 1024; ++i) { - socket->async_write (nano::shared_const_buffer{ buffer }, [&ec, &done] (boost::system::error_code const & ec_a, size_t size_a) { + socket->async_write (nano::shared_const_buffer{ buffer }, [&done] (boost::system::error_code const & ec_a, size_t size_a) { if (ec_a) { - ec = ec_a; done = true; } }); @@ -612,12 +416,11 @@ TEST (socket_timeout, write) }); // check that the callback was called and we got an error - ASSERT_TIMELY_EQ (10s, done, true); - ASSERT_TRUE (ec); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_error, nano::stat::dir::in)); + ASSERT_TIMELY (10s, done); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_error, nano::stat::dir::in)); // check that the socket was closed due to tcp_io_timeout timeout - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); } TEST (socket_timeout, read_overlapped) @@ -641,8 +444,8 @@ TEST (socket_timeout, read_overlapped) auto buffer = std::make_shared> (1); nano::async_write (newsock, nano::shared_const_buffer (buffer), [] (boost::system::error_code const & ec_a, size_t size_a) { - debug_assert (!ec_a); - debug_assert (size_a == 1); + EXPECT_TRUE (!ec_a); + EXPECT_TRUE (size_a == 1); }); }); @@ -656,11 +459,12 @@ TEST (socket_timeout, read_overlapped) auto buffer = std::make_shared> (1); socket->async_read (buffer, 1, [] (boost::system::error_code const & ec_a, size_t size_a) { - debug_assert (size_a == 1); + EXPECT_FALSE (ec_a); + EXPECT_TRUE (size_a == 1); }); socket->async_read (buffer, 1, [&ec, &done] (boost::system::error_code const & ec_a, size_t size_a) { - debug_assert (size_a == 0); + EXPECT_EQ (size_a, 0); if (ec_a) { ec = ec_a; @@ -672,14 +476,16 @@ TEST (socket_timeout, read_overlapped) // check that the callback was called and we got an error ASSERT_TIMELY_EQ (10s, done, true); ASSERT_TRUE (ec); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_read_error, nano::stat::dir::in)); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_read_error, nano::stat::dir::in)); // check that the socket was closed due to tcp_io_timeout timeout - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); } TEST (socket_timeout, write_overlapped) { + std::atomic done = false; + // create one node and set timeout to 1 second nano::test::system system (1); std::shared_ptr node = system.nodes[0]; @@ -699,30 +505,29 @@ TEST (socket_timeout, write_overlapped) EXPECT_FALSE (ec_a); boost::asio::async_read (newsock, boost::asio::buffer (buffer->data (), buffer->size ()), [] (boost::system::error_code const & ec_a, size_t size_a) { - debug_assert (size_a == 1); + EXPECT_FALSE (ec_a); + EXPECT_EQ (size_a, 1); }); }); // create a client socket and send lots of data to fill the socket queue on the local and remote side // eventually, the all tcp queues should fill up and async_write will not be able to progress // and the timeout should kick in and close the socket, which will cause the async_write to return an error - auto socket = std::make_shared (*node, nano::transport::socket_endpoint::client, 1024 * 64); // socket with a max queue size much larger than OS buffers - std::atomic done = false; - boost::system::error_code ec; - socket->async_connect (acceptor.local_endpoint (), [&socket, &ec, &done] (boost::system::error_code const & ec_a) { + auto socket = std::make_shared (*node, nano::transport::socket_endpoint::client, 1024 * 1024); // socket with a max queue size much larger than OS buffers + socket->async_connect (acceptor.local_endpoint (), [&socket, &done] (boost::system::error_code const & ec_a) { EXPECT_FALSE (ec_a); auto buffer1 = std::make_shared> (1); auto buffer2 = std::make_shared> (128 * 1024); socket->async_write (nano::shared_const_buffer{ buffer1 }, [] (boost::system::error_code const & ec_a, size_t size_a) { - debug_assert (size_a == 1); + EXPECT_FALSE (ec_a); + EXPECT_EQ (size_a, 1); }); for (auto i = 0; i < 1024; ++i) { - socket->async_write (nano::shared_const_buffer{ buffer2 }, [&ec, &done] (boost::system::error_code const & ec_a, size_t size_a) { + socket->async_write (nano::shared_const_buffer{ buffer2 }, [&done] (boost::system::error_code const & ec_a, size_t size_a) { if (ec_a) { - ec = ec_a; done = true; } }); @@ -731,9 +536,8 @@ TEST (socket_timeout, write_overlapped) // check that the callback was called and we got an error ASSERT_TIMELY_EQ (10s, done, true); - ASSERT_TRUE (ec); - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_error, nano::stat::dir::in)); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_write_error, nano::stat::dir::in)); // check that the socket was closed due to tcp_io_timeout timeout - ASSERT_EQ (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); + ASSERT_LE (1, node->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_io_timeout_drop, nano::stat::dir::out)); } diff --git a/nano/core_test/system.cpp b/nano/core_test/system.cpp index 7f9f1088a0..4c30b7b478 100644 --- a/nano/core_test/system.cpp +++ b/nano/core_test/system.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -210,7 +211,7 @@ TEST (system, transport_basic) nano::transport::inproc::channel channel{ node0, node1 }; // Send a keepalive message since they are easy to construct nano::keepalive junk{ nano::dev::network_params.network }; - channel.send (junk); + channel.send (junk, nano::transport::traffic_type::test); // Ensure the keepalive has been reecived on the target. ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::message, nano::stat::detail::keepalive, nano::stat::dir::in) > 0); } diff --git a/nano/core_test/tcp_listener.cpp b/nano/core_test/tcp_listener.cpp new file mode 100644 index 0000000000..787066f608 --- /dev/null +++ b/nano/core_test/tcp_listener.cpp @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +TEST (tcp_listener, max_connections) +{ + nano::test::system system; + + nano::node_flags node_flags; + nano::node_config node_config = system.default_config (); + node_config.tcp.max_inbound_connections = 2; + auto node = system.add_node (node_config, node_flags); + + // client side connection tracking + std::atomic connection_attempts = 0; + auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { + ASSERT_EQ (ec_a.value (), 0); + ++connection_attempts; + }; + + // start 3 clients, 2 will persist but 1 will be dropped + auto client1 = std::make_shared (*node); + client1->async_connect (node->network.endpoint (), connect_handler); + + auto client2 = std::make_shared (*node); + client2->async_connect (node->network.endpoint (), connect_handler); + + auto client3 = std::make_shared (*node); + client3->async_connect (node->network.endpoint (), connect_handler); + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 2); + ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 2); + ASSERT_TIMELY_EQ (5s, connection_attempts, 3); + + // create space for one socket and fill the connections table again + { + auto sockets1 = node->tcp_listener.sockets (); + ASSERT_EQ (sockets1.size (), 2); + sockets1[0]->close (); + } + ASSERT_TIMELY_EQ (10s, node->tcp_listener.sockets ().size (), 1); + + auto client4 = std::make_shared (*node); + client4->async_connect (node->network.endpoint (), connect_handler); + + auto client5 = std::make_shared (*node); + client5->async_connect (node->network.endpoint (), connect_handler); + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 3); + ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 3); + ASSERT_TIMELY_EQ (5s, connection_attempts, 5); + + // close all existing sockets and fill the connections table again + { + auto sockets2 = node->tcp_listener.sockets (); + ASSERT_EQ (sockets2.size (), 2); + sockets2[0]->close (); + sockets2[1]->close (); + } + ASSERT_TIMELY_EQ (10s, node->tcp_listener.sockets ().size (), 0); + + auto client6 = std::make_shared (*node); + client6->async_connect (node->network.endpoint (), connect_handler); + + auto client7 = std::make_shared (*node); + client7->async_connect (node->network.endpoint (), connect_handler); + + auto client8 = std::make_shared (*node); + client8->async_connect (node->network.endpoint (), connect_handler); + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 5); + ASSERT_ALWAYS_EQ (1s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), 5); + ASSERT_TIMELY_EQ (5s, connection_attempts, 8); // connections initiated by the client +} + +TEST (tcp_listener, max_connections_per_ip) +{ + nano::test::system system; + + nano::node_flags node_flags; + nano::node_config node_config = system.default_config (); + node_config.network.max_peers_per_ip = 3; + auto node = system.add_node (node_config, node_flags); + ASSERT_FALSE (node->flags.disable_max_peers_per_ip); + + auto server_port = system.get_available_port (); + + const auto max_ip_connections = node->config.network.max_peers_per_ip; + ASSERT_GE (max_ip_connections, 1); + + // client side connection tracking + std::atomic connection_attempts = 0; + auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { + ASSERT_EQ (ec_a.value (), 0); + ++connection_attempts; + }; + + // start n clients, n-1 will persist but 1 will be dropped, where n == max_ip_connections + std::vector> client_list; + client_list.reserve (max_ip_connections + 1); + + for (auto idx = 0; idx < max_ip_connections + 1; ++idx) + { + auto client = std::make_shared (*node); + client->async_connect (node->network.endpoint (), connect_handler); + client_list.push_back (client); + } + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_ip_connections); + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_ip), 1); + ASSERT_TIMELY_EQ (5s, connection_attempts, max_ip_connections + 1); +} + +TEST (tcp_listener, max_connections_per_subnetwork) +{ + nano::test::system system; + + nano::node_flags node_flags; + // disabling IP limit because it will be used the same IP address to check they come from the same subnetwork. + node_flags.disable_max_peers_per_ip = true; + node_flags.disable_max_peers_per_subnetwork = false; + nano::node_config node_config = system.default_config (); + node_config.network.max_peers_per_subnetwork = 3; + auto node = system.add_node (node_config, node_flags); + + ASSERT_TRUE (node->flags.disable_max_peers_per_ip); + ASSERT_FALSE (node->flags.disable_max_peers_per_subnetwork); + + const auto max_subnetwork_connections = node->config.network.max_peers_per_subnetwork; + ASSERT_GE (max_subnetwork_connections, 1); + + // client side connection tracking + std::atomic connection_attempts = 0; + auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { + ASSERT_EQ (ec_a.value (), 0); + ++connection_attempts; + }; + + // start n clients, n-1 will persist but 1 will be dropped, where n == max_subnetwork_connections + std::vector> client_list; + client_list.reserve (max_subnetwork_connections + 1); + + for (auto idx = 0; idx < max_subnetwork_connections + 1; ++idx) + { + auto client = std::make_shared (*node); + client->async_connect (node->network.endpoint (), connect_handler); + client_list.push_back (client); + } + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_subnetwork_connections); + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_subnetwork), 1); + ASSERT_TIMELY_EQ (5s, connection_attempts, max_subnetwork_connections + 1); +} + +TEST (tcp_listener, max_peers_per_ip) +{ + nano::test::system system; + + nano::node_flags node_flags; + node_flags.disable_max_peers_per_ip = true; + nano::node_config node_config = system.default_config (); + node_config.network.max_peers_per_ip = 3; + auto node = system.add_node (node_config, node_flags); + + ASSERT_TRUE (node->flags.disable_max_peers_per_ip); + + auto server_port = system.get_available_port (); + + const auto max_ip_connections = node->config.network.max_peers_per_ip; + ASSERT_GE (max_ip_connections, 1); + + // client side connection tracking + std::atomic connection_attempts = 0; + auto connect_handler = [&connection_attempts] (boost::system::error_code const & ec_a) { + ASSERT_EQ (ec_a.value (), 0); + ++connection_attempts; + }; + + // start n clients, n-1 will persist but 1 will be dropped, where n == max_ip_connections + std::vector> client_list; + client_list.reserve (max_ip_connections + 1); + + for (auto idx = 0; idx < max_ip_connections + 1; ++idx) + { + auto client = std::make_shared (*node); + client->async_connect (node->network.endpoint (), connect_handler); + client_list.push_back (client); + } + + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener, nano::stat::detail::accept_success), max_ip_connections + 1); + ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_ip), 0); + ASSERT_TIMELY_EQ (5s, connection_attempts, max_ip_connections + 1); +} + +TEST (tcp_listener, tcp_node_id_handshake) +{ + nano::test::system system (1); + auto socket (std::make_shared (*system.nodes[0])); + auto bootstrap_endpoint (system.nodes[0]->tcp_listener.endpoint ()); + auto cookie (system.nodes[0]->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (bootstrap_endpoint))); + ASSERT_TRUE (cookie); + nano::node_id_handshake::query_payload query{ *cookie }; + nano::node_id_handshake node_id_handshake{ nano::dev::network_params.network, query }; + auto input (node_id_handshake.to_shared_const_buffer ()); + std::atomic write_done (false); + socket->async_connect (bootstrap_endpoint, [&input, socket, &write_done] (boost::system::error_code const & ec) { + ASSERT_FALSE (ec); + socket->async_write (input, [&input, &write_done] (boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + ASSERT_EQ (input.size (), size_a); + write_done = true; + }); + }); + + ASSERT_TIMELY (5s, write_done); + + nano::node_id_handshake::response_payload response_zero{ 0 }; + nano::node_id_handshake node_id_handshake_response{ nano::dev::network_params.network, std::nullopt, response_zero }; + auto output (node_id_handshake_response.to_bytes ()); + std::atomic done (false); + socket->async_read (output, output->size (), [&output, &done] (boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + ASSERT_EQ (output->size (), size_a); + done = true; + }); + ASSERT_TIMELY (5s, done); +} + +// Test disabled because it's failing intermittently. +// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3611 +// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3615 +TEST (tcp_listener, DISABLED_tcp_listener_timeout_empty) +{ + nano::test::system system (1); + auto node0 (system.nodes[0]); + auto socket (std::make_shared (*node0)); + std::atomic connected (false); + socket->async_connect (node0->tcp_listener.endpoint (), [&connected] (boost::system::error_code const & ec) { + ASSERT_FALSE (ec); + connected = true; + }); + ASSERT_TIMELY (5s, connected); + bool disconnected (false); + system.deadline_set (std::chrono::seconds (6)); + while (!disconnected) + { + disconnected = node0->tcp_listener.connection_count () == 0; + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (tcp_listener, tcp_listener_timeout_node_id_handshake) +{ + nano::test::system system (1); + auto node0 (system.nodes[0]); + auto socket (std::make_shared (*node0)); + auto cookie (node0->network.syn_cookies.assign (nano::transport::map_tcp_to_endpoint (node0->tcp_listener.endpoint ()))); + ASSERT_TRUE (cookie); + nano::node_id_handshake::query_payload query{ *cookie }; + nano::node_id_handshake node_id_handshake{ nano::dev::network_params.network, query }; + auto channel = std::make_shared (*node0, socket); + socket->async_connect (node0->tcp_listener.endpoint (), [&node_id_handshake, channel] (boost::system::error_code const & ec) { + ASSERT_FALSE (ec); + channel->send (node_id_handshake, nano::transport::traffic_type::test, [] (boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + }); + ASSERT_TIMELY (5s, node0->stats.count (nano::stat::type::tcp_server, nano::stat::detail::node_id_handshake) != 0); + ASSERT_EQ (node0->tcp_listener.connection_count (), 1); + bool disconnected (false); + system.deadline_set (std::chrono::seconds (20)); + while (!disconnected) + { + disconnected = node0->tcp_listener.connection_count () == 0; + ASSERT_NO_ERROR (system.poll ()); + } +} \ No newline at end of file diff --git a/nano/core_test/telemetry.cpp b/nano/core_test/telemetry.cpp index 2d120fe260..aa213e776b 100644 --- a/nano/core_test/telemetry.cpp +++ b/nano/core_test/telemetry.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -142,7 +143,7 @@ TEST (telemetry, dos_tcp) nano::telemetry_req message{ nano::dev::network_params.network }; auto channel = node_client->network.tcp_channels.find_node_id (node_server->get_node_id ()); ASSERT_NE (nullptr, channel); - channel->send (message, [] (boost::system::error_code const & ec, size_t size_a) { + channel->send (message, nano::transport::traffic_type::test, [] (boost::system::error_code const & ec, size_t size_a) { ASSERT_FALSE (ec); }); @@ -151,7 +152,7 @@ TEST (telemetry, dos_tcp) auto orig = std::chrono::steady_clock::now (); for (int i = 0; i < 10; ++i) { - channel->send (message, [] (boost::system::error_code const & ec, size_t size_a) { + channel->send (message, nano::transport::traffic_type::test, [] (boost::system::error_code const & ec, size_t size_a) { ASSERT_FALSE (ec); }); } @@ -164,7 +165,7 @@ TEST (telemetry, dos_tcp) // Now spam messages waiting for it to be processed while (node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_req, nano::stat::dir::in) == 1) { - channel->send (message); + channel->send (message, nano::transport::traffic_type::test); ASSERT_NO_ERROR (system.poll ()); } } @@ -213,7 +214,7 @@ TEST (telemetry, max_possible_size) auto channel = node_client->network.tcp_channels.find_node_id (node_server->get_node_id ()); ASSERT_NE (nullptr, channel); - channel->send (message, [] (boost::system::error_code const & ec, size_t size_a) { + channel->send (message, nano::transport::traffic_type::test, [] (boost::system::error_code const & ec, size_t size_a) { ASSERT_FALSE (ec); }); diff --git a/nano/core_test/throttle.cpp b/nano/core_test/throttle.cpp index 965ba1f5f0..361712d482 100644 --- a/nano/core_test/throttle.cpp +++ b/nano/core_test/throttle.cpp @@ -1,16 +1,16 @@ -#include +#include #include TEST (throttle, construction) { - nano::bootstrap_ascending::throttle throttle{ 2 }; + nano::bootstrap::throttle throttle{ 2 }; ASSERT_FALSE (throttle.throttled ()); } TEST (throttle, throttled) { - nano::bootstrap_ascending::throttle throttle{ 2 }; + nano::bootstrap::throttle throttle{ 2 }; throttle.add (false); ASSERT_FALSE (throttle.throttled ()); throttle.add (false); @@ -19,7 +19,7 @@ TEST (throttle, throttled) TEST (throttle, resize_up) { - nano::bootstrap_ascending::throttle throttle{ 2 }; + nano::bootstrap::throttle throttle{ 2 }; throttle.add (false); throttle.resize (4); ASSERT_FALSE (throttle.throttled ()); @@ -29,7 +29,7 @@ TEST (throttle, resize_up) TEST (throttle, resize_down) { - nano::bootstrap_ascending::throttle throttle{ 4 }; + nano::bootstrap::throttle throttle{ 4 }; throttle.add (false); ASSERT_FALSE (throttle.throttled ()); throttle.resize (2); diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index 45107a3706..29a9916675 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -67,7 +67,125 @@ TEST (toml, diff_equal) ASSERT_TRUE (other.empty ()); } -TEST (toml, daemon_config_update_array) +TEST (toml, optional_child) +{ + std::stringstream ss; + ss << R"toml( + [child] + val=1 + )toml"; + + nano::tomlconfig t; + t.read (ss); + auto c1 = t.get_required_child ("child"); + int val = 0; + c1.get_required ("val", val); + ASSERT_EQ (val, 1); + auto c2 = t.get_optional_child ("child2"); + ASSERT_FALSE (c2); +} + +/** Config settings passed via CLI overrides the config file settings. This is solved +using an override stream. */ +TEST (toml, dot_child_syntax) +{ + std::stringstream ss_override; + ss_override << R"toml( + node.a = 1 + node.b = 2 + )toml"; + + std::stringstream ss; + ss << R"toml( + [node] + b=5 + c=3 + )toml"; + + nano::tomlconfig t; + t.read (ss_override, ss); + + auto node = t.get_required_child ("node"); + uint16_t a, b, c; + node.get ("a", a); + ASSERT_EQ (a, 1); + node.get ("b", b); + ASSERT_EQ (b, 2); + node.get ("c", c); + ASSERT_EQ (c, 3); +} + +TEST (toml, base_override) +{ + std::stringstream ss_base; + ss_base << R"toml( + node.peering_port=7075 + )toml"; + + std::stringstream ss_override; + ss_override << R"toml( + node.peering_port=8075 + node.too_big=70000 + )toml"; + + nano::tomlconfig t; + t.read (ss_override, ss_base); + + // Query optional existent value + uint16_t port = 0; + t.get_optional ("node.peering_port", port); + ASSERT_EQ (port, 8075); + ASSERT_FALSE (t.get_error ()); + + // Query optional non-existent value, make sure we get default and no errors + port = 65535; + t.get_optional ("node.peering_port_non_existent", port); + ASSERT_EQ (port, 65535); + ASSERT_FALSE (t.get_error ()); + t.get_error ().clear (); + + // Query required non-existent value, make sure it errors + t.get_required ("node.peering_port_not_existent", port); + ASSERT_EQ (port, 65535); + ASSERT_TRUE (t.get_error ()); + ASSERT_EQ (t.get_error (), nano::error_config::missing_value); + t.get_error ().clear (); + + // Query uint16 that's too big, make sure we have an error + t.get_required ("node.too_big", port); + ASSERT_TRUE (t.get_error ()); + ASSERT_EQ (t.get_error (), nano::error_config::invalid_value); +} + +TEST (toml, put) +{ + nano::tomlconfig config; + nano::tomlconfig config_node; + // Overwrite value and add to child node + config_node.put ("port", "7074"); + config_node.put ("port", "7075"); + config.put_child ("node", config_node); + uint16_t port; + config.get_required ("node.port", port); + ASSERT_EQ (port, 7075); + ASSERT_FALSE (config.get_error ()); +} + +TEST (toml, array) +{ + nano::tomlconfig config; + nano::tomlconfig config_node; + config.put_child ("node", config_node); + config_node.push ("items", "item 1"); + config_node.push ("items", "item 2"); + int i = 1; + config_node.array_entries_required ("items", [&i] (std::string item) { + ASSERT_EQ (item, std::string ("item ") + std::to_string (i)); + i++; + }); +} + +TEST (toml_config, daemon_config_update_array) { nano::tomlconfig t; std::filesystem::path data_path ("."); @@ -79,7 +197,7 @@ TEST (toml, daemon_config_update_array) } /** Empty rpc config file should match a default config object */ -TEST (toml, rpc_config_deserialize_defaults) +TEST (toml_config, rpc_config_deserialize_defaults) { std::stringstream ss; @@ -111,13 +229,14 @@ TEST (toml, rpc_config_deserialize_defaults) } /** Empty config file should match a default config object */ -TEST (toml, daemon_config_deserialize_defaults) +TEST (toml_config, daemon_config_deserialize_defaults) { std::stringstream ss; ss << R"toml( [node] - [node.backlog_population] - [node.bootstrap_ascending] + [node.backlog_scan] + [node.bounded_backlog] + [node.bootstrap] [node.bootstrap_server] [node.block_processor] [node.diagnostics.txn_tracking] @@ -131,6 +250,7 @@ TEST (toml, daemon_config_deserialize_defaults) [node.websocket] [node.lmdb] [node.rocksdb] + [node.tcp] [opencl] [rpc] [rpc.child_process] @@ -186,7 +306,6 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.preconfigured_representatives, defaults.node.preconfigured_representatives); ASSERT_EQ (conf.node.receive_minimum, defaults.node.receive_minimum); ASSERT_EQ (conf.node.signature_checker_threads, defaults.node.signature_checker_threads); - ASSERT_EQ (conf.node.tcp_incoming_connections_max, defaults.node.tcp_incoming_connections_max); ASSERT_EQ (conf.node.tcp_io_timeout, defaults.node.tcp_io_timeout); ASSERT_EQ (conf.node.unchecked_cutoff_time, defaults.node.unchecked_cutoff_time); ASSERT_EQ (conf.node.use_memory_pools, defaults.node.use_memory_pools); @@ -197,11 +316,18 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.max_queued_requests, defaults.node.max_queued_requests); ASSERT_EQ (conf.node.request_aggregator_threads, defaults.node.request_aggregator_threads); ASSERT_EQ (conf.node.max_unchecked_blocks, defaults.node.max_unchecked_blocks); - ASSERT_EQ (conf.node.backlog_population.enable, defaults.node.backlog_population.enable); - ASSERT_EQ (conf.node.backlog_population.batch_size, defaults.node.backlog_population.batch_size); - ASSERT_EQ (conf.node.backlog_population.frequency, defaults.node.backlog_population.frequency); + ASSERT_EQ (conf.node.max_backlog, defaults.node.max_backlog); ASSERT_EQ (conf.node.enable_upnp, defaults.node.enable_upnp); + ASSERT_EQ (conf.node.backlog_scan.enable, defaults.node.backlog_scan.enable); + ASSERT_EQ (conf.node.backlog_scan.batch_size, defaults.node.backlog_scan.batch_size); + ASSERT_EQ (conf.node.backlog_scan.rate_limit, defaults.node.backlog_scan.rate_limit); + + ASSERT_EQ (conf.node.bounded_backlog.enable, defaults.node.bounded_backlog.enable); + ASSERT_EQ (conf.node.bounded_backlog.batch_size, defaults.node.bounded_backlog.batch_size); + ASSERT_EQ (conf.node.bounded_backlog.max_queued_notifications, defaults.node.bounded_backlog.max_queued_notifications); + ASSERT_EQ (conf.node.bounded_backlog.scan_rate, defaults.node.bounded_backlog.scan_rate); + ASSERT_EQ (conf.node.websocket_config.enabled, defaults.node.websocket_config.enabled); ASSERT_EQ (conf.node.websocket_config.address, defaults.node.websocket_config.address); ASSERT_EQ (conf.node.websocket_config.port, defaults.node.websocket_config.port); @@ -244,11 +370,11 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache); ASSERT_EQ (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache); - ASSERT_EQ (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled); + ASSERT_EQ (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable); ASSERT_EQ (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold); ASSERT_EQ (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size); - ASSERT_EQ (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled); + ASSERT_EQ (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable); ASSERT_EQ (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent); ASSERT_EQ (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ()); ASSERT_EQ (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ()); @@ -269,18 +395,18 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.vote_processor.threads, defaults.node.vote_processor.threads); ASSERT_EQ (conf.node.vote_processor.batch_size, defaults.node.vote_processor.batch_size); - ASSERT_EQ (conf.node.bootstrap_ascending.enable, defaults.node.bootstrap_ascending.enable); - ASSERT_EQ (conf.node.bootstrap_ascending.enable_database_scan, defaults.node.bootstrap_ascending.enable_database_scan); - ASSERT_EQ (conf.node.bootstrap_ascending.enable_dependency_walker, defaults.node.bootstrap_ascending.enable_dependency_walker); - ASSERT_EQ (conf.node.bootstrap_ascending.channel_limit, defaults.node.bootstrap_ascending.channel_limit); - ASSERT_EQ (conf.node.bootstrap_ascending.database_rate_limit, defaults.node.bootstrap_ascending.database_rate_limit); - ASSERT_EQ (conf.node.bootstrap_ascending.database_warmup_ratio, defaults.node.bootstrap_ascending.database_warmup_ratio); - ASSERT_EQ (conf.node.bootstrap_ascending.max_pull_count, defaults.node.bootstrap_ascending.max_pull_count); - ASSERT_EQ (conf.node.bootstrap_ascending.request_timeout, defaults.node.bootstrap_ascending.request_timeout); - ASSERT_EQ (conf.node.bootstrap_ascending.throttle_coefficient, defaults.node.bootstrap_ascending.throttle_coefficient); - ASSERT_EQ (conf.node.bootstrap_ascending.throttle_wait, defaults.node.bootstrap_ascending.throttle_wait); - ASSERT_EQ (conf.node.bootstrap_ascending.block_processor_threshold, defaults.node.bootstrap_ascending.block_processor_threshold); - ASSERT_EQ (conf.node.bootstrap_ascending.max_requests, defaults.node.bootstrap_ascending.max_requests); + ASSERT_EQ (conf.node.bootstrap.enable, defaults.node.bootstrap.enable); + ASSERT_EQ (conf.node.bootstrap.enable_database_scan, defaults.node.bootstrap.enable_database_scan); + ASSERT_EQ (conf.node.bootstrap.enable_dependency_walker, defaults.node.bootstrap.enable_dependency_walker); + ASSERT_EQ (conf.node.bootstrap.channel_limit, defaults.node.bootstrap.channel_limit); + ASSERT_EQ (conf.node.bootstrap.database_rate_limit, defaults.node.bootstrap.database_rate_limit); + ASSERT_EQ (conf.node.bootstrap.database_warmup_ratio, defaults.node.bootstrap.database_warmup_ratio); + ASSERT_EQ (conf.node.bootstrap.max_pull_count, defaults.node.bootstrap.max_pull_count); + ASSERT_EQ (conf.node.bootstrap.request_timeout, defaults.node.bootstrap.request_timeout); + ASSERT_EQ (conf.node.bootstrap.throttle_coefficient, defaults.node.bootstrap.throttle_coefficient); + ASSERT_EQ (conf.node.bootstrap.throttle_wait, defaults.node.bootstrap.throttle_wait); + ASSERT_EQ (conf.node.bootstrap.block_processor_threshold, defaults.node.bootstrap.block_processor_threshold); + ASSERT_EQ (conf.node.bootstrap.max_requests, defaults.node.bootstrap.max_requests); ASSERT_EQ (conf.node.bootstrap_server.max_queue, defaults.node.bootstrap_server.max_queue); ASSERT_EQ (conf.node.bootstrap_server.threads, defaults.node.bootstrap_server.threads); @@ -292,128 +418,18 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.message_processor.threads, defaults.node.message_processor.threads); ASSERT_EQ (conf.node.message_processor.max_queue, defaults.node.message_processor.max_queue); -} -TEST (toml, optional_child) -{ - std::stringstream ss; - ss << R"toml( - [child] - val=1 - )toml"; - - nano::tomlconfig t; - t.read (ss); - auto c1 = t.get_required_child ("child"); - int val = 0; - c1.get_required ("val", val); - ASSERT_EQ (val, 1); - auto c2 = t.get_optional_child ("child2"); - ASSERT_FALSE (c2); -} - -/** Config settings passed via CLI overrides the config file settings. This is solved -using an override stream. */ -TEST (toml, dot_child_syntax) -{ - std::stringstream ss_override; - ss_override << R"toml( - node.a = 1 - node.b = 2 - )toml"; - - std::stringstream ss; - ss << R"toml( - [node] - b=5 - c=3 - )toml"; - - nano::tomlconfig t; - t.read (ss_override, ss); - - auto node = t.get_required_child ("node"); - uint16_t a, b, c; - node.get ("a", a); - ASSERT_EQ (a, 1); - node.get ("b", b); - ASSERT_EQ (b, 2); - node.get ("c", c); - ASSERT_EQ (c, 3); -} - -TEST (toml, base_override) -{ - std::stringstream ss_base; - ss_base << R"toml( - node.peering_port=7075 - )toml"; - - std::stringstream ss_override; - ss_override << R"toml( - node.peering_port=8075 - node.too_big=70000 - )toml"; - - nano::tomlconfig t; - t.read (ss_override, ss_base); - - // Query optional existent value - uint16_t port = 0; - t.get_optional ("node.peering_port", port); - ASSERT_EQ (port, 8075); - ASSERT_FALSE (t.get_error ()); - - // Query optional non-existent value, make sure we get default and no errors - port = 65535; - t.get_optional ("node.peering_port_non_existent", port); - ASSERT_EQ (port, 65535); - ASSERT_FALSE (t.get_error ()); - t.get_error ().clear (); - - // Query required non-existent value, make sure it errors - t.get_required ("node.peering_port_not_existent", port); - ASSERT_EQ (port, 65535); - ASSERT_TRUE (t.get_error ()); - ASSERT_EQ (t.get_error (), nano::error_config::missing_value); - t.get_error ().clear (); - - // Query uint16 that's too big, make sure we have an error - t.get_required ("node.too_big", port); - ASSERT_TRUE (t.get_error ()); - ASSERT_EQ (t.get_error (), nano::error_config::invalid_value); -} - -TEST (toml, put) -{ - nano::tomlconfig config; - nano::tomlconfig config_node; - // Overwrite value and add to child node - config_node.put ("port", "7074"); - config_node.put ("port", "7075"); - config.put_child ("node", config_node); - uint16_t port; - config.get_required ("node.port", port); - ASSERT_EQ (port, 7075); - ASSERT_FALSE (config.get_error ()); -} - -TEST (toml, array) -{ - nano::tomlconfig config; - nano::tomlconfig config_node; - config.put_child ("node", config_node); - config_node.push ("items", "item 1"); - config_node.push ("items", "item 2"); - int i = 1; - config_node.array_entries_required ("items", [&i] (std::string item) { - ASSERT_EQ (item, std::string ("item ") + std::to_string (i)); - i++; - }); + ASSERT_EQ (conf.node.tcp.max_inbound_connections, defaults.node.tcp.max_inbound_connections); + ASSERT_EQ (conf.node.tcp.max_outbound_connections, defaults.node.tcp.max_outbound_connections); + ASSERT_EQ (conf.node.tcp.max_attempts, defaults.node.tcp.max_attempts); + ASSERT_EQ (conf.node.tcp.max_attempts_per_ip, defaults.node.tcp.max_attempts_per_ip); + ASSERT_EQ (conf.node.tcp.connect_timeout, defaults.node.tcp.connect_timeout); + ASSERT_EQ (conf.node.tcp.handshake_timeout, defaults.node.tcp.handshake_timeout); + ASSERT_EQ (conf.node.tcp.io_timeout, defaults.node.tcp.io_timeout); } /** Deserialize a node config with non-default values */ -TEST (toml, daemon_config_deserialize_no_defaults) +TEST (toml_config, daemon_config_deserialize_no_defaults) { std::stringstream ss; @@ -450,7 +466,6 @@ TEST (toml, daemon_config_deserialize_no_defaults) preconfigured_representatives = ["nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"] receive_minimum = "999" signature_checker_threads = 999 - tcp_incoming_connections_max = 999 tcp_io_timeout = 999 unchecked_cutoff_time = 999 use_memory_pools = false @@ -462,13 +477,20 @@ TEST (toml, daemon_config_deserialize_no_defaults) max_queued_requests = 999 request_aggregator_threads = 999 max_unchecked_blocks = 999 + max_backlog = 999 frontiers_confirmation = "always" enable_upnp = false - [node.backlog_population] + [node.backlog_scan] enable = false batch_size = 999 - frequency = 999 + rate_limit = 999 + + [node.bounded_backlog] + enable = false + batch_size = 999 + max_queued_notifications = 999 + scan_rate = 999 [node.block_processor] max_peer_queue = 999 @@ -597,9 +619,10 @@ TEST (toml, daemon_config_deserialize_no_defaults) threads = 999 batch_size = 999 - [node.bootstrap_ascending] + [node.bootstrap] enable = false - enable_database_scan = false + enable_frontier_scan = false + enable_database_scan = true enable_dependency_walker = false channel_limit = 999 database_rate_limit = 999 @@ -625,6 +648,15 @@ TEST (toml, daemon_config_deserialize_no_defaults) threads = 999 max_queue = 999 + [node.tcp] + max_inbound_connections = 999 + max_outbound_connections = 999 + max_attempts = 999 + max_attempts_per_ip = 999 + connect_timeout = 999 + handshake_timeout = 999 + io_timeout = 999 + [opencl] device = 999 enable = true @@ -678,6 +710,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.io_threads, defaults.node.io_threads); ASSERT_NE (conf.node.max_work_generate_multiplier, defaults.node.max_work_generate_multiplier); ASSERT_NE (conf.node.max_unchecked_blocks, defaults.node.max_unchecked_blocks); + ASSERT_NE (conf.node.max_backlog, defaults.node.max_backlog); ASSERT_NE (conf.node.network_threads, defaults.node.network_threads); ASSERT_NE (conf.node.background_threads, defaults.node.background_threads); ASSERT_NE (conf.node.secondary_work_peers, defaults.node.secondary_work_peers); @@ -693,7 +726,6 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.preconfigured_representatives, defaults.node.preconfigured_representatives); ASSERT_NE (conf.node.receive_minimum, defaults.node.receive_minimum); ASSERT_NE (conf.node.signature_checker_threads, defaults.node.signature_checker_threads); - ASSERT_NE (conf.node.tcp_incoming_connections_max, defaults.node.tcp_incoming_connections_max); ASSERT_NE (conf.node.tcp_io_timeout, defaults.node.tcp_io_timeout); ASSERT_NE (conf.node.unchecked_cutoff_time, defaults.node.unchecked_cutoff_time); ASSERT_NE (conf.node.use_memory_pools, defaults.node.use_memory_pools); @@ -703,11 +735,17 @@ TEST (toml, daemon_config_deserialize_no_defaults) 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.request_aggregator_threads, defaults.node.request_aggregator_threads); - ASSERT_NE (conf.node.backlog_population.enable, defaults.node.backlog_population.enable); - ASSERT_NE (conf.node.backlog_population.batch_size, defaults.node.backlog_population.batch_size); - ASSERT_NE (conf.node.backlog_population.frequency, defaults.node.backlog_population.frequency); ASSERT_NE (conf.node.enable_upnp, defaults.node.enable_upnp); + ASSERT_NE (conf.node.backlog_scan.enable, defaults.node.backlog_scan.enable); + ASSERT_NE (conf.node.backlog_scan.batch_size, defaults.node.backlog_scan.batch_size); + ASSERT_NE (conf.node.backlog_scan.rate_limit, defaults.node.backlog_scan.rate_limit); + + ASSERT_NE (conf.node.bounded_backlog.enable, defaults.node.bounded_backlog.enable); + ASSERT_NE (conf.node.bounded_backlog.batch_size, defaults.node.bounded_backlog.batch_size); + ASSERT_NE (conf.node.bounded_backlog.max_queued_notifications, defaults.node.bounded_backlog.max_queued_notifications); + ASSERT_NE (conf.node.bounded_backlog.scan_rate, defaults.node.bounded_backlog.scan_rate); + ASSERT_NE (conf.node.websocket_config.enabled, defaults.node.websocket_config.enabled); ASSERT_NE (conf.node.websocket_config.address, defaults.node.websocket_config.address); ASSERT_NE (conf.node.websocket_config.port, defaults.node.websocket_config.port); @@ -751,11 +789,11 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache); ASSERT_NE (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache); - ASSERT_NE (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled); + ASSERT_NE (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable); ASSERT_NE (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold); ASSERT_NE (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size); - ASSERT_NE (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled); + ASSERT_NE (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable); ASSERT_NE (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent); ASSERT_NE (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ()); ASSERT_NE (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ()); @@ -776,18 +814,19 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.vote_processor.threads, defaults.node.vote_processor.threads); ASSERT_NE (conf.node.vote_processor.batch_size, defaults.node.vote_processor.batch_size); - ASSERT_NE (conf.node.bootstrap_ascending.enable, defaults.node.bootstrap_ascending.enable); - ASSERT_NE (conf.node.bootstrap_ascending.enable_database_scan, defaults.node.bootstrap_ascending.enable_database_scan); - ASSERT_NE (conf.node.bootstrap_ascending.enable_dependency_walker, defaults.node.bootstrap_ascending.enable_dependency_walker); - ASSERT_NE (conf.node.bootstrap_ascending.channel_limit, defaults.node.bootstrap_ascending.channel_limit); - ASSERT_NE (conf.node.bootstrap_ascending.database_rate_limit, defaults.node.bootstrap_ascending.database_rate_limit); - ASSERT_NE (conf.node.bootstrap_ascending.database_warmup_ratio, defaults.node.bootstrap_ascending.database_warmup_ratio); - ASSERT_NE (conf.node.bootstrap_ascending.max_pull_count, defaults.node.bootstrap_ascending.max_pull_count); - ASSERT_NE (conf.node.bootstrap_ascending.request_timeout, defaults.node.bootstrap_ascending.request_timeout); - ASSERT_NE (conf.node.bootstrap_ascending.throttle_coefficient, defaults.node.bootstrap_ascending.throttle_coefficient); - ASSERT_NE (conf.node.bootstrap_ascending.throttle_wait, defaults.node.bootstrap_ascending.throttle_wait); - ASSERT_NE (conf.node.bootstrap_ascending.block_processor_threshold, defaults.node.bootstrap_ascending.block_processor_threshold); - ASSERT_NE (conf.node.bootstrap_ascending.max_requests, defaults.node.bootstrap_ascending.max_requests); + ASSERT_NE (conf.node.bootstrap.enable, defaults.node.bootstrap.enable); + ASSERT_NE (conf.node.bootstrap.enable_database_scan, defaults.node.bootstrap.enable_database_scan); + ASSERT_NE (conf.node.bootstrap.enable_frontier_scan, defaults.node.bootstrap.enable_frontier_scan); + ASSERT_NE (conf.node.bootstrap.enable_dependency_walker, defaults.node.bootstrap.enable_dependency_walker); + ASSERT_NE (conf.node.bootstrap.channel_limit, defaults.node.bootstrap.channel_limit); + ASSERT_NE (conf.node.bootstrap.database_rate_limit, defaults.node.bootstrap.database_rate_limit); + ASSERT_NE (conf.node.bootstrap.database_warmup_ratio, defaults.node.bootstrap.database_warmup_ratio); + ASSERT_NE (conf.node.bootstrap.max_pull_count, defaults.node.bootstrap.max_pull_count); + ASSERT_NE (conf.node.bootstrap.request_timeout, defaults.node.bootstrap.request_timeout); + ASSERT_NE (conf.node.bootstrap.throttle_coefficient, defaults.node.bootstrap.throttle_coefficient); + ASSERT_NE (conf.node.bootstrap.throttle_wait, defaults.node.bootstrap.throttle_wait); + ASSERT_NE (conf.node.bootstrap.block_processor_threshold, defaults.node.bootstrap.block_processor_threshold); + ASSERT_NE (conf.node.bootstrap.max_requests, defaults.node.bootstrap.max_requests); ASSERT_NE (conf.node.bootstrap_server.max_queue, defaults.node.bootstrap_server.max_queue); ASSERT_NE (conf.node.bootstrap_server.threads, defaults.node.bootstrap_server.threads); @@ -799,10 +838,18 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.message_processor.threads, defaults.node.message_processor.threads); ASSERT_NE (conf.node.message_processor.max_queue, defaults.node.message_processor.max_queue); + + ASSERT_NE (conf.node.tcp.max_inbound_connections, defaults.node.tcp.max_inbound_connections); + ASSERT_NE (conf.node.tcp.max_outbound_connections, defaults.node.tcp.max_outbound_connections); + ASSERT_NE (conf.node.tcp.max_attempts, defaults.node.tcp.max_attempts); + ASSERT_NE (conf.node.tcp.max_attempts_per_ip, defaults.node.tcp.max_attempts_per_ip); + ASSERT_NE (conf.node.tcp.connect_timeout, defaults.node.tcp.connect_timeout); + ASSERT_NE (conf.node.tcp.handshake_timeout, defaults.node.tcp.handshake_timeout); + ASSERT_NE (conf.node.tcp.io_timeout, defaults.node.tcp.io_timeout); } /** There should be no required values **/ -TEST (toml, daemon_config_no_required) +TEST (toml_config, daemon_config_no_required) { std::stringstream ss; @@ -833,7 +880,7 @@ TEST (toml, daemon_config_no_required) } /** Deserialize an rpc config with non-default values */ -TEST (toml, rpc_config_deserialize_no_defaults) +TEST (toml_config, rpc_config_deserialize_no_defaults) { std::stringstream ss; @@ -876,7 +923,7 @@ TEST (toml, rpc_config_deserialize_no_defaults) } /** There should be no required values **/ -TEST (toml, rpc_config_no_required) +TEST (toml_config, rpc_config_no_required) { std::stringstream ss; @@ -898,7 +945,7 @@ TEST (toml, rpc_config_no_required) } /** Deserialize a node config with incorrect values */ -TEST (toml, daemon_config_deserialize_errors) +TEST (toml_config, daemon_config_deserialize_errors) { { std::stringstream ss; @@ -930,7 +977,7 @@ TEST (toml, daemon_config_deserialize_errors) } } -TEST (toml, daemon_read_config) +TEST (toml_config, daemon_read_config) { auto path (nano::unique_path ()); std::filesystem::create_directories (path); @@ -974,7 +1021,7 @@ TEST (toml, daemon_read_config) } } -TEST (toml, log_config_defaults) +TEST (toml_config, log_config_defaults) { std::stringstream ss; @@ -1000,7 +1047,7 @@ TEST (toml, log_config_defaults) ASSERT_EQ (confg.file.rotation_count, defaults.file.rotation_count); } -TEST (toml, log_config_no_defaults) +TEST (toml_config, log_config_no_defaults) { std::stringstream ss; @@ -1021,7 +1068,7 @@ TEST (toml, log_config_no_defaults) [log.levels] active_elections = "trace" - blockprocessor = "trace" + block_processor = "trace" )toml"; nano::tomlconfig toml; @@ -1042,7 +1089,7 @@ TEST (toml, log_config_no_defaults) ASSERT_NE (confg.file.rotation_count, defaults.file.rotation_count); } -TEST (toml, log_config_no_required) +TEST (toml_config, log_config_no_required) { std::stringstream ss; @@ -1063,7 +1110,7 @@ TEST (toml, log_config_no_required) ASSERT_FALSE (toml.get_error ()) << toml.get_error ().get_message (); } -TEST (toml, merge_config_files) +TEST (toml_config, merge_config_files) { nano::network_params network_params{ nano::network_constants::active_network }; nano::tomlconfig default_toml; @@ -1079,7 +1126,7 @@ TEST (toml, merge_config_files) [node] active_elections.size = 999 # background_threads = 7777 - [node.bootstrap_ascending] + [node.bootstrap] block_processor_threshold = 33333 old_entry = 34 )toml"; @@ -1103,6 +1150,6 @@ TEST (toml, merge_config_files) ASSERT_NE (merged_config.node.active_elections.size, default_config.node.active_elections.size); ASSERT_EQ (merged_config.node.active_elections.size, 999); ASSERT_NE (merged_config.node.background_threads, 7777); - ASSERT_EQ (merged_config.node.bootstrap_ascending.block_processor_threshold, 33333); + ASSERT_EQ (merged_config.node.bootstrap.block_processor_threshold, 33333); ASSERT_TRUE (merged_config_string.find ("old_entry") == std::string::npos); } \ No newline at end of file diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index 30ee29544e..049484fa5e 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -15,110 +16,6 @@ using namespace std::chrono_literals; -TEST (rate, basic) -{ - nano::rate::token_bucket bucket (10, 10); - - // Initial burst - ASSERT_TRUE (bucket.try_consume (10)); - ASSERT_FALSE (bucket.try_consume (10)); - - // With a fill rate of 10 tokens/sec, await 1/3 sec and get 3 tokens - std::this_thread::sleep_for (300ms); - ASSERT_TRUE (bucket.try_consume (3)); - ASSERT_FALSE (bucket.try_consume (10)); - - // Allow time for the bucket to completely refill and do a full burst - std::this_thread::sleep_for (1s); - ASSERT_TRUE (bucket.try_consume (10)); - ASSERT_EQ (bucket.largest_burst (), 10); -} - -TEST (rate, network) -{ - // For the purpose of the test, one token represents 1MB instead of one byte. - // Allow for 10 mb/s bursts (max bucket size), 5 mb/s long term rate - nano::rate::token_bucket bucket (10, 5); - - // Initial burst of 10 mb/s over two calls - ASSERT_TRUE (bucket.try_consume (5)); - ASSERT_EQ (bucket.largest_burst (), 5); - ASSERT_TRUE (bucket.try_consume (5)); - ASSERT_EQ (bucket.largest_burst (), 10); - ASSERT_FALSE (bucket.try_consume (5)); - - // After 200 ms, the 5 mb/s fillrate means we have 1 mb available - std::this_thread::sleep_for (200ms); - ASSERT_TRUE (bucket.try_consume (1)); - ASSERT_FALSE (bucket.try_consume (1)); -} - -TEST (rate, reset) -{ - nano::rate::token_bucket bucket (0, 0); - - // consume lots of tokens, buckets should be unlimited - ASSERT_TRUE (bucket.try_consume (1000000)); - ASSERT_TRUE (bucket.try_consume (1000000)); - - // set bucket to be limited - bucket.reset (1000, 1000); - ASSERT_FALSE (bucket.try_consume (1001)); - ASSERT_TRUE (bucket.try_consume (1000)); - ASSERT_FALSE (bucket.try_consume (1000)); - std::this_thread::sleep_for (2ms); - ASSERT_TRUE (bucket.try_consume (2)); - - // reduce the limit - bucket.reset (100, 100 * 1000); - ASSERT_FALSE (bucket.try_consume (101)); - ASSERT_TRUE (bucket.try_consume (100)); - std::this_thread::sleep_for (1ms); - ASSERT_TRUE (bucket.try_consume (100)); - - // increase the limit - bucket.reset (2000, 1); - ASSERT_FALSE (bucket.try_consume (2001)); - ASSERT_TRUE (bucket.try_consume (2000)); - - // back to unlimited - bucket.reset (0, 0); - ASSERT_TRUE (bucket.try_consume (1000000)); - ASSERT_TRUE (bucket.try_consume (1000000)); -} - -TEST (rate, unlimited) -{ - nano::rate::token_bucket bucket (0, 0); - ASSERT_TRUE (bucket.try_consume (5)); - ASSERT_EQ (bucket.largest_burst (), 5); - ASSERT_TRUE (bucket.try_consume (static_cast (1e9))); - ASSERT_EQ (bucket.largest_burst (), static_cast (1e9)); - - // With unlimited tokens, consuming always succeed - ASSERT_TRUE (bucket.try_consume (static_cast (1e9))); - ASSERT_EQ (bucket.largest_burst (), static_cast (1e9)); -} - -TEST (rate, busy_spin) -{ - // Bucket should refill at a rate of 1 token per second - nano::rate::token_bucket bucket (1, 1); - - // Run a very tight loop for 5 seconds + a bit of wiggle room - int counter = 0; - for (auto start = std::chrono::steady_clock::now (), now = start; now < start + std::chrono::milliseconds{ 5500 }; now = std::chrono::steady_clock::now ()) - { - if (bucket.try_consume ()) - { - ++counter; - } - } - - // Bucket starts fully refilled, therefore we see 1 additional request - ASSERT_EQ (counter, 6); -} - TEST (optional_ptr, basic) { struct valtype diff --git a/nano/core_test/vote_cache.cpp b/nano/core_test/vote_cache.cpp index 711f8a31b4..4817e737a5 100644 --- a/nano/core_test/vote_cache.cpp +++ b/nano/core_test/vote_cache.cpp @@ -57,7 +57,9 @@ TEST (vote_cache, insert_one_hash) auto rep1 = create_rep (7); auto hash1 = nano::test::random_hash (); auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024); + ASSERT_FALSE (vote_cache.contains (hash1)); vote_cache.insert (vote1); + ASSERT_TRUE (vote_cache.contains (hash1)); ASSERT_EQ (1, vote_cache.size ()); auto peek1 = vote_cache.find (hash1); @@ -265,9 +267,14 @@ TEST (vote_cache, erase) auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024); auto vote2 = nano::test::make_vote (rep2, { hash2 }, 1024 * 1024); auto vote3 = nano::test::make_vote (rep3, { hash3 }, 1024 * 1024); + ASSERT_TRUE (vote_cache.empty ()); + ASSERT_FALSE (vote_cache.contains (hash1)); vote_cache.insert (vote1); vote_cache.insert (vote2); vote_cache.insert (vote3); + ASSERT_TRUE (vote_cache.contains (hash1)); + ASSERT_TRUE (vote_cache.contains (hash2)); + ASSERT_TRUE (vote_cache.contains (hash3)); ASSERT_EQ (3, vote_cache.size ()); ASSERT_FALSE (vote_cache.empty ()); ASSERT_FALSE (vote_cache.find (hash1).empty ()); @@ -275,11 +282,16 @@ TEST (vote_cache, erase) ASSERT_FALSE (vote_cache.find (hash3).empty ()); vote_cache.erase (hash2); ASSERT_EQ (2, vote_cache.size ()); + ASSERT_FALSE (vote_cache.contains (hash2)); ASSERT_FALSE (vote_cache.find (hash1).empty ()); ASSERT_TRUE (vote_cache.find (hash2).empty ()); ASSERT_FALSE (vote_cache.find (hash3).empty ()); vote_cache.erase (hash1); vote_cache.erase (hash3); + ASSERT_EQ (0, vote_cache.size ()); + ASSERT_FALSE (vote_cache.contains (hash1)); + ASSERT_FALSE (vote_cache.contains (hash2)); + ASSERT_FALSE (vote_cache.contains (hash3)); ASSERT_TRUE (vote_cache.find (hash1).empty ()); ASSERT_TRUE (vote_cache.find (hash2).empty ()); ASSERT_TRUE (vote_cache.find (hash3).empty ()); diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp index 3f656794e8..8f93ab726e 100644 --- a/nano/core_test/vote_processor.cpp +++ b/nano/core_test/vote_processor.cpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -19,9 +21,9 @@ TEST (vote_processor, codes) nano::test::system system; auto node_config = system.default_config (); // Disable all election schedulers - node_config.backlog_population.enable = false; - node_config.hinted_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.backlog_scan.enable = false; + node_config.hinted_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; auto & node = *system.add_node (node_config); auto blocks = nano::test::setup_chain (system, node, 1, nano::dev::genesis_key, false); @@ -112,7 +114,6 @@ TEST (vote_processor, weights) auto & node (*system.nodes[0]); // Create representatives of different weight levels - // FIXME: Using `online_weight_minimum` because calculation of trended and online weight is broken when running tests auto const stake = node.config.online_weight_minimum.number (); auto const level0 = stake / 5000; // 0.02% auto const level1 = stake / 500; // 0.2% @@ -140,10 +141,10 @@ TEST (vote_processor, weights) node.stats.clear (); ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::rep_tiers, nano::stat::detail::updated) >= 2); - ASSERT_EQ (node.rep_tiers.tier (key0.pub), nano::rep_tier::none); - ASSERT_EQ (node.rep_tiers.tier (key1.pub), nano::rep_tier::tier_1); - ASSERT_EQ (node.rep_tiers.tier (key2.pub), nano::rep_tier::tier_2); - ASSERT_EQ (node.rep_tiers.tier (nano::dev::genesis_key.pub), nano::rep_tier::tier_3); + ASSERT_TIMELY_EQ (5s, node.rep_tiers.tier (key0.pub), nano::rep_tier::none); + ASSERT_TIMELY_EQ (5s, node.rep_tiers.tier (key1.pub), nano::rep_tier::tier_1); + ASSERT_TIMELY_EQ (5s, node.rep_tiers.tier (key2.pub), nano::rep_tier::tier_2); + ASSERT_TIMELY_EQ (5s, node.rep_tiers.tier (nano::dev::genesis_key.pub), nano::rep_tier::tier_3); } // Issue that tracks last changes on this test: https://github.com/nanocurrency/nano-node/issues/3485 @@ -157,10 +158,10 @@ TEST (vote_processor, no_broadcast_local) flags.disable_request_loop = true; nano::node_config config1, config2; config1.representative_vote_weight_minimum = 0; - config1.backlog_population.enable = false; + config1.backlog_scan.enable = false; auto & node (*system.add_node (config1, flags)); config2.representative_vote_weight_minimum = 0; - config2.backlog_population.enable = false; + config2.backlog_scan.enable = false; config2.peering_port = system.get_available_port (); system.add_node (config2, flags); nano::block_builder builder; @@ -212,10 +213,10 @@ TEST (vote_processor, local_broadcast_without_a_representative) flags.disable_request_loop = true; nano::node_config config1, config2; config1.representative_vote_weight_minimum = 0; - config1.backlog_population.enable = false; + config1.backlog_scan.enable = false; auto & node (*system.add_node (config1, flags)); config2.representative_vote_weight_minimum = 0; - config2.backlog_population.enable = false; + config2.backlog_scan.enable = false; config2.peering_port = system.get_available_port (); system.add_node (config2, flags); nano::block_builder builder; @@ -261,9 +262,9 @@ TEST (vote_processor, no_broadcast_local_with_a_principal_representative) nano::node_flags flags; flags.disable_request_loop = true; nano::node_config config1, config2; - config1.backlog_population.enable = false; + config1.backlog_scan.enable = false; auto & node (*system.add_node (config1, flags)); - config2.backlog_population.enable = false; + config2.backlog_scan.enable = false; config2.peering_port = system.get_available_port (); system.add_node (config2, flags); nano::block_builder builder; diff --git a/nano/core_test/voting.cpp b/nano/core_test/voting.cpp index 3389a8a96f..dd0487ddfc 100644 --- a/nano/core_test/voting.cpp +++ b/nano/core_test/voting.cpp @@ -1,9 +1,10 @@ #include -#include +#include #include #include #include #include +#include #include #include @@ -145,7 +146,7 @@ TEST (vote_spacing, prune) TEST (vote_spacing, vote_generator) { nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; config.active_elections.hinted_limit_percentage = 0; // Disable election hinting nano::test::system system; nano::node_flags node_flags; @@ -189,7 +190,7 @@ TEST (vote_spacing, vote_generator) TEST (vote_spacing, rapid) { nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; config.active_elections.hinted_limit_percentage = 0; // Disable election hinting nano::test::system system; nano::node_flags node_flags; diff --git a/nano/core_test/wallet.cpp b/nano/core_test/wallet.cpp index e9e170f6d5..6a95ba34ec 100644 --- a/nano/core_test/wallet.cpp +++ b/nano/core_test/wallet.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -1149,7 +1150,7 @@ TEST (wallet, search_receivable) nano::test::system system; nano::node_config config = system.default_config (); config.enable_voting = false; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; nano::node_flags flags; flags.disable_search_pending = true; auto & node (*system.add_node (config, flags)); diff --git a/nano/core_test/wallets.cpp b/nano/core_test/wallets.cpp index 71caf12ad1..4b6e2a09f0 100644 --- a/nano/core_test/wallets.cpp +++ b/nano/core_test/wallets.cpp @@ -195,7 +195,7 @@ TEST (wallets, search_receivable) nano::test::system system; nano::node_config config = system.default_config (); config.enable_voting = false; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; nano::node_flags flags; flags.disable_search_pending = true; auto & node (*system.add_node (config, flags)); diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index 90991493d3..de52c6ab1f 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include #include +#include #include #include #include @@ -840,121 +843,6 @@ TEST (websocket, work) ASSERT_EQ (contents.get ("reason"), ""); } -// Test client subscribing to notifications for bootstrap -TEST (websocket, bootstrap) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.websocket_config.enabled = true; - config.websocket_config.port = system.get_available_port (); - auto node1 (system.add_node (config)); - - ASSERT_EQ (0, node1->websocket.server->subscriber_count (nano::websocket::topic::bootstrap)); - - // Subscribe to bootstrap and wait for response asynchronously - std::atomic ack_ready{ false }; - auto task = ([&ack_ready, config, &node1] () { - fake_websocket_client client (node1->websocket.server->listening_port ()); - client.send_message (R"json({"action": "subscribe", "topic": "bootstrap", "ack": true})json"); - client.await_ack (); - ack_ready = true; - EXPECT_EQ (1, node1->websocket.server->subscriber_count (nano::websocket::topic::bootstrap)); - return client.get_response (); - }); - auto future = std::async (std::launch::async, task); - - // Wait for acknowledge - ASSERT_TIMELY (5s, ack_ready); - - // Start bootstrap attempt - node1->bootstrap_initiator.bootstrap (true, "123abc"); - ASSERT_TIMELY_EQ (5s, nullptr, node1->bootstrap_initiator.current_attempt ()); - - // Wait for the bootstrap notification - ASSERT_TIMELY_EQ (5s, future.wait_for (0s), std::future_status::ready); - - // Check the bootstrap notification message - auto response = future.get (); - ASSERT_TRUE (response); - std::stringstream stream; - stream << response; - boost::property_tree::ptree event; - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "bootstrap"); - - auto & contents = event.get_child ("message"); - ASSERT_EQ (contents.get ("reason"), "started"); - ASSERT_EQ (contents.get ("id"), "123abc"); - ASSERT_EQ (contents.get ("mode"), "legacy"); - - // Wait for bootstrap finish - ASSERT_TIMELY (5s, !node1->bootstrap_initiator.in_progress ()); -} - -TEST (websocket, bootstrap_exited) -{ - nano::test::system system; - nano::node_config config = system.default_config (); - config.websocket_config.enabled = true; - config.websocket_config.port = system.get_available_port (); - auto node1 (system.add_node (config)); - - // Start bootstrap, exit after subscription - std::atomic bootstrap_started{ false }; - nano::test::counted_completion subscribed_completion (1); - std::thread bootstrap_thread ([node1, &system, &bootstrap_started, &subscribed_completion] () { - std::shared_ptr attempt; - while (attempt == nullptr) - { - std::this_thread::sleep_for (50ms); - node1->bootstrap_initiator.bootstrap (true, "123abc"); - attempt = node1->bootstrap_initiator.current_attempt (); - } - ASSERT_NE (nullptr, attempt); - bootstrap_started = true; - EXPECT_FALSE (subscribed_completion.await_count_for (5s)); - }); - - // Wait for bootstrap start - ASSERT_TIMELY (5s, bootstrap_started); - - // Subscribe to bootstrap and wait for response asynchronously - std::atomic ack_ready{ false }; - auto task = ([&ack_ready, config, &node1] () { - fake_websocket_client client (node1->websocket.server->listening_port ()); - client.send_message (R"json({"action": "subscribe", "topic": "bootstrap", "ack": true})json"); - client.await_ack (); - ack_ready = true; - EXPECT_EQ (1, node1->websocket.server->subscriber_count (nano::websocket::topic::bootstrap)); - return client.get_response (); - }); - auto future = std::async (std::launch::async, task); - - // Wait for acknowledge - ASSERT_TIMELY (5s, ack_ready); - - // Wait for the bootstrap notification - subscribed_completion.increment (); - bootstrap_thread.join (); - ASSERT_TIMELY_EQ (5s, future.wait_for (0s), std::future_status::ready); - - // Check the bootstrap notification message - auto response = future.get (); - ASSERT_TRUE (response); - std::stringstream stream; - stream << response; - boost::property_tree::ptree event; - boost::property_tree::read_json (stream, event); - ASSERT_EQ (event.get ("topic"), "bootstrap"); - - auto & contents = event.get_child ("message"); - ASSERT_EQ (contents.get ("reason"), "exited"); - ASSERT_EQ (contents.get ("id"), "123abc"); - ASSERT_EQ (contents.get ("mode"), "legacy"); - ASSERT_EQ (contents.get ("total_blocks"), 0U); - ASSERT_LT (contents.get ("duration"), 15000U); -} - // Tests sending keepalive TEST (websocket, ws_keepalive) { diff --git a/nano/core_test/work_pool.cpp b/nano/core_test/work_pool.cpp index 63eae4cf7f..cb4eed4606 100644 --- a/nano/core_test/work_pool.cpp +++ b/nano/core_test/work_pool.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index 76d53944f0..c05152a287 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -21,6 +21,8 @@ add_library( ${platform_sources} asio.hpp asio.cpp + assert.hpp + assert.cpp block_sideband.hpp block_sideband.cpp block_type.hpp @@ -33,9 +35,12 @@ add_library( char_traits.hpp cli.hpp cli.cpp + common.hpp config.hpp config.cpp configbase.hpp + constants.hpp + constants.cpp container_info.hpp container_info.cpp diagnosticsconfig.hpp @@ -44,8 +49,13 @@ add_library( env.cpp epoch.hpp epoch.cpp + epochs.cpp + epochs.hpp errors.hpp errors.cpp + files.hpp + files.cpp + fwd.hpp id_dispenser.hpp interval.hpp ipc.hpp @@ -69,6 +79,7 @@ add_library( network_filter.cpp numbers.hpp numbers.cpp + numbers_templ.hpp object_stream.hpp object_stream.cpp object_stream_adapters.hpp @@ -110,7 +121,8 @@ add_library( walletconfig.hpp walletconfig.cpp work.hpp - work.cpp) + work.cpp + work_version.hpp) include_directories(${CMAKE_SOURCE_DIR}/submodules) include_directories( diff --git a/nano/lib/assert.cpp b/nano/lib/assert.cpp new file mode 100644 index 0000000000..cc2a9bb32d --- /dev/null +++ b/nano/lib/assert.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +#include + +#include +#include + +/* + * Backing code for "release_assert" & "debug_assert", which are macros + */ +void assert_internal (char const * check_expr, char const * func, char const * file, unsigned int line, bool is_release_assert, std::string_view error_msg) +{ + std::cerr << "Assertion (" << check_expr << ") failed\n" + << func << "\n" + << file << ":" << line << "\n"; + if (!error_msg.empty ()) + { + std::cerr << "Error: " << error_msg << "\n"; + } + std::cerr << "\n"; + + // Output stack trace to cerr + auto backtrace_str = nano::generate_stacktrace (); + std::cerr << backtrace_str << std::endl; + + // "abort" at the end of this function will go into any signal handlers (the daemon ones will generate a stack trace and load memory address files on non-Windows systems). + // As there is no async-signal-safe way to generate stacktraces on Windows it must be done before aborting +#ifdef _WIN32 + { + // Try construct the stacktrace dump in the same folder as the running executable, otherwise use the current directory. + boost::system::error_code err; + auto running_executable_filepath = boost::dll::program_location (err); + std::string filename = is_release_assert ? "nano_node_backtrace_release_assert.txt" : "nano_node_backtrace_assert.txt"; + std::string filepath = filename; + if (!err) + { + filepath = (running_executable_filepath.parent_path () / filename).string (); + } + + std::ofstream file (filepath); + nano::set_secure_perm_file (filepath); + file << backtrace_str; + } +#endif + + abort (); +} \ No newline at end of file diff --git a/nano/lib/assert.hpp b/nano/lib/assert.hpp new file mode 100644 index 0000000000..94eb205595 --- /dev/null +++ b/nano/lib/assert.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include + +[[noreturn]] void assert_internal (char const * check_expr, char const * func, char const * file, unsigned int line, bool is_release_assert, std::string_view error = ""); + +#define release_assert_1(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, true) +#define release_assert_2(check, error_msg) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, true, error_msg) +#if !BOOST_PP_VARIADICS_MSVC +#define release_assert(...) \ + BOOST_PP_OVERLOAD (release_assert_, __VA_ARGS__) \ + (__VA_ARGS__) +#else +#define release_assert(...) BOOST_PP_CAT (BOOST_PP_OVERLOAD (release_assert_, __VA_ARGS__) (__VA_ARGS__), BOOST_PP_EMPTY ()) +#endif + +#ifdef NDEBUG +#define debug_assert(...) (void)0 +#else +#define debug_assert_1(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, false) +#define debug_assert_2(check, error_msg) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, false, error_msg) +#if !BOOST_PP_VARIADICS_MSVC +#define debug_assert(...) \ + BOOST_PP_OVERLOAD (debug_assert_, __VA_ARGS__) \ + (__VA_ARGS__) +#else +#define debug_assert(...) BOOST_PP_CAT (BOOST_PP_OVERLOAD (debug_assert_, __VA_ARGS__) (__VA_ARGS__), BOOST_PP_EMPTY ()) +#endif +#endif \ No newline at end of file diff --git a/nano/lib/async.hpp b/nano/lib/async.hpp index 818d6bcafd..1cc8b0bfc5 100644 --- a/nano/lib/async.hpp +++ b/nano/lib/async.hpp @@ -4,6 +4,7 @@ #include +#include #include namespace asio = boost::asio; @@ -21,6 +22,12 @@ inline asio::awaitable sleep_for (auto duration) debug_assert (!ec || ec == asio::error::operation_aborted); } +inline asio::awaitable cancelled () +{ + auto state = co_await asio::this_coro::cancellation_state; + co_return state.cancelled () != asio::cancellation_type::none; +} + /** * A cancellation signal that can be emitted from any thread. * It follows the same semantics as asio::cancellation_signal. @@ -40,7 +47,6 @@ class cancellation { // Can only move if the strands are the same debug_assert (strand == other.strand); - if (this != &other) { signal = std::move (other.signal); @@ -70,6 +76,106 @@ class cancellation bool slotted{ false }; // For debugging purposes }; +class condition +{ +public: + explicit condition (nano::async::strand & strand) : + strand{ strand }, + state{ std::make_shared (strand) } + { + } + + condition (condition &&) = default; + + condition & operator= (condition && other) + { + // Can only move if the strands are the same + debug_assert (strand == other.strand); + if (this != &other) + { + state = std::move (other.state); + } + return *this; + } + + void notify () + { + // Avoid unnecessary dispatch if already scheduled + release_assert (state); + if (state->scheduled.exchange (true) == false) + { + asio::dispatch (strand, [state_s = state] () { + state_s->scheduled = false; + state_s->timer.cancel (); + }); + } + } + + // Spuriously wakes up + asio::awaitable wait () + { + debug_assert (strand.running_in_this_thread ()); + co_await wait_for (std::chrono::seconds{ 1 }); + } + + asio::awaitable wait_for (auto duration) + { + debug_assert (strand.running_in_this_thread ()); + release_assert (state); + state->timer.expires_after (duration); + boost::system::error_code ec; // Swallow error from cancellation + co_await state->timer.async_wait (asio::redirect_error (asio::use_awaitable, ec)); + debug_assert (!ec || ec == asio::error::operation_aborted); + } + + void cancel () + { + release_assert (state); + asio::dispatch (strand, [state_s = state] () { + state_s->scheduled = false; + state_s->timer.cancel (); + }); + } + + bool valid () const + { + return state != nullptr; + } + + nano::async::strand & strand; + +private: + struct shared_state + { + asio::steady_timer timer; + std::atomic scheduled{ false }; + + explicit shared_state (nano::async::strand & strand) : + timer{ strand } {}; + }; + std::shared_ptr state; +}; + +// Concept for awaitables +template +concept async_task = std::same_as>; + +// Concept for callables that return an awaitable +template +concept async_factory = requires (T t) { + { + t () + } -> std::same_as>; +}; + +// Concept for callables that take a condition and return an awaitable +template +concept async_factory_with_condition = requires (T t, condition & c) { + { + t (c) + } -> std::same_as>; +}; + /** * Wrapper with convenience functions and safety checks for asynchronous tasks. * Aims to provide interface similar to std::thread. @@ -86,13 +192,38 @@ class task { } - task (nano::async::strand & strand, auto && func) : + template + task (nano::async::strand & strand, Func && func) : + strand{ strand }, + cancellation{ strand } + { + future = asio::co_spawn ( + strand, + std::forward (func), + asio::bind_cancellation_slot (cancellation.slot (), asio::use_future)); + } + + template + task (nano::async::strand & strand, Func && func) : strand{ strand }, cancellation{ strand } { future = asio::co_spawn ( strand, - std::forward (func), + func (), + asio::bind_cancellation_slot (cancellation.slot (), asio::use_future)); + } + + template + task (nano::async::strand & strand, Func && func) : + strand{ strand }, + cancellation{ strand }, + condition{ std::make_unique (strand) } + { + auto awaitable_func = func (*condition); + future = asio::co_spawn ( + strand, + func (*condition), asio::bind_cancellation_slot (cancellation.slot (), asio::use_future)); } @@ -107,11 +238,11 @@ class task { // Can only move if the strands are the same debug_assert (strand == other.strand); - if (this != &other) { future = std::move (other.future); cancellation = std::move (other.cancellation); + condition = std::move (other.condition); } return *this; } @@ -139,6 +270,18 @@ class task { debug_assert (joinable ()); cancellation.emit (); + if (condition) + { + condition->cancel (); + } + } + + void notify () + { + if (condition) + { + condition->notify (); + } } nano::async::strand & strand; @@ -146,5 +289,6 @@ class task private: std::future future; nano::async::cancellation cancellation; + std::unique_ptr condition; }; } \ No newline at end of file diff --git a/nano/lib/block_sideband.cpp b/nano/lib/block_sideband.cpp index e69de29bb2..8b37f71dba 100644 --- a/nano/lib/block_sideband.cpp +++ b/nano/lib/block_sideband.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include + +#include + +/* + * block_details + */ + +nano::block_details::block_details (nano::epoch const epoch_a, bool const is_send_a, bool const is_receive_a, bool const is_epoch_a) : + epoch (epoch_a), is_send (is_send_a), is_receive (is_receive_a), is_epoch (is_epoch_a) +{ +} + +bool nano::block_details::operator== (nano::block_details const & other_a) const +{ + return epoch == other_a.epoch && is_send == other_a.is_send && is_receive == other_a.is_receive && is_epoch == other_a.is_epoch; +} + +uint8_t nano::block_details::packed () const +{ + std::bitset<8> result (static_cast (epoch)); + result.set (7, is_send); + result.set (6, is_receive); + result.set (5, is_epoch); + return static_cast (result.to_ulong ()); +} + +void nano::block_details::unpack (uint8_t details_a) +{ + constexpr std::bitset<8> epoch_mask{ 0b00011111 }; + auto as_bitset = static_cast> (details_a); + is_send = as_bitset.test (7); + is_receive = as_bitset.test (6); + is_epoch = as_bitset.test (5); + epoch = static_cast ((as_bitset & epoch_mask).to_ulong ()); +} + +void nano::block_details::serialize (nano::stream & stream_a) const +{ + nano::write (stream_a, packed ()); +} + +bool nano::block_details::deserialize (nano::stream & stream_a) +{ + bool result (false); + try + { + uint8_t packed{ 0 }; + nano::read (stream_a, packed); + unpack (packed); + } + catch (std::runtime_error &) + { + result = true; + } + + return result; +} + +void nano::block_details::operator() (nano::object_stream & obs) const +{ + obs.write ("epoch", epoch); + obs.write ("is_send", is_send); + obs.write ("is_receive", is_receive); + obs.write ("is_epoch", is_epoch); +} + +std::string nano::state_subtype (nano::block_details const details_a) +{ + debug_assert (details_a.is_epoch + details_a.is_receive + details_a.is_send <= 1); + if (details_a.is_send) + { + return "send"; + } + else if (details_a.is_receive) + { + return "receive"; + } + else if (details_a.is_epoch) + { + return "epoch"; + } + else + { + return "change"; + } +} + +/* + * block_sideband + */ + +nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t const height_a, nano::seconds_t const timestamp_a, nano::block_details const & details_a, nano::epoch const source_epoch_a) : + successor (successor_a), + account (account_a), + balance (balance_a), + height (height_a), + timestamp (timestamp_a), + details (details_a), + source_epoch (source_epoch_a) +{ +} + +nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t const height_a, nano::seconds_t const timestamp_a, nano::epoch const epoch_a, bool const is_send, bool const is_receive, bool const is_epoch, nano::epoch const source_epoch_a) : + successor (successor_a), + account (account_a), + balance (balance_a), + height (height_a), + timestamp (timestamp_a), + details (epoch_a, is_send, is_receive, is_epoch), + source_epoch (source_epoch_a) +{ +} + +size_t nano::block_sideband::size (nano::block_type type_a) +{ + size_t result (0); + result += sizeof (successor); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + result += sizeof (account); + } + if (type_a != nano::block_type::open) + { + result += sizeof (height); + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + result += sizeof (balance); + } + result += sizeof (timestamp); + if (type_a == nano::block_type::state) + { + static_assert (sizeof (nano::epoch) == nano::block_details::size (), "block_details is larger than the epoch enum"); + result += nano::block_details::size () + sizeof (nano::epoch); + } + return result; +} + +void nano::block_sideband::serialize (nano::stream & stream_a, nano::block_type type_a) const +{ + nano::write (stream_a, successor.bytes); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + nano::write (stream_a, account.bytes); + } + if (type_a != nano::block_type::open) + { + nano::write (stream_a, boost::endian::native_to_big (height)); + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + nano::write (stream_a, balance.bytes); + } + nano::write (stream_a, boost::endian::native_to_big (timestamp)); + if (type_a == nano::block_type::state) + { + details.serialize (stream_a); + nano::write (stream_a, static_cast (source_epoch)); + } +} + +bool nano::block_sideband::deserialize (nano::stream & stream_a, nano::block_type type_a) +{ + bool result (false); + try + { + nano::read (stream_a, successor.bytes); + if (type_a != nano::block_type::state && type_a != nano::block_type::open) + { + nano::read (stream_a, account.bytes); + } + if (type_a != nano::block_type::open) + { + nano::read (stream_a, height); + boost::endian::big_to_native_inplace (height); + } + else + { + height = 1; + } + if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) + { + nano::read (stream_a, balance.bytes); + } + nano::read (stream_a, timestamp); + boost::endian::big_to_native_inplace (timestamp); + if (type_a == nano::block_type::state) + { + result = details.deserialize (stream_a); + uint8_t source_epoch_uint8_t{ 0 }; + nano::read (stream_a, source_epoch_uint8_t); + source_epoch = static_cast (source_epoch_uint8_t); + } + } + catch (std::runtime_error &) + { + result = true; + } + + return result; +} + +void nano::block_sideband::operator() (nano::object_stream & obs) const +{ + obs.write ("successor", successor); + obs.write ("account", account); + obs.write ("balance", balance); + obs.write ("height", height); + obs.write ("timestamp", timestamp); + obs.write ("source_epoch", source_epoch); + obs.write ("details", details); +} diff --git a/nano/lib/block_sideband.hpp b/nano/lib/block_sideband.hpp index 871f9bfcfc..09dcc8a96a 100644 --- a/nano/lib/block_sideband.hpp +++ b/nano/lib/block_sideband.hpp @@ -1,19 +1,13 @@ #pragma once -#include #include +#include #include -#include #include #include #include -namespace nano -{ -class object_stream; -} - namespace nano { class block_details diff --git a/nano/lib/block_type.cpp b/nano/lib/block_type.cpp index 70ec28b40c..5be00af002 100644 --- a/nano/lib/block_type.cpp +++ b/nano/lib/block_type.cpp @@ -5,8 +5,3 @@ std::string_view nano::to_string (nano::block_type type) { return nano::enum_util::name (type); } - -void nano::serialize_block_type (nano::stream & stream, const nano::block_type & type) -{ - nano::write (stream, type); -} diff --git a/nano/lib/block_type.hpp b/nano/lib/block_type.hpp index 020722e315..591c799992 100644 --- a/nano/lib/block_type.hpp +++ b/nano/lib/block_type.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include #include @@ -19,8 +17,4 @@ enum class block_type : uint8_t }; std::string_view to_string (block_type); -/** - * Serialize block type as an 8-bit value - */ -void serialize_block_type (nano::stream &, nano::block_type const &); } // namespace nano diff --git a/nano/lib/block_uniquer.hpp b/nano/lib/block_uniquer.hpp index d39bb3afc4..4a0c7076b9 100644 --- a/nano/lib/block_uniquer.hpp +++ b/nano/lib/block_uniquer.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace nano diff --git a/nano/lib/blocks.cpp b/nano/lib/blocks.cpp index 4fc527eb0d..d7d3cb8663 100644 --- a/nano/lib/blocks.cpp +++ b/nano/lib/blocks.cpp @@ -1,9 +1,13 @@ #include +#include +#include #include #include #include #include +#include #include +#include #include #include @@ -206,7 +210,7 @@ nano::block_hash nano::block::full_hash () const nano::block_sideband const & nano::block::sideband () const { - debug_assert (sideband_m.is_initialized ()); + release_assert (sideband_m.is_initialized ()); return *sideband_m; } @@ -565,6 +569,11 @@ nano::send_block::send_block (bool & error_a, boost::property_tree::ptree const } } +std::shared_ptr nano::send_block::clone () const +{ + return std::make_shared (*this); +} + bool nano::send_block::operator== (nano::block const & other_a) const { return blocks_equal (*this, other_a); @@ -754,6 +763,11 @@ nano::open_block::open_block (bool & error_a, boost::property_tree::ptree const } } +std::shared_ptr nano::open_block::clone () const +{ + return std::make_shared (*this); +} + void nano::open_block::generate_hash (blake2b_state & hash_a) const { hashables.hash (hash_a); @@ -1025,6 +1039,11 @@ nano::change_block::change_block (bool & error_a, boost::property_tree::ptree co } } +std::shared_ptr nano::change_block::clone () const +{ + return std::make_shared (*this); +} + void nano::change_block::generate_hash (blake2b_state & hash_a) const { hashables.hash (hash_a); @@ -1322,6 +1341,11 @@ nano::state_block::state_block (bool & error_a, boost::property_tree::ptree cons } } +std::shared_ptr nano::state_block::clone () const +{ + return std::make_shared (*this); +} + void nano::state_block::generate_hash (blake2b_state & hash_a) const { nano::uint256_union preamble (static_cast (nano::block_type::state)); @@ -1582,7 +1606,7 @@ std::shared_ptr nano::deserialize_block_json (boost::property_tree: void nano::serialize_block (nano::stream & stream_a, nano::block const & block_a) { - nano::serialize_block_type (stream_a, block_a.type ()); + nano::write (stream_a, block_a.type ()); block_a.serialize (stream_a); } @@ -1788,6 +1812,11 @@ nano::receive_block::receive_block (bool & error_a, boost::property_tree::ptree } } +std::shared_ptr nano::receive_block::clone () const +{ + return std::make_shared (*this); +} + void nano::receive_block::generate_hash (blake2b_state & hash_a) const { hashables.hash (hash_a); @@ -1908,213 +1937,3 @@ void nano::receive_block::operator() (nano::object_stream & obs) const obs.write ("signature", signature); obs.write ("work", work); } - -/* - * block_details - */ - -nano::block_details::block_details (nano::epoch const epoch_a, bool const is_send_a, bool const is_receive_a, bool const is_epoch_a) : - epoch (epoch_a), is_send (is_send_a), is_receive (is_receive_a), is_epoch (is_epoch_a) -{ -} - -bool nano::block_details::operator== (nano::block_details const & other_a) const -{ - return epoch == other_a.epoch && is_send == other_a.is_send && is_receive == other_a.is_receive && is_epoch == other_a.is_epoch; -} - -uint8_t nano::block_details::packed () const -{ - std::bitset<8> result (static_cast (epoch)); - result.set (7, is_send); - result.set (6, is_receive); - result.set (5, is_epoch); - return static_cast (result.to_ulong ()); -} - -void nano::block_details::unpack (uint8_t details_a) -{ - constexpr std::bitset<8> epoch_mask{ 0b00011111 }; - auto as_bitset = static_cast> (details_a); - is_send = as_bitset.test (7); - is_receive = as_bitset.test (6); - is_epoch = as_bitset.test (5); - epoch = static_cast ((as_bitset & epoch_mask).to_ulong ()); -} - -void nano::block_details::serialize (nano::stream & stream_a) const -{ - nano::write (stream_a, packed ()); -} - -bool nano::block_details::deserialize (nano::stream & stream_a) -{ - bool result (false); - try - { - uint8_t packed{ 0 }; - nano::read (stream_a, packed); - unpack (packed); - } - catch (std::runtime_error &) - { - result = true; - } - - return result; -} - -void nano::block_details::operator() (nano::object_stream & obs) const -{ - obs.write ("epoch", epoch); - obs.write ("is_send", is_send); - obs.write ("is_receive", is_receive); - obs.write ("is_epoch", is_epoch); -} - -std::string nano::state_subtype (nano::block_details const details_a) -{ - debug_assert (details_a.is_epoch + details_a.is_receive + details_a.is_send <= 1); - if (details_a.is_send) - { - return "send"; - } - else if (details_a.is_receive) - { - return "receive"; - } - else if (details_a.is_epoch) - { - return "epoch"; - } - else - { - return "change"; - } -} - -/* - * block_sideband - */ - -nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t const height_a, nano::seconds_t const timestamp_a, nano::block_details const & details_a, nano::epoch const source_epoch_a) : - successor (successor_a), - account (account_a), - balance (balance_a), - height (height_a), - timestamp (timestamp_a), - details (details_a), - source_epoch (source_epoch_a) -{ -} - -nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t const height_a, nano::seconds_t const timestamp_a, nano::epoch const epoch_a, bool const is_send, bool const is_receive, bool const is_epoch, nano::epoch const source_epoch_a) : - successor (successor_a), - account (account_a), - balance (balance_a), - height (height_a), - timestamp (timestamp_a), - details (epoch_a, is_send, is_receive, is_epoch), - source_epoch (source_epoch_a) -{ -} - -size_t nano::block_sideband::size (nano::block_type type_a) -{ - size_t result (0); - result += sizeof (successor); - if (type_a != nano::block_type::state && type_a != nano::block_type::open) - { - result += sizeof (account); - } - if (type_a != nano::block_type::open) - { - result += sizeof (height); - } - if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) - { - result += sizeof (balance); - } - result += sizeof (timestamp); - if (type_a == nano::block_type::state) - { - static_assert (sizeof (nano::epoch) == nano::block_details::size (), "block_details is larger than the epoch enum"); - result += nano::block_details::size () + sizeof (nano::epoch); - } - return result; -} - -void nano::block_sideband::serialize (nano::stream & stream_a, nano::block_type type_a) const -{ - nano::write (stream_a, successor.bytes); - if (type_a != nano::block_type::state && type_a != nano::block_type::open) - { - nano::write (stream_a, account.bytes); - } - if (type_a != nano::block_type::open) - { - nano::write (stream_a, boost::endian::native_to_big (height)); - } - if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) - { - nano::write (stream_a, balance.bytes); - } - nano::write (stream_a, boost::endian::native_to_big (timestamp)); - if (type_a == nano::block_type::state) - { - details.serialize (stream_a); - nano::write (stream_a, static_cast (source_epoch)); - } -} - -bool nano::block_sideband::deserialize (nano::stream & stream_a, nano::block_type type_a) -{ - bool result (false); - try - { - nano::read (stream_a, successor.bytes); - if (type_a != nano::block_type::state && type_a != nano::block_type::open) - { - nano::read (stream_a, account.bytes); - } - if (type_a != nano::block_type::open) - { - nano::read (stream_a, height); - boost::endian::big_to_native_inplace (height); - } - else - { - height = 1; - } - if (type_a == nano::block_type::receive || type_a == nano::block_type::change || type_a == nano::block_type::open) - { - nano::read (stream_a, balance.bytes); - } - nano::read (stream_a, timestamp); - boost::endian::big_to_native_inplace (timestamp); - if (type_a == nano::block_type::state) - { - result = details.deserialize (stream_a); - uint8_t source_epoch_uint8_t{ 0 }; - nano::read (stream_a, source_epoch_uint8_t); - source_epoch = static_cast (source_epoch_uint8_t); - } - } - catch (std::runtime_error &) - { - result = true; - } - - return result; -} - -void nano::block_sideband::operator() (nano::object_stream & obs) const -{ - obs.write ("successor", successor); - obs.write ("account", account); - obs.write ("balance", balance); - obs.write ("height", height); - obs.write ("timestamp", timestamp); - obs.write ("source_epoch", source_epoch); - obs.write ("details", details); -} diff --git a/nano/lib/blocks.hpp b/nano/lib/blocks.hpp index 2312855cb6..5bdb0a4af0 100644 --- a/nano/lib/blocks.hpp +++ b/nano/lib/blocks.hpp @@ -1,27 +1,29 @@ #pragma once #include -#include -#include #include #include +#include #include #include -#include #include +#include + typedef struct blake2b_state__ blake2b_state; namespace nano { -class block_visitor; -class mutable_block_visitor; -class object_stream; +using block_uniquer = uniquer; +} +namespace nano +{ class block { public: + virtual ~block () = default; // Return a digest of the hashables in this block. nano::block_hash const & hash () const; // Return a digest of hashables and non-hashables in this block. @@ -45,10 +47,10 @@ class block virtual nano::block_type type () const = 0; virtual nano::signature const & block_signature () const = 0; virtual void signature_set (nano::signature const &) = 0; - virtual ~block () = default; virtual bool valid_predecessor (nano::block const &) const = 0; static size_t size (nano::block_type); virtual nano::work_version work_version () const; + virtual std::shared_ptr clone () const = 0; // If there are any changes to the hashables, call this to update the cached hash void refresh (); bool is_send () const noexcept; @@ -137,6 +139,7 @@ class send_block : public nano::block bool operator== (nano::block const &) const override; bool operator== (nano::send_block const &) const; bool valid_predecessor (nano::block const &) const override; + std::shared_ptr clone () const override; send_hashables hashables; nano::signature signature; uint64_t work; @@ -191,6 +194,7 @@ class receive_block : public nano::block bool operator== (nano::block const &) const override; bool operator== (nano::receive_block const &) const; bool valid_predecessor (nano::block const &) const override; + std::shared_ptr clone () const override; receive_hashables hashables; nano::signature signature; uint64_t work; @@ -246,6 +250,7 @@ class open_block : public nano::block bool operator== (nano::block const &) const override; bool operator== (nano::open_block const &) const; bool valid_predecessor (nano::block const &) const override; + std::shared_ptr clone () const override; nano::open_hashables hashables; nano::signature signature; uint64_t work; @@ -301,6 +306,7 @@ class change_block : public nano::block bool operator== (nano::block const &) const override; bool operator== (nano::change_block const &) const; bool valid_predecessor (nano::block const &) const override; + std::shared_ptr clone () const override; nano::change_hashables hashables; nano::signature signature; uint64_t work; @@ -367,6 +373,7 @@ class state_block : public nano::block bool operator== (nano::block const &) const override; bool operator== (nano::state_block const &) const; bool valid_predecessor (nano::block const &) const override; + std::shared_ptr clone () const override; nano::state_hashables hashables; nano::signature signature; uint64_t work; diff --git a/nano/lib/common.hpp b/nano/lib/common.hpp index bd1f309948..1e54097d05 100644 --- a/nano/lib/common.hpp +++ b/nano/lib/common.hpp @@ -1,9 +1,14 @@ #pragma once -#include +namespace boost::asio::ip +{ +class tcp; +template +class basic_endpoint; +} namespace nano { -using endpoint = boost::asio::ip::tcp::endpoint; +using endpoint = boost::asio::ip::basic_endpoint; using tcp_endpoint = endpoint; -} \ No newline at end of file +} diff --git a/nano/lib/config.cpp b/nano/lib/config.cpp index 4dda3dcf15..e8d0f928da 100644 --- a/nano/lib/config.cpp +++ b/nano/lib/config.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include @@ -10,221 +12,8 @@ #include -namespace -{ -// useful for boost_lexical cast to allow conversion of hex strings -template -struct HexTo -{ - ElemT value; - - HexTo () = default; - - HexTo (ElemT val) : - value{ val } - { - } - - operator ElemT () const - { - return value; - } - - friend std::istream & operator>> (std::istream & in, HexTo & out) - { - in >> std::hex >> out.value; - return in; - } -}; -} // namespace - -nano::work_thresholds const nano::work_thresholds::publish_full ( -0xffffffc000000000, -0xfffffff800000000, // 8x higher than epoch_1 -0xfffffe0000000000 // 8x lower than epoch_1 -); - -nano::work_thresholds const nano::work_thresholds::publish_beta ( -0xfffff00000000000, // 64x lower than publish_full.epoch_1 -0xfffff00000000000, // same as epoch_1 -0xffffe00000000000 // 2x lower than epoch_1 -); - -nano::work_thresholds const nano::work_thresholds::publish_dev ( -0xfe00000000000000, // Very low for tests -0xffc0000000000000, // 8x higher than epoch_1 -0xf000000000000000 // 8x lower than epoch_1 -); - -nano::work_thresholds const nano::work_thresholds::publish_test ( // defaults to live network levels -nano::env::get> ("NANO_TEST_EPOCH_1").value_or (0xffffffc000000000), -nano::env::get> ("NANO_TEST_EPOCH_2").value_or (0xfffffff800000000), // 8x higher than epoch_1 -nano::env::get> ("NANO_TEST_EPOCH_2_RECV").value_or (0xfffffe0000000000) // 8x lower than epoch_1 -); - -uint64_t nano::work_thresholds::threshold_entry (nano::work_version const version_a, nano::block_type const type_a) const -{ - uint64_t result{ std::numeric_limits::max () }; - if (type_a == nano::block_type::state) - { - switch (version_a) - { - case nano::work_version::work_1: - result = entry; - break; - default: - debug_assert (false && "Invalid version specified to work_threshold_entry"); - } - } - else - { - result = epoch_1; - } - return result; -} - -#ifndef NANO_FUZZER_TEST -uint64_t nano::work_thresholds::value (nano::root const & root_a, uint64_t work_a) const -{ - uint64_t result; - blake2b_state hash; - blake2b_init (&hash, sizeof (result)); - blake2b_update (&hash, reinterpret_cast (&work_a), sizeof (work_a)); - blake2b_update (&hash, root_a.bytes.data (), root_a.bytes.size ()); - blake2b_final (&hash, reinterpret_cast (&result), sizeof (result)); - return result; -} -#else -uint64_t nano::work_thresholds::value (nano::root const & root_a, uint64_t work_a) const -{ - return base + 1; -} -#endif - -uint64_t nano::work_thresholds::threshold (nano::block_details const & details_a) const -{ - static_assert (nano::epoch::max == nano::epoch::epoch_2, "work_v1::threshold is ill-defined"); - - uint64_t result{ std::numeric_limits::max () }; - switch (details_a.epoch) - { - case nano::epoch::epoch_2: - result = (details_a.is_receive || details_a.is_epoch) ? epoch_2_receive : epoch_2; - break; - case nano::epoch::epoch_1: - case nano::epoch::epoch_0: - result = epoch_1; - break; - default: - debug_assert (false && "Invalid epoch specified to work_v1 ledger work_threshold"); - } - return result; -} - -uint64_t nano::work_thresholds::threshold (nano::work_version const version_a, nano::block_details const details_a) const -{ - uint64_t result{ std::numeric_limits::max () }; - switch (version_a) - { - case nano::work_version::work_1: - result = threshold (details_a); - break; - default: - debug_assert (false && "Invalid version specified to ledger work_threshold"); - } - return result; -} - -double nano::work_thresholds::normalized_multiplier (double const multiplier_a, uint64_t const threshold_a) const -{ - debug_assert (multiplier_a >= 1); - auto multiplier (multiplier_a); - /* Normalization rules - ratio = multiplier of max work threshold (send epoch 2) from given threshold - i.e. max = 0xfe00000000000000, given = 0xf000000000000000, ratio = 8.0 - normalized = (multiplier + (ratio - 1)) / ratio; - Epoch 1 - multiplier | normalized - 1.0 | 1.0 - 9.0 | 2.0 - 25.0 | 4.0 - Epoch 2 (receive / epoch subtypes) - multiplier | normalized - 1.0 | 1.0 - 65.0 | 2.0 - 241.0 | 4.0 - */ - if (threshold_a == epoch_1 || threshold_a == epoch_2_receive) - { - auto ratio (nano::difficulty::to_multiplier (epoch_2, threshold_a)); - debug_assert (ratio >= 1); - multiplier = (multiplier + (ratio - 1.0)) / ratio; - debug_assert (multiplier >= 1); - } - return multiplier; -} - -double nano::work_thresholds::denormalized_multiplier (double const multiplier_a, uint64_t const threshold_a) const -{ - debug_assert (multiplier_a >= 1); - auto multiplier (multiplier_a); - if (threshold_a == epoch_1 || threshold_a == epoch_2_receive) - { - auto ratio (nano::difficulty::to_multiplier (epoch_2, threshold_a)); - debug_assert (ratio >= 1); - multiplier = multiplier * ratio + 1.0 - ratio; - debug_assert (multiplier >= 1); - } - return multiplier; -} - -uint64_t nano::work_thresholds::threshold_base (nano::work_version const version_a) const -{ - uint64_t result{ std::numeric_limits::max () }; - switch (version_a) - { - case nano::work_version::work_1: - result = base; - break; - default: - debug_assert (false && "Invalid version specified to work_threshold_base"); - } - return result; -} - -uint64_t nano::work_thresholds::difficulty (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) const -{ - uint64_t result{ 0 }; - switch (version_a) - { - case nano::work_version::work_1: - result = value (root_a, work_a); - break; - default: - debug_assert (false && "Invalid version specified to work_difficulty"); - } - return result; -} - -uint64_t nano::work_thresholds::difficulty (nano::block const & block_a) const -{ - return difficulty (block_a.work_version (), block_a.root (), block_a.block_work ()); -} - -bool nano::work_thresholds::validate_entry (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) const -{ - return difficulty (version_a, root_a, work_a) < threshold_entry (version_a, nano::block_type::state); -} - -bool nano::work_thresholds::validate_entry (nano::block const & block_a) const -{ - return difficulty (block_a) < threshold_entry (block_a.work_version (), block_a.type ()); -} - namespace nano { -char const * network_constants::active_network_err_msg = "Invalid network. Valid values are live, test, beta and dev."; - uint8_t get_major_node_version () { return boost::numeric_cast (boost::lexical_cast (NANO_MAJOR_VERSION_STRING)); @@ -247,6 +36,11 @@ void force_nano_dev_network () nano::network_constants::set_active_network (nano::networks::nano_dev_network); } +bool is_dev_run () +{ + return nano::network_constants::get_active_network () == nano::networks::nano_dev_network; +} + bool running_within_valgrind () { return (RUNNING_ON_VALGRIND > 0); diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index c67095967b..a0eb430af3 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -41,7 +41,7 @@ consteval bool is_asan_build () #else return false; #endif -// GCC builds + // GCC builds #elif defined(__SANITIZE_ADDRESS__) return true; #else @@ -57,7 +57,7 @@ consteval bool is_tsan_build () #else return false; #endif -// GCC builds + // GCC builds #elif defined(__SANITIZE_THREAD__) return true; #else @@ -84,285 +84,7 @@ uint16_t test_rpc_port (); uint16_t test_ipc_port (); uint16_t test_websocket_port (); std::array test_magic_number (); -/// How often to scan for representatives in local wallet, in milliseconds -uint32_t test_scan_wallet_reps_delay (); - -/** - * Network variants with different genesis blocks and network parameters - */ -enum class networks : uint16_t -{ - invalid = 0x0, - // Low work parameters, publicly known genesis key, dev IP ports - nano_dev_network = 0x5241, // 'R', 'A' - // Normal work parameters, secret beta genesis key, beta IP ports - nano_beta_network = 0x5242, // 'R', 'B' - // Normal work parameters, secret live key, live IP ports - nano_live_network = 0x5243, // 'R', 'C' - // Normal work parameters, secret test genesis key, test IP ports - nano_test_network = 0x5258, // 'R', 'X' -}; - -std::string_view to_string (nano::networks); - -enum class work_version -{ - unspecified, - work_1 -}; -enum class block_type : uint8_t; -class root; -class block; -class block_details; - -class work_thresholds -{ -public: - uint64_t const epoch_1; - uint64_t const epoch_2; - uint64_t const epoch_2_receive; - - // Automatically calculated. The base threshold is the maximum of all thresholds and is used for all work multiplier calculations - uint64_t const base; - - // Automatically calculated. The entry threshold is the minimum of all thresholds and defines the required work to enter the node, but does not guarantee a block is processed - uint64_t const entry; - - constexpr work_thresholds (uint64_t epoch_1_a, uint64_t epoch_2_a, uint64_t epoch_2_receive_a) : - epoch_1 (epoch_1_a), epoch_2 (epoch_2_a), epoch_2_receive (epoch_2_receive_a), - base (std::max ({ epoch_1, epoch_2, epoch_2_receive })), - entry (std::min ({ epoch_1, epoch_2, epoch_2_receive })) - { - } - work_thresholds () = delete; - work_thresholds operator= (nano::work_thresholds const & other_a) - { - return other_a; - } - - uint64_t threshold_entry (nano::work_version const, nano::block_type const) const; - uint64_t threshold (nano::block_details const &) const; - // Ledger threshold - uint64_t threshold (nano::work_version const, nano::block_details const) const; - uint64_t threshold_base (nano::work_version const) const; - uint64_t value (nano::root const & root_a, uint64_t work_a) const; - double normalized_multiplier (double const, uint64_t const) const; - double denormalized_multiplier (double const, uint64_t const) const; - uint64_t difficulty (nano::work_version const, nano::root const &, uint64_t const) const; - uint64_t difficulty (nano::block const & block_a) const; - bool validate_entry (nano::work_version const, nano::root const &, uint64_t const) const; - bool validate_entry (nano::block const &) const; - - /** Network work thresholds. Define these inline as constexpr when moving to cpp17. */ - static nano::work_thresholds const publish_full; - static nano::work_thresholds const publish_beta; - static nano::work_thresholds const publish_dev; - static nano::work_thresholds const publish_test; -}; - -class network_constants -{ - static constexpr std::chrono::seconds default_cleanup_period = std::chrono::seconds (60); - -public: - network_constants (nano::work_thresholds & work_, nano::networks network_a) : - current_network (network_a), - work (work_), - principal_weight_factor (1000), // 0.1% A representative is classified as principal based on its weight and this factor - default_node_port (44000), - default_rpc_port (45000), - default_ipc_port (46000), - default_websocket_port (47000), - aec_loop_interval_ms (300), // Update AEC ~3 times per second - cleanup_period (default_cleanup_period), - merge_period (std::chrono::milliseconds (250)), - keepalive_period (std::chrono::seconds (15)), - idle_timeout (default_cleanup_period * 2), - silent_connection_tolerance_time (std::chrono::seconds (120)), - syn_cookie_cutoff (std::chrono::seconds (5)), - bootstrap_interval (std::chrono::seconds (15 * 60)), - ipv6_subnetwork_prefix_for_limiting (64), // Equivalent to network prefix /64. - peer_dump_interval (std::chrono::seconds (5 * 60)), - vote_broadcast_interval (15 * 1000), - block_broadcast_interval (150 * 1000) - { - if (is_live_network ()) - { - default_node_port = 7075; - default_rpc_port = 7076; - default_ipc_port = 7077; - default_websocket_port = 7078; - } - else if (is_beta_network ()) - { - default_node_port = 54000; - default_rpc_port = 55000; - default_ipc_port = 56000; - default_websocket_port = 57000; - } - else if (is_test_network ()) - { - default_node_port = test_node_port (); - default_rpc_port = test_rpc_port (); - default_ipc_port = test_ipc_port (); - default_websocket_port = test_websocket_port (); - } - else if (is_dev_network ()) - { - aec_loop_interval_ms = 20; - cleanup_period = std::chrono::seconds (1); - merge_period = std::chrono::milliseconds (10); - keepalive_period = std::chrono::seconds (1); - idle_timeout = cleanup_period * 15; - peer_dump_interval = std::chrono::seconds (1); - vote_broadcast_interval = 500ms; - block_broadcast_interval = 500ms; - telemetry_request_cooldown = 500ms; - telemetry_cache_cutoff = 2000ms; - telemetry_request_interval = 500ms; - telemetry_broadcast_interval = 500ms; - optimistic_activation_delay = 2s; - rep_crawler_normal_interval = 500ms; - rep_crawler_warmup_interval = 500ms; - } - } - - /** Error message when an invalid network is specified */ - static char const * active_network_err_msg; - - /** The network this param object represents. This may differ from the global active network; this is needed for certain --debug... commands */ - nano::networks current_network{ nano::network_constants::active_network }; - nano::work_thresholds & work; - - unsigned principal_weight_factor; - uint16_t default_node_port; - uint16_t default_rpc_port; - uint16_t default_ipc_port; - uint16_t default_websocket_port; - unsigned aec_loop_interval_ms; - - std::chrono::seconds cleanup_period; - std::chrono::milliseconds cleanup_period_half () const - { - return std::chrono::duration_cast (cleanup_period) / 2; - } - std::chrono::seconds cleanup_cutoff () const - { - return cleanup_period * 5; - } - /** How often to connect to other peers */ - std::chrono::milliseconds merge_period; - /** How often to send keepalive messages */ - std::chrono::seconds keepalive_period; - /** Default maximum idle time for a socket before it's automatically closed */ - std::chrono::seconds idle_timeout; - std::chrono::seconds silent_connection_tolerance_time; - std::chrono::seconds syn_cookie_cutoff; - std::chrono::seconds bootstrap_interval; - size_t ipv6_subnetwork_prefix_for_limiting; - std::chrono::seconds peer_dump_interval; - - /** Time to wait before rebroadcasts for active elections */ - std::chrono::milliseconds vote_broadcast_interval; - std::chrono::milliseconds block_broadcast_interval; - - /** We do not reply to telemetry requests made within cooldown period */ - std::chrono::milliseconds telemetry_request_cooldown{ 1000 * 15 }; - /** How often to request telemetry from peers */ - std::chrono::milliseconds telemetry_request_interval{ 1000 * 60 }; - /** How often to broadcast telemetry to peers */ - std::chrono::milliseconds telemetry_broadcast_interval{ 1000 * 60 }; - /** Telemetry data older than this value is considered stale */ - std::chrono::milliseconds telemetry_cache_cutoff{ 1000 * 130 }; // 2 * `telemetry_broadcast_interval` + some margin - - /** How much to delay activation of optimistic elections to avoid interfering with election scheduler */ - std::chrono::seconds optimistic_activation_delay{ 30 }; - - std::chrono::milliseconds rep_crawler_normal_interval{ 1000 * 7 }; - std::chrono::milliseconds rep_crawler_warmup_interval{ 1000 * 3 }; - - /** Returns the network this object contains values for */ - nano::networks network () const - { - return current_network; - } - - /** - * Optionally called on startup to override the global active network. - * If not called, the compile-time option will be used. - * @param network_a The new active network - */ - static void set_active_network (nano::networks network_a) - { - active_network = network_a; - } - - /** - * Optionally called on startup to override the global active network. - * If not called, the compile-time option will be used. - * @param network_a The new active network. Valid values are "live", "beta" and "dev" - */ - static bool set_active_network (std::string network_a) - { - auto error{ false }; - if (network_a == "live") - { - active_network = nano::networks::nano_live_network; - } - else if (network_a == "beta") - { - active_network = nano::networks::nano_beta_network; - } - else if (network_a == "dev") - { - active_network = nano::networks::nano_dev_network; - } - else if (network_a == "test") - { - active_network = nano::networks::nano_test_network; - } - else - { - error = true; - } - return error; - } - - char const * get_current_network_as_string () - { - return is_live_network () ? "live" : is_beta_network () ? "beta" - : is_test_network () ? "test" - : "dev"; - } - - bool is_live_network () const - { - return current_network == nano::networks::nano_live_network; - } - bool is_beta_network () const - { - return current_network == nano::networks::nano_beta_network; - } - bool is_dev_network () const - { - return current_network == nano::networks::nano_dev_network; - } - bool is_test_network () const - { - return current_network == nano::networks::nano_test_network; - } - - /** Initial value is ACTIVE_NETWORK compile flag, but can be overridden by a CLI flag */ - static nano::networks active_network; - - /** Current protocol version */ - uint8_t const protocol_version = 0x15; - /** Minimum accepted protocol version */ - uint8_t const protocol_version_min = 0x14; - - /** Minimum accepted protocol version used when bootstrapping */ - uint8_t const bootstrap_protocol_version_min = 0x14; -}; +uint32_t test_scan_wallet_reps_delay (); // How often to scan for representatives in local wallet, in milliseconds std::string get_node_toml_config_path (std::filesystem::path const & data_path); std::string get_rpc_toml_config_path (std::filesystem::path const & data_path); @@ -384,6 +106,12 @@ bool slow_instrumentation (); /** Set the active network to the dev network */ void force_nano_dev_network (); +/** Checks that we are running in test mode */ +bool is_dev_run (); +} + +namespace nano +{ /** * Attempt to read a configuration file from specified directory. Returns empty tomlconfig if nothing is found. * @throws std::runtime_error with error code if the file or overrides are not valid toml diff --git a/nano/lib/constants.cpp b/nano/lib/constants.cpp new file mode 100644 index 0000000000..f32bbafd78 --- /dev/null +++ b/nano/lib/constants.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// useful for boost_lexical cast to allow conversion of hex strings +template +struct HexTo +{ + ElemT value; + + HexTo () = default; + + HexTo (ElemT val) : + value{ val } + { + } + + operator ElemT () const + { + return value; + } + + friend std::istream & operator>> (std::istream & in, HexTo & out) + { + in >> std::hex >> out.value; + return in; + } +}; +} + +nano::work_thresholds const nano::work_thresholds::publish_full ( +0xffffffc000000000, +0xfffffff800000000, // 8x higher than epoch_1 +0xfffffe0000000000 // 8x lower than epoch_1 +); + +nano::work_thresholds const nano::work_thresholds::publish_beta ( +0xfffff00000000000, // 64x lower than publish_full.epoch_1 +0xfffff00000000000, // same as epoch_1 +0xffffe00000000000 // 2x lower than epoch_1 +); + +nano::work_thresholds const nano::work_thresholds::publish_dev ( +0xfe00000000000000, // Very low for tests +0xffc0000000000000, // 8x higher than epoch_1 +0xf000000000000000 // 8x lower than epoch_1 +); + +nano::work_thresholds const nano::work_thresholds::publish_test ( // defaults to live network levels +nano::env::get> ("NANO_TEST_EPOCH_1").value_or (0xffffffc000000000), +nano::env::get> ("NANO_TEST_EPOCH_2").value_or (0xfffffff800000000), // 8x higher than epoch_1 +nano::env::get> ("NANO_TEST_EPOCH_2_RECV").value_or (0xfffffe0000000000) // 8x lower than epoch_1 +); + +uint64_t nano::work_thresholds::threshold_entry (nano::work_version const version_a, nano::block_type const type_a) const +{ + uint64_t result{ std::numeric_limits::max () }; + if (type_a == nano::block_type::state) + { + switch (version_a) + { + case nano::work_version::work_1: + result = entry; + break; + default: + debug_assert (false && "Invalid version specified to work_threshold_entry"); + } + } + else + { + result = epoch_1; + } + return result; +} + +#ifndef NANO_FUZZER_TEST +uint64_t nano::work_thresholds::value (nano::root const & root_a, uint64_t work_a) const +{ + uint64_t result; + blake2b_state hash; + blake2b_init (&hash, sizeof (result)); + blake2b_update (&hash, reinterpret_cast (&work_a), sizeof (work_a)); + blake2b_update (&hash, root_a.bytes.data (), root_a.bytes.size ()); + blake2b_final (&hash, reinterpret_cast (&result), sizeof (result)); + return result; +} +#else +uint64_t nano::work_thresholds::value (nano::root const & root_a, uint64_t work_a) const +{ + return base + 1; +} +#endif + +uint64_t nano::work_thresholds::threshold (nano::block_details const & details_a) const +{ + static_assert (nano::epoch::max == nano::epoch::epoch_2, "work_v1::threshold is ill-defined"); + + uint64_t result{ std::numeric_limits::max () }; + switch (details_a.epoch) + { + case nano::epoch::epoch_2: + result = (details_a.is_receive || details_a.is_epoch) ? epoch_2_receive : epoch_2; + break; + case nano::epoch::epoch_1: + case nano::epoch::epoch_0: + result = epoch_1; + break; + default: + debug_assert (false && "Invalid epoch specified to work_v1 ledger work_threshold"); + } + return result; +} + +uint64_t nano::work_thresholds::threshold (nano::work_version const version_a, nano::block_details const details_a) const +{ + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) + { + case nano::work_version::work_1: + result = threshold (details_a); + break; + default: + debug_assert (false && "Invalid version specified to ledger work_threshold"); + } + return result; +} + +double nano::work_thresholds::normalized_multiplier (double const multiplier_a, uint64_t const threshold_a) const +{ + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + /* Normalization rules + ratio = multiplier of max work threshold (send epoch 2) from given threshold + i.e. max = 0xfe00000000000000, given = 0xf000000000000000, ratio = 8.0 + normalized = (multiplier + (ratio - 1)) / ratio; + Epoch 1 + multiplier | normalized + 1.0 | 1.0 + 9.0 | 2.0 + 25.0 | 4.0 + Epoch 2 (receive / epoch subtypes) + multiplier | normalized + 1.0 | 1.0 + 65.0 | 2.0 + 241.0 | 4.0 + */ + if (threshold_a == epoch_1 || threshold_a == epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = (multiplier + (ratio - 1.0)) / ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + +double nano::work_thresholds::denormalized_multiplier (double const multiplier_a, uint64_t const threshold_a) const +{ + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + if (threshold_a == epoch_1 || threshold_a == epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = multiplier * ratio + 1.0 - ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + +uint64_t nano::work_thresholds::threshold_base (nano::work_version const version_a) const +{ + uint64_t result{ std::numeric_limits::max () }; + switch (version_a) + { + case nano::work_version::work_1: + result = base; + break; + default: + debug_assert (false && "Invalid version specified to work_threshold_base"); + } + return result; +} + +uint64_t nano::work_thresholds::difficulty (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) const +{ + uint64_t result{ 0 }; + switch (version_a) + { + case nano::work_version::work_1: + result = value (root_a, work_a); + break; + default: + debug_assert (false && "Invalid version specified to work_difficulty"); + } + return result; +} + +uint64_t nano::work_thresholds::difficulty (nano::block const & block_a) const +{ + return difficulty (block_a.work_version (), block_a.root (), block_a.block_work ()); +} + +bool nano::work_thresholds::validate_entry (nano::work_version const version_a, nano::root const & root_a, uint64_t const work_a) const +{ + return difficulty (version_a, root_a, work_a) < threshold_entry (version_a, nano::block_type::state); +} + +bool nano::work_thresholds::validate_entry (nano::block const & block_a) const +{ + return difficulty (block_a) < threshold_entry (block_a.work_version (), block_a.type ()); +} \ No newline at end of file diff --git a/nano/lib/constants.hpp b/nano/lib/constants.hpp new file mode 100644 index 0000000000..de6e822225 --- /dev/null +++ b/nano/lib/constants.hpp @@ -0,0 +1,290 @@ +#pragma once + +#include +#include + +#include +#include + +namespace nano +{ +/** + * Network variants with different genesis blocks and network parameters + */ +enum class networks : uint16_t +{ + invalid = 0x0, + // Low work parameters, publicly known genesis key, dev IP ports + nano_dev_network = 0x5241, // 'R', 'A' + // Normal work parameters, secret beta genesis key, beta IP ports + nano_beta_network = 0x5242, // 'R', 'B' + // Normal work parameters, secret live key, live IP ports + nano_live_network = 0x5243, // 'R', 'C' + // Normal work parameters, secret test genesis key, test IP ports + nano_test_network = 0x5258, // 'R', 'X' +}; + +std::string_view to_string (nano::networks); + +class work_thresholds +{ +public: + uint64_t const epoch_1; + uint64_t const epoch_2; + uint64_t const epoch_2_receive; + + // Automatically calculated. The base threshold is the maximum of all thresholds and is used for all work multiplier calculations + uint64_t const base; + + // Automatically calculated. The entry threshold is the minimum of all thresholds and defines the required work to enter the node, but does not guarantee a block is processed + uint64_t const entry; + + constexpr work_thresholds (uint64_t epoch_1_a, uint64_t epoch_2_a, uint64_t epoch_2_receive_a) : + epoch_1 (epoch_1_a), epoch_2 (epoch_2_a), epoch_2_receive (epoch_2_receive_a), + base (std::max ({ epoch_1, epoch_2, epoch_2_receive })), + entry (std::min ({ epoch_1, epoch_2, epoch_2_receive })) + { + } + work_thresholds () = delete; + work_thresholds operator= (nano::work_thresholds const & other_a) + { + return other_a; + } + + uint64_t threshold_entry (nano::work_version const, nano::block_type const) const; + uint64_t threshold (nano::block_details const &) const; + // Ledger threshold + uint64_t threshold (nano::work_version const, nano::block_details const) const; + uint64_t threshold_base (nano::work_version const) const; + uint64_t value (nano::root const & root_a, uint64_t work_a) const; + double normalized_multiplier (double const, uint64_t const) const; + double denormalized_multiplier (double const, uint64_t const) const; + uint64_t difficulty (nano::work_version const, nano::root const &, uint64_t const) const; + uint64_t difficulty (nano::block const & block_a) const; + bool validate_entry (nano::work_version const, nano::root const &, uint64_t const) const; + bool validate_entry (nano::block const &) const; + + /** Network work thresholds. Define these inline as constexpr when moving to cpp17. */ + static nano::work_thresholds const publish_full; + static nano::work_thresholds const publish_beta; + static nano::work_thresholds const publish_dev; + static nano::work_thresholds const publish_test; +}; + +class network_constants +{ + static constexpr std::chrono::seconds default_cleanup_period = std::chrono::seconds (60); + +public: + network_constants (nano::work_thresholds & work_, nano::networks network_a) : + current_network (network_a), + work (work_), + principal_weight_factor (1000), // 0.1% A representative is classified as principal based on its weight and this factor + default_node_port (44000), + default_rpc_port (45000), + default_ipc_port (46000), + default_websocket_port (47000), + aec_loop_interval_ms (300), // Update AEC ~3 times per second + cleanup_period (default_cleanup_period), + merge_period (std::chrono::milliseconds (250)), + keepalive_period (std::chrono::seconds (15)), + idle_timeout (default_cleanup_period * 2), + silent_connection_tolerance_time (std::chrono::seconds (120)), + syn_cookie_cutoff (std::chrono::seconds (5)), + bootstrap_interval (std::chrono::seconds (15 * 60)), + ipv6_subnetwork_prefix_for_limiting (64), // Equivalent to network prefix /64. + peer_dump_interval (std::chrono::seconds (5 * 60)), + vote_broadcast_interval (15 * 1000), + block_broadcast_interval (150 * 1000) + { + if (is_live_network ()) + { + default_node_port = 7075; + default_rpc_port = 7076; + default_ipc_port = 7077; + default_websocket_port = 7078; + } + else if (is_beta_network ()) + { + default_node_port = 54000; + default_rpc_port = 55000; + default_ipc_port = 56000; + default_websocket_port = 57000; + } + else if (is_test_network ()) + { + default_node_port = test_node_port (); + default_rpc_port = test_rpc_port (); + default_ipc_port = test_ipc_port (); + default_websocket_port = test_websocket_port (); + } + else if (is_dev_network ()) + { + aec_loop_interval_ms = 20; + cleanup_period = std::chrono::seconds (1); + merge_period = std::chrono::milliseconds (10); + keepalive_period = std::chrono::seconds (1); + idle_timeout = cleanup_period * 15; + peer_dump_interval = std::chrono::seconds (1); + vote_broadcast_interval = 500ms; + block_broadcast_interval = 500ms; + telemetry_request_cooldown = 500ms; + telemetry_cache_cutoff = 2000ms; + telemetry_request_interval = 500ms; + telemetry_broadcast_interval = 500ms; + optimistic_activation_delay = 2s; + rep_crawler_normal_interval = 500ms; + rep_crawler_warmup_interval = 500ms; + } + } + + /** The network this param object represents. This may differ from the global active network; this is needed for certain --debug... commands */ + nano::networks current_network{ nano::network_constants::active_network }; + nano::work_thresholds & work; + + unsigned principal_weight_factor; + uint16_t default_node_port; + uint16_t default_rpc_port; + uint16_t default_ipc_port; + uint16_t default_websocket_port; + unsigned aec_loop_interval_ms; + + std::chrono::seconds cleanup_period; + std::chrono::milliseconds cleanup_period_half () const + { + return std::chrono::duration_cast (cleanup_period) / 2; + } + std::chrono::seconds cleanup_cutoff () const + { + return cleanup_period * 5; + } + /** How often to connect to other peers */ + std::chrono::milliseconds merge_period; + /** How often to send keepalive messages */ + std::chrono::seconds keepalive_period; + /** Default maximum idle time for a socket before it's automatically closed */ + std::chrono::seconds idle_timeout; + std::chrono::seconds silent_connection_tolerance_time; + std::chrono::seconds syn_cookie_cutoff; + std::chrono::seconds bootstrap_interval; + size_t ipv6_subnetwork_prefix_for_limiting; + std::chrono::seconds peer_dump_interval; + + /** Time to wait before rebroadcasts for active elections */ + std::chrono::milliseconds vote_broadcast_interval; + std::chrono::milliseconds block_broadcast_interval; + + /** We do not reply to telemetry requests made within cooldown period */ + std::chrono::milliseconds telemetry_request_cooldown{ 1000 * 15 }; + /** How often to request telemetry from peers */ + std::chrono::milliseconds telemetry_request_interval{ 1000 * 60 }; + /** How often to broadcast telemetry to peers */ + std::chrono::milliseconds telemetry_broadcast_interval{ 1000 * 60 }; + /** Telemetry data older than this value is considered stale */ + std::chrono::milliseconds telemetry_cache_cutoff{ 1000 * 130 }; // 2 * `telemetry_broadcast_interval` + some margin + + /** How much to delay activation of optimistic elections to avoid interfering with election scheduler */ + std::chrono::seconds optimistic_activation_delay{ 30 }; + + std::chrono::milliseconds rep_crawler_normal_interval{ 1000 * 7 }; + std::chrono::milliseconds rep_crawler_warmup_interval{ 1000 * 3 }; + + /** Returns the network this object contains values for */ + nano::networks network () const + { + return current_network; + } + + /** + * Optionally called on startup to override the global active network. + * If not called, the compile-time option will be used. + * @param network_a The new active network + */ + static void set_active_network (nano::networks network_a) + { + active_network = network_a; + } + + static nano::networks get_active_network () + { + return active_network; + } + + /** + * Optionally called on startup to override the global active network. + * If not called, the compile-time option will be used. + * @param network_a The new active network. Valid values are "live", "beta" and "dev" + */ + static bool set_active_network (std::string network_a) + { + auto error{ false }; + if (network_a == "live") + { + active_network = nano::networks::nano_live_network; + } + else if (network_a == "beta") + { + active_network = nano::networks::nano_beta_network; + } + else if (network_a == "dev") + { + active_network = nano::networks::nano_dev_network; + } + else if (network_a == "test") + { + active_network = nano::networks::nano_test_network; + } + else + { + error = true; + } + return error; + } + + std::string_view get_current_network_as_string () const + { + switch (current_network) + { + case nano::networks::nano_live_network: + return "live"; + case nano::networks::nano_beta_network: + return "beta"; + case nano::networks::nano_dev_network: + return "dev"; + case nano::networks::nano_test_network: + return "test"; + case networks::invalid: + break; + } + release_assert (false, "invalid network"); + } + + bool is_live_network () const + { + return current_network == nano::networks::nano_live_network; + } + bool is_beta_network () const + { + return current_network == nano::networks::nano_beta_network; + } + bool is_dev_network () const + { + return current_network == nano::networks::nano_dev_network; + } + bool is_test_network () const + { + return current_network == nano::networks::nano_test_network; + } + + /** Initial value is ACTIVE_NETWORK compile flag, but can be overridden by a CLI flag */ + static nano::networks active_network; + + /** Current protocol version */ + uint8_t const protocol_version = 0x15; + /** Minimum accepted protocol version */ + uint8_t const protocol_version_min = 0x14; + + /** Minimum accepted protocol version used when bootstrapping */ + uint8_t const bootstrap_protocol_version_min = 0x14; +}; +} \ No newline at end of file diff --git a/nano/lib/epoch.cpp b/nano/lib/epoch.cpp index 8fb6cd20f4..1de1a0476f 100644 --- a/nano/lib/epoch.cpp +++ b/nano/lib/epoch.cpp @@ -1,43 +1,6 @@ #include #include -#include - -nano::link const & nano::epochs::link (nano::epoch epoch_a) const -{ - return epochs_m.at (epoch_a).link; -} - -bool nano::epochs::is_epoch_link (nano::link const & link_a) const -{ - return std::any_of (epochs_m.begin (), epochs_m.end (), [&link_a] (auto const & item_a) { return item_a.second.link == link_a; }); -} - -nano::public_key const & nano::epochs::signer (nano::epoch epoch_a) const -{ - return epochs_m.at (epoch_a).signer; -} - -nano::epoch nano::epochs::epoch (nano::link const & link_a) const -{ - auto existing (std::find_if (epochs_m.begin (), epochs_m.end (), [&link_a] (auto const & item_a) { return item_a.second.link == link_a; })); - debug_assert (existing != epochs_m.end ()); - return existing->first; -} - -void nano::epochs::add (nano::epoch epoch_a, nano::public_key const & signer_a, nano::link const & link_a) -{ - debug_assert (epochs_m.find (epoch_a) == epochs_m.end ()); - epochs_m[epoch_a] = { signer_a, link_a }; -} - -bool nano::epochs::is_sequential (nano::epoch epoch_a, nano::epoch new_epoch_a) -{ - auto head_epoch = std::underlying_type_t (epoch_a); - bool is_valid_epoch (head_epoch >= std::underlying_type_t (nano::epoch::epoch_0)); - return is_valid_epoch && (std::underlying_type_t (new_epoch_a) == (head_epoch + 1)); -} - std::underlying_type_t nano::normalized_epoch (nano::epoch epoch_a) { // Currently assumes that the epoch versions in the enum are sequential. diff --git a/nano/lib/epoch.hpp b/nano/lib/epoch.hpp index df0a24f7ba..4fae5e1a52 100644 --- a/nano/lib/epoch.hpp +++ b/nano/lib/epoch.hpp @@ -1,9 +1,8 @@ #pragma once -#include - +#include +#include #include -#include namespace nano { @@ -31,42 +30,7 @@ struct hash<::nano::epoch> { std::size_t operator() (::nano::epoch const & epoch_a) const { - std::hash> hash; - return hash (static_cast> (epoch_a)); + return std::underlying_type_t<::nano::epoch> (epoch_a); } }; } -namespace nano -{ -class epoch_info -{ -public: - nano::public_key signer; - nano::link link; -}; -class epochs -{ -public: - /** Returns true if link matches one of the released epoch links. - * WARNING: just because a legal block contains an epoch link, it does not mean it is an epoch block. - * A legal block containing an epoch link can easily be constructed by sending to an address identical - * to one of the epoch links. - * Epoch blocks follow the following rules and a block must satisfy them all to be a true epoch block: - * epoch blocks are always state blocks - * epoch blocks never change the balance of an account - * epoch blocks always have a link field that starts with the ascii bytes "epoch v1 block" or "epoch v2 block" (and possibly others in the future) - * epoch blocks never change the representative - * epoch blocks are not signed by the account key, they are signed either by genesis or by special epoch keys - */ - bool is_epoch_link (nano::link const & link_a) const; - nano::link const & link (nano::epoch epoch_a) const; - nano::public_key const & signer (nano::epoch epoch_a) const; - nano::epoch epoch (nano::link const & link_a) const; - void add (nano::epoch epoch_a, nano::public_key const & signer_a, nano::link const & link_a); - /** Checks that new_epoch is 1 version higher than epoch */ - static bool is_sequential (nano::epoch epoch_a, nano::epoch new_epoch_a); - -private: - std::unordered_map epochs_m; -}; -} diff --git a/nano/lib/epochs.cpp b/nano/lib/epochs.cpp new file mode 100644 index 0000000000..426d5ab10d --- /dev/null +++ b/nano/lib/epochs.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include + +nano::link const & nano::epochs::link (nano::epoch epoch_a) const +{ + return epochs_m.at (epoch_a).link; +} + +bool nano::epochs::is_epoch_link (nano::link const & link_a) const +{ + return std::any_of (epochs_m.begin (), epochs_m.end (), [&link_a] (auto const & item_a) { return item_a.second.link == link_a; }); +} + +nano::public_key const & nano::epochs::signer (nano::epoch epoch_a) const +{ + return epochs_m.at (epoch_a).signer; +} + +nano::epoch nano::epochs::epoch (nano::link const & link_a) const +{ + auto existing (std::find_if (epochs_m.begin (), epochs_m.end (), [&link_a] (auto const & item_a) { return item_a.second.link == link_a; })); + debug_assert (existing != epochs_m.end ()); + return existing->first; +} + +void nano::epochs::add (nano::epoch epoch_a, nano::public_key const & signer_a, nano::link const & link_a) +{ + debug_assert (epochs_m.find (epoch_a) == epochs_m.end ()); + epochs_m[epoch_a] = { signer_a, link_a }; +} + +bool nano::epochs::is_sequential (nano::epoch epoch_a, nano::epoch new_epoch_a) +{ + auto head_epoch = std::underlying_type_t (epoch_a); + bool is_valid_epoch (head_epoch >= std::underlying_type_t (nano::epoch::epoch_0)); + return is_valid_epoch && (std::underlying_type_t (new_epoch_a) == (head_epoch + 1)); +} diff --git a/nano/lib/epochs.hpp b/nano/lib/epochs.hpp new file mode 100644 index 0000000000..c81ade7f2d --- /dev/null +++ b/nano/lib/epochs.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include +#include + +namespace nano +{ +class epoch_info +{ +public: + nano::public_key signer; + nano::link link; +}; +class epochs +{ +public: + /** Returns true if link matches one of the released epoch links. + * WARNING: just because a legal block contains an epoch link, it does not mean it is an epoch block. + * A legal block containing an epoch link can easily be constructed by sending to an address identical + * to one of the epoch links. + * Epoch blocks follow the following rules and a block must satisfy them all to be a true epoch block: + * epoch blocks are always state blocks + * epoch blocks never change the balance of an account + * epoch blocks always have a link field that starts with the ascii bytes "epoch v1 block" or "epoch v2 block" (and possibly others in the future) + * epoch blocks never change the representative + * epoch blocks are not signed by the account key, they are signed either by genesis or by special epoch keys + */ + bool is_epoch_link (nano::link const & link_a) const; + nano::link const & link (nano::epoch epoch_a) const; + nano::public_key const & signer (nano::epoch epoch_a) const; + nano::epoch epoch (nano::link const & link_a) const; + void add (nano::epoch epoch_a, nano::public_key const & signer_a, nano::link const & link_a); + /** Checks that new_epoch is 1 version higher than epoch */ + static bool is_sequential (nano::epoch epoch_a, nano::epoch new_epoch_a); + +private: + std::unordered_map epochs_m; +}; +} diff --git a/nano/lib/files.cpp b/nano/lib/files.cpp new file mode 100644 index 0000000000..434a32c8f3 --- /dev/null +++ b/nano/lib/files.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +std::size_t nano::get_file_descriptor_limit () +{ + std::size_t fd_limit = std::numeric_limits::max (); +#ifndef _WIN32 + rlimit limit{}; + if (getrlimit (RLIMIT_NOFILE, &limit) == 0) + { + fd_limit = static_cast (limit.rlim_cur); + } +#endif + return fd_limit; +} + +void nano::set_file_descriptor_limit (std::size_t limit) +{ +#ifndef _WIN32 + rlimit fd_limit{}; + if (-1 == getrlimit (RLIMIT_NOFILE, &fd_limit)) + { + std::cerr << "WARNING: Unable to get current limits for the number of open file descriptors: " << std::strerror (errno); + return; + } + + if (fd_limit.rlim_cur >= limit) + { + return; + } + + fd_limit.rlim_cur = std::min (static_cast (limit), fd_limit.rlim_max); + if (-1 == setrlimit (RLIMIT_NOFILE, &fd_limit)) + { + std::cerr << "WARNING: Unable to set limits for the number of open file descriptors: " << std::strerror (errno); + return; + } +#endif +} + +void nano::initialize_file_descriptor_limit () +{ + nano::set_file_descriptor_limit (DEFAULT_FILE_DESCRIPTOR_LIMIT); + auto limit = nano::get_file_descriptor_limit (); + if (limit < DEFAULT_FILE_DESCRIPTOR_LIMIT) + { + std::cerr << "WARNING: Current file descriptor limit of " << limit << " is lower than the " << DEFAULT_FILE_DESCRIPTOR_LIMIT << " recommended. Node was unable to change it." << std::endl; + } +} + +void nano::remove_all_files_in_dir (std::filesystem::path const & dir) +{ + for (auto & p : std::filesystem::directory_iterator (dir)) + { + auto path = p.path (); + if (std::filesystem::is_regular_file (path)) + { + std::filesystem::remove (path); + } + } +} + +void nano::move_all_files_to_dir (std::filesystem::path const & from, std::filesystem::path const & to) +{ + for (auto & p : std::filesystem::directory_iterator (from)) + { + auto path = p.path (); + if (std::filesystem::is_regular_file (path)) + { + std::filesystem::rename (path, to / path.filename ()); + } + } +} \ No newline at end of file diff --git a/nano/lib/files.hpp b/nano/lib/files.hpp new file mode 100644 index 0000000000..b0ed9dcaa6 --- /dev/null +++ b/nano/lib/files.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace nano +{ +/* + * Functions for managing filesystem permissions, platform specific + */ +void set_umask (); +void set_secure_perm_directory (std::filesystem::path const & path); +void set_secure_perm_directory (std::filesystem::path const & path, std::error_code & ec); +void set_secure_perm_file (std::filesystem::path const & path); +void set_secure_perm_file (std::filesystem::path const & path, std::error_code & ec); + +/* + * Function to check if running Windows as an administrator + */ +bool is_windows_elevated (); + +/* + * Function to check if the Windows Event log registry key exists + */ +bool event_log_reg_entry_exists (); + +/* + * Create the load memory addresses for the executable and shared libraries. + */ +void create_load_memory_address_files (); + +/** + * Some systems, especially in virtualized environments, may have very low file descriptor limits, + * causing the node to fail. This function attempts to query the limit and returns the value. If the + * limit cannot be queried, or running on a Windows system, this returns max-value of std::size_t. + * Increasing the limit programmatically can be done only for the soft limit, the hard one requiring + * super user permissions to modify. + */ +std::size_t get_file_descriptor_limit (); +void set_file_descriptor_limit (std::size_t limit); +/** + * This should be called from entry points. It sets the file descriptor limit to the maximum allowed and logs any errors. + */ +constexpr std::size_t DEFAULT_FILE_DESCRIPTOR_LIMIT = 16384; +void initialize_file_descriptor_limit (); + +void remove_all_files_in_dir (std::filesystem::path const & dir); +void move_all_files_to_dir (std::filesystem::path const & from, std::filesystem::path const & to); +} \ No newline at end of file diff --git a/nano/lib/fwd.hpp b/nano/lib/fwd.hpp new file mode 100644 index 0000000000..8c7e915c6d --- /dev/null +++ b/nano/lib/fwd.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +struct uint8_char_traits; + +namespace nano +{ +class block; +class block_details; +class block_visitor; +class container_info; +class jsonconfig; +class mutable_block_visitor; +class network_constants; +class object_stream; +class root; +class thread_pool; +class thread_runner; +class tomlconfig; +template +class uniquer; + +enum class block_type : uint8_t; +enum class epoch : uint8_t; +enum class work_version; + +using stream = std::basic_streambuf; +} + +namespace nano::stat +{ +enum class type; +enum class detail; +enum class dir; +} diff --git a/nano/lib/jsonconfig.cpp b/nano/lib/jsonconfig.cpp index 74523154c2..a92ac7ca00 100644 --- a/nano/lib/jsonconfig.cpp +++ b/nano/lib/jsonconfig.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/nano/lib/jsonconfig.hpp b/nano/lib/jsonconfig.hpp index 27827dfc72..d7ca5fc33f 100644 --- a/nano/lib/jsonconfig.hpp +++ b/nano/lib/jsonconfig.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace boost diff --git a/nano/lib/logging_enums.hpp b/nano/lib/logging_enums.hpp index 85136b98b7..64f520bc62 100644 --- a/nano/lib/logging_enums.hpp +++ b/nano/lib/logging_enums.hpp @@ -47,7 +47,7 @@ enum class type tls, active_elections, election, - blockprocessor, + block_processor, network, message, channel, @@ -55,6 +55,7 @@ enum class type socket, socket_server, tcp, + tcp_socket, tcp_server, tcp_listener, tcp_channels, @@ -80,9 +81,11 @@ enum class type signal_manager, peer_history, message_processor, + online_reps, local_block_broadcaster, monitor, confirming_set, + bounded_backlog, // bootstrap bulk_pull_client, @@ -119,7 +122,7 @@ enum class detail election_expired, broadcast_vote, - // blockprocessor + // block_processor block_processed, // vote_processor diff --git a/nano/lib/memory.hpp b/nano/lib/memory.hpp index 333a5ff980..3d0a248a22 100644 --- a/nano/lib/memory.hpp +++ b/nano/lib/memory.hpp @@ -48,6 +48,16 @@ class cleanup_guard final std::vector> cleanup_funcs; }; +/** Helper guard which contains all the necessary purge (remove all memory even if used) functions */ +class node_singleton_memory_pool_purge_guard +{ +public: + node_singleton_memory_pool_purge_guard (); + +private: + nano::cleanup_guard cleanup_guard; +}; + template std::shared_ptr make_shared (Args &&... args) { diff --git a/nano/lib/network_filter.hpp b/nano/lib/network_filter.hpp index c9cca1030f..84cb65f412 100644 --- a/nano/lib/network_filter.hpp +++ b/nano/lib/network_filter.hpp @@ -1,6 +1,7 @@ #pragma once +#include #include #include diff --git a/nano/lib/numbers.cpp b/nano/lib/numbers.cpp index 916329b673..6c00712f7b 100644 --- a/nano/lib/numbers.cpp +++ b/nano/lib/numbers.cpp @@ -32,6 +32,26 @@ uint8_t account_decode (char value) } } +/* + * public_key + */ + +nano::public_key nano::public_key::from_account (std::string const & text) +{ + nano::public_key result; + bool error = result.decode_account (text); + release_assert (!error); + return result; +} + +nano::public_key nano::public_key::from_node_id (std::string const & text) +{ + nano::public_key result; + bool error = result.decode_node_id (text); + release_assert (!error); + return result; +} + void nano::public_key::encode_account (std::string & destination_a) const { debug_assert (destination_a.empty ()); @@ -138,6 +158,10 @@ bool nano::public_key::decode_account (std::string const & source_a) return error; } +/* + * uint256_union + */ + // Construct a uint256_union = AES_ENC_CTR (cleartext, key, iv) void nano::uint256_union::encrypt (nano::raw_key const & cleartext, nano::raw_key const & key, uint128_union const & iv) { diff --git a/nano/lib/numbers.hpp b/nano/lib/numbers.hpp index bf8137c657..716046001e 100644 --- a/nano/lib/numbers.hpp +++ b/nano/lib/numbers.hpp @@ -1,12 +1,15 @@ #pragma once -#include +#include + +#include #include #include #include #include #include +#include #include @@ -21,6 +24,9 @@ nano::uint128_t const Knano_ratio = nano::uint128_t ("10000000000000000000000000 nano::uint128_t const nano_ratio = nano::uint128_t ("1000000000000000000000000000000"); // 10^30 = 1 nano nano::uint128_t const raw_ratio = nano::uint128_t ("1"); // 10^0 +using bucket_index = uint64_t; +using priority_timestamp = uint64_t; // Priority within the bucket + class uint128_union { public: @@ -236,6 +242,18 @@ class public_key final : public uint256_union std::string to_node_id () const; std::string to_account () const; + /** + * Decode from account string + * @warning Aborts at runtime if the input is invalid + */ + static public_key from_account (std::string const &); + + /** + * Decode from node id string + * @warning Aborts at runtime if the input is invalid + */ + static public_key from_node_id (std::string const &); + public: // Keep operators inlined auto operator<=> (nano::public_key const & other) const { @@ -338,6 +356,12 @@ class link final : public hash_or_account public: using hash_or_account::hash_or_account; + explicit link (std::string_view str) + { + release_assert (str.size () <= bytes.size ()); + std::copy_n (str.data (), str.size (), bytes.begin ()); + } + public: // Keep operators inlined auto operator<=> (nano::link const & other) const { @@ -507,6 +531,33 @@ namespace difficulty uint64_t from_multiplier (double const, uint64_t const); double to_multiplier (uint64_t const, uint64_t const); } + +/** + * Add to or substract from a value without overflow + * TODO: C++26 replace with std::add_sat and std::sub_sat + */ +template +T add_sat (T const & value, T const & diff) noexcept +{ + static_assert (std::numeric_limits::is_specialized, "std::numeric_limits must be specialized"); + return (value > std::numeric_limits::max () - diff) ? std::numeric_limits::max () : value + diff; +} +template +T sub_sat (T const & value, T const & diff) noexcept +{ + static_assert (std::numeric_limits::is_specialized, "std::numeric_limits must be specialized"); + return (value < std::numeric_limits::min () + diff) ? std::numeric_limits::min () : value - diff; +} +template +T inc_sat (T const & value) noexcept +{ + return add_sat (value, static_cast (1)); +} +template +T dec_sat (T const & value) noexcept +{ + return sub_sat (value, static_cast (1)); +} } /* @@ -516,185 +567,53 @@ namespace difficulty namespace std { template <> -struct hash<::nano::uint128_union> -{ - size_t operator() (::nano::uint128_union const & value) const noexcept - { - return value.qwords[0] + value.qwords[1]; - } -}; +struct hash<::nano::uint128_union>; template <> -struct hash<::nano::uint256_union> -{ - size_t operator() (::nano::uint256_union const & value) const noexcept - { - return value.qwords[0] + value.qwords[1] + value.qwords[2] + value.qwords[3]; - } -}; +struct hash<::nano::uint256_union>; template <> -struct hash<::nano::public_key> -{ - size_t operator() (::nano::public_key const & value) const noexcept - { - return hash<::nano::uint256_union>{}(value); - } -}; +struct hash<::nano::public_key>; template <> -struct hash<::nano::block_hash> -{ - size_t operator() (::nano::block_hash const & value) const noexcept - { - return hash<::nano::uint256_union>{}(value); - } -}; +struct hash<::nano::block_hash>; template <> -struct hash<::nano::hash_or_account> -{ - size_t operator() (::nano::hash_or_account const & value) const noexcept - { - return hash<::nano::block_hash>{}(value.as_block_hash ()); - } -}; +struct hash<::nano::hash_or_account>; template <> -struct hash<::nano::root> -{ - size_t operator() (::nano::root const & value) const noexcept - { - return hash<::nano::hash_or_account>{}(value); - } -}; +struct hash<::nano::root>; template <> -struct hash<::nano::link> -{ - size_t operator() (::nano::link const & value) const noexcept - { - return hash<::nano::hash_or_account>{}(value); - } -}; +struct hash<::nano::link>; template <> -struct hash<::nano::raw_key> -{ - size_t operator() (::nano::raw_key const & value) const noexcept - { - return hash<::nano::uint256_union>{}(value); - } -}; +struct hash<::nano::raw_key>; template <> -struct hash<::nano::wallet_id> -{ - size_t operator() (::nano::wallet_id const & value) const noexcept - { - return hash<::nano::uint256_union>{}(value); - } -}; +struct hash<::nano::wallet_id>; template <> -struct hash<::nano::uint512_union> -{ - size_t operator() (::nano::uint512_union const & value) const noexcept - { - return hash<::nano::uint256_union>{}(value.uint256s[0]) + hash<::nano::uint256_union> () (value.uint256s[1]); - } -}; +struct hash<::nano::uint512_union>; template <> -struct hash<::nano::qualified_root> -{ - size_t operator() (::nano::qualified_root const & value) const noexcept - { - return hash<::nano::uint512_union>{}(value); - } -}; +struct hash<::nano::qualified_root>; } namespace boost { template <> -struct hash<::nano::uint128_union> -{ - size_t operator() (::nano::uint128_union const & value) const noexcept - { - return std::hash<::nano::uint128_union> () (value); - } -}; +struct hash<::nano::uint128_union>; template <> -struct hash<::nano::uint256_union> -{ - size_t operator() (::nano::uint256_union const & value) const noexcept - { - return std::hash<::nano::uint256_union> () (value); - } -}; +struct hash<::nano::uint256_union>; template <> -struct hash<::nano::public_key> -{ - size_t operator() (::nano::public_key const & value) const noexcept - { - return std::hash<::nano::public_key> () (value); - } -}; +struct hash<::nano::public_key>; template <> -struct hash<::nano::block_hash> -{ - size_t operator() (::nano::block_hash const & value) const noexcept - { - return std::hash<::nano::block_hash> () (value); - } -}; +struct hash<::nano::block_hash>; template <> -struct hash<::nano::hash_or_account> -{ - size_t operator() (::nano::hash_or_account const & value) const noexcept - { - return std::hash<::nano::hash_or_account> () (value); - } -}; +struct hash<::nano::hash_or_account>; template <> -struct hash<::nano::root> -{ - size_t operator() (::nano::root const & value) const noexcept - { - return std::hash<::nano::root> () (value); - } -}; +struct hash<::nano::root>; template <> -struct hash<::nano::link> -{ - size_t operator() (::nano::link const & value) const noexcept - { - return std::hash<::nano::link> () (value); - } -}; +struct hash<::nano::link>; template <> -struct hash<::nano::raw_key> -{ - size_t operator() (::nano::raw_key const & value) const noexcept - { - return std::hash<::nano::raw_key> () (value); - } -}; +struct hash<::nano::raw_key>; template <> -struct hash<::nano::wallet_id> -{ - size_t operator() (::nano::wallet_id const & value) const noexcept - { - return std::hash<::nano::wallet_id> () (value); - } -}; +struct hash<::nano::wallet_id>; template <> -struct hash<::nano::uint512_union> -{ - size_t operator() (::nano::uint512_union const & value) const noexcept - { - return std::hash<::nano::uint512_union> () (value); - } -}; +struct hash<::nano::uint512_union>; template <> -struct hash<::nano::qualified_root> -{ - size_t operator() (::nano::qualified_root const & value) const noexcept - { - return std::hash<::nano::qualified_root> () (value); - } -}; +struct hash<::nano::qualified_root>; } /* diff --git a/nano/lib/numbers_templ.hpp b/nano/lib/numbers_templ.hpp new file mode 100644 index 0000000000..d40de592a0 --- /dev/null +++ b/nano/lib/numbers_templ.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include + +#include + +namespace std +{ +template <> +struct hash<::nano::uint128_union> +{ + size_t operator() (::nano::uint128_union const & value) const noexcept + { + return value.qwords[0] + value.qwords[1]; + } +}; +template <> +struct hash<::nano::uint256_union> +{ + size_t operator() (::nano::uint256_union const & value) const noexcept + { + return value.qwords[0] + value.qwords[1] + value.qwords[2] + value.qwords[3]; + } +}; +template <> +struct hash<::nano::public_key> +{ + size_t operator() (::nano::public_key const & value) const noexcept + { + return hash<::nano::uint256_union>{}(value); + } +}; +template <> +struct hash<::nano::block_hash> +{ + size_t operator() (::nano::block_hash const & value) const noexcept + { + return hash<::nano::uint256_union>{}(value); + } +}; +template <> +struct hash<::nano::hash_or_account> +{ + size_t operator() (::nano::hash_or_account const & value) const noexcept + { + return hash<::nano::block_hash>{}(value.as_block_hash ()); + } +}; +template <> +struct hash<::nano::root> +{ + size_t operator() (::nano::root const & value) const noexcept + { + return hash<::nano::hash_or_account>{}(value); + } +}; +template <> +struct hash<::nano::link> +{ + size_t operator() (::nano::link const & value) const noexcept + { + return hash<::nano::hash_or_account>{}(value); + } +}; +template <> +struct hash<::nano::raw_key> +{ + size_t operator() (::nano::raw_key const & value) const noexcept + { + return hash<::nano::uint256_union>{}(value); + } +}; +template <> +struct hash<::nano::wallet_id> +{ + size_t operator() (::nano::wallet_id const & value) const noexcept + { + return hash<::nano::uint256_union>{}(value); + } +}; +template <> +struct hash<::nano::uint512_union> +{ + size_t operator() (::nano::uint512_union const & value) const noexcept + { + return hash<::nano::uint256_union>{}(value.uint256s[0]) + hash<::nano::uint256_union> () (value.uint256s[1]); + } +}; +template <> +struct hash<::nano::qualified_root> +{ + size_t operator() (::nano::qualified_root const & value) const noexcept + { + return hash<::nano::uint512_union>{}(value); + } +}; +} + +namespace boost +{ +template <> +struct hash<::nano::uint128_union> +{ + size_t operator() (::nano::uint128_union const & value) const noexcept + { + return std::hash<::nano::uint128_union> () (value); + } +}; +template <> +struct hash<::nano::uint256_union> +{ + size_t operator() (::nano::uint256_union const & value) const noexcept + { + return std::hash<::nano::uint256_union> () (value); + } +}; +template <> +struct hash<::nano::public_key> +{ + size_t operator() (::nano::public_key const & value) const noexcept + { + return std::hash<::nano::public_key> () (value); + } +}; +template <> +struct hash<::nano::block_hash> +{ + size_t operator() (::nano::block_hash const & value) const noexcept + { + return std::hash<::nano::block_hash> () (value); + } +}; +template <> +struct hash<::nano::hash_or_account> +{ + size_t operator() (::nano::hash_or_account const & value) const noexcept + { + return std::hash<::nano::hash_or_account> () (value); + } +}; +template <> +struct hash<::nano::root> +{ + size_t operator() (::nano::root const & value) const noexcept + { + return std::hash<::nano::root> () (value); + } +}; +template <> +struct hash<::nano::link> +{ + size_t operator() (::nano::link const & value) const noexcept + { + return std::hash<::nano::link> () (value); + } +}; +template <> +struct hash<::nano::raw_key> +{ + size_t operator() (::nano::raw_key const & value) const noexcept + { + return std::hash<::nano::raw_key> () (value); + } +}; +template <> +struct hash<::nano::wallet_id> +{ + size_t operator() (::nano::wallet_id const & value) const noexcept + { + return std::hash<::nano::wallet_id> () (value); + } +}; +template <> +struct hash<::nano::uint512_union> +{ + size_t operator() (::nano::uint512_union const & value) const noexcept + { + return std::hash<::nano::uint512_union> () (value); + } +}; +template <> +struct hash<::nano::qualified_root> +{ + size_t operator() (::nano::qualified_root const & value) const noexcept + { + return std::hash<::nano::qualified_root> () (value); + } +}; +} diff --git a/nano/lib/observer_set.hpp b/nano/lib/observer_set.hpp index cc4e02e9ca..5630515fa6 100644 --- a/nano/lib/observer_set.hpp +++ b/nano/lib/observer_set.hpp @@ -12,21 +12,25 @@ template class observer_set final { public: - void add (std::function const & observer_a) + using observer_type = std::function; + +public: + void add (observer_type observer) { nano::lock_guard lock{ mutex }; - observers.push_back (observer_a); + observers.push_back (observer); } - void notify (T... args) const + void notify (T const &... args) const { + // Make observers copy to allow parallel notifications nano::unique_lock lock{ mutex }; auto observers_copy = observers; lock.unlock (); - for (auto & i : observers_copy) + for (auto const & observer : observers_copy) { - i (args...); + observer (args...); } } @@ -53,7 +57,7 @@ class observer_set final private: mutable nano::mutex mutex{ mutex_identifier (mutexes::observer_set) }; - std::vector> observers; + std::vector observers; }; } diff --git a/nano/lib/optional_ptr.hpp b/nano/lib/optional_ptr.hpp index 8c5fdc2fe7..9939ebf30d 100644 --- a/nano/lib/optional_ptr.hpp +++ b/nano/lib/optional_ptr.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include #include diff --git a/nano/lib/plat/default/debugging.cpp b/nano/lib/plat/default/debugging.cpp index 1489fa80ac..3ca179f024 100644 --- a/nano/lib/plat/default/debugging.cpp +++ b/nano/lib/plat/default/debugging.cpp @@ -1,3 +1,4 @@ +#include #include void nano::create_load_memory_address_files () diff --git a/nano/lib/plat/linux/debugging.cpp b/nano/lib/plat/linux/debugging.cpp index caee1b910e..e9f4207a88 100644 --- a/nano/lib/plat/linux/debugging.cpp +++ b/nano/lib/plat/linux/debugging.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/lib/plat/posix/perms.cpp b/nano/lib/plat/posix/perms.cpp index 5512c4546b..2544451af6 100644 --- a/nano/lib/plat/posix/perms.cpp +++ b/nano/lib/plat/posix/perms.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/lib/plat/windows/perms.cpp b/nano/lib/plat/windows/perms.cpp index dc5122b7f2..ac2ff14840 100644 --- a/nano/lib/plat/windows/perms.cpp +++ b/nano/lib/plat/windows/perms.cpp @@ -1,3 +1,4 @@ +#include #include // clang-format off diff --git a/nano/lib/plat/windows/priority.cpp b/nano/lib/plat/windows/priority.cpp index e9945b0b0f..4a87451708 100644 --- a/nano/lib/plat/windows/priority.cpp +++ b/nano/lib/plat/windows/priority.cpp @@ -1,4 +1,5 @@ #include + namespace nano { void work_thread_reprioritize () diff --git a/nano/lib/random.hpp b/nano/lib/random.hpp index f2f35a7e2e..be4dde06a5 100644 --- a/nano/lib/random.hpp +++ b/nano/lib/random.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace nano @@ -28,4 +29,32 @@ class random_generator final std::random_device device; std::default_random_engine rng{ device () }; }; + +/** + * Not safe for any crypto related code, use for non-crypto PRNG only. + * Thread safe. + */ +class random_generator_mt final +{ +public: + /// Generate a random number in the range [min, max) + auto random (auto min, auto max) + { + release_assert (min < max); + std::lock_guard lock{ mutex }; + std::uniform_int_distribution dist (min, max - 1); + return dist (rng); + } + + /// Generate a random number in the range [0, max) + auto random (auto max) + { + return random (decltype (max){ 0 }, max); + } + +private: + std::random_device device; + std::default_random_engine rng{ device () }; + std::mutex mutex; +}; } \ No newline at end of file diff --git a/nano/lib/rate_limiting.cpp b/nano/lib/rate_limiting.cpp index 2a4715c9ab..ced8828cd1 100644 --- a/nano/lib/rate_limiting.cpp +++ b/nano/lib/rate_limiting.cpp @@ -45,11 +45,6 @@ void nano::rate::token_bucket::refill () } } -std::size_t nano::rate::token_bucket::largest_burst () const -{ - return max_token_count - smallest_size; -} - void nano::rate::token_bucket::reset (std::size_t max_token_count_a, std::size_t refill_rate_a) { // A token count of 0 indicates unlimited capacity. We use 1e9 as @@ -63,6 +58,16 @@ void nano::rate::token_bucket::reset (std::size_t max_token_count_a, std::size_t last_refill = std::chrono::steady_clock::now (); } +std::size_t nano::rate::token_bucket::largest_burst () const +{ + return max_token_count - smallest_size; +} + +std::size_t nano::rate::token_bucket::size () const +{ + return current_size; +} + /* * rate_limiter */ @@ -82,4 +87,10 @@ void nano::rate_limiter::reset (std::size_t limit_a, double burst_ratio_a) { nano::lock_guard guard{ mutex }; bucket.reset (static_cast (limit_a * burst_ratio_a), limit_a); +} + +std::size_t nano::rate_limiter::size () const +{ + nano::lock_guard guard{ mutex }; + return bucket.size (); } \ No newline at end of file diff --git a/nano/lib/rate_limiting.hpp b/nano/lib/rate_limiting.hpp index f672467872..3ba0f3b771 100644 --- a/nano/lib/rate_limiting.hpp +++ b/nano/lib/rate_limiting.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -36,12 +38,13 @@ class token_bucket */ bool try_consume (unsigned tokens_required = 1); - /** Returns the largest burst observed */ - std::size_t largest_burst () const; - /** Update the max_token_count and/or refill_rate_a parameters */ void reset (std::size_t max_token_count, std::size_t refill_rate); + /** Returns the largest burst observed */ + std::size_t largest_burst () const; + std::size_t size () const; + private: void refill (); @@ -64,13 +67,15 @@ class rate_limiter final { public: // initialize with limit 0 = unbounded - rate_limiter (std::size_t limit, double burst_ratio); + rate_limiter (std::size_t limit, double burst_ratio = 1.0); bool should_pass (std::size_t buffer_size); - void reset (std::size_t limit, double burst_ratio); + void reset (std::size_t limit, double burst_ratio = 1.0); + + std::size_t size () const; private: nano::rate::token_bucket bucket; mutable nano::mutex mutex; }; -} \ No newline at end of file +} diff --git a/nano/lib/rpcconfig.hpp b/nano/lib/rpcconfig.hpp index 230cbd197e..012764cee9 100644 --- a/nano/lib/rpcconfig.hpp +++ b/nano/lib/rpcconfig.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index 1e3889d180..429741d2f3 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -13,15 +13,12 @@ enum class type _invalid = 0, // Default value, should not be used test, - traffic_tcp, error, message, block, ledger, rollback, - bootstrap, network, - tcp_server, vote, vote_processor, vote_processor_tier, @@ -32,17 +29,26 @@ enum class type http_callback, ipc, tcp, + tcp_server, + tcp_channel, + tcp_channel_queued, + tcp_channel_send, + tcp_channel_drop, + tcp_channel_ec, + tcp_channel_wait, tcp_channels, tcp_channels_rejected, tcp_channels_purge, tcp_listener, tcp_listener_rejected, + traffic_tcp, + traffic_tcp_type, channel, socket, confirmation_height, confirmation_observer, confirming_set, - drop, + drop, // TODO: Rename to message_drop aggregator, requests, request_aggregator, @@ -54,21 +60,30 @@ enum class type vote_cache, vote_cache_processor, hinting, - blockprocessor, - blockprocessor_source, - blockprocessor_result, - blockprocessor_overfill, - bootstrap_ascending, - bootstrap_ascending_accounts, - bootstrap_ascending_verify, - bootstrap_ascending_process, - bootstrap_ascending_request, - bootstrap_ascending_reply, - bootstrap_ascending_next, + block_processor, + block_processor_source, + block_processor_result, + block_processor_overfill, + bootstrap, + bootstrap_verify, + bootstrap_verify_blocks, + bootstrap_verify_frontiers, + bootstrap_process, + bootstrap_request, + bootstrap_request_ec, + bootstrap_request_blocks, + bootstrap_reply, + bootstrap_next, + bootstrap_frontiers, + bootstrap_account_sets, + bootstrap_frontier_scan, + bootstrap_timeout, bootstrap_server, bootstrap_server_request, bootstrap_server_overfill, bootstrap_server_response, + bootstrap_server_send, + bootstrap_server_ec, active, active_elections, active_elections_started, @@ -78,6 +93,8 @@ enum class type active_elections_timeout, active_elections_cancelled, active_elections_cemented, + backlog_scan, + bounded_backlog, backlog, unchecked, election_scheduler, @@ -85,6 +102,7 @@ enum class type optimistic_scheduler, handshake, rep_crawler, + rep_crawler_ec, local_block_broadcaster, rep_tiers, syn_cookies, @@ -94,6 +112,7 @@ enum class type message_processor_overfill, message_processor_type, process_confirmed, + online_reps, _last // Must be the last enum }; @@ -118,6 +137,8 @@ enum class detail inserted, erased, request, + request_failed, + request_success, broadcast, cleanup, top, @@ -137,6 +158,14 @@ enum class detail empty, done, retry, + prioritized, + pending, + sync, + requeued, + evicted, + other, + drop, + queued, // processing queue queue, @@ -177,7 +206,7 @@ enum class detail representative_mismatch, block_position, - // blockprocessor + // block_processor process_blocking, process_blocking_timeout, force, @@ -190,6 +219,7 @@ enum class detail unchecked, local, forced, + election, // message specific not_a_type, @@ -284,12 +314,25 @@ enum class detail loop_reachout, loop_reachout_cached, merge_peer, + merge_peer_failed, reachout_live, reachout_cached, + connected, + + // traffic type + generic, + bootstrap_server, + bootstrap_requests, + block_broadcast, + block_broadcast_initial, + block_broadcast_rpc, + confirmation_requests, + vote_rebroadcast, + vote_reply, + rep_crawler, + telemetry, // tcp - tcp_write_drop, - tcp_write_no_socket_drop, tcp_silent_connection_drop, tcp_io_timeout_drop, tcp_connect_error, @@ -316,6 +359,10 @@ enum class detail attempt_timeout, not_a_peer, + // tcp_channel + wait_socket, + wait_bandwidth, + // tcp_channels channel_accepted, channel_rejected, @@ -403,10 +450,13 @@ enum class detail activate_failed, activate_skip, activate_full, + scanned, // active insert, insert_failed, + transition_priority, + transition_priority_failed, election_cleanup, // active_elections @@ -430,7 +480,7 @@ enum class detail missing_cookie, invalid_genesis, - // bootstrap ascending + // bootstrap missing_tag, reply, throttled, @@ -438,43 +488,65 @@ enum class detail timeout, nothing_new, account_info_empty, + frontiers_empty, loop_database, loop_dependencies, + loop_frontiers, + loop_frontiers_processing, duplicate_request, invalid_response_type, + invalid_response, timestamp_reset, + processing_frontiers, + frontiers_dropped, + sync_accounts, - // bootstrap ascending accounts prioritize, prioritize_failed, block, + block_failed, unblock, unblock_failed, dependency_update, dependency_update_failed, + done_range, + done_empty, + next_by_requests, + next_by_timestamp, + advance, + advance_failed, + next_none, next_priority, next_database, next_blocking, next_dependency, + next_frontier, blocking_insert, - blocking_erase_overflow, + blocking_overflow, priority_insert, - priority_erase_by_threshold, - priority_erase_by_blocking, - priority_erase_overflow, + priority_set, + priority_unblocked, + erase_by_threshold, + erase_by_blocking, + priority_overflow, deprioritize, deprioritize_failed, sync_dependencies, + dependency_synced, request_blocks, request_account_info, + safe, + base, + // active started_hinted, started_optimistic, + // rep_crawler channel_dead, query_target_failed, @@ -506,6 +578,7 @@ enum class detail cementing, cemented_hash, cementing_failed, + deferred_failed, // election_state passive, @@ -529,6 +602,29 @@ enum class detail blocks_by_account, account_info_by_hash, + // bounded backlog, + gathered_targets, + performing_rollbacks, + no_targets, + rollback_missing_block, + rollback_skipped, + loop_scan, + + // online_reps + trim_trend, + sanitize_old, + sanitize_future, + sample, + rep_new, + rep_update, + update_online, + + // error codes + no_buffer_space, + timed_out, + host_unreachable, + not_supported, + _last // Must be the last enum }; diff --git a/nano/lib/thread_roles.cpp b/nano/lib/thread_roles.cpp index 76f467cd0e..ccb34be3bb 100644 --- a/nano/lib/thread_roles.cpp +++ b/nano/lib/thread_roles.cpp @@ -22,6 +22,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::io_daemon: thread_role_name_string = "I/O (daemon)"; break; + case nano::thread_role::name::io_ipc: + thread_role_name_string = "I/O (IPC)"; + break; case nano::thread_role::name::work: thread_role_name_string = "Work pool"; break; @@ -37,6 +40,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::block_processing: thread_role_name_string = "Blck processing"; break; + case nano::thread_role::name::block_processing_notifications: + thread_role_name_string = "Blck proc notif"; + break; case nano::thread_role::name::request_loop: thread_role_name_string = "Request loop"; break; @@ -70,9 +76,6 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::worker: thread_role_name_string = "Worker"; break; - case nano::thread_role::name::bootstrap_worker: - thread_role_name_string = "Bootstrap work"; - break; case nano::thread_role::name::wallet_worker: thread_role_name_string = "Wallet work"; break; @@ -94,14 +97,38 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::unchecked: thread_role_name_string = "Unchecked"; break; - case nano::thread_role::name::backlog_population: - thread_role_name_string = "Backlog"; + case nano::thread_role::name::backlog_scan: + thread_role_name_string = "Backlog scan"; + break; + case nano::thread_role::name::bounded_backlog: + thread_role_name_string = "Bounded backlog"; + break; + case nano::thread_role::name::bounded_backlog_scan: + thread_role_name_string = "Bounded b scan"; + break; + case nano::thread_role::name::bounded_backlog_notifications: + thread_role_name_string = "Bounded b notif"; break; case nano::thread_role::name::vote_generator_queue: thread_role_name_string = "Voting que"; break; - case nano::thread_role::name::ascending_bootstrap: - thread_role_name_string = "Bootstrap asc"; + case nano::thread_role::name::bootstrap: + thread_role_name_string = "Bootstrap"; + break; + case nano::thread_role::name::bootstrap_database_scan: + thread_role_name_string = "Bootstrap db"; + break; + case nano::thread_role::name::bootstrap_dependency_walker: + thread_role_name_string = "Bootstrap walkr"; + break; + case nano::thread_role::name::bootstrap_frontier_scan: + thread_role_name_string = "Bootstrap front"; + break; + case nano::thread_role::name::bootstrap_cleanup: + thread_role_name_string = "Bootstrap clean"; + break; + case nano::thread_role::name::bootstrap_worker: + thread_role_name_string = "Bootstrap work"; break; case nano::thread_role::name::bootstrap_server: thread_role_name_string = "Bootstrap serv"; @@ -157,6 +184,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::vote_router: thread_role_name_string = "Vote router"; break; + case nano::thread_role::name::online_reps: + thread_role_name_string = "Online reps"; + break; case nano::thread_role::name::monitor: thread_role_name_string = "Monitor"; break; @@ -191,9 +221,12 @@ std::string nano::thread_role::get_string () void nano::thread_role::set (nano::thread_role::name role) { - auto thread_role_name_string (get_string (role)); - - nano::thread_role::set_os_name (thread_role_name_string); - + auto thread_role_name_string = get_string (role); + nano::thread_role::set_os_name (thread_role_name_string); // Implementation is platform specific current_thread_role = role; } + +bool nano::thread_role::is_network_io () +{ + return nano::thread_role::get () == nano::thread_role::name::io; +} \ No newline at end of file diff --git a/nano/lib/thread_roles.hpp b/nano/lib/thread_roles.hpp index 5896318c42..66f7455d3e 100644 --- a/nano/lib/thread_roles.hpp +++ b/nano/lib/thread_roles.hpp @@ -12,11 +12,13 @@ enum class name unknown, io, io_daemon, + io_ipc, work, message_processing, vote_processing, vote_cache_processing, block_processing, + block_processing_notifications, request_loop, wallet_actions, bootstrap_initiator, @@ -28,7 +30,6 @@ enum class name confirmation_height, confirmation_height_notifications, worker, - bootstrap_worker, wallet_worker, election_worker, request_aggregator, @@ -36,13 +37,19 @@ enum class name epoch_upgrader, db_parallel_traversal, unchecked, - backlog_population, + backlog_scan, + bounded_backlog, + bounded_backlog_scan, + bounded_backlog_notifications, vote_generator_queue, - bootstrap_server, telemetry, - ascending_bootstrap, - bootstrap_server_requests, - bootstrap_server_responses, + bootstrap, + bootstrap_database_scan, + bootstrap_dependency_walker, + bootstrap_frontier_scan, + bootstrap_cleanup, + bootstrap_worker, + bootstrap_server, scheduler_hinted, scheduler_manual, scheduler_optimistic, @@ -59,6 +66,7 @@ enum class name port_mapping, stats, vote_router, + online_reps, monitor, }; @@ -84,4 +92,9 @@ std::string get_string (); * Internal only, should not be called directly */ void set_os_name (std::string const &); + +/* + * Check if the current thread is a network IO thread + */ +bool is_network_io (); } diff --git a/nano/lib/thread_runner.cpp b/nano/lib/thread_runner.cpp index 576504b0d7..bd9ab6ddf7 100644 --- a/nano/lib/thread_runner.cpp +++ b/nano/lib/thread_runner.cpp @@ -60,16 +60,18 @@ void nano::thread_runner::join () { io_guard.reset (); - for (auto & i : threads) + for (auto & thread : threads) { - if (i.joinable ()) + if (thread.joinable ()) { - i.join (); + logger.debug (nano::log::type::thread_runner, "Joining thread: {}", fmt::streamed (thread.get_id ())); + thread.join (); } } + threads.clear (); - logger.debug (nano::log::type::thread_runner, "Stopped threads ({})", to_string (role)); + logger.debug (nano::log::type::thread_runner, "Stopped all threads ({})", to_string (role)); io_ctx.reset (); // Release shared_ptr to io_context } diff --git a/nano/lib/thread_runner.hpp b/nano/lib/thread_runner.hpp index 7ad2004793..ec5befaf5f 100644 --- a/nano/lib/thread_runner.hpp +++ b/nano/lib/thread_runner.hpp @@ -18,11 +18,11 @@ class thread_runner final thread_runner (std::shared_ptr, nano::logger &, unsigned num_threads = nano::hardware_concurrency (), nano::thread_role::name thread_role = nano::thread_role::name::io); ~thread_runner (); - /** Wait for IO threads to complete */ + // Wait for IO threads to complete void join (); - /** Tells the IO context to stop processing events. - * NOTE: This shouldn't really be used, node should stop gracefully by cancelling any outstanding async operations and calling join() */ + // Tells the IO context to stop processing events. + // TODO: Ideally this shouldn't be needed, node should stop gracefully by cancelling any outstanding async operations and calling join() void abort (); private: diff --git a/nano/lib/tomlconfig.cpp b/nano/lib/tomlconfig.cpp index ff998dbab9..4922d67fcf 100644 --- a/nano/lib/tomlconfig.cpp +++ b/nano/lib/tomlconfig.cpp @@ -1,4 +1,5 @@ #include +#include #include nano::tomlconfig::tomlconfig () : diff --git a/nano/lib/tomlconfig.hpp b/nano/lib/tomlconfig.hpp index a56524c021..bf32806cdd 100644 --- a/nano/lib/tomlconfig.hpp +++ b/nano/lib/tomlconfig.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include namespace boost diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index d33a7e765d..9fa56e9589 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -1,133 +1,7 @@ -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#ifndef _WIN32 -#include -#endif - -std::size_t nano::get_file_descriptor_limit () -{ - std::size_t fd_limit = std::numeric_limits::max (); -#ifndef _WIN32 - rlimit limit{}; - if (getrlimit (RLIMIT_NOFILE, &limit) == 0) - { - fd_limit = static_cast (limit.rlim_cur); - } -#endif - return fd_limit; -} - -void nano::set_file_descriptor_limit (std::size_t limit) -{ -#ifndef _WIN32 - rlimit fd_limit{}; - if (-1 == getrlimit (RLIMIT_NOFILE, &fd_limit)) - { - std::cerr << "WARNING: Unable to get current limits for the number of open file descriptors: " << std::strerror (errno); - return; - } - - if (fd_limit.rlim_cur >= limit) - { - return; - } - - fd_limit.rlim_cur = std::min (static_cast (limit), fd_limit.rlim_max); - if (-1 == setrlimit (RLIMIT_NOFILE, &fd_limit)) - { - std::cerr << "WARNING: Unable to set limits for the number of open file descriptors: " << std::strerror (errno); - return; - } -#endif -} - -void nano::initialize_file_descriptor_limit () -{ - nano::set_file_descriptor_limit (DEFAULT_FILE_DESCRIPTOR_LIMIT); - auto limit = nano::get_file_descriptor_limit (); - if (limit < DEFAULT_FILE_DESCRIPTOR_LIMIT) - { - std::cerr << "WARNING: Current file descriptor limit of " << limit << " is lower than the " << DEFAULT_FILE_DESCRIPTOR_LIMIT << " recommended. Node was unable to change it." << std::endl; - } -} - -void nano::remove_all_files_in_dir (std::filesystem::path const & dir) -{ - for (auto & p : std::filesystem::directory_iterator (dir)) - { - auto path = p.path (); - if (std::filesystem::is_regular_file (path)) - { - std::filesystem::remove (path); - } - } -} - -void nano::move_all_files_to_dir (std::filesystem::path const & from, std::filesystem::path const & to) -{ - for (auto & p : std::filesystem::directory_iterator (from)) - { - auto path = p.path (); - if (std::filesystem::is_regular_file (path)) - { - std::filesystem::rename (path, to / path.filename ()); - } - } -} - -/* - * Backing code for "release_assert" & "debug_assert", which are macros - */ -void assert_internal (char const * check_expr, char const * func, char const * file, unsigned int line, bool is_release_assert, std::string_view error_msg) -{ - std::cerr << "Assertion (" << check_expr << ") failed\n" - << func << "\n" - << file << ":" << line << "\n"; - if (!error_msg.empty ()) - { - std::cerr << "Error: " << error_msg << "\n"; - } - std::cerr << "\n"; - - // Output stack trace to cerr - auto backtrace_str = nano::generate_stacktrace (); - std::cerr << backtrace_str << std::endl; - - // "abort" at the end of this function will go into any signal handlers (the daemon ones will generate a stack trace and load memory address files on non-Windows systems). - // As there is no async-signal-safe way to generate stacktraces on Windows it must be done before aborting -#ifdef _WIN32 - { - // Try construct the stacktrace dump in the same folder as the running executable, otherwise use the current directory. - boost::system::error_code err; - auto running_executable_filepath = boost::dll::program_location (err); - std::string filename = is_release_assert ? "nano_node_backtrace_release_assert.txt" : "nano_node_backtrace_assert.txt"; - std::string filepath = filename; - if (!err) - { - filepath = (running_executable_filepath.parent_path () / filename).string (); - } - - std::ofstream file (filepath); - nano::set_secure_perm_file (filepath); - file << backtrace_str; - } -#endif - - abort (); -} - // Issue #3748 void nano::sort_options_description (const boost::program_options::options_description & source, boost::program_options::options_description & target) { diff --git a/nano/lib/utility.hpp b/nano/lib/utility.hpp index d8bb29bec1..ef31b01e35 100644 --- a/nano/lib/utility.hpp +++ b/nano/lib/utility.hpp @@ -1,17 +1,12 @@ #pragma once +#include #include #include -#include #include -#include -#include -#include -#include #include -#include #include #include @@ -28,79 +23,11 @@ namespace program_options } } -[[noreturn]] void assert_internal (char const * check_expr, char const * func, char const * file, unsigned int line, bool is_release_assert, std::string_view error = ""); - -#define release_assert_1(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, true) -#define release_assert_2(check, error_msg) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, true, error_msg) -#if !BOOST_PP_VARIADICS_MSVC -#define release_assert(...) \ - BOOST_PP_OVERLOAD (release_assert_, __VA_ARGS__) \ - (__VA_ARGS__) -#else -#define release_assert(...) BOOST_PP_CAT (BOOST_PP_OVERLOAD (release_assert_, __VA_ARGS__) (__VA_ARGS__), BOOST_PP_EMPTY ()) -#endif - -#ifdef NDEBUG -#define debug_assert(...) (void)0 -#else -#define debug_assert_1(check) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, false) -#define debug_assert_2(check, error_msg) check ? (void)0 : assert_internal (#check, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, false, error_msg) -#if !BOOST_PP_VARIADICS_MSVC -#define debug_assert(...) \ - BOOST_PP_OVERLOAD (debug_assert_, __VA_ARGS__) \ - (__VA_ARGS__) -#else -#define debug_assert(...) BOOST_PP_CAT (BOOST_PP_OVERLOAD (debug_assert_, __VA_ARGS__) (__VA_ARGS__), BOOST_PP_EMPTY ()) -#endif -#endif - namespace nano { // Lower priority of calling work generating thread void work_thread_reprioritize (); -/* - * Functions for managing filesystem permissions, platform specific - */ -void set_umask (); -void set_secure_perm_directory (std::filesystem::path const & path); -void set_secure_perm_directory (std::filesystem::path const & path, std::error_code & ec); -void set_secure_perm_file (std::filesystem::path const & path); -void set_secure_perm_file (std::filesystem::path const & path, std::error_code & ec); - -/* - * Function to check if running Windows as an administrator - */ -bool is_windows_elevated (); - -/* - * Function to check if the Windows Event log registry key exists - */ -bool event_log_reg_entry_exists (); - -/* - * Create the load memory addresses for the executable and shared libraries. - */ -void create_load_memory_address_files (); - -/** - * Some systems, especially in virtualized environments, may have very low file descriptor limits, - * causing the node to fail. This function attempts to query the limit and returns the value. If the - * limit cannot be queried, or running on a Windows system, this returns max-value of std::size_t. - * Increasing the limit programmatically can be done only for the soft limit, the hard one requiring - * super user permissions to modify. - */ -std::size_t get_file_descriptor_limit (); -void set_file_descriptor_limit (std::size_t limit); -/** - * This should be called from entry points. It sets the file descriptor limit to the maximum allowed and logs any errors. - */ -constexpr std::size_t DEFAULT_FILE_DESCRIPTOR_LIMIT = 16384; -void initialize_file_descriptor_limit (); - -void remove_all_files_in_dir (std::filesystem::path const & dir); -void move_all_files_to_dir (std::filesystem::path const & from, std::filesystem::path const & to); - template void transform_if (InputIt first, InputIt last, OutputIt dest, Pred pred, Func transform) { diff --git a/nano/lib/work.cpp b/nano/lib/work.cpp index 7ee17cd1f9..1785e146f0 100644 --- a/nano/lib/work.cpp +++ b/nano/lib/work.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -41,7 +43,7 @@ nano::work_pool::work_pool (nano::network_constants & network_constants, unsigne } for (auto i (0u); i < count; ++i) { - threads.emplace_back (nano::thread_attributes::get_default (), [this, i] () { + threads.emplace_back ([this, i] () { nano::thread_role::set (nano::thread_role::name::work); nano::work_thread_reprioritize (); loop (i); @@ -240,4 +242,4 @@ nano::container_info nano::work_pool::container_info () const info.put ("pending", pending); info.add ("work_observers", work_observers.container_info ()); return info; -} \ No newline at end of file +} diff --git a/nano/lib/work.hpp b/nano/lib/work.hpp index 1640648934..0336d10a09 100644 --- a/nano/lib/work.hpp +++ b/nano/lib/work.hpp @@ -8,10 +8,10 @@ #include #include -#include #include #include +#include namespace nano { @@ -54,7 +54,7 @@ class work_pool final nano::network_constants & network_constants; std::atomic ticket; bool done; - std::vector threads; + std::vector threads; std::list pending; mutable nano::mutex mutex{ mutex_identifier (mutexes::work_pool) }; nano::condition_variable producer_condition; diff --git a/nano/lib/work_version.hpp b/nano/lib/work_version.hpp new file mode 100644 index 0000000000..fb24b82d32 --- /dev/null +++ b/nano/lib/work_version.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace nano +{ +enum class work_version +{ + unspecified, + work_1 +}; +} diff --git a/nano/load_test/entry.cpp b/nano/load_test/entry.cpp index aaf62fc0a8..cf7362c9d2 100644 --- a/nano/load_test/entry.cpp +++ b/nano/load_test/entry.cpp @@ -580,7 +580,7 @@ int main (int argc, char * const * argv) data_paths.push_back (std::move (data_path)); } - auto current_network = nano::dev::network_params.network.get_current_network_as_string (); + std::string current_network{ nano::dev::network_params.network.get_current_network_as_string () }; std::vector> nodes; std::vector> rpc_servers; for (auto const & data_path : data_paths) diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index 45eaea5509..8275752432 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -169,8 +170,7 @@ void nano::daemon::run (std::filesystem::path const & data_path, nano::node_flag throw std::runtime_error (std::string ("RPC is configured to spawn a new process however the file cannot be found at: ") + config.rpc.child_process.rpc_path); } - auto network = node->network_params.network.get_current_network_as_string (); - + std::string network{ node->network_params.network.get_current_network_as_string () }; rpc_process = std::make_unique (config.rpc.child_process.rpc_path, "--daemon", "--data_path", data_path.string (), "--network", network); } debug_assert (rpc || rpc_process); diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 37c2786bf2..b096cc7eaa 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -1,8 +1,11 @@ #include +#include #include #include +#include #include #include +#include #include #include #include @@ -12,9 +15,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -169,7 +174,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - std::cerr << nano::network_constants::active_network_err_msg << std::endl; + std::cerr << "Invalid network. Valid values are live, test, beta and dev." << std::endl; std::exit (1); } } diff --git a/nano/nano_rpc/entry.cpp b/nano/nano_rpc/entry.cpp index c54b335869..1afb4b3c9b 100644 --- a/nano/nano_rpc/entry.cpp +++ b/nano/nano_rpc/entry.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -117,7 +118,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - std::cerr << nano::network_constants::active_network_err_msg << std::endl; + std::cerr << "Invalid network. Valid values are live, test, beta and dev." << std::endl; std::exit (1); } } diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index 4f149aa51d..195ac64f8d 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -191,7 +192,7 @@ class wallet_daemon final throw std::runtime_error (std::string ("RPC is configured to spawn a new process however the file cannot be found at: ") + config.rpc.child_process.rpc_path); } - auto network = node->network_params.network.get_current_network_as_string (); + std::string network{ node->network_params.network.get_current_network_as_string () }; rpc_process = std::make_unique (config.rpc.child_process.rpc_path, "--daemon", "--data_path", data_path.string (), "--network", network); } } @@ -280,7 +281,7 @@ int main (int argc, char * const * argv) auto err (nano::network_constants::set_active_network (network->second.as ())); if (err) { - daemon.show_error (nano::network_constants::active_network_err_msg); + daemon.show_error ("Invalid network. Valid values are live, test, beta and dev."); std::exit (1); } } diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 419e00a72c..53241a3c9d 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -16,49 +16,37 @@ add_library( ${platform_sources} active_elections.hpp active_elections.cpp - backlog_population.hpp - backlog_population.cpp + backlog_scan.hpp + backlog_scan.cpp bandwidth_limiter.hpp bandwidth_limiter.cpp - blockprocessor.hpp - blockprocessor.cpp - bootstrap/block_deserializer.hpp - bootstrap/block_deserializer.cpp - bootstrap/bootstrap_attempt.hpp - bootstrap/bootstrap_attempt.cpp - bootstrap/bootstrap_bulk_pull.hpp - bootstrap/bootstrap_bulk_pull.cpp - bootstrap/bootstrap_bulk_push.hpp - bootstrap/bootstrap_bulk_push.cpp + block_processor.hpp + block_processor.cpp + bucketing.hpp + bucketing.cpp + bounded_backlog.hpp + bounded_backlog.cpp + bootstrap_weights_beta.hpp + bootstrap_weights_live.hpp + bootstrap/account_sets.hpp + bootstrap/account_sets.cpp bootstrap/bootstrap_config.hpp bootstrap/bootstrap_config.cpp - bootstrap/bootstrap_connections.hpp - bootstrap/bootstrap_connections.cpp - bootstrap/bootstrap_frontier.hpp - bootstrap/bootstrap_frontier.cpp - bootstrap/bootstrap_lazy.hpp - bootstrap/bootstrap_lazy.cpp - bootstrap/bootstrap_legacy.hpp - bootstrap/bootstrap_legacy.cpp - bootstrap/bootstrap.hpp - bootstrap/bootstrap.cpp bootstrap/bootstrap_server.hpp bootstrap/bootstrap_server.cpp - bootstrap_ascending/common.hpp - bootstrap_ascending/throttle.hpp - bootstrap_ascending/throttle.cpp - bootstrap_ascending/account_sets.hpp - bootstrap_ascending/account_sets.cpp - bootstrap_ascending/database_scan.hpp - bootstrap_ascending/database_scan.cpp - bootstrap_ascending/peer_scoring.hpp - bootstrap_ascending/peer_scoring.cpp - bootstrap_ascending/service.hpp - bootstrap_ascending/service.cpp + bootstrap/bootstrap_service.hpp + bootstrap/bootstrap_service.cpp + bootstrap/common.hpp + bootstrap/throttle.hpp + bootstrap/throttle.cpp + bootstrap/database_scan.hpp + bootstrap/database_scan.cpp + bootstrap/frontier_scan.hpp + bootstrap/frontier_scan.cpp + bootstrap/peer_scoring.hpp + bootstrap/peer_scoring.cpp cli.hpp cli.cpp - common.hpp - common.cpp confirming_set.hpp confirming_set.cpp confirmation_solicitor.hpp @@ -74,9 +62,13 @@ add_library( election_behavior.hpp election_insertion_result.hpp election_status.hpp + endpoint.cpp + endpoint.hpp + endpoint_templ.hpp epoch_upgrader.hpp epoch_upgrader.cpp fair_queue.hpp + fwd.hpp ipc/action_handler.hpp ipc/action_handler.cpp ipc/flatbuffers_handler.hpp @@ -157,24 +149,31 @@ add_library( scheduler/priority.cpp telemetry.hpp telemetry.cpp + transport/block_deserializer.hpp + transport/block_deserializer.cpp transport/channel.hpp transport/channel.cpp transport/tcp_channel.hpp transport/tcp_channel.cpp transport/fake.hpp transport/fake.cpp + transport/fwd.hpp transport/inproc.hpp transport/inproc.cpp transport/message_deserializer.hpp transport/message_deserializer.cpp transport/tcp_channels.hpp transport/tcp_channels.cpp + transport/tcp_config.hpp + transport/tcp_config.cpp transport/tcp_listener.hpp transport/tcp_listener.cpp transport/tcp_server.hpp transport/tcp_server.cpp transport/tcp_socket.hpp transport/tcp_socket.cpp + transport/traffic_type.hpp + transport/traffic_type.cpp transport/transport.hpp transport/transport.cpp unchecked_map.cpp diff --git a/nano/node/active_elections.cpp b/nano/node/active_elections.cpp index 4d8c953f56..b6dc0bd648 100644 --- a/nano/node/active_elections.cpp +++ b/nano/node/active_elections.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +64,17 @@ nano::active_elections::active_elections (nano::node & node_a, nano::confirming_ } } }); + + // Stop all rolled back active transactions except initial + block_processor.rolled_back.add ([this] (auto const & blocks, auto const & rollback_root) { + for (auto const & block : blocks) + { + if (block->qualified_root () != rollback_root) + { + erase (block->qualified_root ()); + } + } + }); } nano::active_elections::~active_elections () @@ -393,6 +406,7 @@ nano::election_insertion_result nano::active_elections::insert (std::shared_ptr< { result.inserted = true; auto observe_rep_cb = [&node = node] (auto const & rep_a) { + // TODO: Is this neccessary? Move this outside of the election class // Representative is defined as online if replying to live votes or rep_crawler queries node.online_reps.observe (rep_a); }; @@ -404,6 +418,14 @@ nano::election_insertion_result nano::active_elections::insert (std::shared_ptr< debug_assert (count_by_behavior[result.election->behavior ()] >= 0); count_by_behavior[result.election->behavior ()]++; + // Skip passive phase for blocks without cached votes to avoid bootstrap delays + bool active_immediately = false; + if (node.vote_cache.contains (hash)) + { + result.election->transition_active (); + active_immediately = true; + } + node.stats.inc (nano::stat::type::active_elections, nano::stat::detail::started); node.stats.inc (nano::stat::type::active_elections_started, to_stat_detail (election_behavior_a)); @@ -411,9 +433,10 @@ nano::election_insertion_result nano::active_elections::insert (std::shared_ptr< nano::log::arg{ "behavior", election_behavior_a }, nano::log::arg{ "election", result.election }); - node.logger.debug (nano::log::type::active_elections, "Started new election for block: {} (behavior: {})", + node.logger.debug (nano::log::type::active_elections, "Started new election for block: {} (behavior: {}, active immediately: {})", hash.to_string (), - to_string (election_behavior_a)); + to_string (election_behavior_a), + active_immediately); } else { @@ -423,6 +446,23 @@ nano::election_insertion_result nano::active_elections::insert (std::shared_ptr< else { result.election = existing->election; + + // Upgrade to priority election to enable immediate vote broadcasting. + auto previous_behavior = result.election->behavior (); + if (election_behavior_a == nano::election_behavior::priority && previous_behavior != nano::election_behavior::priority) + { + bool transitioned = result.election->transition_priority (); + if (transitioned) + { + count_by_behavior[previous_behavior]--; + count_by_behavior[election_behavior_a]++; + node.stats.inc (nano::stat::type::active_elections, nano::stat::detail::transition_priority); + } + else + { + node.stats.inc (nano::stat::type::active_elections, nano::stat::detail::transition_priority_failed); + } + } } lock.unlock (); @@ -624,4 +664,4 @@ nano::stat::type nano::to_stat_type (nano::election_state state) nano::stat::detail nano::to_stat_detail (nano::election_status_type type) { return nano::enum_util::cast (type); -} \ No newline at end of file +} diff --git a/nano/node/backlog_population.cpp b/nano/node/backlog_population.cpp deleted file mode 100644 index 284b7c599e..0000000000 --- a/nano/node/backlog_population.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -nano::backlog_population::backlog_population (backlog_population_config const & config_a, nano::scheduler::component & schedulers, nano::ledger & ledger, nano::stats & stats_a) : - config{ config_a }, - schedulers{ schedulers }, - ledger{ ledger }, - stats{ stats_a } -{ -} - -nano::backlog_population::~backlog_population () -{ - // Thread must be stopped before destruction - debug_assert (!thread.joinable ()); -} - -void nano::backlog_population::start () -{ - debug_assert (!thread.joinable ()); - - thread = std::thread{ [this] () { - nano::thread_role::set (nano::thread_role::name::backlog_population); - run (); - } }; -} - -void nano::backlog_population::stop () -{ - { - nano::lock_guard lock{ mutex }; - stopped = true; - } - notify (); - nano::join_or_pass (thread); -} - -void nano::backlog_population::trigger () -{ - { - nano::unique_lock lock{ mutex }; - triggered = true; - } - notify (); -} - -void nano::backlog_population::notify () -{ - condition.notify_all (); -} - -bool nano::backlog_population::predicate () const -{ - return triggered || config.enable; -} - -void nano::backlog_population::run () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - if (predicate ()) - { - stats.inc (nano::stat::type::backlog, nano::stat::detail::loop); - - triggered = false; - populate_backlog (lock); - } - - condition.wait (lock, [this] () { - return stopped || predicate (); - }); - } -} - -void nano::backlog_population::populate_backlog (nano::unique_lock & lock) -{ - debug_assert (config.frequency > 0); - - const auto chunk_size = config.batch_size / config.frequency; - auto done = false; - nano::account next = 0; - uint64_t total = 0; - while (!stopped && !done) - { - lock.unlock (); - - { - auto transaction = ledger.tx_begin_read (); - - auto it = ledger.store.account.begin (transaction, next); - auto const end = ledger.store.account.end (transaction); - - auto should_refresh = [&transaction] () { - auto cutoff = std::chrono::steady_clock::now () - 100ms; // TODO: Make this configurable - return transaction.timestamp () < cutoff; - }; - - for (size_t count = 0; it != end && count < chunk_size && !should_refresh (); ++it, ++count, ++total) - { - stats.inc (nano::stat::type::backlog, nano::stat::detail::total); - - auto const & account = it->first; - auto const & account_info = it->second; - - activate (transaction, account, account_info); - - next = account.number () + 1; - } - - done = ledger.store.account.begin (transaction, next) == end; - } - - lock.lock (); - - // Give the rest of the node time to progress without holding database lock - condition.wait_for (lock, std::chrono::milliseconds{ 1000 / config.frequency }); - } -} - -void nano::backlog_population::activate (secure::transaction const & transaction, nano::account const & account, nano::account_info const & account_info) -{ - auto const maybe_conf_info = ledger.store.confirmation_height.get (transaction, account); - auto const conf_info = maybe_conf_info.value_or (nano::confirmation_height_info{}); - - // If conf info is empty then it means then it means nothing is confirmed yet - if (conf_info.height < account_info.block_count) - { - stats.inc (nano::stat::type::backlog, nano::stat::detail::activated); - - activate_callback.notify (transaction, account); - - schedulers.optimistic.activate (account, account_info, conf_info); - schedulers.priority.activate (transaction, account, account_info, conf_info); - } -} - -/* - * backlog_population_config - */ - -nano::error nano::backlog_population_config::serialize (nano::tomlconfig & toml) const -{ - toml.put ("enable", enable, "Control if ongoing backlog population is enabled. If not, backlog population can still be triggered by RPC \ntype:bool"); - toml.put ("batch_size", batch_size, "Number of accounts per second to process when doing backlog population scan. Increasing this value will help unconfirmed frontiers get into election prioritization queue faster, however it will also increase resource usage. \ntype:uint"); - toml.put ("frequency", frequency, "Backlog scan divides the scan into smaller batches, number of which is controlled by this value. Higher frequency helps to utilize resources more uniformly, however it also introduces more overhead. The resulting number of accounts per single batch is `backlog_scan_batch_size / backlog_scan_frequency` \ntype:uint"); - - return toml.get_error (); -} - -nano::error nano::backlog_population_config::deserialize (nano::tomlconfig & toml) -{ - toml.get ("enable", enable); - toml.get ("batch_size", batch_size); - toml.get ("frequency", frequency); - - return toml.get_error (); -} diff --git a/nano/node/backlog_scan.cpp b/nano/node/backlog_scan.cpp new file mode 100644 index 0000000000..c725c04b1f --- /dev/null +++ b/nano/node/backlog_scan.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +nano::backlog_scan::backlog_scan (backlog_scan_config const & config_a, nano::ledger & ledger_a, nano::stats & stats_a) : + config{ config_a }, + ledger{ ledger_a }, + stats{ stats_a }, + limiter{ config.rate_limit } +{ +} + +nano::backlog_scan::~backlog_scan () +{ + // Thread must be stopped before destruction + debug_assert (!thread.joinable ()); +} + +void nano::backlog_scan::start () +{ + debug_assert (!thread.joinable ()); + + thread = std::thread{ [this] () { + nano::thread_role::set (nano::thread_role::name::backlog_scan); + run (); + } }; +} + +void nano::backlog_scan::stop () +{ + { + nano::lock_guard lock{ mutex }; + stopped = true; + } + notify (); + nano::join_or_pass (thread); +} + +void nano::backlog_scan::trigger () +{ + { + nano::unique_lock lock{ mutex }; + triggered = true; + } + notify (); +} + +void nano::backlog_scan::notify () +{ + condition.notify_all (); +} + +bool nano::backlog_scan::predicate () const +{ + return triggered || config.enable; +} + +void nano::backlog_scan::run () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + if (predicate ()) + { + stats.inc (nano::stat::type::backlog_scan, nano::stat::detail::loop); + triggered = false; + populate_backlog (lock); // Does a single iteration over all accounts + debug_assert (lock.owns_lock ()); + } + else + { + condition.wait (lock, [this] () { + return stopped || predicate (); + }); + } + } +} + +void nano::backlog_scan::populate_backlog (nano::unique_lock & lock) +{ + uint64_t total = 0; + + nano::account next = 0; + bool done = false; + while (!stopped && !done) + { + // Wait for the rate limiter + while (!limiter.should_pass (config.batch_size)) + { + std::chrono::milliseconds const wait_time{ 1000 / std::max ((config.rate_limit / config.batch_size), size_t{ 1 }) / 2 }; + condition.wait_for (lock, std::max (wait_time, 10ms)); + if (stopped) + { + return; + } + } + + lock.unlock (); + + std::deque scanned; + std::deque activated; + { + auto transaction = ledger.tx_begin_read (); + + auto it = ledger.store.account.begin (transaction, next); + auto const end = ledger.store.account.end (transaction); + + for (size_t count = 0; it != end && count < config.batch_size; ++it, ++count, ++total) + { + stats.inc (nano::stat::type::backlog_scan, nano::stat::detail::total); + + auto const [account, account_info] = *it; + auto const maybe_conf_info = ledger.store.confirmation_height.get (transaction, account); + auto const conf_info = maybe_conf_info.value_or (nano::confirmation_height_info{}); + + activated_info info{ account, account_info, conf_info }; + + scanned.push_back (info); + if (conf_info.height < account_info.block_count) + { + activated.push_back (info); + } + + next = inc_sat (account.number ()); + } + + done = (it == end); + } + + stats.add (nano::stat::type::backlog_scan, nano::stat::detail::scanned, scanned.size ()); + stats.add (nano::stat::type::backlog_scan, nano::stat::detail::activated, activated.size ()); + + // Notify about scanned and activated accounts without holding database transaction + batch_scanned.notify (scanned); + batch_activated.notify (activated); + + lock.lock (); + } +} + +nano::container_info nano::backlog_scan::container_info () const +{ + nano::lock_guard guard{ mutex }; + nano::container_info info; + info.put ("limiter", limiter.size ()); + return info; +} + +/* + * backlog_scan_config + */ + +nano::error nano::backlog_scan_config::serialize (nano::tomlconfig & toml) const +{ + toml.put ("enable", enable, "Control if ongoing backlog population is enabled. If not, backlog population can still be triggered by RPC \ntype:bool"); + toml.put ("batch_size", batch_size, "Size of a single batch. Larger batches reduce overhead, but may put more pressure on other node components. \ntype:uint"); + toml.put ("rate_limit", rate_limit, "Number of accounts per second to process when doing backlog population scan. Increasing this value will help unconfirmed frontiers get into election prioritization queue faster. Use 0 to process as fast as possible, but be aware that it may consume a lot of resources. \ntype:uint"); + + return toml.get_error (); +} + +nano::error nano::backlog_scan_config::deserialize (nano::tomlconfig & toml) +{ + toml.get ("enable", enable); + toml.get ("batch_size", batch_size); + toml.get ("rate_limit", rate_limit); + + return toml.get_error (); +} diff --git a/nano/node/backlog_population.hpp b/nano/node/backlog_scan.hpp similarity index 56% rename from nano/node/backlog_population.hpp rename to nano/node/backlog_scan.hpp index 37123f4ba9..5ff41629a1 100644 --- a/nano/node/backlog_population.hpp +++ b/nano/node/backlog_scan.hpp @@ -3,24 +3,18 @@ #include #include #include -#include +#include +#include +#include #include #include +#include #include -namespace nano::secure -{ -class transaction; -} namespace nano { -class account_info; -class election_scheduler; -class ledger; -class stats; - -class backlog_population_config final +class backlog_scan_config final { public: nano::error deserialize (nano::tomlconfig &); @@ -29,17 +23,17 @@ class backlog_population_config final public: /** Control if ongoing backlog population is enabled. If not, backlog population can still be triggered by RPC */ bool enable{ true }; - /** Number of accounts per second to process. Number of accounts per single batch is this value divided by `frequency` */ - unsigned batch_size{ 10 * 1000 }; - /** Number of batches to run per second. Batches run in 1 second / `frequency` intervals */ - unsigned frequency{ 10 }; + /** Number of accounts to scan per second. */ + size_t rate_limit{ 10000 }; + /** Number of accounts per second to process. */ + size_t batch_size{ 1000 }; }; -class backlog_population final +class backlog_scan final { public: - backlog_population (backlog_population_config const &, nano::scheduler::component &, nano::ledger &, nano::stats &); - ~backlog_population (); + backlog_scan (backlog_scan_config const &, nano::ledger &, nano::stats &); + ~backlog_scan (); void start (); void stop (); @@ -50,16 +44,22 @@ class backlog_population final /** Notify about AEC vacancy */ void notify (); + nano::container_info container_info () const; + public: - /** - * Callback called for each backlogged account - */ - using callback_t = nano::observer_set; - callback_t activate_callback; + struct activated_info + { + nano::account account; + nano::account_info account_info; + nano::confirmation_height_info conf_info; + }; + + using batch_event_t = nano::observer_set>; + batch_event_t batch_scanned; // Accounts scanned but not activated + batch_event_t batch_activated; // Accounts activated private: // Dependencies - backlog_population_config const & config; - nano::scheduler::component & schedulers; + backlog_scan_config const & config; nano::ledger & ledger; nano::stats & stats; @@ -67,9 +67,10 @@ class backlog_population final void run (); bool predicate () const; void populate_backlog (nano::unique_lock & lock); - void activate (secure::transaction const &, nano::account const &, nano::account_info const &); private: + nano::rate_limiter limiter; + /** This is a manual trigger, the ongoing backlog population does not use this. * It can be triggered even when backlog population (frontiers confirmation) is disabled. */ bool triggered{ false }; diff --git a/nano/node/bandwidth_limiter.cpp b/nano/node/bandwidth_limiter.cpp index ade752051e..0dca8f7d8c 100644 --- a/nano/node/bandwidth_limiter.cpp +++ b/nano/node/bandwidth_limiter.cpp @@ -17,16 +17,11 @@ nano::rate_limiter & nano::bandwidth_limiter::select_limiter (nano::transport::t { switch (type) { - case nano::transport::traffic_type::bootstrap: + case nano::transport::traffic_type::bootstrap_server: return limiter_bootstrap; - case nano::transport::traffic_type::generic: - return limiter_generic; - break; default: - debug_assert (false, "missing traffic type"); - break; + return limiter_generic; } - return limiter_generic; } bool nano::bandwidth_limiter::should_pass (std::size_t buffer_size, nano::transport::traffic_type type) @@ -41,6 +36,14 @@ void nano::bandwidth_limiter::reset (std::size_t limit, double burst_ratio, nano limiter.reset (limit, burst_ratio); } +nano::container_info nano::bandwidth_limiter::container_info () const +{ + nano::container_info info; + info.put ("generic", limiter_generic.size ()); + info.put ("bootstrap", limiter_bootstrap.size ()); + return info; +} + /* * bandwidth_limiter_config */ diff --git a/nano/node/bandwidth_limiter.hpp b/nano/node/bandwidth_limiter.hpp index 1bd9e58339..0cd774e1aa 100644 --- a/nano/node/bandwidth_limiter.hpp +++ b/nano/node/bandwidth_limiter.hpp @@ -37,6 +37,8 @@ class bandwidth_limiter final */ void reset (std::size_t limit, double burst_ratio, nano::transport::traffic_type type = nano::transport::traffic_type::generic); + nano::container_info container_info () const; + private: /** * Returns reference to limiter corresponding to the limit type @@ -50,4 +52,4 @@ class bandwidth_limiter final nano::rate_limiter limiter_generic; nano::rate_limiter limiter_bootstrap; }; -} \ No newline at end of file +} diff --git a/nano/node/blockprocessor.cpp b/nano/node/block_processor.cpp similarity index 61% rename from nano/node/blockprocessor.cpp rename to nano/node/block_processor.cpp index d0e8716494..dd72521c8c 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/block_processor.cpp @@ -1,56 +1,32 @@ +#include #include #include #include #include #include -#include +#include #include #include +#include #include #include #include #include -/* - * block_processor::context - */ - -nano::block_processor::context::context (std::shared_ptr block, nano::block_source source_a, callback_t callback_a) : - block{ std::move (block) }, - source{ source_a }, - callback{ std::move (callback_a) } -{ - debug_assert (source != nano::block_source::unknown); -} - -auto nano::block_processor::context::get_future () -> std::future -{ - return promise.get_future (); -} - -void nano::block_processor::context::set_result (result_t const & result) -{ - promise.set_value (result); -} - /* * block_processor */ -nano::block_processor::block_processor (nano::node & node_a) : - config{ node_a.config.block_processor }, - node (node_a), - next_log (std::chrono::steady_clock::now ()) +nano::block_processor::block_processor (nano::node_config const & node_config, nano::ledger & ledger_a, nano::unchecked_map & unchecked_a, nano::stats & stats_a, nano::logger & logger_a) : + config{ node_config.block_processor }, + network_params{ node_config.network_params }, + ledger{ ledger_a }, + unchecked{ unchecked_a }, + stats{ stats_a }, + logger{ logger_a }, + workers{ 1, nano::thread_role::name::block_processing_notifications } { - batch_processed.add ([this] (auto const & items) { - // For every batch item: notify the 'processed' observer. - for (auto const & [result, context] : items) - { - block_processed.notify (result, context); - } - }); - queue.max_size_query = [this] (auto const & origin) { switch (origin.source) { @@ -75,21 +51,29 @@ nano::block_processor::block_processor (nano::node & node_a) : case nano::block_source::local: return config.priority_local; default: - return 1; + return config.priority_system; } }; + + // Requeue blocks that could not be immediately processed + unchecked.satisfied.add ([this] (nano::unchecked_info const & info) { + add (info.block, nano::block_source::unchecked); + }); } nano::block_processor::~block_processor () { // Thread must be stopped before destruction debug_assert (!thread.joinable ()); + debug_assert (!workers.alive ()); } void nano::block_processor::start () { debug_assert (!thread.joinable ()); + workers.start (); + thread = std::thread ([this] () { nano::thread_role::set (nano::thread_role::name::block_processing); run (); @@ -107,6 +91,7 @@ void nano::block_processor::stop () { thread.join (); } + workers.stop (); } // TODO: Remove and replace all checks with calls to size (block_source) @@ -124,14 +109,14 @@ std::size_t nano::block_processor::size (nano::block_source source) const bool nano::block_processor::add (std::shared_ptr const & block, block_source const source, std::shared_ptr const & channel, std::function callback) { - if (node.network_params.work.validate_entry (*block)) // true => error + if (network_params.work.validate_entry (*block)) // true => error { - node.stats.inc (nano::stat::type::blockprocessor, nano::stat::detail::insufficient_work); + stats.inc (nano::stat::type::block_processor, nano::stat::detail::insufficient_work); return false; // Not added } - node.stats.inc (nano::stat::type::blockprocessor, nano::stat::detail::process); - node.logger.debug (nano::log::type::blockprocessor, "Processing block (async): {} (source: {} {})", + stats.inc (nano::stat::type::block_processor, nano::stat::detail::process); + logger.debug (nano::log::type::block_processor, "Processing block (async): {} (source: {} {})", block->hash ().to_string (), to_string (source), channel ? channel->to_string () : ""); // TODO: Lazy eval @@ -141,8 +126,8 @@ bool nano::block_processor::add (std::shared_ptr const & block, blo std::optional nano::block_processor::add_blocking (std::shared_ptr const & block, block_source const source) { - node.stats.inc (nano::stat::type::blockprocessor, nano::stat::detail::process_blocking); - node.logger.debug (nano::log::type::blockprocessor, "Processing block (blocking): {} (source: {})", block->hash ().to_string (), to_string (source)); + stats.inc (nano::stat::type::block_processor, nano::stat::detail::process_blocking); + logger.debug (nano::log::type::block_processor, "Processing block (blocking): {} (source: {})", block->hash ().to_string (), to_string (source)); context ctx{ block, source }; auto future = ctx.get_future (); @@ -155,8 +140,8 @@ std::optional nano::block_processor::add_blocking (std::shar } catch (std::future_error const &) { - node.stats.inc (nano::stat::type::blockprocessor, nano::stat::detail::process_blocking_timeout); - node.logger.error (nano::log::type::blockprocessor, "Block dropped when processing: {}", block->hash ().to_string ()); + stats.inc (nano::stat::type::block_processor, nano::stat::detail::process_blocking_timeout); + logger.error (nano::log::type::block_processor, "Block dropped when processing: {}", block->hash ().to_string ()); } return std::nullopt; @@ -164,8 +149,8 @@ std::optional nano::block_processor::add_blocking (std::shar void nano::block_processor::force (std::shared_ptr const & block_a) { - node.stats.inc (nano::stat::type::blockprocessor, nano::stat::detail::force); - node.logger.debug (nano::log::type::blockprocessor, "Forcing block: {}", block_a->hash ().to_string ()); + stats.inc (nano::stat::type::block_processor, nano::stat::detail::force); + logger.debug (nano::log::type::block_processor, "Forcing block: {}", block_a->hash ().to_string ()); add_impl (context{ block_a, block_source::forced }); } @@ -184,101 +169,95 @@ bool nano::block_processor::add_impl (context ctx, std::shared_ptrhash () != hash) { // Replace our block with the winner and roll back any dependent blocks - node.logger.debug (nano::log::type::blockprocessor, "Rolling back: {} and replacing with: {}", successor->hash ().to_string (), hash.to_string ()); + logger.debug (nano::log::type::block_processor, "Rolling back: {} and replacing with: {}", successor->hash ().to_string (), hash.to_string ()); - std::vector> rollback_list; - if (node.ledger.rollback (transaction, successor->hash (), rollback_list)) + std::deque> rollback_list; + if (ledger.rollback (transaction, successor->hash (), rollback_list)) { - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::rollback_failed); - node.logger.error (nano::log::type::blockprocessor, "Failed to roll back: {} because it or a successor was confirmed", successor->hash ().to_string ()); + stats.inc (nano::stat::type::ledger, nano::stat::detail::rollback_failed); + logger.error (nano::log::type::block_processor, "Failed to roll back: {} because it or a successor was confirmed", successor->hash ().to_string ()); } else { - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::rollback); - node.logger.debug (nano::log::type::blockprocessor, "Blocks rolled back: {}", rollback_list.size ()); + stats.inc (nano::stat::type::ledger, nano::stat::detail::rollback); + logger.debug (nano::log::type::block_processor, "Blocks rolled back: {}", rollback_list.size ()); } - // Deleting from votes cache, stop active transaction - for (auto & i : rollback_list) - { - rolled_back.notify (i); - - node.history.erase (i->root ()); - // Stop all rolled back active transactions except initial - if (i->hash () != successor->hash ()) - { - node.active.erase (*i); - } - } + // Notify observers of the rolled back blocks on a background thread while not holding the ledger write lock + workers.post ([this, rollback_list = std::move (rollback_list), root = fork_block.qualified_root ()] () { + rolled_back.notify (rollback_list, root); + }); } } void nano::block_processor::run () { + nano::interval log_interval; nano::unique_lock lock{ mutex }; while (!stopped) { if (!queue.empty ()) { - // TODO: Cleaner periodical logging - if (should_log ()) + // It's possible that ledger processing happens faster than the notifications can be processed by other components, cooldown here + while (workers.queued_tasks () >= config.max_queued_notifications) + { + stats.inc (nano::stat::type::block_processor, nano::stat::detail::cooldown); + condition.wait_for (lock, 100ms, [this] { return stopped; }); + if (stopped) + { + return; + } + } + + if (log_interval.elapsed (15s)) { - node.logger.info (nano::log::type::blockprocessor, "{} blocks (+ {} forced) in processing queue", + logger.info (nano::log::type::block_processor, "{} blocks (+ {} forced) in processing queue", queue.size (), queue.size ({ nano::block_source::forced })); } auto processed = process_batch (lock); debug_assert (!lock.owns_lock ()); + lock.lock (); - // Set results for futures when not holding the lock - for (auto & [result, context] : processed) - { - if (context.callback) + // Queue notifications to be dispatched in the background + workers.post ([this, processed = std::move (processed)] () mutable { + stats.inc (nano::stat::type::block_processor, nano::stat::detail::notify); + // Set results for futures when not holding the lock + for (auto & [result, context] : processed) { - context.callback (result); + if (context.callback) + { + context.callback (result); + } + context.set_result (result); } - context.set_result (result); - } - - batch_processed.notify (processed); - - lock.lock (); + batch_processed.notify (processed); + }); } else { - condition.notify_one (); - condition.wait (lock); + condition.wait (lock, [this] { + return stopped || !queue.empty (); + }); } } } -bool nano::block_processor::should_log () -{ - auto result (false); - auto now (std::chrono::steady_clock::now ()); - if (next_log < now) - { - next_log = now + std::chrono::seconds (15); - result = true; - } - return result; -} - auto nano::block_processor::next () -> context { debug_assert (!mutex.try_lock ()); @@ -315,11 +294,11 @@ auto nano::block_processor::process_batch (nano::unique_lock & lock debug_assert (!mutex.try_lock ()); debug_assert (!queue.empty ()); - auto batch = next_batch (256); + auto batch = next_batch (config.batch_size); lock.unlock (); - auto transaction = node.ledger.tx_begin_write (nano::store::writer::blockprocessor); + auto transaction = ledger.tx_begin_write (nano::store::writer::block_processor); nano::timer timer; timer.start (); @@ -350,7 +329,7 @@ auto nano::block_processor::process_batch (nano::unique_lock & lock if (number_of_blocks_processed != 0 && timer.stop () > std::chrono::milliseconds (100)) { - node.logger.debug (nano::log::type::blockprocessor, "Processed {} blocks ({} forced) in {} {}", number_of_blocks_processed, number_of_forced_processed, timer.value ().count (), timer.unit ()); + logger.debug (nano::log::type::block_processor, "Processed {} blocks ({} forced) in {} {}", number_of_blocks_processed, number_of_forced_processed, timer.value ().count (), timer.unit ()); } return processed; @@ -360,12 +339,12 @@ nano::block_status nano::block_processor::process_one (secure::write_transaction { auto block = context.block; auto const hash = block->hash (); - nano::block_status result = node.ledger.process (transaction_a, block); + nano::block_status result = ledger.process (transaction_a, block); - node.stats.inc (nano::stat::type::blockprocessor_result, to_stat_detail (result)); - node.stats.inc (nano::stat::type::blockprocessor_source, to_stat_detail (context.source)); + stats.inc (nano::stat::type::block_processor_result, to_stat_detail (result)); + stats.inc (nano::stat::type::block_processor_source, to_stat_detail (context.source)); - node.logger.trace (nano::log::type::blockprocessor, nano::log::detail::block_processed, + logger.trace (nano::log::type::block_processor, nano::log::detail::block_processed, nano::log::arg{ "result", result }, nano::log::arg{ "source", context.source }, nano::log::arg{ "arrival", nano::log::microseconds (context.arrival) }, @@ -376,40 +355,41 @@ nano::block_status nano::block_processor::process_one (secure::write_transaction { case nano::block_status::progress: { - queue_unchecked (transaction_a, hash); - /* For send blocks check epoch open unchecked (gap pending). - For state blocks check only send subtype and only if block epoch is not last epoch. - If epoch is last, then pending entry shouldn't trigger same epoch open block for destination account. */ + unchecked.trigger (hash); + + /* + * For send blocks check epoch open unchecked (gap pending). + * For state blocks check only send subtype and only if block epoch is not last epoch. + * If epoch is last, then pending entry shouldn't trigger same epoch open block for destination account. + */ if (block->type () == nano::block_type::send || (block->type () == nano::block_type::state && block->is_send () && std::underlying_type_t (block->sideband ().details.epoch) < std::underlying_type_t (nano::epoch::max))) { - /* block->destination () for legacy send blocks - block->link () for state blocks (send subtype) */ - queue_unchecked (transaction_a, block->destination ()); + unchecked.trigger (block->destination ()); } break; } case nano::block_status::gap_previous: { - node.unchecked.put (block->previous (), block); - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_previous); + unchecked.put (block->previous (), block); + stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_previous); break; } case nano::block_status::gap_source: { release_assert (block->source_field () || block->link_field ()); - node.unchecked.put (block->source_field ().value_or (block->link_field ().value_or (0).as_block_hash ()), block); - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); + unchecked.put (block->source_field ().value_or (block->link_field ().value_or (0).as_block_hash ()), block); + stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); break; } case nano::block_status::gap_epoch_open_pending: { - node.unchecked.put (block->account_field ().value_or (0), block); // Specific unchecked key starting with epoch open block account public key - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); + unchecked.put (block->account_field ().value_or (0), block); // Specific unchecked key starting with epoch open block account public key + stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); break; } case nano::block_status::old: { - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::old); + stats.inc (nano::stat::type::ledger, nano::stat::detail::old); break; } case nano::block_status::bad_signature: @@ -426,7 +406,7 @@ nano::block_status nano::block_processor::process_one (secure::write_transaction } case nano::block_status::fork: { - node.stats.inc (nano::stat::type::ledger, nano::stat::detail::fork); + stats.inc (nano::stat::type::ledger, nano::stat::detail::fork); break; } case nano::block_status::opened_burn_account: @@ -453,11 +433,6 @@ nano::block_status nano::block_processor::process_one (secure::write_transaction return result; } -void nano::block_processor::queue_unchecked (secure::write_transaction const & transaction_a, nano::hash_or_account const & hash_or_account_a) -{ - node.unchecked.trigger (hash_or_account_a); -} - nano::container_info nano::block_processor::container_info () const { nano::lock_guard guard{ mutex }; @@ -466,9 +441,32 @@ nano::container_info nano::block_processor::container_info () const info.put ("blocks", queue.size ()); info.put ("forced", queue.size ({ nano::block_source::forced })); info.add ("queue", queue.container_info ()); + info.add ("workers", workers.container_info ()); return info; } +/* + * block_processor::context + */ + +nano::block_processor::context::context (std::shared_ptr block, nano::block_source source_a, callback_t callback_a) : + block{ std::move (block) }, + source{ source_a }, + callback{ std::move (callback_a) } +{ + debug_assert (source != nano::block_source::unknown); +} + +auto nano::block_processor::context::get_future () -> std::future +{ + return promise.get_future (); +} + +void nano::block_processor::context::set_result (result_t const & result) +{ + promise.set_value (result); +} + /* * block_processor_config */ @@ -511,4 +509,4 @@ std::string_view nano::to_string (nano::block_source source) nano::stat::detail nano::to_stat_detail (nano::block_source type) { return nano::enum_util::cast (type); -} \ No newline at end of file +} diff --git a/nano/node/blockprocessor.hpp b/nano/node/block_processor.hpp similarity index 78% rename from nano/node/blockprocessor.hpp rename to nano/node/block_processor.hpp index 837631b0ba..1adec9ca62 100644 --- a/nano/node/blockprocessor.hpp +++ b/nano/node/block_processor.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -23,6 +24,7 @@ enum class block_source unchecked, local, forced, + election, }; std::string_view to_string (block_source); @@ -46,6 +48,10 @@ class block_processor_config final size_t priority_live{ 1 }; size_t priority_bootstrap{ 8 }; size_t priority_local{ 16 }; + size_t priority_system{ 32 }; + + size_t batch_size{ 256 }; + size_t max_queued_notifications{ 8 }; }; /** @@ -78,7 +84,7 @@ class block_processor final }; public: - explicit block_processor (nano::node &); + block_processor (nano::node_config const &, nano::ledger &, nano::unchecked_map &, nano::stats &, nano::logger &); ~block_processor (); void start (); @@ -89,44 +95,47 @@ class block_processor final bool add (std::shared_ptr const &, nano::block_source = nano::block_source::live, std::shared_ptr const & channel = nullptr, std::function callback = {}); std::optional add_blocking (std::shared_ptr const & block, nano::block_source); void force (std::shared_ptr const &); - bool should_log (); nano::container_info container_info () const; std::atomic flushing{ false }; public: // Events - using processed_t = std::tuple; - using processed_batch_t = std::deque; + // All processed blocks including forks, rejected etc + using processed_batch_t = std::deque>; + using processed_batch_event_t = nano::observer_set; + processed_batch_event_t batch_processed; - // The batch observer feeds the processed observer - nano::observer_set block_processed; - nano::observer_set batch_processed; - nano::observer_set const &> rolled_back; + // Rolled back blocks + using rolled_back_event_t = nano::observer_set>, nano::qualified_root>; + rolled_back_event_t rolled_back; + +private: // Dependencies + block_processor_config const & config; + nano::network_params const & network_params; + nano::ledger & ledger; + nano::unchecked_map & unchecked; + nano::stats & stats; + nano::logger & logger; private: void run (); // Roll back block in the ledger that conflicts with 'block' void rollback_competitor (secure::write_transaction const &, nano::block const & block); nano::block_status process_one (secure::write_transaction const &, context const &, bool forced = false); - void queue_unchecked (secure::write_transaction const &, nano::hash_or_account const &); processed_batch_t process_batch (nano::unique_lock &); std::deque next_batch (size_t max_count); context next (); bool add_impl (context, std::shared_ptr const & channel = nullptr); -private: // Dependencies - block_processor_config const & config; - nano::node & node; - private: nano::fair_queue queue; - std::chrono::steady_clock::time_point next_log; - bool stopped{ false }; nano::condition_variable condition; mutable nano::mutex mutex{ mutex_identifier (mutexes::block_processor) }; std::thread thread; + + nano::thread_pool workers; }; } diff --git a/nano/node/bootstrap/account_sets.cpp b/nano/node/bootstrap/account_sets.cpp new file mode 100644 index 0000000000..421ffddb46 --- /dev/null +++ b/nano/node/bootstrap/account_sets.cpp @@ -0,0 +1,358 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* + * account_sets + */ + +nano::bootstrap::account_sets::account_sets (nano::account_sets_config const & config_a, nano::stats & stats_a) : + config{ config_a }, + stats{ stats_a } +{ +} + +void nano::bootstrap::account_sets::priority_up (nano::account const & account) +{ + if (account.is_zero ()) + { + return; + } + + if (!blocked (account)) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::prioritize); + + if (auto it = priorities.get ().find (account); it != priorities.get ().end ()) + { + priorities.get ().modify (it, [] (auto & val) { + val.priority = std::min ((val.priority + account_sets::priority_increase), account_sets::priority_max); + val.fails = 0; + }); + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::priority_insert); + priorities.get ().insert ({ account, account_sets::priority_initial }); + trim_overflow (); + } + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::prioritize_failed); + } +} + +void nano::bootstrap::account_sets::priority_down (nano::account const & account) +{ + if (account.is_zero ()) + { + return; + } + + if (auto it = priorities.get ().find (account); it != priorities.get ().end ()) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::deprioritize); + + auto priority = it->priority / account_sets::priority_divide; + + if (it->fails >= account_sets::max_fails || it->fails >= it->priority || priority <= account_sets::priority_cutoff) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::erase_by_threshold); + priorities.get ().erase (it); + } + else + { + priorities.get ().modify (it, [priority] (auto & val) { + val.fails += 1; + val.priority = priority; + }); + } + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::deprioritize_failed); + } +} + +void nano::bootstrap::account_sets::priority_set (nano::account const & account, double priority) +{ + if (account.is_zero ()) + { + return; + } + + if (!blocked (account)) + { + if (!priorities.get ().contains (account)) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::priority_set); + priorities.get ().insert ({ account, priority }); + trim_overflow (); + } + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::prioritize_failed); + } +} + +void nano::bootstrap::account_sets::block (nano::account const & account, nano::block_hash const & dependency) +{ + debug_assert (!account.is_zero ()); + + auto erased = priorities.get ().erase (account); + if (erased > 0) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::erase_by_blocking); + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::block); + + debug_assert (blocking.get ().count (account) == 0); + blocking.get ().insert ({ account, dependency }); + trim_overflow (); + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::block_failed); + } +} + +void nano::bootstrap::account_sets::unblock (nano::account const & account, std::optional const & hash) +{ + if (account.is_zero ()) + { + return; + } + + // Unblock only if the dependency is fulfilled + auto existing = blocking.get ().find (account); + if (existing != blocking.get ().end () && (!hash || existing->dependency == *hash)) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::unblock); + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::priority_unblocked); + + debug_assert (priorities.get ().count (account) == 0); + priorities.get ().insert ({ account, account_sets::priority_initial }); + blocking.get ().erase (account); + trim_overflow (); + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::unblock_failed); + } +} + +void nano::bootstrap::account_sets::timestamp_set (const nano::account & account) +{ + debug_assert (!account.is_zero ()); + + auto iter = priorities.get ().find (account); + if (iter != priorities.get ().end ()) + { + priorities.get ().modify (iter, [] (auto & entry) { + entry.timestamp = std::chrono::steady_clock::now (); + }); + } +} + +void nano::bootstrap::account_sets::timestamp_reset (const nano::account & account) +{ + debug_assert (!account.is_zero ()); + + auto iter = priorities.get ().find (account); + if (iter != priorities.get ().end ()) + { + priorities.get ().modify (iter, [] (auto & entry) { + entry.timestamp = {}; + }); + } +} + +void nano::bootstrap::account_sets::dependency_update (nano::block_hash const & hash, nano::account const & dependency_account) +{ + debug_assert (!dependency_account.is_zero ()); + + auto [it, end] = blocking.get ().equal_range (hash); + if (it != end) + { + while (it != end) + { + if (it->dependency_account != dependency_account) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::dependency_update); + + blocking.get ().modify (it++, [dependency_account] (auto & entry) { + entry.dependency_account = dependency_account; + }); + } + else + { + ++it; + } + } + } + else + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::dependency_update_failed); + } +} + +void nano::bootstrap::account_sets::trim_overflow () +{ + while (!priorities.empty () && priorities.size () > config.priorities_max) + { + // Erase the lowest priority entry + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::priority_overflow); + priorities.get ().erase (std::prev (priorities.get ().end ())); + } + while (!blocking.empty () && blocking.size () > config.blocking_max) + { + // Erase the lowest priority entry + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::blocking_overflow); + blocking.pop_front (); + } +} + +auto nano::bootstrap::account_sets::next_priority (std::function const & filter) -> priority_result +{ + if (priorities.empty ()) + { + return { 0 }; + } + + auto const cutoff = std::chrono::steady_clock::now () - config.cooldown; + + for (auto const & entry : priorities.get ()) + { + if (entry.timestamp > cutoff) + { + continue; + } + if (!filter (entry.account)) + { + continue; + } + return { + .account = entry.account, + .priority = entry.priority, + .fails = entry.fails + }; + } + + return {}; +} + +nano::block_hash nano::bootstrap::account_sets::next_blocking (std::function const & filter) +{ + if (blocking.empty ()) + { + return { 0 }; + } + + // Scan all entries with unknown dependency account + auto [begin, end] = blocking.get ().equal_range (nano::account{ 0 }); + for (auto const & entry : boost::make_iterator_range (begin, end)) + { + debug_assert (entry.dependency_account.is_zero ()); + if (!filter (entry.dependency)) + { + continue; + } + return entry.dependency; + } + + return { 0 }; +} + +void nano::bootstrap::account_sets::sync_dependencies () +{ + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::sync_dependencies); + + // Sample all accounts with a known dependency account (> account 0) + auto begin = blocking.get ().upper_bound (nano::account{ 0 }); + auto end = blocking.get ().end (); + + for (auto const & entry : boost::make_iterator_range (begin, end)) + { + debug_assert (!entry.dependency_account.is_zero ()); + + if (priorities.size () >= config.priorities_max) + { + break; + } + + if (!blocked (entry.dependency_account) && !prioritized (entry.dependency_account)) + { + stats.inc (nano::stat::type::bootstrap_account_sets, nano::stat::detail::dependency_synced); + priority_set (entry.dependency_account); + } + } + + trim_overflow (); +} + +bool nano::bootstrap::account_sets::blocked (nano::account const & account) const +{ + return blocking.get ().contains (account); +} + +bool nano::bootstrap::account_sets::prioritized (nano::account const & account) const +{ + return priorities.get ().contains (account); +} + +std::size_t nano::bootstrap::account_sets::priority_size () const +{ + return priorities.size (); +} + +std::size_t nano::bootstrap::account_sets::blocked_size () const +{ + return blocking.size (); +} + +bool nano::bootstrap::account_sets::priority_half_full () const +{ + return priorities.size () > config.priorities_max / 2; +} + +bool nano::bootstrap::account_sets::blocked_half_full () const +{ + return blocking.size () > config.blocking_max / 2; +} + +double nano::bootstrap::account_sets::priority (nano::account const & account) const +{ + if (!blocked (account)) + { + if (auto existing = priorities.get ().find (account); existing != priorities.get ().end ()) + { + return existing->priority; + } + } + return 0.0; +} + +auto nano::bootstrap::account_sets::info () const -> nano::bootstrap::account_sets::info_t +{ + return { blocking, priorities }; +} + +nano::container_info nano::bootstrap::account_sets::container_info () const +{ + // Count blocking entries with their dependency account unknown + auto blocking_unknown = blocking.get ().count (nano::account{ 0 }); + + nano::container_info info; + info.put ("priorities", priorities); + info.put ("blocking", blocking); + info.put ("blocking_unknown", blocking_unknown); + return info; +} diff --git a/nano/node/bootstrap_ascending/account_sets.hpp b/nano/node/bootstrap/account_sets.hpp similarity index 90% rename from nano/node/bootstrap_ascending/account_sets.hpp rename to nano/node/bootstrap/account_sets.hpp index d83d3a8898..59bdbf95af 100644 --- a/nano/node/bootstrap_ascending/account_sets.hpp +++ b/nano/node/bootstrap/account_sets.hpp @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include #include @@ -18,13 +19,19 @@ namespace mi = boost::multi_index; namespace nano { -class stats; - -namespace bootstrap_ascending +namespace bootstrap { /** This class tracks accounts various account sets which are shared among the multiple bootstrap threads */ class account_sets { + public: // Constants + static double constexpr priority_initial = 2.0; + static double constexpr priority_increase = 2.0; + static double constexpr priority_divide = 2.0; + static double constexpr priority_max = 128.0; + static double constexpr priority_cutoff = 0.15; + static unsigned constexpr max_fails = 3; + public: account_sets (account_sets_config const &, nano::stats &); @@ -39,7 +46,7 @@ namespace bootstrap_ascending * Current implementation divides priority by 2.0f and saturates down to 1.0f. */ void priority_down (nano::account const & account); - void priority_set (nano::account const & account); + void priority_set (nano::account const & account, double priority = priority_initial); void block (nano::account const & account, nano::block_hash const & dependency); void unblock (nano::account const & account, std::optional const & hash = std::nullopt); @@ -56,10 +63,17 @@ namespace bootstrap_ascending */ void sync_dependencies (); + struct priority_result + { + nano::account account; + double priority; + unsigned fails; + }; + /** * Sampling */ - nano::account next_priority (std::function const & filter); + priority_result next_priority (std::function const & filter); nano::block_hash next_blocking (std::function const & filter); bool blocked (nano::account const & account) const; @@ -87,27 +101,17 @@ namespace bootstrap_ascending { nano::account account; double priority; - - id_t id{ generate_id () }; // Uniformly distributed, used for random querying + unsigned fails{ 0 }; std::chrono::steady_clock::time_point timestamp{}; + id_t id{ generate_id () }; // Uniformly distributed, used for random querying }; struct blocking_entry { - priority_entry original_entry; + nano::account account; nano::block_hash dependency; nano::account dependency_account{ 0 }; - id_t id{ generate_id () }; // Uniformly distributed, used for random querying - - nano::account account () const - { - return original_entry.account; - } - double priority () const - { - return original_entry.priority; - } }; // clang-format off @@ -136,7 +140,7 @@ namespace bootstrap_ascending mi::indexed_by< mi::sequenced>, mi::ordered_unique, - mi::const_mem_fun>, + mi::member>, mi::ordered_non_unique, mi::member>, mi::ordered_non_unique, @@ -149,16 +153,9 @@ namespace bootstrap_ascending ordered_priorities priorities; ordered_blocking blocking; - public: // Constants - static double constexpr priority_initial = 2.0; - static double constexpr priority_increase = 2.0; - static double constexpr priority_divide = 2.0; - static double constexpr priority_max = 128.0; - static double constexpr priority_cutoff = 0.15; - public: using info_t = std::tuple; // info_t info () const; }; } -} \ No newline at end of file +} diff --git a/nano/node/bootstrap/block_deserializer.hpp b/nano/node/bootstrap/block_deserializer.hpp deleted file mode 100644 index dce67edf92..0000000000 --- a/nano/node/bootstrap/block_deserializer.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include - -namespace nano -{ -class block; - -namespace bootstrap -{ - /** - * Class to read a block-type byte followed by a serialised block from a stream. - * It is typically used to read a series of block-types and blocks terminated by a not-a-block type. - */ - class block_deserializer : public std::enable_shared_from_this - { - public: - using callback_type = std::function)>; - - block_deserializer (); - /** - * Read a type-prefixed block from 'socket' and pass the result, or an error, to 'callback' - * A normal end to series of blocks is a marked by return no error and a nullptr for block. - */ - void read (nano::transport::tcp_socket & socket, callback_type const && callback); - - private: - /** - * Called by read method on receipt of a block type byte. - * The type byte will be in the read_buffer. - */ - void received_type (nano::transport::tcp_socket & socket, callback_type const && callback); - - /** - * Called by received_type when a block is received, it parses the block and calls the callback. - */ - void received_block (nano::block_type type, callback_type const && callback); - - std::shared_ptr> read_buffer; - }; -} -} diff --git a/nano/node/bootstrap/bootstrap.cpp b/nano/node/bootstrap/bootstrap.cpp deleted file mode 100644 index 8779400eec..0000000000 --- a/nano/node/bootstrap/bootstrap.cpp +++ /dev/null @@ -1,390 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -nano::bootstrap_initiator::bootstrap_initiator (nano::node & node_a) : - node (node_a) -{ - connections = std::make_shared (node); - bootstrap_initiator_threads.push_back (boost::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::bootstrap_connections); - connections->run (); - })); - for (std::size_t i = 0; i < node.config.bootstrap_initiator_threads; ++i) - { - bootstrap_initiator_threads.push_back (boost::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::bootstrap_initiator); - run_bootstrap (); - })); - } -} - -nano::bootstrap_initiator::~bootstrap_initiator () -{ - stop (); -} - -void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a, uint32_t const frontiers_age_a, nano::account const & start_account_a) -{ - if (force) - { - stop_attempts (); - } - nano::unique_lock lock{ mutex }; - if (!stopped && find_attempt (nano::bootstrap_mode::legacy) == nullptr) - { - node.stats.inc (nano::stat::type::bootstrap, frontiers_age_a == std::numeric_limits::max () ? nano::stat::detail::initiate : nano::stat::detail::initiate_legacy_age, nano::stat::dir::out); - auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a, frontiers_age_a, start_account_a)); - attempts_list.push_back (legacy_attempt); - attempts.add (legacy_attempt); - lock.unlock (); - condition.notify_all (); - } -} - -void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bool add_to_peers, std::string id_a) -{ - if (add_to_peers) - { - if (!node.flags.disable_tcp_realtime) - { - node.network.merge_peer (nano::transport::map_endpoint_to_v6 (endpoint_a)); - } - } - if (!stopped) - { - stop_attempts (); - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); - nano::lock_guard lock{ mutex }; - auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a, std::numeric_limits::max (), 0)); - attempts_list.push_back (legacy_attempt); - attempts.add (legacy_attempt); - if (!node.network.excluded_peers.check (nano::transport::map_endpoint_to_tcp (endpoint_a))) - { - connections->add_connection (endpoint_a); - } - } - condition.notify_all (); -} - -bool nano::bootstrap_initiator::bootstrap_lazy (nano::hash_or_account const & hash_or_account_a, bool force, std::string id_a) -{ - bool key_inserted (false); - auto lazy_attempt (current_lazy_attempt ()); - if (lazy_attempt == nullptr || force) - { - if (force) - { - stop_attempts (); - } - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_lazy, nano::stat::dir::out); - nano::lock_guard lock{ mutex }; - if (!stopped && find_attempt (nano::bootstrap_mode::lazy) == nullptr) - { - lazy_attempt = std::make_shared (node.shared (), attempts.incremental++, id_a.empty () ? hash_or_account_a.to_string () : id_a); - attempts_list.push_back (lazy_attempt); - attempts.add (lazy_attempt); - key_inserted = lazy_attempt->lazy_start (hash_or_account_a); - } - } - else - { - key_inserted = lazy_attempt->lazy_start (hash_or_account_a); - } - condition.notify_all (); - return key_inserted; -} - -void nano::bootstrap_initiator::bootstrap_wallet (std::deque & accounts_a) -{ - debug_assert (!accounts_a.empty ()); - auto wallet_attempt (current_wallet_attempt ()); - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate_wallet_lazy, nano::stat::dir::out); - if (wallet_attempt == nullptr) - { - nano::lock_guard lock{ mutex }; - std::string id (!accounts_a.empty () ? accounts_a[0].to_account () : ""); - wallet_attempt = std::make_shared (node.shared (), attempts.incremental++, id); - attempts_list.push_back (wallet_attempt); - attempts.add (wallet_attempt); - wallet_attempt->wallet_start (accounts_a); - } - else - { - wallet_attempt->wallet_start (accounts_a); - } - condition.notify_all (); -} - -void nano::bootstrap_initiator::run_bootstrap () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - if (has_new_attempts ()) - { - auto attempt (new_attempt ()); - lock.unlock (); - if (attempt != nullptr) - { - attempt->run (); - remove_attempt (attempt); - } - lock.lock (); - } - else - { - condition.wait (lock); - } - } -} - -void nano::bootstrap_initiator::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a) -{ - auto lazy_attempt (current_lazy_attempt ()); - if (lazy_attempt != nullptr) - { - lazy_attempt->lazy_requeue (hash_a, previous_a); - } -} - -void nano::bootstrap_initiator::add_observer (std::function const & observer_a) -{ - nano::lock_guard lock{ observers_mutex }; - observers.push_back (observer_a); -} - -bool nano::bootstrap_initiator::in_progress () -{ - nano::lock_guard lock{ mutex }; - return !attempts_list.empty (); -} - -void nano::bootstrap_initiator::block_processed (store::transaction const & tx, nano::block_status const & result, nano::block const & block) -{ - nano::lock_guard lock{ mutex }; - for (auto & i : attempts_list) - { - i->block_processed (tx, result, block); - } -} - -std::shared_ptr nano::bootstrap_initiator::find_attempt (nano::bootstrap_mode mode_a) -{ - for (auto & i : attempts_list) - { - if (i->mode == mode_a) - { - return i; - } - } - return nullptr; -} - -void nano::bootstrap_initiator::remove_attempt (std::shared_ptr attempt_a) -{ - nano::unique_lock lock{ mutex }; - auto attempt (std::find (attempts_list.begin (), attempts_list.end (), attempt_a)); - if (attempt != attempts_list.end ()) - { - auto attempt_ptr (*attempt); - attempts.remove (attempt_ptr->incremental_id); - attempts_list.erase (attempt); - debug_assert (attempts.size () == attempts_list.size ()); - lock.unlock (); - attempt_ptr->stop (); - } - else - { - lock.unlock (); - } - condition.notify_all (); -} - -std::shared_ptr nano::bootstrap_initiator::new_attempt () -{ - for (auto & i : attempts_list) - { - if (!i->started.exchange (true)) - { - return i; - } - } - return nullptr; -} - -bool nano::bootstrap_initiator::has_new_attempts () -{ - for (auto & i : attempts_list) - { - if (!i->started) - { - return true; - } - } - return false; -} - -std::shared_ptr nano::bootstrap_initiator::current_attempt () -{ - nano::lock_guard lock{ mutex }; - return find_attempt (nano::bootstrap_mode::legacy); -} - -std::shared_ptr nano::bootstrap_initiator::current_lazy_attempt () -{ - nano::lock_guard lock{ mutex }; - return std::dynamic_pointer_cast (find_attempt (nano::bootstrap_mode::lazy)); -} - -std::shared_ptr nano::bootstrap_initiator::current_wallet_attempt () -{ - nano::lock_guard lock{ mutex }; - return std::dynamic_pointer_cast (find_attempt (nano::bootstrap_mode::wallet_lazy)); -} - -void nano::bootstrap_initiator::stop_attempts () -{ - nano::unique_lock lock{ mutex }; - std::vector> copy_attempts; - copy_attempts.swap (attempts_list); - attempts.clear (); - lock.unlock (); - for (auto & i : copy_attempts) - { - i->stop (); - } -} - -void nano::bootstrap_initiator::stop () -{ - if (!stopped.exchange (true)) - { - stop_attempts (); - connections->stop (); - condition.notify_all (); - - for (auto & thread : bootstrap_initiator_threads) - { - if (thread.joinable ()) - { - thread.join (); - } - } - } -} - -void nano::bootstrap_initiator::notify_listeners (bool in_progress_a) -{ - nano::lock_guard lock{ observers_mutex }; - for (auto & i : observers) - { - i (in_progress_a); - } -} - -nano::container_info nano::bootstrap_initiator::container_info () const -{ - nano::container_info info; - { - nano::lock_guard guard{ observers_mutex }; - info.put ("observers", observers.size ()); - } - { - nano::lock_guard guard{ cache.pulls_cache_mutex }; - info.put ("pulls_cache", cache.cache.size ()); - } - return info; -} - -void nano::pulls_cache::add (nano::pull_info const & pull_a) -{ - if (pull_a.processed > 500) - { - nano::lock_guard guard{ pulls_cache_mutex }; - // Clean old pull - if (cache.size () > cache_size_max) - { - cache.erase (cache.begin ()); - } - debug_assert (cache.size () <= cache_size_max); - nano::uint512_union head_512 (pull_a.account_or_head.as_union (), pull_a.head_original); - auto existing (cache.get ().find (head_512)); - if (existing == cache.get ().end ()) - { - // Insert new pull - auto inserted (cache.emplace (nano::cached_pulls{ std::chrono::steady_clock::now (), head_512, pull_a.head })); - (void)inserted; - debug_assert (inserted.second); - } - else - { - // Update existing pull - cache.get ().modify (existing, [pull_a] (nano::cached_pulls & cache_a) { - cache_a.time = std::chrono::steady_clock::now (); - cache_a.new_head = pull_a.head; - }); - } - } -} - -void nano::pulls_cache::update_pull (nano::pull_info & pull_a) -{ - nano::lock_guard guard{ pulls_cache_mutex }; - nano::uint512_union head_512 (pull_a.account_or_head.as_union (), pull_a.head_original); - auto existing (cache.get ().find (head_512)); - if (existing != cache.get ().end ()) - { - pull_a.head = existing->new_head; - } -} - -void nano::pulls_cache::remove (nano::pull_info const & pull_a) -{ - nano::lock_guard guard{ pulls_cache_mutex }; - nano::uint512_union head_512 (pull_a.account_or_head.as_union (), pull_a.head_original); - cache.get ().erase (head_512); -} - -void nano::bootstrap_attempts::add (std::shared_ptr attempt_a) -{ - nano::lock_guard lock{ bootstrap_attempts_mutex }; - attempts.emplace (attempt_a->incremental_id, attempt_a); -} - -void nano::bootstrap_attempts::remove (uint64_t incremental_id_a) -{ - nano::lock_guard lock{ bootstrap_attempts_mutex }; - attempts.erase (incremental_id_a); -} - -void nano::bootstrap_attempts::clear () -{ - nano::lock_guard lock{ bootstrap_attempts_mutex }; - attempts.clear (); -} - -std::shared_ptr nano::bootstrap_attempts::find (uint64_t incremental_id_a) -{ - nano::lock_guard lock{ bootstrap_attempts_mutex }; - auto find_attempt (attempts.find (incremental_id_a)); - if (find_attempt != attempts.end ()) - { - return find_attempt->second; - } - else - { - return nullptr; - } -} - -std::size_t nano::bootstrap_attempts::size () -{ - nano::lock_guard lock{ bootstrap_attempts_mutex }; - return attempts.size (); -} diff --git a/nano/node/bootstrap/bootstrap.hpp b/nano/node/bootstrap/bootstrap.hpp deleted file mode 100644 index f06f83196b..0000000000 --- a/nano/node/bootstrap/bootstrap.hpp +++ /dev/null @@ -1,157 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace mi = boost::multi_index; - -namespace nano::store -{ -class transaction; -} - -namespace nano -{ -class node; - -class bootstrap_connections; -namespace transport -{ - class tcp_channel; -} -enum class bootstrap_mode -{ - legacy, - lazy, - wallet_lazy, - ascending -}; -enum class sync_result -{ - success, - error, - fork -}; -class cached_pulls final -{ -public: - std::chrono::steady_clock::time_point time; - nano::uint512_union account_head; - nano::block_hash new_head; -}; -class pulls_cache final -{ -public: - void add (nano::pull_info const &); - void update_pull (nano::pull_info &); - void remove (nano::pull_info const &); - mutable nano::mutex pulls_cache_mutex; - class account_head_tag - { - }; - // clang-format off - boost::multi_index_container>, - mi::hashed_unique, - mi::member>>> - cache; - // clang-format on - constexpr static std::size_t cache_size_max = 10000; -}; - -/** - * Container for bootstrap sessions that are active. Owned by bootstrap_initiator. - */ -class bootstrap_attempts final -{ -public: - void add (std::shared_ptr); - void remove (uint64_t); - void clear (); - std::shared_ptr find (uint64_t); - std::size_t size (); - std::atomic incremental{ 0 }; - nano::mutex bootstrap_attempts_mutex; - std::map> attempts; -}; - -class bootstrap_attempt_lazy; -class bootstrap_attempt_wallet; -/** - * Client side portion to initiate bootstrap sessions. Prevents multiple legacy-type bootstrap sessions from being started at the same time. Does permit - * lazy/wallet bootstrap sessions to overlap with legacy sessions. - */ -class bootstrap_initiator final -{ -public: - explicit bootstrap_initiator (nano::node &); - ~bootstrap_initiator (); - void bootstrap (nano::endpoint const &, bool add_to_peers = true, std::string id_a = ""); - void bootstrap (bool force = false, std::string id_a = "", uint32_t const frontiers_age_a = std::numeric_limits::max (), nano::account const & start_account_a = nano::account{}); - bool bootstrap_lazy (nano::hash_or_account const &, bool force = false, std::string id_a = ""); - void bootstrap_wallet (std::deque &); - void run_bootstrap (); - void lazy_requeue (nano::block_hash const &, nano::block_hash const &); - void notify_listeners (bool); - void add_observer (std::function const &); - bool in_progress (); - void block_processed (store::transaction const & tx, nano::block_status const & result, nano::block const & block); - std::shared_ptr connections; - std::shared_ptr new_attempt (); - bool has_new_attempts (); - void remove_attempt (std::shared_ptr); - std::shared_ptr current_attempt (); - std::shared_ptr current_lazy_attempt (); - std::shared_ptr current_wallet_attempt (); - nano::pulls_cache cache; - nano::bootstrap_attempts attempts; - void stop (); - nano::container_info container_info () const; - -private: - nano::node & node; - std::shared_ptr find_attempt (nano::bootstrap_mode); - void stop_attempts (); - std::vector> attempts_list; - std::atomic stopped{ false }; - mutable nano::mutex mutex; - nano::condition_variable condition; - mutable nano::mutex observers_mutex; - std::vector> observers; - std::vector bootstrap_initiator_threads; -}; - -/** - * Defines the numeric values for the bootstrap feature. - */ -class bootstrap_limits final -{ -public: - static constexpr double bootstrap_connection_scale_target_blocks = 10000.0; - static constexpr double bootstrap_connection_warmup_time_sec = 5.0; - static constexpr double bootstrap_minimum_blocks_per_sec = 10.0; - static constexpr double bootstrap_minimum_elapsed_seconds_blockrate = 0.02; - static constexpr double bootstrap_minimum_frontier_blocks_per_sec = 1000.0; - static constexpr double bootstrap_minimum_termination_time_sec = 30.0; - static constexpr unsigned bootstrap_max_new_connections = 32; - static constexpr unsigned requeued_pulls_limit = 256; - static constexpr unsigned requeued_pulls_limit_dev = 1; - static constexpr unsigned requeued_pulls_processed_blocks_factor = 4096; - static constexpr uint64_t pull_count_per_check = 8 * 1024; - static constexpr unsigned bulk_push_cost_limit = 200; - static constexpr std::chrono::seconds lazy_flush_delay_sec = std::chrono::seconds (5); - static constexpr uint64_t lazy_batch_pull_count_resize_blocks_limit = 4 * 1024 * 1024; - static constexpr double lazy_batch_pull_count_resize_ratio = 2.0; - static constexpr std::size_t lazy_blocks_restart_limit = 1024 * 1024; -}; -} diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp deleted file mode 100644 index e40a9e0f89..0000000000 --- a/nano/node/bootstrap/bootstrap_attempt.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include - -constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit; -constexpr unsigned nano::bootstrap_limits::requeued_pulls_limit_dev; - -nano::bootstrap_attempt::bootstrap_attempt (std::shared_ptr const & node_a, nano::bootstrap_mode mode_a, uint64_t incremental_id_a, std::string id_a) : - node (node_a), - incremental_id (incremental_id_a), - id (id_a), - mode (mode_a) -{ - if (id.empty ()) - { - id = nano::hardened_constants::get ().random_128.to_string (); - } - - node_a->logger.debug (nano::log::type::bootstrap, "Starting bootstrap attempt with ID: {} (mode: {})", mode_text (), id); - - node_a->bootstrap_initiator.notify_listeners (true); - if (node_a->websocket.server) - { - nano::websocket::message_builder builder; - node_a->websocket.server->broadcast (builder.bootstrap_started (id, mode_text ())); - } -} - -nano::bootstrap_attempt::~bootstrap_attempt () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - - node->logger.debug (nano::log::type::bootstrap, "Exiting bootstrap attempt with ID: {} (mode: {})", mode_text (), id); - - node->bootstrap_initiator.notify_listeners (false); - if (node->websocket.server) - { - nano::websocket::message_builder builder; - node->websocket.server->broadcast (builder.bootstrap_exited (id, mode_text (), attempt_start, total_blocks)); - } -} - -bool nano::bootstrap_attempt::should_log () -{ - nano::lock_guard guard{ next_log_mutex }; - auto result (false); - auto now (std::chrono::steady_clock::now ()); - if (next_log < now) - { - result = true; - next_log = now + std::chrono::seconds (15); - } - return result; -} - -bool nano::bootstrap_attempt::still_pulling () -{ - debug_assert (!mutex.try_lock ()); - auto running (!stopped); - auto still_pulling (pulling > 0); - return running && still_pulling; -} - -void nano::bootstrap_attempt::pull_started () -{ - { - nano::lock_guard guard{ mutex }; - ++pulling; - } - condition.notify_all (); -} - -void nano::bootstrap_attempt::pull_finished () -{ - { - nano::lock_guard guard{ mutex }; - --pulling; - } - condition.notify_all (); -} - -void nano::bootstrap_attempt::stop () -{ - { - nano::lock_guard lock{ mutex }; - stopped = true; - } - condition.notify_all (); - auto node_l = node.lock (); - if (!node_l) - { - return; - } - node_l->bootstrap_initiator.connections->clear_pulls (incremental_id); -} - -char const * nano::bootstrap_attempt::mode_text () -{ - switch (mode) - { - case nano::bootstrap_mode::legacy: - return "legacy"; - case nano::bootstrap_mode::lazy: - return "lazy"; - case nano::bootstrap_mode::wallet_lazy: - return "wallet_lazy"; - case nano::bootstrap_mode::ascending: - return "ascending"; - } - return "unknown"; -} - -bool nano::bootstrap_attempt::process_block (std::shared_ptr const & block_a, nano::account const & known_account_a, uint64_t pull_blocks_processed, nano::bulk_pull::count_t max_blocks, bool block_expected, unsigned retry_limit) -{ - auto node_l = node.lock (); - if (!node_l) - { - return true; - } - bool stop_pull (false); - // If block already exists in the ledger, then we can avoid next part of long account chain - if (pull_blocks_processed % nano::bootstrap_limits::pull_count_per_check == 0 && node_l->block_or_pruned_exists (block_a->hash ())) - { - stop_pull = true; - } - else - { - node_l->block_processor.add (block_a, nano::block_source::bootstrap_legacy); - } - return stop_pull; -} - -void nano::bootstrap_attempt::block_processed (store::transaction const & tx, nano::block_status const & result, nano::block const & block) -{ -} diff --git a/nano/node/bootstrap/bootstrap_attempt.hpp b/nano/node/bootstrap/bootstrap_attempt.hpp deleted file mode 100644 index ea64fe9fd0..0000000000 --- a/nano/node/bootstrap/bootstrap_attempt.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace nano::store -{ -class transaction; -} - -namespace nano -{ -class node; - -class frontier_req_client; -class bulk_push_client; - -/** - * Polymorphic base class for bootstrap sessions. - */ -class bootstrap_attempt : public std::enable_shared_from_this -{ -public: - explicit bootstrap_attempt (std::shared_ptr const & node_a, nano::bootstrap_mode mode_a, uint64_t incremental_id_a, std::string id_a); - virtual ~bootstrap_attempt (); - virtual void run () = 0; - virtual void stop (); - bool still_pulling (); - void pull_started (); - void pull_finished (); - bool should_log (); - char const * mode_text (); - virtual bool process_block (std::shared_ptr const &, nano::account const &, uint64_t, nano::bulk_pull::count_t, bool, unsigned); - virtual void get_information (boost::property_tree::ptree &) = 0; - virtual void block_processed (store::transaction const & tx, nano::block_status const & result, nano::block const & block); - nano::mutex next_log_mutex; - std::chrono::steady_clock::time_point next_log{ std::chrono::steady_clock::now () }; - std::atomic pulling{ 0 }; - std::weak_ptr node; - std::atomic total_blocks{ 0 }; - std::atomic requeued_pulls{ 0 }; - std::atomic started{ false }; - std::atomic stopped{ false }; - uint64_t incremental_id{ 0 }; - std::string id; - std::chrono::steady_clock::time_point attempt_start{ std::chrono::steady_clock::now () }; - std::atomic frontiers_received{ false }; - nano::bootstrap_mode mode; - nano::mutex mutex; - nano::condition_variable condition; -}; -} diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.cpp b/nano/node/bootstrap/bootstrap_bulk_pull.cpp deleted file mode 100644 index f17b08f939..0000000000 --- a/nano/node/bootstrap/bootstrap_bulk_pull.cpp +++ /dev/null @@ -1,912 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -nano::pull_info::pull_info (nano::hash_or_account const & account_or_head_a, nano::block_hash const & head_a, nano::block_hash const & end_a, uint64_t bootstrap_id_a, count_t count_a, unsigned retry_limit_a) : - account_or_head (account_or_head_a), - head (head_a), - head_original (head_a), - end (end_a), - count (count_a), - retry_limit (retry_limit_a), - bootstrap_id (bootstrap_id_a) -{ -} - -nano::bulk_pull_client::bulk_pull_client (std::shared_ptr const & connection_a, std::shared_ptr const & attempt_a, nano::pull_info const & pull_a) : - connection{ connection_a }, - attempt{ attempt_a }, - pull{ pull_a }, - block_deserializer{ std::make_shared () } -{ - attempt->condition.notify_all (); -} - -nano::bulk_pull_client::~bulk_pull_client () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - /* If received end block is not expected end block - Or if given start and end blocks are from different chains (i.e. forked node or malicious node) */ - if (expected != pull.end && !expected.is_zero ()) - { - pull.head = expected; - if (attempt->mode != nano::bootstrap_mode::legacy) - { - pull.account_or_head = expected; - } - pull.processed += pull_blocks - unexpected_count; - node->bootstrap_initiator.connections->requeue_pull (pull, network_error); - - node->logger.debug (nano::log::type::bulk_pull_client, "Bulk pull end block is not expected {} for account {} or head block {}", pull.end.to_string (), pull.account_or_head.to_account (), pull.account_or_head.to_string ()); - } - else - { - node->bootstrap_initiator.cache.remove (pull); - } - attempt->pull_finished (); -} - -void nano::bulk_pull_client::request () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - debug_assert (!pull.head.is_zero () || pull.retry_limit <= node->network_params.bootstrap.lazy_retry_limit); - expected = pull.head; - nano::bulk_pull req{ node->network_params.network }; - if (pull.head == pull.head_original && pull.attempts % 4 < 3) - { - // Account for new pulls - req.start = pull.account_or_head; - } - else - { - // Head for cached pulls or accounts with public key equal to existing block hash (25% of attempts) - req.start = pull.head; - } - req.end = pull.end; - req.count = pull.count; - req.set_count_present (pull.count != 0); - - node->logger.trace (nano::log::type::bulk_pull_client, nano::log::detail::requesting_account_or_head, - nano::log::arg{ "account_or_head", pull.account_or_head }, - nano::log::arg{ "channel", connection->channel }); - - if (attempt->should_log ()) - { - node->logger.debug (nano::log::type::bulk_pull_client, "Accounts in pull queue: {}", attempt->pulling.load ()); - } - - auto this_l (shared_from_this ()); - connection->channel->send ( - req, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->throttled_receive_block (); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_client, "Error sending bulk pull request to: {} ({})", this_l->connection->channel->to_string (), ec.message ()); - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_request_failure, nano::stat::dir::in); - } - }, - nano::transport::buffer_drop_policy::no_limiter_drop); -} - -void nano::bulk_pull_client::throttled_receive_block () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - debug_assert (!network_error); - if (node->block_processor.size (nano::block_source::bootstrap_legacy) < 1024 && !node->block_processor.flushing) - { - receive_block (); - } - else - { - auto this_l (shared_from_this ()); - node->workers.post_delayed (std::chrono::seconds (1), [this_l] () { - if (!this_l->connection->pending_stop && !this_l->attempt->stopped) - { - this_l->throttled_receive_block (); - } - }); - } -} - -void nano::bulk_pull_client::receive_block () -{ - block_deserializer->read (*connection->socket, [this_l = shared_from_this ()] (boost::system::error_code ec, std::shared_ptr block) { - this_l->received_block (ec, block); - }); -} - -void nano::bulk_pull_client::received_block (boost::system::error_code ec, std::shared_ptr block) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (ec) - { - network_error = true; - return; - } - if (block == nullptr) - { - // Avoid re-using slow peers, or peers that sent the wrong blocks. - if (!connection->pending_stop && (expected == pull.end || (pull.count != 0 && pull.count == pull_blocks))) - { - node->bootstrap_initiator.connections->pool_connection (connection); - } - return; - } - if (node->network_params.work.validate_entry (*block)) - { - node->logger.debug (nano::log::type::bulk_pull_client, "Insufficient work for bulk pull block: {}", block->hash ().to_string ()); - node->stats.inc (nano::stat::type::error, nano::stat::detail::insufficient_work); - return; - } - auto hash = block->hash (); - - node->logger.trace (nano::log::type::bulk_pull_client, nano::log::detail::pulled_block, nano::log::arg{ "block", block }); - - // Is block expected? - bool block_expected (false); - // Unconfirmed head is used only for lazy destinations if legacy bootstrap is not available, see nano::bootstrap_attempt::lazy_destinations_increment (...) - bool unconfirmed_account_head = node->flags.disable_legacy_bootstrap && pull_blocks == 0 && pull.retry_limit <= node->network_params.bootstrap.lazy_retry_limit && (expected == pull.account_or_head.as_block_hash ()) && (block->account_field ().value_or (0) == pull.account_or_head.as_account ()); - if (hash == expected || unconfirmed_account_head) - { - expected = block->previous (); - block_expected = true; - } - else - { - unexpected_count++; - } - if (pull_blocks == 0 && block_expected) - { - known_account = block->account_field ().value_or (0); - } - if (connection->block_count++ == 0) - { - connection->set_start_time (std::chrono::steady_clock::now ()); - } - attempt->total_blocks++; - pull_blocks++; - bool stop_pull (attempt->process_block (block, known_account, pull_blocks, pull.count, block_expected, pull.retry_limit)); - if (!stop_pull && !connection->hard_stop.load ()) - { - /* Process block in lazy pull if not stopped - Stop usual pull request with unexpected block & more than 16k blocks processed - to prevent spam */ - if (attempt->mode != nano::bootstrap_mode::legacy || unexpected_count < 16384) - { - throttled_receive_block (); - } - } - else if (!stop_pull && block_expected) - { - node->bootstrap_initiator.connections->pool_connection (connection); - } -} - -nano::bulk_pull_account_client::bulk_pull_account_client (std::shared_ptr const & connection_a, std::shared_ptr const & attempt_a, nano::account const & account_a) : - connection (connection_a), - attempt (attempt_a), - account (account_a), - pull_blocks (0) -{ - attempt->condition.notify_all (); -} - -nano::bulk_pull_account_client::~bulk_pull_account_client () -{ - attempt->pull_finished (); -} - -void nano::bulk_pull_account_client::request () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - nano::bulk_pull_account req{ node->network_params.network }; - req.account = account; - req.minimum_amount = node->config.receive_minimum; - req.flags = nano::bulk_pull_account_flags::pending_hash_and_amount; - - node->logger.trace (nano::log::type::bulk_pull_account_client, nano::log::detail::requesting_pending, - nano::log::arg{ "account", req.account.to_account () }, // TODO: Convert to lazy eval - nano::log::arg{ "connection", connection->channel }); - - if (attempt->should_log ()) - { - node->logger.debug (nano::log::type::bulk_pull_account_client, "Accounts in pull queue: {}", attempt->wallet_size ()); - } - - auto this_l (shared_from_this ()); - connection->channel->send ( - req, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->receive_pending (); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_client, "Error starting bulk pull request to: {} ({})", this_l->connection->channel->to_string (), ec.message ()); - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_error_starting_request, nano::stat::dir::in); - - this_l->attempt->requeue_pending (this_l->account); - } - }, - nano::transport::buffer_drop_policy::no_limiter_drop); -} - -void nano::bulk_pull_account_client::receive_pending () -{ - auto this_l (shared_from_this ()); - std::size_t size_l (sizeof (nano::uint256_union) + sizeof (nano::uint128_union)); - connection->socket->async_read (connection->receive_buffer, size_l, [this_l, size_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - // An issue with asio is that sometimes, instead of reporting a bad file descriptor during disconnect, - // we simply get a size of 0. - if (size_a == size_l) - { - if (!ec) - { - nano::block_hash pending; - nano::bufferstream frontier_stream (this_l->connection->receive_buffer->data (), sizeof (nano::uint256_union)); - auto error1 (nano::try_read (frontier_stream, pending)); - (void)error1; - debug_assert (!error1); - nano::amount balance; - nano::bufferstream balance_stream (this_l->connection->receive_buffer->data () + sizeof (nano::uint256_union), sizeof (nano::uint128_union)); - auto error2 (nano::try_read (balance_stream, balance)); - (void)error2; - debug_assert (!error2); - if (this_l->pull_blocks == 0 || !pending.is_zero ()) - { - if (this_l->pull_blocks == 0 || balance.number () >= node->config.receive_minimum.number ()) - { - this_l->pull_blocks++; - { - if (!pending.is_zero ()) - { - if (!node->block_or_pruned_exists (pending)) - { - node->bootstrap_initiator.bootstrap_lazy (pending, false); - } - } - } - this_l->receive_pending (); - } - else - { - this_l->attempt->requeue_pending (this_l->account); - } - } - else - { - node->bootstrap_initiator.connections->pool_connection (this_l->connection); - } - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_client, "Error while receiving bulk pull account frontier: {}", ec.message ()); - - this_l->attempt->requeue_pending (this_l->account); - } - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_client, "Invalid size: Expected {}, got: {}", size_l, size_a); - - this_l->attempt->requeue_pending (this_l->account); - } - }); -} - -/** - * Handle a request for the pull of all blocks associated with an account - * The account is supplied as the "start" member, and the final block to - * send is the "end" member. The "start" member may also be a block - * hash, in which case the that hash is used as the start of a chain - * to send. To determine if "start" is interpreted as an account or - * hash, the ledger is checked to see if the block specified exists, - * if not then it is interpreted as an account. - * - * Additionally, if "start" is specified as a block hash the range - * is inclusive of that block hash, that is the range will be: - * [start, end); In the case that a block hash is not specified the - * range will be exclusive of the frontier for that account with - * a range of (frontier, end) - */ -void nano::bulk_pull_server::set_current_end () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - include_start = false; - debug_assert (request != nullptr); - auto transaction = node->ledger.tx_begin_read (); - if (!node->ledger.any.block_exists (transaction, request->end)) - { - node->logger.debug (nano::log::type::bulk_pull_server, "Bulk pull end block doesn't exist: {}, sending everything", request->end.to_string ()); - - request->end.clear (); - } - - if (node->ledger.any.block_exists (transaction, request->start.as_block_hash ())) - { - node->logger.debug (nano::log::type::bulk_pull_server, "Bulk pull request for block hash: {}", request->start.to_string ()); - - current = ascending () ? node->ledger.any.block_successor (transaction, request->start.as_block_hash ()).value_or (0) : request->start.as_block_hash (); - include_start = true; - } - else - { - auto info = node->ledger.any.account_get (transaction, request->start.as_account ()); - if (!info) - { - node->logger.debug (nano::log::type::bulk_pull_server, "Request for unknown account: {}", request->start.to_account ()); - - current = request->end; - } - else - { - current = ascending () ? info->open_block : info->head; - if (!request->end.is_zero ()) - { - auto account (node->ledger.any.block_account (transaction, request->end)); - if (account.value_or (0) != request->start.as_account ()) - { - node->logger.debug (nano::log::type::bulk_pull_server, "Request for block that is not on account chain: {} not on {}", request->end.to_string (), request->start.to_account ()); - - current = request->end; - } - } - } - } - - sent_count = 0; - if (request->is_count_present ()) - { - max_count = request->count; - } - else - { - max_count = 0; - } -} - -void nano::bulk_pull_server::send_next () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - auto block = get_next (); - if (block != nullptr) - { - node->logger.trace (nano::log::type::bulk_pull_server, nano::log::detail::sending_block, - nano::log::arg{ "block", block }, - nano::log::arg{ "socket", connection->socket }); - - std::vector send_buffer; - { - nano::vectorstream stream (send_buffer); - nano::serialize_block (stream, *block); - } - - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l = shared_from_this ()] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->sent_action (ec, size_a); - }); - } - else - { - send_finished (); - } -} - -std::shared_ptr nano::bulk_pull_server::get_next () -{ - auto node = connection->node.lock (); - if (!node) - { - return nullptr; - } - std::shared_ptr result; - bool send_current = false, set_current_to_end = false; - - /* - * Determine if we should reply with a block - * - * If our cursor is on the final block, we should signal that we - * are done by returning a null result. - * - * Unless we are including the "start" member and this is the - * start member, then include it anyway. - */ - if (current != request->end) - { - send_current = true; - } - else if (current == request->end && include_start == true) - { - send_current = true; - - /* - * We also need to ensure that the next time - * are invoked that we return a null result - */ - set_current_to_end = true; - } - - /* - * Account for how many blocks we have provided. If this - * exceeds the requested maximum, return an empty object - * to signal the end of results - */ - if (max_count != 0 && sent_count >= max_count) - { - send_current = false; - } - - if (send_current) - { - result = node->block (current); - if (result != nullptr && set_current_to_end == false) - { - auto next = ascending () ? result->sideband ().successor : result->previous (); - if (!next.is_zero ()) - { - current = next; - } - else - { - current = request->end; - } - } - else - { - current = request->end; - } - - sent_count++; - } - - /* - * Once we have processed "get_next()" once our cursor is no longer on - * the "start" member, so this flag is not relevant is always false. - */ - include_start = false; - - return result; -} - -void nano::bulk_pull_server::sent_action (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - node->bootstrap_workers.post ([this_l = shared_from_this ()] () { - this_l->send_next (); - }); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_server, "Unable to bulk send block: {}", ec.message ()); - } -} - -void nano::bulk_pull_server::send_finished () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - nano::shared_const_buffer send_buffer (static_cast (nano::block_type::not_a_block)); - auto this_l (shared_from_this ()); - - node->logger.debug (nano::log::type::bulk_pull_server, "Bulk sending finished"); - - connection->socket->async_write (send_buffer, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->no_block_sent (ec, size_a); - }); -} - -void nano::bulk_pull_server::no_block_sent (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - debug_assert (size_a == 1); - connection->start (); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_server, "Unable to bulk send not-a-block: {}", ec.message ()); - } -} - -bool nano::bulk_pull_server::ascending () const -{ - return request->header.bulk_pull_ascending (); -} - -nano::bulk_pull_server::bulk_pull_server (std::shared_ptr const & connection_a, std::unique_ptr request_a) : - connection (connection_a), - request (std::move (request_a)) -{ - set_current_end (); -} - -/** - * Bulk pull blocks related to an account - */ -void nano::bulk_pull_account_server::set_params () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - debug_assert (request != nullptr); - - /* - * Parse the flags - */ - invalid_request = false; - pending_include_address = false; - pending_address_only = false; - if (request->flags == nano::bulk_pull_account_flags::pending_address_only) - { - pending_address_only = true; - } - else if (request->flags == nano::bulk_pull_account_flags::pending_hash_amount_and_address) - { - /** - ** This is the same as "pending_hash_and_amount" but with the - ** sending address appended, for UI purposes mainly. - **/ - pending_include_address = true; - } - else if (request->flags == nano::bulk_pull_account_flags::pending_hash_and_amount) - { - /** The defaults are set above **/ - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_server, "Invalid bulk_pull_account flags supplied: {}", static_cast (request->flags)); - - invalid_request = true; - return; - } - - /* - * Initialize the current item from the requested account - */ - current_key.account = request->account; - current_key.hash = 0; -} - -void nano::bulk_pull_account_server::send_frontier () -{ - /* - * This function is really the entry point into this class, - * so handle the invalid_request case by terminating the - * request without any response - */ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!invalid_request) - { - auto stream_transaction = node->ledger.tx_begin_read (); - - // Get account balance and frontier block hash - auto account_frontier_hash (node->ledger.any.account_head (stream_transaction, request->account)); - auto account_frontier_balance_int (node->ledger.any.account_balance (stream_transaction, request->account).value_or (0)); - nano::uint128_union account_frontier_balance (account_frontier_balance_int); - - // Write the frontier block hash and balance into a buffer - std::vector send_buffer; - { - nano::vectorstream output_stream (send_buffer); - write (output_stream, account_frontier_hash.bytes); - write (output_stream, account_frontier_balance.bytes); - } - - // Send the buffer to the requestor - auto this_l (shared_from_this ()); - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->sent_action (ec, size_a); - }); - } -} - -void nano::bulk_pull_account_server::send_next_block () -{ - /* - * Get the next item from the queue, it is a tuple with the key (which - * contains the account and hash) and data (which contains the amount) - */ - auto node = connection->node.lock (); - if (!node) - { - return; - } - auto block_data (get_next ()); - auto block_info_key (block_data.first.get ()); - auto block_info (block_data.second.get ()); - - if (block_info_key != nullptr) - { - /* - * If we have a new item, emit it to the socket - */ - - std::vector send_buffer; - if (pending_address_only) - { - node->logger.trace (nano::log::type::bulk_pull_account_server, nano::log::detail::sending_pending, - nano::log::arg{ "pending", block_info->source }); - - nano::vectorstream output_stream (send_buffer); - write (output_stream, block_info->source.bytes); - } - else - { - node->logger.trace (nano::log::type::bulk_pull_account_server, nano::log::detail::sending_block, - nano::log::arg{ "block", block_info_key->hash }); - - nano::vectorstream output_stream (send_buffer); - write (output_stream, block_info_key->hash.bytes); - write (output_stream, block_info->amount.bytes); - - if (pending_include_address) - { - /** - ** Write the source address as well, if requested - **/ - write (output_stream, block_info->source.bytes); - } - } - - auto this_l (shared_from_this ()); - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->sent_action (ec, size_a); - }); - } - else - { - /* - * Otherwise, finalize the connection - */ - node->logger.debug (nano::log::type::bulk_pull_account_server, "Done sending blocks"); - - send_finished (); - } -} - -std::pair, std::unique_ptr> nano::bulk_pull_account_server::get_next () -{ - auto node = connection->node.lock (); - if (!node) - { - return { nullptr, nullptr }; - } - std::pair, std::unique_ptr> result; - - while (true) - { - /* - * For each iteration of this loop, establish and then - * destroy a database transaction, to avoid locking the - * database for a prolonged period. - */ - auto tx = node->ledger.tx_begin_read (); - auto & ledger = node->ledger; - auto stream = ledger.any.receivable_upper_bound (tx, current_key.account, current_key.hash); - - if (stream == ledger.any.receivable_end ()) - { - break; - } - auto const & [key, info] = *stream; - current_key = key; - - /* - * Skip entries where the amount is less than the requested - * minimum - */ - if (info.amount < request->minimum_amount) - { - continue; - } - - /* - * If the pending_address_only flag is set, de-duplicate the - * responses. The responses are the address of the sender, - * so they are part of the pending table's information - * and not key, so we have to de-duplicate them manually. - */ - if (pending_address_only) - { - if (!deduplication.insert (info.source).second) - { - /* - * If the deduplication map gets too - * large, clear it out. This may - * result in some duplicates getting - * sent to the client, but we do not - * want to commit too much memory - */ - if (deduplication.size () > 4096) - { - deduplication.clear (); - } - continue; - } - } - - result.first = std::make_unique (key); - result.second = std::make_unique (info); - - break; - } - - return result; -} - -void nano::bulk_pull_account_server::sent_action (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - node->bootstrap_workers.post ([this_l = shared_from_this ()] () { - this_l->send_next_block (); - }); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_server, "Unable to bulk send block: {}", ec.message ()); - } -} - -void nano::bulk_pull_account_server::send_finished () -{ - /* - * The "bulk_pull_account" final sequence is a final block of all - * zeros. If we are sending only account public keys (with the - * "pending_address_only" flag) then it will be 256-bits of zeros, - * otherwise it will be either 384-bits of zeros (if the - * "pending_include_address" flag is not set) or 640-bits of zeros - * (if that flag is set). - */ - auto node = connection->node.lock (); - if (!node) - { - return; - } - std::vector send_buffer; - { - nano::vectorstream output_stream (send_buffer); - nano::uint256_union account_zero (0); - nano::uint128_union balance_zero (0); - - write (output_stream, account_zero.bytes); - - if (!pending_address_only) - { - write (output_stream, balance_zero.bytes); - if (pending_include_address) - { - write (output_stream, account_zero.bytes); - } - } - } - - node->logger.debug (nano::log::type::bulk_pull_account_server, "Bulk sending for an account finished"); - - auto this_l (shared_from_this ()); - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->complete (ec, size_a); - }); -} - -void nano::bulk_pull_account_server::complete (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - if (pending_address_only) - { - debug_assert (size_a == 32); - } - else - { - if (pending_include_address) - { - debug_assert (size_a == 80); - } - else - { - debug_assert (size_a == 48); - } - } - - connection->start (); - } - else - { - node->logger.debug (nano::log::type::bulk_pull_account_server, "Unable to pending-as-zero: {}", ec.message ()); - } -} - -nano::bulk_pull_account_server::bulk_pull_account_server (std::shared_ptr const & connection_a, std::unique_ptr request_a) : - connection (connection_a), - request (std::move (request_a)), - current_key (0, 0) -{ - /* - * Setup the streaming response for the first call to "send_frontier" and "send_next_block" - */ - set_params (); -} diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.hpp b/nano/node/bootstrap/bootstrap_bulk_pull.hpp deleted file mode 100644 index a2e410db48..0000000000 --- a/nano/node/bootstrap/bootstrap_bulk_pull.hpp +++ /dev/null @@ -1,140 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace nano -{ -class bootstrap_attempt; -namespace transport -{ - class tcp_server; -} -namespace bootstrap -{ - class block_deserializer; -}; - -class pull_info -{ -public: - using count_t = nano::bulk_pull::count_t; - pull_info () = default; - pull_info (nano::hash_or_account const &, nano::block_hash const &, nano::block_hash const &, uint64_t, count_t = 0, unsigned = 16); - nano::hash_or_account account_or_head{ 0 }; - nano::block_hash head{ 0 }; - nano::block_hash head_original{ 0 }; - nano::block_hash end{ 0 }; - count_t count{ 0 }; - unsigned attempts{ 0 }; - uint64_t processed{ 0 }; - unsigned retry_limit{ 0 }; - uint64_t bootstrap_id{ 0 }; -}; -class bootstrap_client; - -/** - * Client side of a bulk_pull request. Created when the bootstrap_attempt wants to make a bulk_pull request to the remote side. - */ -class bulk_pull_client final : public std::enable_shared_from_this -{ -public: - bulk_pull_client (std::shared_ptr const &, std::shared_ptr const &, nano::pull_info const &); - ~bulk_pull_client (); - void request (); - void receive_block (); - void throttled_receive_block (); - void received_block (boost::system::error_code ec, std::shared_ptr block); - nano::block_hash first (); - std::shared_ptr connection; - std::shared_ptr attempt; - bool network_error{ false }; - -private: - /** - * Tracks the next block expected to be received starting with the block hash that was expected and followed by previous blocks for this account chain - */ - nano::block_hash expected{ 0 }; - /** - * Tracks the account number for this account chain - * Used when an account chain has a mix between state blocks and legacy blocks which do not encode the account number in the block - * 0 if the account is unknown - */ - nano::account known_account{ 0 }; - /** - * Original pull request - */ - nano::pull_info pull; - /** - * Tracks the number of blocks successfully deserialized - */ - uint64_t pull_blocks{ 0 }; - /** - * Tracks the number of times an unexpected block was received - */ - uint64_t unexpected_count{ 0 }; - std::shared_ptr block_deserializer; -}; -class bootstrap_attempt_wallet; -class bulk_pull_account_client final : public std::enable_shared_from_this -{ -public: - bulk_pull_account_client (std::shared_ptr const &, std::shared_ptr const &, nano::account const &); - ~bulk_pull_account_client (); - void request (); - void receive_pending (); - std::shared_ptr connection; - std::shared_ptr attempt; - nano::account account; - uint64_t pull_blocks; -}; - -class bulk_pull; - -/** - * Server side of a bulk_pull request. Created when tcp_server receives a bulk_pull message and is exited after the contents - * have been sent. If the 'start' in the bulk_pull message is an account, send blocks for that account down to 'end'. If the 'start' - * is a block hash, send blocks for that chain down to 'end'. If end doesn't exist, send all accounts in the chain. - */ -class bulk_pull_server final : public std::enable_shared_from_this -{ -public: - bulk_pull_server (std::shared_ptr const &, std::unique_ptr); - void set_current_end (); - std::shared_ptr get_next (); - void send_next (); - void sent_action (boost::system::error_code const &, std::size_t); - void send_finished (); - void no_block_sent (boost::system::error_code const &, std::size_t); - bool ascending () const; - std::shared_ptr connection; - std::unique_ptr request; - nano::block_hash current; - bool include_start; - nano::bulk_pull::count_t max_count; - nano::bulk_pull::count_t sent_count; -}; -class bulk_pull_account; -class bulk_pull_account_server final : public std::enable_shared_from_this -{ -public: - bulk_pull_account_server (std::shared_ptr const &, std::unique_ptr); - void set_params (); - std::pair, std::unique_ptr> get_next (); - void send_frontier (); - void send_next_block (); - void sent_action (boost::system::error_code const &, std::size_t); - void send_finished (); - void complete (boost::system::error_code const &, std::size_t); - std::shared_ptr connection; - std::unique_ptr request; - std::unordered_set deduplication; - nano::pending_key current_key; - bool pending_address_only; - bool pending_include_address; - bool invalid_request; -}; -} diff --git a/nano/node/bootstrap/bootstrap_bulk_push.cpp b/nano/node/bootstrap/bootstrap_bulk_push.cpp deleted file mode 100644 index c023ec1fe5..0000000000 --- a/nano/node/bootstrap/bootstrap_bulk_push.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -nano::bulk_push_client::bulk_push_client (std::shared_ptr const & connection_a, std::shared_ptr const & attempt_a) : - connection (connection_a), - attempt (attempt_a) -{ -} - -nano::bulk_push_client::~bulk_push_client () -{ -} - -void nano::bulk_push_client::start () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - nano::bulk_push message{ node->network_params.network }; - auto this_l (shared_from_this ()); - connection->channel->send ( - message, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->push (); - } - else - { - node->logger.debug (nano::log::type::bulk_push_client, "Unable to send bulk push request: {}", ec.message ()); - } - }, - nano::transport::buffer_drop_policy::no_limiter_drop); -} - -void nano::bulk_push_client::push () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - std::shared_ptr block; - bool finished (false); - while (block == nullptr && !finished) - { - if (current_target.first.is_zero () || current_target.first == current_target.second) - { - finished = attempt->request_bulk_push_target (current_target); - } - if (!finished) - { - block = node->block (current_target.first); - if (block == nullptr) - { - current_target.first = nano::block_hash (0); - } - else - { - node->logger.debug (nano::log::type::bulk_push_client, "Bulk pushing range: [{}:{}]", current_target.first.to_string (), current_target.second.to_string ()); - } - } - } - if (finished) - { - send_finished (); - } - else - { - current_target.first = block->previous (); - push_block (*block); - } -} - -void nano::bulk_push_client::send_finished () -{ - nano::shared_const_buffer buffer (static_cast (nano::block_type::not_a_block)); - auto this_l (shared_from_this ()); - connection->channel->send_buffer (buffer, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - try - { - this_l->promise.set_value (false); - } - catch (std::future_error &) - { - } - }); -} - -void nano::bulk_push_client::push_block (nano::block const & block_a) -{ - std::vector buffer; - { - nano::vectorstream stream (buffer); - nano::serialize_block (stream, block_a); - } - auto this_l (shared_from_this ()); - connection->channel->send_buffer (nano::shared_const_buffer (std::move (buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->push (); - } - else - { - node->logger.debug (nano::log::type::bulk_push_client, "Error sending block during bulk push: {}", ec.message ()); - } - }); -} - -nano::bulk_push_server::bulk_push_server (std::shared_ptr const & connection_a) : - receive_buffer (std::make_shared> ()), - connection (connection_a) -{ - receive_buffer->resize (256); -} - -void nano::bulk_push_server::throttled_receive () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (node->block_processor.size (nano::block_source::bootstrap_legacy) < 1024) - { - receive (); - } - else - { - auto this_l (shared_from_this ()); - node->workers.post_delayed (std::chrono::seconds (1), [this_l] () { - if (!this_l->connection->stopped) - { - this_l->throttled_receive (); - } - }); - } -} - -void nano::bulk_push_server::receive () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (node->bootstrap_initiator.in_progress ()) - { - node->logger.debug (nano::log::type::bulk_push_server, "Aborting bulk push because a bootstrap attempt is in progress"); - } - else - { - auto this_l (shared_from_this ()); - connection->socket->async_read (receive_buffer, 1, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->received_type (); - } - else - { - node->logger.debug (nano::log::type::bulk_push_server, "Error receiving block type: {}", ec.message ()); - } - }); - } -} - -void nano::bulk_push_server::received_type () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - auto this_l (shared_from_this ()); - nano::block_type type (static_cast (receive_buffer->data ()[0])); - switch (type) - { - case nano::block_type::send: - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::send, nano::stat::dir::in); - connection->socket->async_read (receive_buffer, nano::send_block::size, [this_l, type] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->received_block (ec, size_a, type); - }); - break; - } - case nano::block_type::receive: - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::receive, nano::stat::dir::in); - connection->socket->async_read (receive_buffer, nano::receive_block::size, [this_l, type] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->received_block (ec, size_a, type); - }); - break; - } - case nano::block_type::open: - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::open, nano::stat::dir::in); - connection->socket->async_read (receive_buffer, nano::open_block::size, [this_l, type] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->received_block (ec, size_a, type); - }); - break; - } - case nano::block_type::change: - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::change, nano::stat::dir::in); - connection->socket->async_read (receive_buffer, nano::change_block::size, [this_l, type] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->received_block (ec, size_a, type); - }); - break; - } - case nano::block_type::state: - { - node->stats.inc (nano::stat::type::bootstrap, nano::stat::detail::state_block, nano::stat::dir::in); - connection->socket->async_read (receive_buffer, nano::state_block::size, [this_l, type] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->received_block (ec, size_a, type); - }); - break; - } - case nano::block_type::not_a_block: - { - connection->start (); - break; - } - default: - { - node->logger.debug (nano::log::type::bulk_push_server, "Unknown type received as block type"); - break; - } - } -} - -void nano::bulk_push_server::received_block (boost::system::error_code const & ec, std::size_t size_a, nano::block_type type_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - nano::bufferstream stream (receive_buffer->data (), size_a); - auto block (nano::deserialize_block (stream, type_a)); - if (block != nullptr) - { - if (node->network_params.work.validate_entry (*block)) - { - node->logger.debug (nano::log::type::bulk_push_server, "Insufficient work for bulk push block: {}", block->hash ().to_string ()); - node->stats.inc (nano::stat::type::error, nano::stat::detail::insufficient_work); - return; - } - node->process_active (std::move (block)); - throttled_receive (); - } - else - { - node->logger.debug (nano::log::type::bulk_push_server, "Error deserializing block received from pull request"); - } - } -} diff --git a/nano/node/bootstrap/bootstrap_bulk_push.hpp b/nano/node/bootstrap/bootstrap_bulk_push.hpp deleted file mode 100644 index d40e0fe5a4..0000000000 --- a/nano/node/bootstrap/bootstrap_bulk_push.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -#include - -namespace nano -{ -class bootstrap_attempt_legacy; -class bootstrap_client; -namespace transport -{ - class tcp_server; -} - -/** - * Client side of a bulk_push request. Sends a sequence of blocks the other side did not report in their frontier_req response. - */ -class bulk_push_client final : public std::enable_shared_from_this -{ -public: - explicit bulk_push_client (std::shared_ptr const &, std::shared_ptr const &); - ~bulk_push_client (); - void start (); - void push (); - void push_block (nano::block const &); - void send_finished (); - std::shared_ptr connection; - std::shared_ptr attempt; - std::promise promise; - std::pair current_target; -}; - -/** - * Server side of a bulk_push request. Receives blocks and puts them in the block processor to be processed. - */ -class bulk_push_server final : public std::enable_shared_from_this -{ -public: - explicit bulk_push_server (std::shared_ptr const &); - void throttled_receive (); - void receive (); - void received_type (); - void received_block (boost::system::error_code const &, std::size_t, nano::block_type); - std::shared_ptr> receive_buffer; - std::shared_ptr connection; -}; -} diff --git a/nano/node/bootstrap/bootstrap_config.cpp b/nano/node/bootstrap/bootstrap_config.cpp index ff1e847da3..b3e2e99b0c 100644 --- a/nano/node/bootstrap/bootstrap_config.cpp +++ b/nano/node/bootstrap/bootstrap_config.cpp @@ -26,16 +26,18 @@ nano::error nano::account_sets_config::serialize (nano::tomlconfig & toml) const } /* - * bootstrap_ascending_config + * bootstrap_config */ -nano::error nano::bootstrap_ascending_config::deserialize (nano::tomlconfig & toml) +nano::error nano::bootstrap_config::deserialize (nano::tomlconfig & toml) { toml.get ("enable", enable); toml.get ("enable_database_scan", enable_database_scan); toml.get ("enable_dependency_walker", enable_dependency_walker); + toml.get ("enable_frontier_scan", enable_frontier_scan); toml.get ("channel_limit", channel_limit); + toml.get ("rate_limit", rate_limit); toml.get ("database_rate_limit", database_rate_limit); toml.get ("database_warmup_ratio", database_warmup_ratio); toml.get ("max_pull_count", max_pull_count); @@ -44,6 +46,7 @@ nano::error nano::bootstrap_ascending_config::deserialize (nano::tomlconfig & to toml.get_duration ("throttle_wait", throttle_wait); toml.get ("block_processor_threshold", block_processor_threshold); toml.get ("max_requests", max_requests); + toml.get ("optimistic_request_percentage", optimistic_request_percentage); if (toml.has_key ("account_sets")) { @@ -54,21 +57,24 @@ nano::error nano::bootstrap_ascending_config::deserialize (nano::tomlconfig & to return toml.get_error (); } -nano::error nano::bootstrap_ascending_config::serialize (nano::tomlconfig & toml) const +nano::error nano::bootstrap_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enable, "Enable or disable the ascending bootstrap. Disabling it is not recommended and will prevent the node from syncing.\ntype:bool"); + toml.put ("enable", enable, "Enable or disable the bootstrap. Disabling it is not recommended and will prevent the node from syncing.\ntype:bool"); toml.put ("enable_database_scan", enable_database_scan, "Enable or disable the 'database scan` strategy for the ascending bootstrap.\ntype:bool"); toml.put ("enable_dependency_walker", enable_dependency_walker, "Enable or disable the 'dependency walker` strategy for the ascending bootstrap.\ntype:bool"); + toml.put ("enable_frontier_scan", enable_frontier_scan, "Enable or disable the 'frontier scan` strategy for the ascending bootstrap.\ntype:bool"); toml.put ("channel_limit", channel_limit, "Maximum number of un-responded requests per channel.\nNote: changing to unlimited (0) is not recommended.\ntype:uint64"); + toml.put ("rate_limit", rate_limit, "Rate limit on requests.\nNote: changing to unlimited (0) is not recommended as this operation competes for resources with realtime traffic.\ntype:uint64"); toml.put ("database_rate_limit", database_rate_limit, "Rate limit on scanning accounts and pending entries from database.\nNote: changing to unlimited (0) is not recommended as this operation competes for resources on querying the database.\ntype:uint64"); toml.put ("database_warmup_ratio", database_warmup_ratio, "Ratio of the database rate limit to use for the initial warmup.\ntype:uint64"); - toml.put ("max_pull_count", max_pull_count, "Maximum number of requested blocks for ascending bootstrap request.\ntype:uint64"); - toml.put ("request_timeout", request_timeout.count (), "Timeout in milliseconds for incoming ascending bootstrap messages to be processed.\ntype:milliseconds"); + toml.put ("max_pull_count", max_pull_count, "Maximum number of requested blocks for bootstrap request.\ntype:uint64"); + toml.put ("request_timeout", request_timeout.count (), "Timeout in milliseconds for incoming bootstrap messages to be processed.\ntype:milliseconds"); toml.put ("throttle_coefficient", throttle_coefficient, "Scales the number of samples to track for bootstrap throttling.\ntype:uint64"); toml.put ("throttle_wait", throttle_wait.count (), "Length of time to wait between requests when throttled.\ntype:milliseconds"); - toml.put ("block_processor_threshold", block_processor_threshold, "Ascending bootstrap will wait while block processor has more than this many blocks queued.\ntype:uint64"); + toml.put ("block_processor_threshold", block_processor_threshold, "Bootstrap will wait while block processor has more than this many blocks queued.\ntype:uint64"); toml.put ("max_requests", max_requests, "Maximum total number of in flight requests.\ntype:uint64"); + toml.put ("optimistic_request_percentage", optimistic_request_percentage, "Percentage of requests that will be optimistic. Optimistic requests start from the (possibly unconfirmed) account frontier and are vulnerable to bootstrap poisoning. Safe requests start from the confirmed frontier and given enough time will eventually resolve forks.\ntype:uint64"); nano::tomlconfig account_sets_l; account_sets.serialize (account_sets_l); diff --git a/nano/node/bootstrap/bootstrap_config.hpp b/nano/node/bootstrap/bootstrap_config.hpp index af7b98bcb3..2896803681 100644 --- a/nano/node/bootstrap/bootstrap_config.hpp +++ b/nano/node/bootstrap/bootstrap_config.hpp @@ -8,7 +8,6 @@ namespace nano { class tomlconfig; -// TODO: This should be moved next to `account_sets` class class account_sets_config final { public: @@ -22,8 +21,19 @@ class account_sets_config final std::chrono::milliseconds cooldown{ 1000 * 3 }; }; -// TODO: This should be moved next to `bootstrap_ascending` class -class bootstrap_ascending_config final +class frontier_scan_config final +{ +public: + // TODO: Serialize & deserialize + + unsigned head_parallelistm{ 128 }; + unsigned consideration_count{ 4 }; + std::size_t candidates{ 1000 }; + std::chrono::milliseconds cooldown{ 1000 * 5 }; + std::size_t max_pending{ 16 }; +}; + +class bootstrap_config final { public: nano::error deserialize (nano::tomlconfig & toml); @@ -31,20 +41,26 @@ class bootstrap_ascending_config final public: bool enable{ true }; - bool enable_database_scan{ true }; + bool enable_scan{ true }; + bool enable_database_scan{ false }; bool enable_dependency_walker{ true }; + bool enable_frontier_scan{ true }; // Maximum number of un-responded requests per channel, should be lower or equal to bootstrap server max queue size std::size_t channel_limit{ 16 }; - std::size_t database_rate_limit{ 256 }; + std::size_t rate_limit{ 500 }; + std::size_t database_rate_limit{ 250 }; + std::size_t frontier_rate_limit{ 8 }; std::size_t database_warmup_ratio{ 10 }; std::size_t max_pull_count{ nano::bootstrap_server::max_blocks }; - std::chrono::milliseconds request_timeout{ 1000 * 5 }; + std::chrono::milliseconds request_timeout{ 1000 * 15 }; std::size_t throttle_coefficient{ 8 * 1024 }; std::chrono::milliseconds throttle_wait{ 100 }; std::size_t block_processor_threshold{ 1000 }; std::size_t max_requests{ 1024 }; + unsigned optimistic_request_percentage{ 75 }; - nano::account_sets_config account_sets; + account_sets_config account_sets; + frontier_scan_config frontier_scan; }; } diff --git a/nano/node/bootstrap/bootstrap_connections.cpp b/nano/node/bootstrap/bootstrap_connections.cpp deleted file mode 100644 index b3f1334b8f..0000000000 --- a/nano/node/bootstrap/bootstrap_connections.cpp +++ /dev/null @@ -1,489 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include - -constexpr double nano::bootstrap_limits::bootstrap_connection_scale_target_blocks; -constexpr double nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec; -constexpr double nano::bootstrap_limits::bootstrap_minimum_termination_time_sec; -constexpr unsigned nano::bootstrap_limits::bootstrap_max_new_connections; -constexpr unsigned nano::bootstrap_limits::requeued_pulls_processed_blocks_factor; - -nano::bootstrap_client::bootstrap_client (std::shared_ptr const & node_a, std::shared_ptr const & channel_a, std::shared_ptr const & socket_a) : - node (node_a), - channel (channel_a), - socket (socket_a), - receive_buffer (std::make_shared> ()), - start_time_m (std::chrono::steady_clock::now ()) -{ - ++node_a->bootstrap_initiator.connections->connections_count; - receive_buffer->resize (256); - channel->update_endpoints (); -} - -nano::bootstrap_client::~bootstrap_client () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - --node->bootstrap_initiator.connections->connections_count; -} - -double nano::bootstrap_client::sample_block_rate () -{ - auto elapsed = std::max (elapsed_seconds (), nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate); - block_rate = static_cast (block_count.load ()) / elapsed; - return block_rate; -} - -void nano::bootstrap_client::set_start_time (std::chrono::steady_clock::time_point start_time_a) -{ - nano::lock_guard guard{ start_time_mutex }; - start_time_m = start_time_a; -} - -double nano::bootstrap_client::elapsed_seconds () const -{ - nano::lock_guard guard{ start_time_mutex }; - return std::chrono::duration_cast> (std::chrono::steady_clock::now () - start_time_m).count (); -} - -void nano::bootstrap_client::stop (bool force) -{ - pending_stop = true; - if (force) - { - hard_stop = true; - } -} - -nano::bootstrap_connections::bootstrap_connections (nano::node & node_a) : - node (node_a) -{ -} - -std::shared_ptr nano::bootstrap_connections::connection (std::shared_ptr const & attempt_a, bool use_front_connection) -{ - nano::unique_lock lock{ mutex }; - condition.wait (lock, [&stopped = stopped, &idle = idle, &new_connections_empty = new_connections_empty] { return stopped || !idle.empty () || new_connections_empty; }); - std::shared_ptr result; - if (!stopped && !idle.empty ()) - { - if (!use_front_connection) - { - result = idle.back (); - idle.pop_back (); - } - else - { - result = idle.front (); - idle.pop_front (); - } - } - if (result == nullptr && connections_count == 0 && new_connections_empty && attempt_a != nullptr) - { - node.logger.debug (nano::log::type::bootstrap, "Bootstrap attempt stopped because there are no peers"); - - lock.unlock (); - attempt_a->stop (); - } - return result; -} - -void nano::bootstrap_connections::pool_connection (std::shared_ptr const & client_a, bool new_client, bool push_front) -{ - nano::unique_lock lock{ mutex }; - auto const & socket_l = client_a->socket; - if (!stopped && !client_a->pending_stop && !node.network.excluded_peers.check (client_a->channel->get_remote_endpoint ())) - { - socket_l->set_timeout (node.network_params.network.idle_timeout); - // Push into idle deque - if (!push_front) - { - idle.push_back (client_a); - } - else - { - idle.push_front (client_a); - } - if (new_client) - { - clients.push_back (client_a); - } - } - else - { - socket_l->close (); - } - lock.unlock (); - condition.notify_all (); -} - -void nano::bootstrap_connections::add_connection (nano::endpoint const & endpoint_a) -{ - connect_client (nano::tcp_endpoint (endpoint_a.address (), endpoint_a.port ()), true); -} - -std::shared_ptr nano::bootstrap_connections::find_connection (nano::tcp_endpoint const & endpoint_a) -{ - nano::lock_guard lock{ mutex }; - std::shared_ptr result; - for (auto i (idle.begin ()), end (idle.end ()); i != end && !stopped; ++i) - { - if ((*i)->channel->get_remote_endpoint () == endpoint_a) - { - result = *i; - idle.erase (i); - break; - } - } - return result; -} - -void nano::bootstrap_connections::connect_client (nano::tcp_endpoint const & endpoint_a, bool push_front) -{ - ++connections_count; - auto socket (std::make_shared (node)); - auto this_l (shared_from_this ()); - socket->async_connect (endpoint_a, - [this_l, socket, endpoint_a, push_front] (boost::system::error_code const & ec) { - if (!ec) - { - this_l->node.logger.debug (nano::log::type::bootstrap, "Connection established to: {}", nano::util::to_str (endpoint_a)); - - auto client (std::make_shared (this_l->node.shared (), std::make_shared (*this_l->node.shared (), socket), socket)); - this_l->pool_connection (client, true, push_front); - } - else - { - switch (ec.value ()) - { - default: - this_l->node.logger.debug (nano::log::type::bootstrap, "Error initiating bootstrap connection to: {} ({})", nano::util::to_str (endpoint_a), ec.message ()); - break; - case boost::system::errc::connection_refused: - case boost::system::errc::operation_canceled: - case boost::system::errc::timed_out: - case 995: // Windows The I/O operation has been aborted because of either a thread exit or an application request - case 10061: // Windows No connection could be made because the target machine actively refused it - break; - } - } - --this_l->connections_count; - }); -} - -unsigned nano::bootstrap_connections::target_connections (std::size_t pulls_remaining, std::size_t attempts_count) const -{ - auto const attempts_factor = nano::narrow_cast (node.config.bootstrap_connections * attempts_count); - if (attempts_factor >= node.config.bootstrap_connections_max) - { - return std::max (1U, node.config.bootstrap_connections_max); - } - - // Only scale up to bootstrap_connections_max for large pulls. - double step_scale = std::min (1.0, std::max (0.0, (double)pulls_remaining / nano::bootstrap_limits::bootstrap_connection_scale_target_blocks)); - double target = (double)attempts_factor + (double)(node.config.bootstrap_connections_max - attempts_factor) * step_scale; - return std::max (1U, (unsigned)(target + 0.5f)); -} - -struct block_rate_cmp -{ - bool operator() (std::shared_ptr const & lhs, std::shared_ptr const & rhs) const - { - return lhs->block_rate > rhs->block_rate; - } -}; - -void nano::bootstrap_connections::populate_connections (bool repeat) -{ - double rate_sum = 0.0; - std::size_t num_pulls = 0; - std::size_t attempts_count = node.bootstrap_initiator.attempts.size (); - std::priority_queue, std::vector>, block_rate_cmp> sorted_connections; - std::unordered_set endpoints; - { - nano::unique_lock lock{ mutex }; - num_pulls = pulls.size (); - std::deque> new_clients; - for (auto & c : clients) - { - if (auto client = c.lock ()) - { - new_clients.push_back (client); - endpoints.insert (client->socket->remote_endpoint ()); - double elapsed_sec = client->elapsed_seconds (); - auto blocks_per_sec = client->sample_block_rate (); - rate_sum += blocks_per_sec; - if (client->elapsed_seconds () > nano::bootstrap_limits::bootstrap_connection_warmup_time_sec && client->block_count > 0) - { - sorted_connections.push (client); - } - // Force-stop the slowest peers, since they can take the whole bootstrap hostage by dribbling out blocks on the last remaining pull. - // This is ~1.5kilobits/sec. - if (elapsed_sec > nano::bootstrap_limits::bootstrap_minimum_termination_time_sec && blocks_per_sec < nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec) - { - node.logger.debug (nano::log::type::bootstrap, "Stopping slow peer {} (elapsed sec {} > {} and {} blocks per second < {})", - client->channel->to_string (), - elapsed_sec, - nano::bootstrap_limits::bootstrap_minimum_termination_time_sec, - blocks_per_sec, - nano::bootstrap_limits::bootstrap_minimum_blocks_per_sec); - - client->stop (true); - new_clients.pop_back (); - } - } - } - // Cleanup expired clients - clients.swap (new_clients); - } - - auto target = target_connections (num_pulls, attempts_count); - - // We only want to drop slow peers when more than 2/3 are active. 2/3 because 1/2 is too aggressive, and 100% rarely happens. - // Probably needs more tuning. - if (sorted_connections.size () >= (target * 2) / 3 && target >= 4) - { - // 4 -> 1, 8 -> 2, 16 -> 4, arbitrary, but seems to work well. - auto drop = (int)roundf (sqrtf ((float)target - 2.0f)); - - node.logger.debug (nano::log::type::bootstrap, "Dropping {} bulk pull peers, target connections {}", drop, target); - - for (int i = 0; i < drop; i++) - { - auto client = sorted_connections.top (); - - node.logger.debug (nano::log::type::bootstrap, "Dropping peer with block rate {} and block count {} ({})", - client->block_rate.load (), - client->block_count.load (), - client->channel->to_string ()); - - client->stop (false); - sorted_connections.pop (); - } - } - - node.logger.debug (nano::log::type::bootstrap, "Bulk pull connections: {}, rate: {} blocks/sec, bootstrap attempts {}, remaining pulls: {}", - connections_count.load (), - (int)rate_sum, - attempts_count, - num_pulls); - - if (connections_count < target && (attempts_count != 0 || new_connections_empty) && !stopped) - { - auto delta = std::min ((target - connections_count) * 2, nano::bootstrap_limits::bootstrap_max_new_connections); - // TODO - tune this better - // Not many peers respond, need to try to make more connections than we need. - for (auto i = 0u; i < delta; i++) - { - auto endpoint (node.network.bootstrap_peer ()); // Legacy bootstrap is compatible with older version of protocol - if (endpoint != nano::tcp_endpoint (boost::asio::ip::address_v6::any (), 0) && (node.flags.allow_bootstrap_peers_duplicates || endpoints.find (endpoint) == endpoints.end ()) && !node.network.excluded_peers.check (endpoint)) - { - connect_client (endpoint); - endpoints.insert (endpoint); - nano::lock_guard lock{ mutex }; - new_connections_empty = false; - } - else if (connections_count == 0) - { - { - nano::lock_guard lock{ mutex }; - new_connections_empty = true; - } - condition.notify_all (); - } - } - } - if (!stopped && repeat) - { - std::weak_ptr this_w (shared_from_this ()); - node.workers.post_delayed (std::chrono::seconds (1), [this_w] () { - if (auto this_l = this_w.lock ()) - { - this_l->populate_connections (); - } - }); - } -} - -void nano::bootstrap_connections::start_populate_connections () -{ - if (!populate_connections_started.exchange (true)) - { - populate_connections (); - } -} - -void nano::bootstrap_connections::add_pull (nano::pull_info const & pull_a) -{ - nano::pull_info pull (pull_a); - node.bootstrap_initiator.cache.update_pull (pull); - { - nano::lock_guard lock{ mutex }; - pulls.push_back (pull); - } - condition.notify_all (); -} - -void nano::bootstrap_connections::request_pull (nano::unique_lock & lock_a) -{ - lock_a.unlock (); - auto connection_l (connection ()); - lock_a.lock (); - if (connection_l != nullptr && !pulls.empty ()) - { - std::shared_ptr attempt_l; - nano::pull_info pull; - // Search pulls with existing attempts - while (attempt_l == nullptr && !pulls.empty ()) - { - pull = pulls.front (); - pulls.pop_front (); - attempt_l = node.bootstrap_initiator.attempts.find (pull.bootstrap_id); - // Check if lazy pull is obsolete (head was processed or head is 0 for destinations requests) - if (auto lazy = std::dynamic_pointer_cast (attempt_l)) - { - if (!pull.head.is_zero () && lazy->lazy_processed_or_exists (pull.head)) - { - attempt_l->pull_finished (); - attempt_l = nullptr; - } - } - } - if (attempt_l != nullptr) - { - // The bulk_pull_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference - // Dispatch request in an external thread in case it needs to be destroyed - node.background ([connection_l, attempt_l, pull] () { - auto client (std::make_shared (connection_l, attempt_l, pull)); - client->request (); - }); - } - } - else if (connection_l != nullptr) - { - // Reuse connection if pulls deque become empty - lock_a.unlock (); - pool_connection (connection_l); - lock_a.lock (); - } -} - -void nano::bootstrap_connections::requeue_pull (nano::pull_info const & pull_a, bool network_error) -{ - auto pull (pull_a); - if (!network_error) - { - ++pull.attempts; - } - auto attempt_l (node.bootstrap_initiator.attempts.find (pull.bootstrap_id)); - if (attempt_l != nullptr) - { - auto lazy = std::dynamic_pointer_cast (attempt_l); - ++attempt_l->requeued_pulls; - if (lazy) - { - pull.count = lazy->lazy_batch_size (); - } - if (attempt_l->mode == nano::bootstrap_mode::legacy && (pull.attempts < pull.retry_limit + (pull.processed / nano::bootstrap_limits::requeued_pulls_processed_blocks_factor))) - { - { - nano::lock_guard lock{ mutex }; - pulls.push_front (pull); - } - attempt_l->pull_started (); - condition.notify_all (); - } - else if (lazy && (pull.attempts <= pull.retry_limit + (pull.processed / node.network_params.bootstrap.lazy_max_pull_blocks))) - { - debug_assert (pull.account_or_head.as_block_hash () == pull.head); - if (!lazy->lazy_processed_or_exists (pull.account_or_head.as_block_hash ())) - { - { - nano::lock_guard lock{ mutex }; - pulls.push_back (pull); - } - attempt_l->pull_started (); - condition.notify_all (); - } - } - else - { - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::bulk_pull_failed_account, nano::stat::dir::in); - node.logger.debug (nano::log::type::bootstrap, "Failed to pull account {} or head block {} down to {} after {} attempts and {} blocks processed", - pull.account_or_head.to_account (), - pull.account_or_head.to_string (), - pull.end.to_string (), - pull.attempts, - pull.processed); - - if (lazy && pull.processed > 0) - { - lazy->lazy_add (pull); - } - else if (attempt_l->mode == nano::bootstrap_mode::legacy) - { - node.bootstrap_initiator.cache.add (pull); - } - } - } -} - -void nano::bootstrap_connections::clear_pulls (uint64_t bootstrap_id_a) -{ - { - nano::lock_guard lock{ mutex }; - - erase_if (pulls, [bootstrap_id_a] (auto const & pull) { - return pull.bootstrap_id == bootstrap_id_a; - }); - } - condition.notify_all (); -} - -void nano::bootstrap_connections::run () -{ - start_populate_connections (); - nano::unique_lock lock{ mutex }; - while (!stopped) - { - if (!pulls.empty ()) - { - request_pull (lock); - } - else - { - condition.wait (lock); - } - } - stopped = true; - lock.unlock (); - condition.notify_all (); -} - -void nano::bootstrap_connections::stop () -{ - nano::unique_lock lock{ mutex }; - stopped = true; - lock.unlock (); - condition.notify_all (); - lock.lock (); - for (auto const & i : clients) - { - if (auto client = i.lock ()) - { - client->socket->close (); - } - } - clients.clear (); - idle.clear (); -} diff --git a/nano/node/bootstrap/bootstrap_connections.hpp b/nano/node/bootstrap/bootstrap_connections.hpp deleted file mode 100644 index d246c23bb7..0000000000 --- a/nano/node/bootstrap/bootstrap_connections.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace nano -{ -class node; -namespace transport -{ - class tcp_channel; -} - -class bootstrap_attempt; -class bootstrap_connections; -class frontier_req_client; -class pull_info; - -/** - * Owns the client side of the bootstrap connection. - */ -class bootstrap_client final : public std::enable_shared_from_this -{ -public: - bootstrap_client (std::shared_ptr const & node_a, std::shared_ptr const & channel_a, std::shared_ptr const & socket_a); - ~bootstrap_client (); - void stop (bool force); - double sample_block_rate (); - double elapsed_seconds () const; - void set_start_time (std::chrono::steady_clock::time_point start_time_a); - std::weak_ptr node; - std::shared_ptr channel; - std::shared_ptr socket; - std::shared_ptr> receive_buffer; - std::atomic block_count{ 0 }; - std::atomic block_rate{ 0 }; - std::atomic pending_stop{ false }; - std::atomic hard_stop{ false }; - -private: - mutable nano::mutex start_time_mutex; - std::chrono::steady_clock::time_point start_time_m; -}; - -/** - * Container for bootstrap_client objects. Owned by bootstrap_initiator which pools open connections and makes them available - * for use by different bootstrap sessions. - */ -class bootstrap_connections final : public std::enable_shared_from_this -{ -public: - explicit bootstrap_connections (nano::node & node_a); - std::shared_ptr connection (std::shared_ptr const & attempt_a = nullptr, bool use_front_connection = false); - void pool_connection (std::shared_ptr const & client_a, bool new_client = false, bool push_front = false); - void add_connection (nano::endpoint const & endpoint_a); - std::shared_ptr find_connection (nano::tcp_endpoint const & endpoint_a); - void connect_client (nano::tcp_endpoint const & endpoint_a, bool push_front = false); - unsigned target_connections (std::size_t pulls_remaining, std::size_t attempts_count) const; - void populate_connections (bool repeat = true); - void start_populate_connections (); - void add_pull (nano::pull_info const & pull_a); - void request_pull (nano::unique_lock & lock_a); - void requeue_pull (nano::pull_info const & pull_a, bool network_error = false); - void clear_pulls (uint64_t); - void run (); - void stop (); - std::deque> clients; - std::atomic connections_count{ 0 }; - nano::node & node; - std::deque> idle; - std::deque pulls; - std::atomic populate_connections_started{ false }; - std::atomic new_connections_empty{ false }; - std::atomic stopped{ false }; - nano::mutex mutex; - nano::condition_variable condition; -}; -} diff --git a/nano/node/bootstrap/bootstrap_frontier.cpp b/nano/node/bootstrap/bootstrap_frontier.cpp deleted file mode 100644 index 8a6067ef2c..0000000000 --- a/nano/node/bootstrap/bootstrap_frontier.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -constexpr double nano::bootstrap_limits::bootstrap_connection_warmup_time_sec; -constexpr double nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate; -constexpr double nano::bootstrap_limits::bootstrap_minimum_frontier_blocks_per_sec; -constexpr unsigned nano::bootstrap_limits::bulk_push_cost_limit; - -constexpr std::size_t nano::frontier_req_client::size_frontier; - -void nano::frontier_req_client::run (nano::account const & start_account_a, uint32_t const frontiers_age_a, uint32_t const count_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - nano::frontier_req request{ node->network_params.network }; - request.start = (start_account_a.is_zero () || start_account_a.number () == std::numeric_limits::max ()) ? start_account_a.number () : start_account_a.number () + 1; - request.age = frontiers_age_a; - request.count = count_a; - current = start_account_a; - frontiers_age = frontiers_age_a; - count_limit = count_a; - next (); // Load accounts from disk - auto this_l (shared_from_this ()); - connection->channel->send ( - request, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - this_l->receive_frontier (); - } - else - { - node->logger.debug (nano::log::type::frontier_req_client, "Error while sending bootstrap request: {}", ec.message ()); - } - }, - nano::transport::buffer_drop_policy::no_limiter_drop); -} - -nano::frontier_req_client::frontier_req_client (std::shared_ptr const & connection_a, std::shared_ptr const & attempt_a) : - connection (connection_a), - attempt (attempt_a), - count (0), - bulk_push_cost (0) -{ -} - -void nano::frontier_req_client::receive_frontier () -{ - auto this_l (shared_from_this ()); - connection->socket->async_read (connection->receive_buffer, nano::frontier_req_client::size_frontier, [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - auto node = this_l->connection->node.lock (); - if (!node) - { - return; - } - // An issue with asio is that sometimes, instead of reporting a bad file descriptor during disconnect, - // we simply get a size of 0. - if (size_a == nano::frontier_req_client::size_frontier) - { - node->bootstrap_workers.post ([this_l, ec, size_a] () { - this_l->received_frontier (ec, size_a); - }); - } - else - { - node->logger.debug (nano::log::type::frontier_req_client, "Invalid size: expected {}, got {}", nano::frontier_req_client::size_frontier, size_a); - } - }); -} - -bool nano::frontier_req_client::bulk_push_available () -{ - return bulk_push_cost < nano::bootstrap_limits::bulk_push_cost_limit && frontiers_age == std::numeric_limits::max (); -} - -void nano::frontier_req_client::unsynced (nano::block_hash const & head, nano::block_hash const & end) -{ - if (bulk_push_available ()) - { - attempt->add_bulk_push_target (head, end); - if (end.is_zero ()) - { - bulk_push_cost += 2; - } - else - { - bulk_push_cost += 1; - } - } -} - -void nano::frontier_req_client::received_frontier (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - debug_assert (size_a == nano::frontier_req_client::size_frontier); - nano::account account; - nano::bufferstream account_stream (connection->receive_buffer->data (), sizeof (account)); - auto error1 (nano::try_read (account_stream, account)); - (void)error1; - debug_assert (!error1); - nano::block_hash latest; - nano::bufferstream latest_stream (connection->receive_buffer->data () + sizeof (account), sizeof (latest)); - auto error2 (nano::try_read (latest_stream, latest)); - (void)error2; - debug_assert (!error2); - if (count == 0) - { - start_time = std::chrono::steady_clock::now (); - } - ++count; - std::chrono::duration time_span = std::chrono::duration_cast> (std::chrono::steady_clock::now () - start_time); - - double elapsed_sec = std::max (time_span.count (), nano::bootstrap_limits::bootstrap_minimum_elapsed_seconds_blockrate); - double blocks_per_sec = static_cast (count) / elapsed_sec; - double age_factor = (frontiers_age == std::numeric_limits::max ()) ? 1.0 : 1.5; // Allow slower frontiers receive for requests with age - if (elapsed_sec > nano::bootstrap_limits::bootstrap_connection_warmup_time_sec && blocks_per_sec * age_factor < nano::bootstrap_limits::bootstrap_minimum_frontier_blocks_per_sec) - { - node->logger.debug (nano::log::type::frontier_req_client, "Aborting frontier req because it was too slow: {} frontiers per second, last {}", blocks_per_sec, account.to_account ()); - - promise.set_value (true); - return; - } - - if (attempt->should_log ()) - { - node->logger.debug (nano::log::type::frontier_req_client, "Received {} frontiers from {}", count, connection->channel->to_string ()); - } - - if (!account.is_zero () && count <= count_limit) - { - last_account = account; - while (!current.is_zero () && current < account) - { - // We know about an account they don't. - unsynced (frontier, 0); - next (); - } - if (!current.is_zero ()) - { - if (account == current) - { - if (latest == frontier) - { - // In sync - } - else - { - if (node->block_or_pruned_exists (latest)) - { - // We know about a block they don't. - unsynced (frontier, latest); - } - else - { - attempt->add_frontier (nano::pull_info (account, latest, frontier, attempt->incremental_id, 0, node->network_params.bootstrap.frontier_retry_limit)); - // Either we're behind or there's a fork we differ on - // Either way, bulk pushing will probably not be effective - bulk_push_cost += 5; - } - } - next (); - } - else - { - debug_assert (account < current); - attempt->add_frontier (nano::pull_info (account, latest, nano::block_hash (0), attempt->incremental_id, 0, node->network_params.bootstrap.frontier_retry_limit)); - } - } - else - { - attempt->add_frontier (nano::pull_info (account, latest, nano::block_hash (0), attempt->incremental_id, 0, node->network_params.bootstrap.frontier_retry_limit)); - } - receive_frontier (); - } - else - { - if (count <= count_limit) - { - while (!current.is_zero () && bulk_push_available ()) - { - // We know about an account they don't. - unsynced (frontier, 0); - next (); - } - // Prevent new frontier_req requests - attempt->set_start_account (std::numeric_limits::max ()); - - node->logger.debug (nano::log::type::frontier_req_client, "Bulk push cost: {}", bulk_push_cost); - } - else - { - // Set last processed account as new start target - attempt->set_start_account (last_account); - } - node->bootstrap_initiator.connections->pool_connection (connection); - try - { - promise.set_value (false); - } - catch (std::future_error &) - { - } - } - } - else - { - node->logger.debug (nano::log::type::frontier_req_client, "Error while receiving frontier: {}", ec.message ()); - } -} - -void nano::frontier_req_client::next () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - // Filling accounts deque to prevent often read transactions - if (accounts.empty ()) - { - std::size_t max_size (128); - auto transaction (node->store.tx_begin_read ()); - for (auto i (node->store.account.begin (transaction, current.number () + 1)), n (node->store.account.end (transaction)); i != n && accounts.size () != max_size; ++i) - { - nano::account_info const & info (i->second); - nano::account const & account (i->first); - accounts.emplace_back (account, info.head); - } - - /* If loop breaks before max_size, then accounts_end () is reached. Add empty record */ - if (accounts.size () != max_size) - { - accounts.emplace_back (nano::account{}, nano::block_hash (0)); - } - } - // Retrieving accounts from deque - auto const & account_pair (accounts.front ()); - current = account_pair.first; - frontier = account_pair.second; - accounts.pop_front (); -} - -nano::frontier_req_server::frontier_req_server (std::shared_ptr const & connection_a, std::unique_ptr request_a) : - connection (connection_a), - current (request_a->start.number () - 1), - frontier (0), - request (std::move (request_a)), - count (0) -{ - next (); -} - -void nano::frontier_req_server::send_next () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!current.is_zero () && count < request->count) - { - node->logger.trace (nano::log::type::frontier_req_server, nano::log::detail::sending_frontier, - nano::log::arg{ "account", current.to_account () }, // TODO: Convert to lazy eval - nano::log::arg{ "frontier", frontier }, - nano::log::arg{ "socket", connection->socket }); - - std::vector send_buffer; - { - nano::vectorstream stream (send_buffer); - write (stream, current.bytes); - write (stream, frontier.bytes); - debug_assert (!current.is_zero ()); - debug_assert (!frontier.is_zero ()); - } - - auto this_l (shared_from_this ()); - next (); - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->sent_action (ec, size_a); - }); - } - else - { - send_finished (); - } -} - -void nano::frontier_req_server::send_finished () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - std::vector send_buffer; - { - nano::vectorstream stream (send_buffer); - nano::uint256_union zero (0); - write (stream, zero.bytes); - write (stream, zero.bytes); - } - - node->logger.debug (nano::log::type::frontier_req_server, "Frontier sending finished"); - - auto this_l (shared_from_this ()); - connection->socket->async_write (nano::shared_const_buffer (std::move (send_buffer)), [this_l] (boost::system::error_code const & ec, std::size_t size_a) { - this_l->no_block_sent (ec, size_a); - }); -} - -void nano::frontier_req_server::no_block_sent (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - connection->start (); - } - else - { - node->logger.debug (nano::log::type::frontier_req_server, "Error sending frontier finish: {}", ec.message ()); - } -} - -void nano::frontier_req_server::sent_action (boost::system::error_code const & ec, std::size_t size_a) -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - if (!ec) - { - count++; - - node->bootstrap_workers.post ([this_l = shared_from_this ()] () { - this_l->send_next (); - }); - } - else - { - node->logger.debug (nano::log::type::frontier_req_server, "Error sending frontier pair: {}", ec.message ()); - } -} - -void nano::frontier_req_server::next () -{ - auto node = connection->node.lock (); - if (!node) - { - return; - } - // Filling accounts deque to prevent often read transactions - if (accounts.empty ()) - { - auto now (nano::seconds_since_epoch ()); - bool disable_age_filter (request->age == std::numeric_limitsage)>::max ()); - std::size_t max_size (128); - auto transaction (node->store.tx_begin_read ()); - if (!send_confirmed ()) - { - for (auto i (node->store.account.begin (transaction, current.number () + 1)), n (node->store.account.end (transaction)); i != n && accounts.size () != max_size; ++i) - { - nano::account_info const & info (i->second); - if (disable_age_filter || (now - info.modified) <= request->age) - { - nano::account const & account (i->first); - accounts.emplace_back (account, info.head); - } - } - } - else - { - for (auto i (node->store.confirmation_height.begin (transaction, current.number () + 1)), n (node->store.confirmation_height.end (transaction)); i != n && accounts.size () != max_size; ++i) - { - nano::confirmation_height_info const & info (i->second); - nano::block_hash const & confirmed_frontier (info.frontier); - if (!confirmed_frontier.is_zero ()) - { - nano::account const & account (i->first); - accounts.emplace_back (account, confirmed_frontier); - } - } - } - - /* If loop breaks before max_size, then accounts_end () is reached. Add empty record to finish frontier_req_server */ - if (accounts.size () != max_size) - { - accounts.emplace_back (nano::account{}, nano::block_hash (0)); - } - } - // Retrieving accounts from deque - auto const & account_pair (accounts.front ()); - current = account_pair.first; - frontier = account_pair.second; - accounts.pop_front (); -} - -bool nano::frontier_req_server::send_confirmed () -{ - return request->header.frontier_req_is_only_confirmed_present (); -} diff --git a/nano/node/bootstrap/bootstrap_frontier.hpp b/nano/node/bootstrap/bootstrap_frontier.hpp deleted file mode 100644 index 75ee9c1ab9..0000000000 --- a/nano/node/bootstrap/bootstrap_frontier.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -namespace nano -{ -class bootstrap_attempt_legacy; -class bootstrap_client; -namespace transport -{ - class tcp_server; -} - -/** - * Client side of a frontier request. Created to send and listen for frontier sequences from the server. - */ -class frontier_req_client final : public std::enable_shared_from_this -{ -public: - explicit frontier_req_client (std::shared_ptr const &, std::shared_ptr const &); - void run (nano::account const & start_account_a, uint32_t const frontiers_age_a, uint32_t const count_a); - void receive_frontier (); - void received_frontier (boost::system::error_code const &, std::size_t); - bool bulk_push_available (); - void unsynced (nano::block_hash const &, nano::block_hash const &); - void next (); - std::shared_ptr connection; - std::shared_ptr attempt; - nano::account current; - nano::block_hash frontier; - unsigned count; - nano::account last_account{ std::numeric_limits::max () }; // Using last possible account stop further frontier requests - std::chrono::steady_clock::time_point start_time; - std::promise promise; - /** A very rough estimate of the cost of `bulk_push`ing missing blocks */ - uint64_t bulk_push_cost; - std::deque> accounts; - uint32_t frontiers_age{ std::numeric_limits::max () }; - uint32_t count_limit{ std::numeric_limits::max () }; - static std::size_t constexpr size_frontier = sizeof (nano::account) + sizeof (nano::block_hash); -}; - -class frontier_req; - -/** - * Server side of a frontier request. Created when a tcp_server receives a frontier_req message and exited when end-of-list is reached. - */ -class frontier_req_server final : public std::enable_shared_from_this -{ -public: - frontier_req_server (std::shared_ptr const &, std::unique_ptr); - void send_next (); - void sent_action (boost::system::error_code const &, std::size_t); - void send_finished (); - void no_block_sent (boost::system::error_code const &, std::size_t); - void next (); - bool send_confirmed (); - std::shared_ptr connection; - nano::account current; - nano::block_hash frontier; - std::unique_ptr request; - std::size_t count; - std::deque> accounts; -}; -} diff --git a/nano/node/bootstrap/bootstrap_lazy.cpp b/nano/node/bootstrap/bootstrap_lazy.cpp deleted file mode 100644 index 439aa0ced0..0000000000 --- a/nano/node/bootstrap/bootstrap_lazy.cpp +++ /dev/null @@ -1,634 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -constexpr std::chrono::seconds nano::bootstrap_limits::lazy_flush_delay_sec; -constexpr uint64_t nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit; -constexpr double nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio; -constexpr std::size_t nano::bootstrap_limits::lazy_blocks_restart_limit; - -nano::bootstrap_attempt_lazy::bootstrap_attempt_lazy (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string const & id_a) : - nano::bootstrap_attempt (node_a, nano::bootstrap_mode::lazy, incremental_id_a, id_a) -{ - node_a->bootstrap_initiator.notify_listeners (true); -} - -nano::bootstrap_attempt_lazy::~bootstrap_attempt_lazy () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - debug_assert (lazy_blocks.size () == lazy_blocks_count); - node->bootstrap_initiator.notify_listeners (false); -} - -bool nano::bootstrap_attempt_lazy::lazy_start (nano::hash_or_account const & hash_or_account_a) -{ - auto node = this->node.lock (); - if (!node) - { - return false; - } - nano::unique_lock lock{ mutex }; - bool inserted (false); - // Add start blocks, limit 1024 (4k with disabled legacy bootstrap) - std::size_t max_keys (node->flags.disable_legacy_bootstrap ? 4 * 1024 : 1024); - if (lazy_keys.size () < max_keys && lazy_keys.find (hash_or_account_a.as_block_hash ()) == lazy_keys.end () && !lazy_blocks_processed (hash_or_account_a.as_block_hash ())) - { - lazy_keys.insert (hash_or_account_a.as_block_hash ()); - lazy_pulls.emplace_back (hash_or_account_a, node->network_params.bootstrap.lazy_retry_limit); - lock.unlock (); - condition.notify_all (); - inserted = true; - } - return inserted; -} - -void nano::bootstrap_attempt_lazy::lazy_add (nano::hash_or_account const & hash_or_account_a, unsigned retry_limit) -{ - // Add only unknown blocks - debug_assert (!mutex.try_lock ()); - if (!lazy_blocks_processed (hash_or_account_a.as_block_hash ())) - { - lazy_pulls.emplace_back (hash_or_account_a, retry_limit); - } -} - -void nano::bootstrap_attempt_lazy::lazy_add (nano::pull_info const & pull_a) -{ - debug_assert (pull_a.account_or_head.as_block_hash () == pull_a.head); - nano::lock_guard lock{ mutex }; - lazy_add (pull_a.account_or_head, pull_a.retry_limit); -} - -void nano::bootstrap_attempt_lazy::lazy_requeue (nano::block_hash const & hash_a, nano::block_hash const & previous_a) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - nano::unique_lock lock{ mutex }; - // Add only known blocks - if (lazy_blocks_processed (hash_a)) - { - lazy_blocks_erase (hash_a); - lock.unlock (); - node->bootstrap_initiator.connections->requeue_pull (nano::pull_info (hash_a, hash_a, previous_a, incremental_id, static_cast (1), node->network_params.bootstrap.lazy_destinations_retry_limit)); - } -} - -uint32_t nano::bootstrap_attempt_lazy::lazy_batch_size () -{ - auto node = this->node.lock (); - if (!node) - { - return 0; - } - auto result (node->network_params.bootstrap.lazy_max_pull_blocks); - if (total_blocks > nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit && lazy_blocks_count != 0) - { - auto lazy_blocks_ratio (static_cast (total_blocks / lazy_blocks_count)); - if (lazy_blocks_ratio > nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio) - { - // Increasing blocks ratio weight as more important (^3). Small batch count should lower blocks ratio below target - double lazy_blocks_factor (std::pow (lazy_blocks_ratio / nano::bootstrap_limits::lazy_batch_pull_count_resize_ratio, 3.0)); - // Decreasing total block count weight as less important (sqrt) - double total_blocks_factor (std::sqrt (total_blocks / nano::bootstrap_limits::lazy_batch_pull_count_resize_blocks_limit)); - uint32_t batch_count_min (node->network_params.bootstrap.lazy_max_pull_blocks / static_cast (lazy_blocks_factor * total_blocks_factor)); - result = std::max (node->network_params.bootstrap.lazy_min_pull_blocks, batch_count_min); - } - } - return result; -} - -void nano::bootstrap_attempt_lazy::lazy_pull_flush (nano::unique_lock & lock_a) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - static std::size_t const max_pulls (static_cast (nano::bootstrap_limits::bootstrap_connection_scale_target_blocks) * 3); - if (pulling < max_pulls) - { - debug_assert (node->network_params.bootstrap.lazy_max_pull_blocks <= std::numeric_limits::max ()); - nano::pull_info::count_t batch_count (lazy_batch_size ()); - uint64_t read_count (0); - std::size_t count (0); - auto transaction = node->ledger.tx_begin_read (); - while (!lazy_pulls.empty () && count < max_pulls) - { - auto pull_start (lazy_pulls.front ()); - lazy_pulls.pop_front (); - // Recheck if block was already processed - if (!lazy_blocks_processed (pull_start.first.as_block_hash ()) && !node->ledger.any.block_exists_or_pruned (transaction, pull_start.first.as_block_hash ())) - { - lock_a.unlock (); - node->bootstrap_initiator.connections->add_pull (nano::pull_info (pull_start.first, pull_start.first.as_block_hash (), nano::block_hash (0), incremental_id, batch_count, pull_start.second)); - ++pulling; - ++count; - lock_a.lock (); - } - // We don't want to open read transactions for too long - ++read_count; - if (read_count % batch_read_size == 0) - { - lock_a.unlock (); - transaction.refresh (); - lock_a.lock (); - } - } - } -} - -bool nano::bootstrap_attempt_lazy::lazy_finished () -{ - auto node = this->node.lock (); - if (!node) - { - return true; - } - debug_assert (!mutex.try_lock ()); - if (stopped) - { - return true; - } - bool result (true); - uint64_t read_count (0); - auto transaction = node->ledger.tx_begin_read (); - for (auto it (lazy_keys.begin ()), end (lazy_keys.end ()); it != end && !stopped;) - { - if (node->ledger.any.block_exists_or_pruned (transaction, *it)) - { - it = lazy_keys.erase (it); - } - else - { - result = false; - break; - // No need to increment `it` as we break above. - } - // We don't want to open read transactions for too long - ++read_count; - if (read_count % batch_read_size == 0) - { - transaction.refresh (); - } - } - // Finish lazy bootstrap without lazy pulls (in combination with still_pulling ()) - if (!result && lazy_pulls.empty () && lazy_state_backlog.empty ()) - { - result = true; - } - return result; -} - -bool nano::bootstrap_attempt_lazy::lazy_has_expired () const -{ - auto node = this->node.lock (); - if (!node) - { - return true; - } - bool result (false); - // Max 30 minutes run with enabled legacy bootstrap - static std::chrono::minutes const max_lazy_time (node->flags.disable_legacy_bootstrap ? 7 * 24 * 60 : 30); - if (std::chrono::steady_clock::now () - lazy_start_time >= max_lazy_time) - { - result = true; - } - else if (!node->flags.disable_legacy_bootstrap && lazy_blocks_count > nano::bootstrap_limits::lazy_blocks_restart_limit) - { - result = true; - } - return result; -} - -void nano::bootstrap_attempt_lazy::run () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - debug_assert (started); - debug_assert (!node->flags.disable_lazy_bootstrap); - node->bootstrap_initiator.connections->populate_connections (false); - lazy_start_time = std::chrono::steady_clock::now (); - nano::unique_lock lock{ mutex }; - while ((still_pulling () || !lazy_finished ()) && !lazy_has_expired ()) - { - unsigned iterations (0); - while (still_pulling () && !lazy_has_expired ()) - { - condition.wait (lock, [this, &stopped = stopped, &pulling = pulling, &lazy_pulls = lazy_pulls] { return stopped || pulling == 0 || (pulling < nano::bootstrap_limits::bootstrap_connection_scale_target_blocks && !lazy_pulls.empty ()) || lazy_has_expired (); }); - ++iterations; - // Flushing lazy pulls - lazy_pull_flush (lock); - // Start backlog cleanup - if (iterations % 100 == 0) - { - lazy_backlog_cleanup (); - } - } - // Flushing lazy pulls - lazy_pull_flush (lock); - // Check if some blocks required for backlog were processed. Start destinations check - if (pulling == 0) - { - lazy_backlog_cleanup (); - lazy_pull_flush (lock); - } - } - if (!stopped) - { - node->logger.debug (nano::log::type::bootstrap_lazy, "Completed lazy pulls"); - } - if (lazy_has_expired ()) - { - node->logger.debug (nano::log::type::bootstrap_lazy, "Lazy bootstrap attempt ID {} expired", id); - } - lock.unlock (); - stop (); - condition.notify_all (); -} - -bool nano::bootstrap_attempt_lazy::process_block (std::shared_ptr const & block_a, nano::account const & known_account_a, uint64_t pull_blocks_processed, nano::bulk_pull::count_t max_blocks, bool block_expected, unsigned retry_limit) -{ - bool stop_pull (false); - if (block_expected) - { - stop_pull = process_block_lazy (block_a, known_account_a, pull_blocks_processed, max_blocks, retry_limit); - } - else - { - // Drop connection with unexpected block for lazy bootstrap - stop_pull = true; - } - return stop_pull; -} - -bool nano::bootstrap_attempt_lazy::process_block_lazy (std::shared_ptr const & block_a, nano::account const & known_account_a, uint64_t pull_blocks_processed, nano::bulk_pull::count_t max_blocks, unsigned retry_limit) -{ - auto node = this->node.lock (); - if (!node) - { - return true; - } - bool stop_pull (false); - auto hash (block_a->hash ()); - nano::unique_lock lock{ mutex }; - // Processing new blocks - if (!lazy_blocks_processed (hash)) - { - // Search for new dependencies - if (block_a->source_field () && !node->block_or_pruned_exists (block_a->source_field ().value ()) && block_a->source_field ().value () != node->network_params.ledger.genesis->account ().as_union ()) - { - lazy_add (block_a->source_field ().value (), retry_limit); - } - else if (block_a->type () == nano::block_type::state) - { - lazy_block_state (block_a, retry_limit); - } - lazy_blocks_insert (hash); - // Adding lazy balances for first processed block in pull - if (pull_blocks_processed == 1 && (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send)) - { - lazy_balances.emplace (hash, block_a->balance_field ().value ().number ()); - } - // Clearing lazy balances for previous block - if (!block_a->previous ().is_zero () && lazy_balances.find (block_a->previous ()) != lazy_balances.end ()) - { - lazy_balances.erase (block_a->previous ()); - } - lazy_block_state_backlog_check (block_a, hash); - lock.unlock (); - node->block_processor.add (block_a, nano::block_source::bootstrap_legacy); - } - // Force drop lazy bootstrap connection for long bulk_pull - if (pull_blocks_processed > max_blocks) - { - stop_pull = true; - } - return stop_pull; -} - -void nano::bootstrap_attempt_lazy::lazy_block_state (std::shared_ptr const & block_a, unsigned retry_limit) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - std::shared_ptr block_l (std::static_pointer_cast (block_a)); - if (block_l != nullptr) - { - auto transaction = node->ledger.tx_begin_read (); - nano::uint128_t balance (block_l->hashables.balance.number ()); - auto const & link (block_l->hashables.link); - // If link is not epoch link or 0. And if block from link is unknown - if (!link.is_zero () && !node->ledger.is_epoch_link (link) && !lazy_blocks_processed (link.as_block_hash ()) && !node->ledger.any.block_exists_or_pruned (transaction, link.as_block_hash ())) - { - auto const & previous (block_l->hashables.previous); - // If state block previous is 0 then source block required - if (previous.is_zero ()) - { - lazy_add (link, retry_limit); - } - // In other cases previous block balance required to find out subtype of state block - else if (node->ledger.any.block_exists_or_pruned (transaction, previous)) - { - auto previous_balance = node->ledger.any.block_balance (transaction, previous); - if (previous_balance) - { - if (previous_balance.value ().number () <= balance) - { - lazy_add (link, retry_limit); - } - } - // Else ignore pruned blocks - } - // Search balance of already processed previous blocks - else if (lazy_blocks_processed (previous)) - { - auto previous_balance (lazy_balances.find (previous)); - if (previous_balance != lazy_balances.end ()) - { - if (previous_balance->second <= balance) - { - lazy_add (link, retry_limit); - } - lazy_balances.erase (previous_balance); - } - } - // Insert in backlog state blocks if previous wasn't already processed - else - { - lazy_state_backlog.emplace (previous, nano::lazy_state_backlog_item{ link, balance, retry_limit }); - } - } - } -} - -void nano::bootstrap_attempt_lazy::lazy_block_state_backlog_check (std::shared_ptr const & block_a, nano::block_hash const & hash_a) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - // Search unknown state blocks balances - auto find_state (lazy_state_backlog.find (hash_a)); - if (find_state != lazy_state_backlog.end ()) - { - auto next_block (find_state->second); - // Retrieve balance for previous state & send blocks - if (block_a->type () == nano::block_type::state || block_a->type () == nano::block_type::send) - { - if (block_a->balance_field ().value ().number () <= next_block.balance) // balance - { - lazy_add (next_block.link, next_block.retry_limit); // link - } - } - // Assumption for other legacy block types - else if (lazy_undefined_links.find (next_block.link.as_block_hash ()) == lazy_undefined_links.end ()) - { - lazy_add (next_block.link, node->network_params.bootstrap.lazy_retry_limit); // Head is not confirmed. It can be account or hash or non-existing - lazy_undefined_links.insert (next_block.link.as_block_hash ()); - } - lazy_state_backlog.erase (find_state); - } -} - -void nano::bootstrap_attempt_lazy::lazy_backlog_cleanup () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - uint64_t read_count (0); - auto transaction = node->ledger.tx_begin_read (); - for (auto it (lazy_state_backlog.begin ()), end (lazy_state_backlog.end ()); it != end && !stopped;) - { - if (node->ledger.any.block_exists_or_pruned (transaction, it->first)) - { - auto next_block (it->second); - auto balance = node->ledger.any.block_balance (transaction, it->first); - if (balance) - { - if (balance.value ().number () <= next_block.balance) // balance - { - lazy_add (next_block.link, next_block.retry_limit); // link - } - } - else - { - lazy_add (next_block.link, node->network_params.bootstrap.lazy_retry_limit); // Not confirmed - } - it = lazy_state_backlog.erase (it); - } - else - { - lazy_add (it->first, it->second.retry_limit); - ++it; - } - // We don't want to open read transactions for too long - ++read_count; - if (read_count % batch_read_size == 0) - { - transaction.refresh (); - } - } -} - -void nano::bootstrap_attempt_lazy::lazy_blocks_insert (nano::block_hash const & hash_a) -{ - debug_assert (!mutex.try_lock ()); - auto inserted (lazy_blocks.insert (std::hash<::nano::block_hash> () (hash_a))); - if (inserted.second) - { - ++lazy_blocks_count; - debug_assert (lazy_blocks_count > 0); - } -} - -void nano::bootstrap_attempt_lazy::lazy_blocks_erase (nano::block_hash const & hash_a) -{ - debug_assert (!mutex.try_lock ()); - auto erased (lazy_blocks.erase (std::hash<::nano::block_hash> () (hash_a))); - if (erased) - { - --lazy_blocks_count; - debug_assert (lazy_blocks_count != std::numeric_limits::max ()); - } -} - -bool nano::bootstrap_attempt_lazy::lazy_blocks_processed (nano::block_hash const & hash_a) -{ - return lazy_blocks.find (std::hash<::nano::block_hash> () (hash_a)) != lazy_blocks.end (); -} - -bool nano::bootstrap_attempt_lazy::lazy_processed_or_exists (nano::block_hash const & hash_a) -{ - auto node = this->node.lock (); - if (!node) - { - return true; - } - bool result (false); - nano::unique_lock lock{ mutex }; - if (lazy_blocks_processed (hash_a)) - { - result = true; - } - else - { - lock.unlock (); - if (node->block_or_pruned_exists (hash_a)) - { - result = true; - } - } - return result; -} - -void nano::bootstrap_attempt_lazy::get_information (boost::property_tree::ptree & tree_a) -{ - nano::lock_guard lock{ mutex }; - tree_a.put ("lazy_blocks", std::to_string (lazy_blocks.size ())); - tree_a.put ("lazy_state_backlog", std::to_string (lazy_state_backlog.size ())); - tree_a.put ("lazy_balances", std::to_string (lazy_balances.size ())); - tree_a.put ("lazy_undefined_links", std::to_string (lazy_undefined_links.size ())); - tree_a.put ("lazy_pulls", std::to_string (lazy_pulls.size ())); - tree_a.put ("lazy_keys", std::to_string (lazy_keys.size ())); - if (!lazy_keys.empty ()) - { - tree_a.put ("lazy_key_1", (*(lazy_keys.begin ())).to_string ()); - } -} - -nano::bootstrap_attempt_wallet::bootstrap_attempt_wallet (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string id_a) : - nano::bootstrap_attempt (node_a, nano::bootstrap_mode::wallet_lazy, incremental_id_a, id_a) -{ - node_a->bootstrap_initiator.notify_listeners (true); -} - -nano::bootstrap_attempt_wallet::~bootstrap_attempt_wallet () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - node->bootstrap_initiator.notify_listeners (false); -} - -void nano::bootstrap_attempt_wallet::request_pending (nano::unique_lock & lock_a) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - lock_a.unlock (); - auto connection_l (node->bootstrap_initiator.connections->connection (shared_from_this ())); - lock_a.lock (); - if (connection_l && !stopped) - { - auto account (wallet_accounts.front ()); - wallet_accounts.pop_front (); - ++pulling; - auto this_l = std::dynamic_pointer_cast (shared_from_this ()); - // The bulk_pull_account_client destructor attempt to requeue_pull which can cause a deadlock if this is the last reference - // Dispatch request in an external thread in case it needs to be destroyed - node->background ([connection_l, this_l, account] () { - auto client (std::make_shared (connection_l, this_l, account)); - client->request (); - }); - } -} - -void nano::bootstrap_attempt_wallet::requeue_pending (nano::account const & account_a) -{ - auto account (account_a); - { - nano::lock_guard lock{ mutex }; - wallet_accounts.push_front (account); - } - condition.notify_all (); -} - -void nano::bootstrap_attempt_wallet::wallet_start (std::deque & accounts_a) -{ - { - nano::lock_guard lock{ mutex }; - wallet_accounts.swap (accounts_a); - } - condition.notify_all (); -} - -bool nano::bootstrap_attempt_wallet::wallet_finished () -{ - debug_assert (!mutex.try_lock ()); - auto running (!stopped); - auto more_accounts (!wallet_accounts.empty ()); - auto still_pulling (pulling > 0); - return running && (more_accounts || still_pulling); -} - -void nano::bootstrap_attempt_wallet::run () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - debug_assert (started); - debug_assert (!node->flags.disable_wallet_bootstrap); - node->bootstrap_initiator.connections->populate_connections (false); - auto start_time (std::chrono::steady_clock::now ()); - auto max_time (std::chrono::minutes (10)); - nano::unique_lock lock{ mutex }; - while (wallet_finished () && std::chrono::steady_clock::now () - start_time < max_time) - { - if (!wallet_accounts.empty ()) - { - request_pending (lock); - } - else - { - condition.wait_for (lock, std::chrono::seconds (1)); - } - } - if (!stopped) - { - node->logger.info (nano::log::type::bootstrap_lazy, "Completed wallet lazy pulls"); - } - lock.unlock (); - stop (); - condition.notify_all (); -} - -std::size_t nano::bootstrap_attempt_wallet::wallet_size () -{ - nano::lock_guard lock{ mutex }; - return wallet_accounts.size (); -} - -void nano::bootstrap_attempt_wallet::get_information (boost::property_tree::ptree & tree_a) -{ - nano::lock_guard lock{ mutex }; - tree_a.put ("wallet_accounts", std::to_string (wallet_accounts.size ())); -} diff --git a/nano/node/bootstrap/bootstrap_lazy.hpp b/nano/node/bootstrap/bootstrap_lazy.hpp deleted file mode 100644 index d355eb796f..0000000000 --- a/nano/node/bootstrap/bootstrap_lazy.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace mi = boost::multi_index; - -namespace nano -{ -class node; -class lazy_state_backlog_item final -{ -public: - nano::link link{ 0 }; - nano::uint128_t balance{ 0 }; - unsigned retry_limit{ 0 }; -}; - -/** - * Lazy bootstrap session. Started with a block hash, this will "trace down" the blocks obtained to find a connection to the ledger. - * This attempts to quickly bootstrap a section of the ledger given a hash that's known to be confirmed. - */ -class bootstrap_attempt_lazy final : public bootstrap_attempt -{ -public: - explicit bootstrap_attempt_lazy (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string const & id_a = ""); - ~bootstrap_attempt_lazy (); - bool process_block (std::shared_ptr const &, nano::account const &, uint64_t, nano::bulk_pull::count_t, bool, unsigned) override; - void run () override; - bool lazy_start (nano::hash_or_account const &); - void lazy_add (nano::hash_or_account const &, unsigned); - void lazy_add (nano::pull_info const &); - void lazy_requeue (nano::block_hash const &, nano::block_hash const &); - bool lazy_finished (); - bool lazy_has_expired () const; - uint32_t lazy_batch_size (); - void lazy_pull_flush (nano::unique_lock & lock_a); - bool process_block_lazy (std::shared_ptr const &, nano::account const &, uint64_t, nano::bulk_pull::count_t, unsigned); - void lazy_block_state (std::shared_ptr const &, unsigned); - void lazy_block_state_backlog_check (std::shared_ptr const &, nano::block_hash const &); - void lazy_backlog_cleanup (); - void lazy_blocks_insert (nano::block_hash const &); - void lazy_blocks_erase (nano::block_hash const &); - bool lazy_blocks_processed (nano::block_hash const &); - bool lazy_processed_or_exists (nano::block_hash const &); - void get_information (boost::property_tree::ptree &) override; - std::unordered_set lazy_blocks; - std::unordered_map lazy_state_backlog; - std::unordered_set lazy_undefined_links; - std::unordered_map lazy_balances; - std::unordered_set lazy_keys; - std::deque> lazy_pulls; - std::chrono::steady_clock::time_point lazy_start_time; - std::atomic lazy_blocks_count{ 0 }; - std::size_t peer_count{ 0 }; - /** The maximum number of records to be read in while iterating over long lazy containers */ - static uint64_t constexpr batch_read_size = 256; -}; - -/** - * Wallet bootstrap session. This session will trace down accounts within local wallets to try and bootstrap those blocks first. - */ -class bootstrap_attempt_wallet final : public bootstrap_attempt -{ -public: - explicit bootstrap_attempt_wallet (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string id_a = ""); - ~bootstrap_attempt_wallet (); - void request_pending (nano::unique_lock &); - void requeue_pending (nano::account const &); - void run () override; - void wallet_start (std::deque &); - bool wallet_finished (); - std::size_t wallet_size (); - void get_information (boost::property_tree::ptree &) override; - std::deque wallet_accounts; -}; -} diff --git a/nano/node/bootstrap/bootstrap_legacy.cpp b/nano/node/bootstrap/bootstrap_legacy.cpp deleted file mode 100644 index 47368eccf2..0000000000 --- a/nano/node/bootstrap/bootstrap_legacy.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include -#include -#include -#include - -#include - -nano::bootstrap_attempt_legacy::bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t const incremental_id_a, std::string const & id_a, uint32_t const frontiers_age_a, nano::account const & start_account_a) : - nano::bootstrap_attempt (node_a, nano::bootstrap_mode::legacy, incremental_id_a, id_a), - frontiers_age (frontiers_age_a), - start_account (start_account_a) -{ - node_a->bootstrap_initiator.notify_listeners (true); -} - -bool nano::bootstrap_attempt_legacy::consume_future (std::future & future_a) -{ - bool result; - try - { - result = future_a.get (); - } - catch (std::future_error &) - { - result = true; - } - return result; -} - -void nano::bootstrap_attempt_legacy::stop () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - nano::unique_lock lock{ mutex }; - stopped = true; - lock.unlock (); - condition.notify_all (); - lock.lock (); - if (auto i = frontiers.lock ()) - { - try - { - i->promise.set_value (true); - } - catch (std::future_error &) - { - } - } - if (auto i = push.lock ()) - { - try - { - i->promise.set_value (true); - } - catch (std::future_error &) - { - } - } - lock.unlock (); - node->bootstrap_initiator.connections->clear_pulls (incremental_id); -} - -void nano::bootstrap_attempt_legacy::request_push (nano::unique_lock & lock_a) -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - bool error (false); - lock_a.unlock (); - auto connection_l (node->bootstrap_initiator.connections->find_connection (endpoint_frontier_request)); - lock_a.lock (); - if (connection_l) - { - std::future future; - { - auto this_l = std::dynamic_pointer_cast (shared_from_this ()); - auto client = std::make_shared (connection_l, this_l); - client->start (); - push = client; - future = client->promise.get_future (); - } - lock_a.unlock (); - error = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. - lock_a.lock (); - } -} - -void nano::bootstrap_attempt_legacy::add_frontier (nano::pull_info const & pull_a) -{ - // Prevent incorrect or malicious pulls with frontier 0 insertion - if (!pull_a.head.is_zero ()) - { - nano::lock_guard lock{ mutex }; - frontier_pulls.push_back (pull_a); - } -} - -void nano::bootstrap_attempt_legacy::add_bulk_push_target (nano::block_hash const & head, nano::block_hash const & end) -{ - nano::lock_guard lock{ mutex }; - bulk_push_targets.emplace_back (head, end); -} - -bool nano::bootstrap_attempt_legacy::request_bulk_push_target (std::pair & current_target_a) -{ - nano::lock_guard lock{ mutex }; - auto empty (bulk_push_targets.empty ()); - if (!empty) - { - current_target_a = bulk_push_targets.back (); - bulk_push_targets.pop_back (); - } - return empty; -} - -void nano::bootstrap_attempt_legacy::set_start_account (nano::account const & start_account_a) -{ - // Add last account from frontier request - nano::lock_guard lock{ mutex }; - start_account = start_account_a; -} - -bool nano::bootstrap_attempt_legacy::request_frontier (nano::unique_lock & lock_a, bool first_attempt) -{ - auto node = this->node.lock (); - if (!node) - { - return true; - } - auto result (true); - lock_a.unlock (); - auto connection_l (node->bootstrap_initiator.connections->connection (shared_from_this (), first_attempt)); - lock_a.lock (); - if (connection_l && !stopped) - { - endpoint_frontier_request = connection_l->channel->get_remote_endpoint (); - std::future future; - { - auto this_l = std::dynamic_pointer_cast (shared_from_this ()); - auto client = std::make_shared (connection_l, this_l); - client->run (start_account, frontiers_age, node->config.bootstrap_frontier_request_count); - frontiers = client; - future = client->promise.get_future (); - } - lock_a.unlock (); - result = consume_future (future); // This is out of scope of `client' so when the last reference via boost::asio::io_context is lost and the client is destroyed, the future throws an exception. - lock_a.lock (); - if (result) - { - frontier_pulls.clear (); - } - else - { - account_count = nano::narrow_cast (frontier_pulls.size ()); - // Shuffle pulls - release_assert (std::numeric_limits::max () > frontier_pulls.size ()); - if (!frontier_pulls.empty ()) - { - for (auto i = static_cast (frontier_pulls.size () - 1); i > 0; --i) - { - auto k = nano::random_pool::generate_word32 (0, i); - std::swap (frontier_pulls[i], frontier_pulls[k]); - } - } - // Add to regular pulls - while (!frontier_pulls.empty ()) - { - auto pull (frontier_pulls.front ()); - lock_a.unlock (); - node->bootstrap_initiator.connections->add_pull (pull); - lock_a.lock (); - ++pulling; - frontier_pulls.pop_front (); - } - } - if (!result) - { - node->logger.debug (nano::log::type::bootstrap_legacy, "Completed frontier request, {} out of sync accounts according to {}", account_count.load (), connection_l->channel->to_string ()); - } - else - { - node->stats.inc (nano::stat::type::error, nano::stat::detail::frontier_req, nano::stat::dir::out); - } - } - return result; -} - -void nano::bootstrap_attempt_legacy::run_start (nano::unique_lock & lock_a) -{ - frontiers_received = false; - auto frontier_failure (true); - uint64_t frontier_attempts (0); - while (!stopped && frontier_failure) - { - ++frontier_attempts; - frontier_failure = request_frontier (lock_a, frontier_attempts == 1); - } - frontiers_received = true; -} - -void nano::bootstrap_attempt_legacy::run () -{ - auto node = this->node.lock (); - if (!node) - { - return; - } - debug_assert (started); - debug_assert (!node->flags.disable_legacy_bootstrap); - node->bootstrap_initiator.connections->populate_connections (false); - nano::unique_lock lock{ mutex }; - run_start (lock); - while (still_pulling ()) - { - while (still_pulling ()) - { - // clang-format off - condition.wait (lock, [&stopped = stopped, &pulling = pulling] { return stopped || pulling == 0; }); - } - - // TODO: This check / wait is a heuristic and should be improved. - auto wait_start = std::chrono::steady_clock::now (); - while (!stopped && node->block_processor.size (nano::block_source::bootstrap_legacy) != 0 && ((std::chrono::steady_clock::now () - wait_start) < std::chrono::seconds{ 10 })) - { - condition.wait_for (lock, std::chrono::milliseconds{ 100 }, [this, node] { return stopped || node->block_processor.size (nano::block_source::bootstrap_legacy) == 0; }); - } - - if (start_account.number () != std::numeric_limits::max ()) - { - node->logger.debug(nano::log::type::bootstrap_legacy, "Requesting new frontiers after: {}", start_account.to_account ()); - - // Requesting new frontiers - run_start (lock); - } - } - if (!stopped) - { - node->logger.debug(nano::log::type::bootstrap_legacy, "Completed legacy pulls"); - - if (!node->flags.disable_bootstrap_bulk_push_client) - { - request_push (lock); - } - } - lock.unlock (); - stop (); - condition.notify_all (); -} - -void nano::bootstrap_attempt_legacy::get_information (boost::property_tree::ptree & tree_a) -{ - nano::lock_guard lock{ mutex }; - tree_a.put ("frontier_pulls", std::to_string (frontier_pulls.size ())); - tree_a.put ("frontiers_received", static_cast (frontiers_received)); - tree_a.put ("frontiers_age", std::to_string (frontiers_age)); - tree_a.put ("last_account", start_account.to_account ()); -} diff --git a/nano/node/bootstrap/bootstrap_legacy.hpp b/nano/node/bootstrap/bootstrap_legacy.hpp deleted file mode 100644 index 51423d778a..0000000000 --- a/nano/node/bootstrap/bootstrap_legacy.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include -#include -#include - -namespace nano -{ -class node; - -/** - * Legacy bootstrap session. This is made up of 3 phases: frontier requests, bootstrap pulls, bootstrap pushes. - */ -class bootstrap_attempt_legacy : public bootstrap_attempt -{ -public: - explicit bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t const incremental_id_a, std::string const & id_a, uint32_t const frontiers_age_a, nano::account const & start_account_a); - void run () override; - bool consume_future (std::future &); - void stop () override; - bool request_frontier (nano::unique_lock &, bool = false); - void request_push (nano::unique_lock &); - void add_frontier (nano::pull_info const &); - void add_bulk_push_target (nano::block_hash const &, nano::block_hash const &); - bool request_bulk_push_target (std::pair &); - void set_start_account (nano::account const &); - void run_start (nano::unique_lock &); - void get_information (boost::property_tree::ptree &) override; - nano::tcp_endpoint endpoint_frontier_request; - std::weak_ptr frontiers; - std::weak_ptr push; - std::deque frontier_pulls; - std::vector> bulk_push_targets; - nano::account start_account{}; - std::atomic account_count{ 0 }; - uint32_t frontiers_age; -}; -} diff --git a/nano/node/bootstrap/bootstrap_server.cpp b/nano/node/bootstrap/bootstrap_server.cpp index d8320164d5..2fd6cb7b6b 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -104,7 +104,7 @@ bool nano::bootstrap_server::verify (const nano::asc_pull_req & message) const return std::visit (verify_visitor{}, message.payload); } -bool nano::bootstrap_server::request (nano::asc_pull_req const & message, std::shared_ptr channel) +bool nano::bootstrap_server::request (nano::asc_pull_req const & message, std::shared_ptr const & channel) { if (!verify (message)) { @@ -113,8 +113,7 @@ bool nano::bootstrap_server::request (nano::asc_pull_req const & message, std::s } // If channel is full our response will be dropped anyway, so filter that early - // TODO: Add per channel limits (this ideally should be done on the channel message processing side) - if (channel->max (nano::transport::traffic_type::bootstrap)) + if (channel->max (nano::transport::traffic_type::bootstrap_server)) { stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::channel_full, nano::stat::dir::in); return false; @@ -160,6 +159,7 @@ void nano::bootstrap_server::respond (nano::asc_pull_ack & response, std::shared } void operator() (nano::asc_pull_ack::account_info_payload const & pld) { + stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::account_info, nano::stat::dir::out); } void operator() (nano::asc_pull_ack::frontiers_payload const & pld) { @@ -171,13 +171,9 @@ void nano::bootstrap_server::respond (nano::asc_pull_ack & response, std::shared on_response.notify (response, channel); channel->send ( - response, [this] (auto & ec, auto size) { - if (ec) - { - stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::write_error, nano::stat::dir::out); - } - }, - nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type::bootstrap); + response, nano::transport::traffic_type::bootstrap_server, [this] (auto & ec, auto size) { + stats.inc (nano::stat::type::bootstrap_server_ec, to_stat_detail (ec), nano::stat::dir::out); + }); } void nano::bootstrap_server::run () @@ -220,7 +216,7 @@ void nano::bootstrap_server::run_batch (nano::unique_lock & lock) transaction.refresh_if_needed (); - if (!channel->max (nano::transport::traffic_type::bootstrap)) + if (!channel->max (nano::transport::traffic_type::bootstrap_server)) { auto response = process (transaction, request); respond (response, channel); diff --git a/nano/node/bootstrap/bootstrap_server.hpp b/nano/node/bootstrap/bootstrap_server.hpp index 41c33404d1..4a211cafb7 100644 --- a/nano/node/bootstrap/bootstrap_server.hpp +++ b/nano/node/bootstrap/bootstrap_server.hpp @@ -42,7 +42,7 @@ class bootstrap_server final * Process `asc_pull_req` message coming from network. * Reply will be sent back over passed in `channel` */ - bool request (nano::asc_pull_req const & message, std::shared_ptr channel); + bool request (nano::asc_pull_req const & message, std::shared_ptr const & channel); public: // Events nano::observer_set const &> on_response; diff --git a/nano/node/bootstrap/bootstrap_service.cpp b/nano/node/bootstrap/bootstrap_service.cpp new file mode 100644 index 0000000000..8f60f9924f --- /dev/null +++ b/nano/node/bootstrap/bootstrap_service.cpp @@ -0,0 +1,1193 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +nano::bootstrap_service::bootstrap_service (nano::node_config const & node_config_a, nano::block_processor & block_processor_a, nano::ledger & ledger_a, nano::network & network_a, nano::stats & stat_a, nano::logger & logger_a) : + config{ node_config_a.bootstrap }, + network_constants{ node_config_a.network_params.network }, + block_processor{ block_processor_a }, + ledger{ ledger_a }, + network{ network_a }, + stats{ stat_a }, + logger{ logger_a }, + accounts{ config.account_sets, stats }, + database_scan{ ledger }, + frontiers{ config.frontier_scan, stats }, + throttle{ compute_throttle_size () }, + scoring{ config, node_config_a.network_params.network }, + limiter{ config.rate_limit }, + database_limiter{ config.database_rate_limit }, + frontiers_limiter{ config.frontier_rate_limit }, + workers{ 1, nano::thread_role::name::bootstrap_worker } +{ + block_processor.batch_processed.add ([this] (auto const & batch) { + { + nano::lock_guard lock{ mutex }; + + auto transaction = ledger.tx_begin_read (); + for (auto const & [result, context] : batch) + { + debug_assert (context.block != nullptr); + inspect (transaction, result, *context.block, context.source); + } + } + condition.notify_all (); + }); + + // Unblock rolled back accounts as the dependency is no longer valid + block_processor.rolled_back.add ([this] (auto const & blocks, auto const & rollback_root) { + nano::lock_guard lock{ mutex }; + for (auto const & block : blocks) + { + debug_assert (block != nullptr); + accounts.unblock (block->account ()); + } + }); + + accounts.priority_set (node_config_a.network_params.ledger.genesis->account_field ().value ()); +} + +nano::bootstrap_service::~bootstrap_service () +{ + // All threads must be stopped before destruction + debug_assert (!priorities_thread.joinable ()); + debug_assert (!database_thread.joinable ()); + debug_assert (!dependencies_thread.joinable ()); + debug_assert (!frontiers_thread.joinable ()); + debug_assert (!cleanup_thread.joinable ()); + debug_assert (!workers.alive ()); +} + +void nano::bootstrap_service::start () +{ + debug_assert (!priorities_thread.joinable ()); + debug_assert (!database_thread.joinable ()); + debug_assert (!dependencies_thread.joinable ()); + debug_assert (!frontiers_thread.joinable ()); + debug_assert (!cleanup_thread.joinable ()); + + if (!config.enable) + { + logger.warn (nano::log::type::bootstrap, "Bootstrap is disabled, node will not be able to synchronize with the network"); + return; + } + + workers.start (); + + if (config.enable_scan) + { + priorities_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::bootstrap); + run_priorities (); + }); + } + + if (config.enable_database_scan) + { + database_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::bootstrap_database_scan); + run_database (); + }); + } + + if (config.enable_dependency_walker) + { + dependencies_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::bootstrap_dependency_walker); + run_dependencies (); + }); + } + + if (config.enable_frontier_scan) + { + frontiers_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::bootstrap_frontier_scan); + run_frontiers (); + }); + } + + cleanup_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::bootstrap_cleanup); + run_timeouts (); + }); +} + +void nano::bootstrap_service::stop () +{ + { + nano::lock_guard lock{ mutex }; + stopped = true; + } + condition.notify_all (); + + nano::join_or_pass (priorities_thread); + nano::join_or_pass (database_thread); + nano::join_or_pass (dependencies_thread); + nano::join_or_pass (frontiers_thread); + nano::join_or_pass (cleanup_thread); + + workers.stop (); +} + +bool nano::bootstrap_service::send (std::shared_ptr const & channel, async_tag tag) +{ + debug_assert (tag.type != query_type::invalid); + debug_assert (tag.source != query_source::invalid); + + { + nano::lock_guard lock{ mutex }; + debug_assert (tags.get ().count (tag.id) == 0); + // Give extra time for the request to be processed by the channel + tag.cutoff = std::chrono::steady_clock::now () + config.request_timeout * 4; + tags.get ().insert (tag); + } + + nano::asc_pull_req request{ network_constants }; + request.id = tag.id; + + switch (tag.type) + { + case query_type::blocks_by_hash: + case query_type::blocks_by_account: + { + request.type = nano::asc_pull_type::blocks; + + nano::asc_pull_req::blocks_payload pld; + pld.start = tag.start; + pld.count = tag.count; + pld.start_type = tag.type == query_type::blocks_by_hash ? nano::asc_pull_req::hash_type::block : nano::asc_pull_req::hash_type::account; + request.payload = pld; + } + break; + case query_type::account_info_by_hash: + { + request.type = nano::asc_pull_type::account_info; + + nano::asc_pull_req::account_info_payload pld; + pld.target_type = nano::asc_pull_req::hash_type::block; // Query account info by block hash + pld.target = tag.start; + request.payload = pld; + } + break; + case query_type::frontiers: + { + request.type = nano::asc_pull_type::frontiers; + + nano::asc_pull_req::frontiers_payload pld; + pld.start = tag.start.as_account (); + pld.count = nano::asc_pull_ack::frontiers_payload::max_frontiers; + request.payload = pld; + } + break; + default: + debug_assert (false); + } + + request.update_header (); + + bool sent = channel->send ( + request, nano::transport::traffic_type::bootstrap_requests, [this, id = tag.id] (auto const & ec, auto size) { + nano::lock_guard lock{ mutex }; + if (auto it = tags.get ().find (id); it != tags.get ().end ()) + { + stats.inc (nano::stat::type::bootstrap_request_ec, to_stat_detail (ec), nano::stat::dir::out); + if (!ec) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::request_success, nano::stat::dir::out); + tags.get ().modify (it, [&] (auto & tag) { + // After the request has been sent, the peer has a limited time to respond + tag.cutoff = std::chrono::steady_clock::now () + config.request_timeout; + }); + } + else + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::request_failed, nano::stat::dir::out); + tags.get ().erase (it); + } + } }); + + if (sent) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::request); + stats.inc (nano::stat::type::bootstrap_request, to_stat_detail (tag.type)); + } + else + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::request_failed); + } + + return sent; +} + +std::size_t nano::bootstrap_service::priority_size () const +{ + nano::lock_guard lock{ mutex }; + return accounts.priority_size (); +} + +std::size_t nano::bootstrap_service::blocked_size () const +{ + nano::lock_guard lock{ mutex }; + return accounts.blocked_size (); +} + +std::size_t nano::bootstrap_service::score_size () const +{ + nano::lock_guard lock{ mutex }; + return scoring.size (); +} + +bool nano::bootstrap_service::prioritized (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.prioritized (account); +} + +bool nano::bootstrap_service::blocked (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.blocked (account); +} + +/** Inspects a block that has been processed by the block processor +- Marks an account as blocked if the result code is gap source as there is no reason request additional blocks for this account until the dependency is resolved +- Marks an account as forwarded if it has been recently referenced by a block that has been inserted. + */ +void nano::bootstrap_service::inspect (secure::transaction const & tx, nano::block_status const & result, nano::block const & block, nano::block_source source) +{ + debug_assert (!mutex.try_lock ()); + + auto const hash = block.hash (); + + switch (result) + { + case nano::block_status::progress: + { + // Progress blocks from live traffic don't need further bootstrapping + if (source != nano::block_source::live) + { + const auto account = block.account (); + + // If we've inserted any block in to an account, unmark it as blocked + accounts.unblock (account); + accounts.priority_up (account); + + if (block.is_send ()) + { + auto destination = block.destination (); + accounts.unblock (destination, hash); // Unblocking automatically inserts account into priority set + accounts.priority_set (destination); + } + } + } + break; + case nano::block_status::gap_source: + { + // Prevent malicious live traffic from filling up the blocked set + if (source == nano::block_source::bootstrap) + { + const auto account = block.previous ().is_zero () ? block.account_field ().value () : ledger.any.block_account (tx, block.previous ()).value_or (0); + const auto source_hash = block.source_field ().value_or (block.link_field ().value_or (0).as_block_hash ()); + + if (!account.is_zero () && !source_hash.is_zero ()) + { + // Mark account as blocked because it is missing the source block + accounts.block (account, source_hash); + } + } + } + break; + case nano::block_status::gap_previous: + { + // Prevent live traffic from evicting accounts from the priority list + if (source == nano::block_source::live && !accounts.priority_half_full () && !accounts.blocked_half_full ()) + { + if (block.type () == block_type::state) + { + const auto account = block.account_field ().value (); + accounts.priority_set (account); + } + } + } + break; + default: // No need to handle other cases + break; + } +} + +void nano::bootstrap_service::wait (std::function const & predicate) const +{ + std::unique_lock lock{ mutex }; + std::chrono::milliseconds interval = 5ms; + while (!stopped && !predicate ()) + { + condition.wait_for (lock, interval); + interval = std::min (interval * 2, config.throttle_wait); + } +} + +void nano::bootstrap_service::wait_block_processor () const +{ + wait ([this] () { + return block_processor.size (nano::block_source::bootstrap) < config.block_processor_threshold; + }); +} + +std::shared_ptr nano::bootstrap_service::wait_channel () +{ + // Limit the number of in-flight requests + wait ([this] () { + return tags.size () < config.max_requests; + }); + + // Wait until more requests can be sent + wait ([this] () { + return limiter.should_pass (1); + }); + + // Wait until a channel is available + std::shared_ptr channel; + wait ([this, &channel] () { + channel = scoring.channel (); + return channel != nullptr; // Wait until a channel is available + }); + return channel; +} + +size_t nano::bootstrap_service::count_tags (nano::account const & account, query_source source) const +{ + debug_assert (!mutex.try_lock ()); + auto [begin, end] = tags.get ().equal_range (account); + return std::count_if (begin, end, [source] (auto const & tag) { return tag.source == source; }); +} + +size_t nano::bootstrap_service::count_tags (nano::block_hash const & hash, query_source source) const +{ + debug_assert (!mutex.try_lock ()); + auto [begin, end] = tags.get ().equal_range (hash); + return std::count_if (begin, end, [source] (auto const & tag) { return tag.source == source; }); +} + +nano::bootstrap::account_sets::priority_result nano::bootstrap_service::next_priority () +{ + debug_assert (!mutex.try_lock ()); + + auto next = accounts.next_priority ([this] (nano::account const & account) { + return count_tags (account, query_source::priority) < 4; + }); + if (next.account.is_zero ()) + { + return {}; + } + stats.inc (nano::stat::type::bootstrap_next, nano::stat::detail::next_priority); + return next; +} + +nano::bootstrap::account_sets::priority_result nano::bootstrap_service::wait_priority () +{ + nano::bootstrap::account_sets::priority_result result{}; + wait ([this, &result] () { + debug_assert (!mutex.try_lock ()); + result = next_priority (); + if (!result.account.is_zero ()) + { + return true; + } + return false; + }); + return result; +} + +nano::account nano::bootstrap_service::next_database (bool should_throttle) +{ + debug_assert (!mutex.try_lock ()); + debug_assert (config.database_warmup_ratio > 0); + + // Throttling increases the weight of database requests + if (!database_limiter.should_pass (should_throttle ? config.database_warmup_ratio : 1)) + { + return { 0 }; + } + auto account = database_scan.next ([this] (nano::account const & account) { + return count_tags (account, query_source::database) == 0; + }); + if (account.is_zero ()) + { + return { 0 }; + } + stats.inc (nano::stat::type::bootstrap_next, nano::stat::detail::next_database); + return account; +} + +nano::account nano::bootstrap_service::wait_database (bool should_throttle) +{ + nano::account result{ 0 }; + wait ([this, &result, should_throttle] () { + debug_assert (!mutex.try_lock ()); + result = next_database (should_throttle); + if (!result.is_zero ()) + { + return true; + } + return false; + }); + return result; +} + +nano::block_hash nano::bootstrap_service::next_blocking () +{ + debug_assert (!mutex.try_lock ()); + + auto blocking = accounts.next_blocking ([this] (nano::block_hash const & hash) { + return count_tags (hash, query_source::dependencies) == 0; + }); + if (blocking.is_zero ()) + { + return { 0 }; + } + stats.inc (nano::stat::type::bootstrap_next, nano::stat::detail::next_blocking); + return blocking; +} + +nano::block_hash nano::bootstrap_service::wait_blocking () +{ + nano::block_hash result{ 0 }; + wait ([this, &result] () { + debug_assert (!mutex.try_lock ()); + result = next_blocking (); + if (!result.is_zero ()) + { + return true; + } + return false; + }); + return result; +} + +nano::account nano::bootstrap_service::wait_frontier () +{ + nano::account result{ 0 }; + wait ([this, &result] () { + debug_assert (!mutex.try_lock ()); + result = frontiers.next (); + if (!result.is_zero ()) + { + stats.inc (nano::stat::type::bootstrap_next, nano::stat::detail::next_frontier); + return true; + } + return false; + }); + return result; +} + +bool nano::bootstrap_service::request (nano::account account, size_t count, std::shared_ptr const & channel, query_source source) +{ + debug_assert (count > 0); + debug_assert (count <= nano::bootstrap_server::max_blocks); + + // Limit the max number of blocks to pull + count = std::min (count, config.max_pull_count); + + async_tag tag{}; + tag.source = source; + tag.account = account; + tag.count = count; + + { + auto transaction = ledger.store.tx_begin_read (); + + // Check if the account picked has blocks, if it does, start the pull from the highest block + if (auto info = ledger.store.account.get (transaction, account)) + { + tag.type = query_type::blocks_by_hash; + + // Probabilistically choose between requesting blocks from account frontier or confirmed frontier + // Optimistic requests start from the (possibly unconfirmed) account frontier and are vulnerable to bootstrap poisoning + // Safe requests start from the confirmed frontier and given enough time will eventually resolve forks + bool optimistic_reuest = rng.random (100) < config.optimistic_request_percentage; + if (!optimistic_reuest) + { + if (auto conf_info = ledger.store.confirmation_height.get (transaction, account)) + { + stats.inc (nano::stat::type::bootstrap_request_blocks, nano::stat::detail::safe); + tag.start = conf_info->frontier; + tag.hash = conf_info->height; + } + } + if (tag.start.is_zero ()) + { + stats.inc (nano::stat::type::bootstrap_request_blocks, nano::stat::detail::optimistic); + tag.start = info->head; + tag.hash = info->head; + } + } + else + { + stats.inc (nano::stat::type::bootstrap_request_blocks, nano::stat::detail::base); + tag.type = query_type::blocks_by_account; + tag.start = account; + } + } + + return send (channel, tag); +} + +bool nano::bootstrap_service::request_info (nano::block_hash hash, std::shared_ptr const & channel, query_source source) +{ + async_tag tag{}; + tag.type = query_type::account_info_by_hash; + tag.source = source; + tag.start = hash; + tag.hash = hash; + return send (channel, tag); +} + +bool nano::bootstrap_service::request_frontiers (nano::account start, std::shared_ptr const & channel, query_source source) +{ + async_tag tag{}; + tag.type = query_type::frontiers; + tag.source = source; + tag.start = start; + return send (channel, tag); +} + +void nano::bootstrap_service::run_one_priority () +{ + wait_block_processor (); + auto channel = wait_channel (); + if (!channel) + { + return; + } + auto [account, priority, fails] = wait_priority (); + if (account.is_zero ()) + { + return; + } + + // Decide how many blocks to request + size_t const min_pull_count = 2; + auto pull_count = std::clamp (static_cast (priority), min_pull_count, nano::bootstrap_server::max_blocks); + + bool sent = request (account, pull_count, channel, query_source::priority); + + // Only cooldown accounts that are likely to have more blocks + // This is to avoid requesting blocks from the same frontier multiple times, before the block processor had a chance to process them + // Not throttling accounts that are probably up-to-date allows us to evict them from the priority set faster + if (sent && fails == 0) + { + nano::lock_guard lock{ mutex }; + accounts.timestamp_set (account); + } +} + +void nano::bootstrap_service::run_priorities () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + lock.unlock (); + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::loop); + run_one_priority (); + lock.lock (); + } +} + +void nano::bootstrap_service::run_one_database (bool should_throttle) +{ + wait_block_processor (); + auto channel = wait_channel (); + if (!channel) + { + return; + } + auto account = wait_database (should_throttle); + if (account.is_zero ()) + { + return; + } + request (account, 2, channel, query_source::database); +} + +void nano::bootstrap_service::run_database () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + // Avoid high churn rate of database requests + bool should_throttle = !database_scan.warmed_up () && throttle.throttled (); + lock.unlock (); + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::loop_database); + run_one_database (should_throttle); + lock.lock (); + } +} + +void nano::bootstrap_service::run_one_dependency () +{ + // No need to wait for block_processor, as we are not processing blocks + auto channel = wait_channel (); + if (!channel) + { + return; + } + auto blocking = wait_blocking (); + if (blocking.is_zero ()) + { + return; + } + request_info (blocking, channel, query_source::dependencies); +} + +void nano::bootstrap_service::run_dependencies () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + lock.unlock (); + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::loop_dependencies); + run_one_dependency (); + lock.lock (); + } +} + +void nano::bootstrap_service::run_one_frontier () +{ + // No need to wait for block_processor, as we are not processing blocks + wait ([this] () { + return !accounts.priority_half_full (); + }); + wait ([this] () { + return frontiers_limiter.should_pass (1); + }); + wait ([this] () { + return workers.queued_tasks () < config.frontier_scan.max_pending; + }); + auto channel = wait_channel (); + if (!channel) + { + return; + } + auto frontier = wait_frontier (); + if (frontier.is_zero ()) + { + return; + } + request_frontiers (frontier, channel, query_source::frontiers); +} + +void nano::bootstrap_service::run_frontiers () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + lock.unlock (); + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::loop_frontiers); + run_one_frontier (); + lock.lock (); + } +} + +void nano::bootstrap_service::cleanup_and_sync () +{ + debug_assert (!mutex.try_lock ()); + + scoring.sync (network.list (/* all */ 0, network_constants.bootstrap_protocol_version_min)); + scoring.timeout (); + + throttle.resize (compute_throttle_size ()); + + auto const now = std::chrono::steady_clock::now (); + auto should_timeout = [&] (async_tag const & tag) { + return tag.cutoff < now; + }; + + auto & tags_by_order = tags.get (); + while (!tags_by_order.empty () && should_timeout (tags_by_order.front ())) + { + auto tag = tags_by_order.front (); + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::timeout); + stats.inc (nano::stat::type::bootstrap_timeout, to_stat_detail (tag.type)); + tags_by_order.pop_front (); + } + + if (sync_dependencies_interval.elapsed (60s)) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::sync_dependencies); + accounts.sync_dependencies (); + } +} + +void nano::bootstrap_service::run_timeouts () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::loop_cleanup); + cleanup_and_sync (); + condition.wait_for (lock, 5s, [this] () { return stopped; }); + } +} + +void nano::bootstrap_service::process (nano::asc_pull_ack const & message, std::shared_ptr const & channel) +{ + nano::unique_lock lock{ mutex }; + + // Only process messages that have a known tag + auto it = tags.get ().find (message.id); + if (it == tags.get ().end ()) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::missing_tag); + return; + } + + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::reply); + + auto tag = *it; + tags.get ().erase (it); // Iterator is invalid after this point + + // Verifies that response type corresponds to our query + struct payload_verifier + { + query_type type; + + bool operator() (const nano::asc_pull_ack::blocks_payload & response) const + { + return type == query_type::blocks_by_hash || type == query_type::blocks_by_account; + } + bool operator() (const nano::asc_pull_ack::account_info_payload & response) const + { + return type == query_type::account_info_by_hash; + } + bool operator() (const nano::asc_pull_ack::frontiers_payload & response) const + { + return type == query_type::frontiers; + } + bool operator() (const nano::empty_payload & response) const + { + return false; // Should not happen + } + }; + + bool valid = std::visit (payload_verifier{ tag.type }, message.payload); + if (!valid) + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::invalid_response_type); + return; + } + + // Track bootstrap request response time + stats.inc (nano::stat::type::bootstrap_reply, to_stat_detail (tag.type)); + stats.sample (nano::stat::sample::bootstrap_tag_duration, nano::log::milliseconds_delta (tag.timestamp), { 0, config.request_timeout.count () }); + + lock.unlock (); + + // Process the response payload + bool ok = std::visit ([this, &tag] (auto && request) { return process (request, tag); }, message.payload); + if (ok) + { + lock.lock (); + scoring.received_message (channel); + lock.unlock (); + } + else + { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::invalid_response); + } + + condition.notify_all (); +} + +bool nano::bootstrap_service::process (const nano::asc_pull_ack::blocks_payload & response, const async_tag & tag) +{ + debug_assert (tag.type == query_type::blocks_by_hash || tag.type == query_type::blocks_by_account); + + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::blocks); + + auto result = verify (response, tag); + switch (result) + { + case verify_result::ok: + { + stats.inc (nano::stat::type::bootstrap_verify_blocks, nano::stat::detail::ok); + stats.add (nano::stat::type::bootstrap, nano::stat::detail::blocks, nano::stat::dir::in, response.blocks.size ()); + + auto blocks = response.blocks; + + // Avoid re-processing the block we already have + release_assert (blocks.size () >= 1); + if (blocks.front ()->hash () == tag.start.as_block_hash ()) + { + blocks.pop_front (); + } + + for (auto const & block : blocks) + { + if (block == blocks.back ()) + { + // It's the last block submitted for this account chain, reset timestamp to allow more requests + block_processor.add (block, nano::block_source::bootstrap, nullptr, [this, account = tag.account] (auto result) { + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::timestamp_reset); + { + nano::lock_guard guard{ mutex }; + accounts.timestamp_reset (account); + } + condition.notify_all (); + }); + } + else + { + block_processor.add (block, nano::block_source::bootstrap); + } + } + + if (tag.source == query_source::database) + { + nano::lock_guard lock{ mutex }; + throttle.add (true); + } + } + break; + case verify_result::nothing_new: + { + stats.inc (nano::stat::type::bootstrap_verify_blocks, nano::stat::detail::nothing_new); + { + nano::lock_guard lock{ mutex }; + + accounts.priority_down (tag.account); + accounts.timestamp_reset (tag.account); + + if (tag.source == query_source::database) + { + throttle.add (false); + } + } + condition.notify_all (); + } + break; + case verify_result::invalid: + { + stats.inc (nano::stat::type::bootstrap_verify_blocks, nano::stat::detail::invalid); + } + break; + } + + return result != verify_result::invalid; +} + +bool nano::bootstrap_service::process (const nano::asc_pull_ack::account_info_payload & response, const async_tag & tag) +{ + debug_assert (tag.type == query_type::account_info_by_hash); + debug_assert (!tag.hash.is_zero ()); + + if (response.account.is_zero ()) + { + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::account_info_empty); + return true; // OK, but nothing to do + } + + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::account_info); + + // Prioritize account containing the dependency + { + nano::lock_guard lock{ mutex }; + accounts.dependency_update (tag.hash, response.account); + accounts.priority_set (response.account, nano::bootstrap::account_sets::priority_cutoff); // Use the lowest possible priority here + } + + return true; // OK, no way to verify the response +} + +bool nano::bootstrap_service::process (const nano::asc_pull_ack::frontiers_payload & response, const async_tag & tag) +{ + debug_assert (tag.type == query_type::frontiers); + debug_assert (!tag.start.is_zero ()); + + if (response.frontiers.empty ()) + { + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::frontiers_empty); + return true; // OK, but nothing to do + } + + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::frontiers); + + auto result = verify (response, tag); + switch (result) + { + case verify_result::ok: + { + stats.inc (nano::stat::type::bootstrap_verify_frontiers, nano::stat::detail::ok); + stats.add (nano::stat::type::bootstrap, nano::stat::detail::frontiers, nano::stat::dir::in, response.frontiers.size ()); + + { + nano::lock_guard lock{ mutex }; + frontiers.process (tag.start.as_account (), response.frontiers); + } + + // Allow some overfill to avoid unnecessarily dropping responses + if (workers.queued_tasks () < config.frontier_scan.max_pending * 4) + { + workers.post ([this, frontiers = response.frontiers] { + process_frontiers (frontiers); + }); + } + else + { + stats.add (nano::stat::type::bootstrap, nano::stat::detail::frontiers_dropped, response.frontiers.size ()); + } + } + break; + case verify_result::nothing_new: + { + stats.inc (nano::stat::type::bootstrap_verify_frontiers, nano::stat::detail::nothing_new); + } + break; + case verify_result::invalid: + { + stats.inc (nano::stat::type::bootstrap_verify_frontiers, nano::stat::detail::invalid); + } + break; + } + + return result != verify_result::invalid; +} + +bool nano::bootstrap_service::process (const nano::empty_payload & response, const async_tag & tag) +{ + stats.inc (nano::stat::type::bootstrap_process, nano::stat::detail::empty); + debug_assert (false, "empty payload"); // Should not happen + return false; // Invalid +} + +void nano::bootstrap_service::process_frontiers (std::deque> const & frontiers) +{ + release_assert (!frontiers.empty ()); + + // Accounts must be passed in ascending order + debug_assert (std::adjacent_find (frontiers.begin (), frontiers.end (), [] (auto const & lhs, auto const & rhs) { + return lhs.first.number () >= rhs.first.number (); + }) + == frontiers.end ()); + + stats.inc (nano::stat::type::bootstrap, nano::stat::detail::processing_frontiers); + + size_t outdated = 0; + size_t pending = 0; + + // Accounts with outdated frontiers to sync + std::deque result; + { + auto transaction = ledger.tx_begin_read (); + + auto const start = frontiers.front ().first; + nano::bootstrap::account_database_crawler account_crawler{ ledger.store, transaction, start }; + nano::bootstrap::pending_database_crawler pending_crawler{ ledger.store, transaction, start }; + + auto block_exists = [&] (nano::block_hash const & hash) { + return ledger.any.block_exists_or_pruned (transaction, hash); + }; + + auto should_prioritize = [&] (nano::account const & account, nano::block_hash const & frontier) { + account_crawler.advance_to (account); + pending_crawler.advance_to (account); + + // Check if account exists in our ledger + if (account_crawler.current && account_crawler.current->first == account) + { + // Check for frontier mismatch + if (account_crawler.current->second.head != frontier) + { + // Check if frontier block exists in our ledger + if (!block_exists (frontier)) + { + outdated++; + return true; // Frontier is outdated + } + } + return false; // Account exists and frontier is up-to-date + } + + // Check if account has pending blocks in our ledger + if (pending_crawler.current && pending_crawler.current->first.account == account) + { + pending++; + return true; // Account doesn't exist but has pending blocks in the ledger + } + + return false; // Account doesn't exist in the ledger and has no pending blocks, can't be prioritized right now + }; + + for (auto const & [account, frontier] : frontiers) + { + if (should_prioritize (account, frontier)) + { + result.push_back (account); + } + } + } + + stats.add (nano::stat::type::bootstrap_frontiers, nano::stat::detail::processed, frontiers.size ()); + stats.add (nano::stat::type::bootstrap_frontiers, nano::stat::detail::prioritized, result.size ()); + stats.add (nano::stat::type::bootstrap_frontiers, nano::stat::detail::outdated, outdated); + stats.add (nano::stat::type::bootstrap_frontiers, nano::stat::detail::pending, pending); + + nano::lock_guard guard{ mutex }; + + for (auto const & account : result) + { + // Use the lowest possible priority here + accounts.priority_set (account, nano::bootstrap::account_sets::priority_cutoff); + } +} + +auto nano::bootstrap_service::verify (const nano::asc_pull_ack::blocks_payload & response, const async_tag & tag) const -> verify_result +{ + auto const & blocks = response.blocks; + + if (blocks.empty ()) + { + return verify_result::nothing_new; + } + if (blocks.size () == 1 && blocks.front ()->hash () == tag.start.as_block_hash ()) + { + return verify_result::nothing_new; + } + if (blocks.size () > tag.count) + { + return verify_result::invalid; + } + + auto const & first = blocks.front (); + switch (tag.type) + { + case query_type::blocks_by_hash: + { + if (first->hash () != tag.start.as_block_hash ()) + { + // TODO: Stat & log + return verify_result::invalid; + } + } + break; + case query_type::blocks_by_account: + { + // Open & state blocks always contain account field + if (first->account_field ().value_or (0) != tag.start.as_account ()) + { + // TODO: Stat & log + return verify_result::invalid; + } + } + break; + default: + return verify_result::invalid; + } + + // Verify blocks make a valid chain + nano::block_hash previous_hash = blocks.front ()->hash (); + for (int n = 1; n < blocks.size (); ++n) + { + auto & block = blocks[n]; + if (block->previous () != previous_hash) + { + // TODO: Stat & log + return verify_result::invalid; // Blocks do not make a chain + } + previous_hash = block->hash (); + } + + return verify_result::ok; +} + +auto nano::bootstrap_service::verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const -> verify_result +{ + auto const & frontiers = response.frontiers; + + if (frontiers.empty ()) + { + return verify_result::nothing_new; + } + + // Ensure frontiers accounts are in ascending order + nano::account previous{ 0 }; + for (auto const & [account, _] : frontiers) + { + if (account.number () <= previous.number ()) + { + return verify_result::invalid; + } + previous = account; + } + + // Ensure the frontiers are larger or equal to the requested frontier + if (frontiers.front ().first.number () < tag.start.as_account ().number ()) + { + return verify_result::invalid; + } + + return verify_result::ok; +} + +auto nano::bootstrap_service::info () const -> nano::bootstrap::account_sets::info_t +{ + nano::lock_guard lock{ mutex }; + return accounts.info (); +} + +std::size_t nano::bootstrap_service::compute_throttle_size () const +{ + auto ledger_size = ledger.account_count (); + size_t target = ledger_size > 0 ? config.throttle_coefficient * static_cast (std::log (ledger_size)) : 0; + size_t min_size = 16; + return std::max (target, min_size); +} + +nano::container_info nano::bootstrap_service::container_info () const +{ + nano::lock_guard lock{ mutex }; + + auto collect_limiters = [this] () { + nano::container_info info; + info.put ("total", limiter.size ()); + info.put ("database", database_limiter.size ()); + info.put ("frontiers", frontiers_limiter.size ()); + return info; + }; + + nano::container_info info; + info.put ("tags", tags); + info.put ("throttle", throttle.size ()); + info.put ("throttle_successes", throttle.successes ()); + info.add ("accounts", accounts.container_info ()); + info.add ("database_scan", database_scan.container_info ()); + info.add ("frontiers", frontiers.container_info ()); + info.add ("workers", workers.container_info ()); + info.add ("peers", scoring.container_info ()); + info.add ("limiters", collect_limiters ()); + return info; +} + +/* + * + */ + +nano::stat::detail nano::to_stat_detail (nano::bootstrap_service::query_type type) +{ + return nano::enum_util::cast (type); +} diff --git a/nano/node/bootstrap/bootstrap_service.hpp b/nano/node/bootstrap/bootstrap_service.hpp new file mode 100644 index 0000000000..f28d3fbe14 --- /dev/null +++ b/nano/node/bootstrap/bootstrap_service.hpp @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class bootstrap_service +{ +public: + bootstrap_service (nano::node_config const &, nano::block_processor &, nano::ledger &, nano::network &, nano::stats &, nano::logger &); + ~bootstrap_service (); + + void start (); + void stop (); + + /** + * Process `asc_pull_ack` message coming from network + */ + void process (nano::asc_pull_ack const & message, std::shared_ptr const &); + + std::size_t blocked_size () const; + std::size_t priority_size () const; + std::size_t score_size () const; + + bool prioritized (nano::account const &) const; + bool blocked (nano::account const &) const; + + nano::container_info container_info () const; + + nano::bootstrap::account_sets::info_t info () const; + +private: // Dependencies + bootstrap_config const & config; + nano::network_constants const & network_constants; + nano::block_processor & block_processor; + nano::ledger & ledger; + nano::network & network; + nano::stats & stats; + nano::logger & logger; + +public: // Tag + enum class query_type + { + invalid = 0, // Default initialization + blocks_by_hash, + blocks_by_account, + account_info_by_hash, + frontiers, + }; + + enum class query_source + { + invalid, + priority, + database, + dependencies, + frontiers, + }; + + struct async_tag + { + using id_t = nano::bootstrap::id_t; + + query_type type{ query_type::invalid }; + query_source source{ query_source::invalid }; + nano::hash_or_account start{ 0 }; + nano::account account{ 0 }; + nano::block_hash hash{ 0 }; + size_t count{ 0 }; + std::chrono::steady_clock::time_point cutoff{}; + std::chrono::steady_clock::time_point timestamp{ std::chrono::steady_clock::now () }; + id_t id{ nano::bootstrap::generate_id () }; + }; + +private: + /* Inspects a block that has been processed by the block processor */ + void inspect (secure::transaction const &, nano::block_status const & result, nano::block const & block, nano::block_source); + + void run_priorities (); + void run_one_priority (); + void run_database (); + void run_one_database (bool should_throttle); + void run_dependencies (); + void run_one_dependency (); + void run_frontiers (); + void run_one_frontier (); + void run_timeouts (); + void cleanup_and_sync (); + + /* Waits for a condition to be satisfied with incremental backoff */ + void wait (std::function const & predicate) const; + + /* Ensure there is enough space in block_processor for queuing new blocks */ + void wait_block_processor () const; + /* Waits for a channel that is not full */ + std::shared_ptr wait_channel (); + /* Waits until a suitable account outside of cooldown period is available */ + using priority_result = nano::bootstrap::account_sets::priority_result; + priority_result next_priority (); + priority_result wait_priority (); + /* Gets the next account from the database */ + nano::account next_database (bool should_throttle); + nano::account wait_database (bool should_throttle); + /* Waits for next available blocking block */ + nano::block_hash next_blocking (); + nano::block_hash wait_blocking (); + /* Waits for next available frontier scan range */ + nano::account wait_frontier (); + + bool request (nano::account, size_t count, std::shared_ptr const &, query_source); + bool request_info (nano::block_hash, std::shared_ptr const &, query_source); + bool request_frontiers (nano::account, std::shared_ptr const &, query_source); + bool send (std::shared_ptr const &, async_tag tag); + + bool process (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag); + bool process (nano::asc_pull_ack::account_info_payload const & response, async_tag const & tag); + bool process (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag); + bool process (nano::empty_payload const & response, async_tag const & tag); + + void process_frontiers (std::deque> const & frontiers); + + enum class verify_result + { + ok, + nothing_new, + invalid, + }; + + /** + * Verifies whether the received response is valid. Returns: + * - invalid: when received blocks do not correspond to requested hash/account or they do not make a valid chain + * - nothing_new: when received response indicates that the account chain does not have more blocks + * - ok: otherwise, if all checks pass + */ + verify_result verify (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag) const; + verify_result verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const; + + size_t count_tags (nano::account const & account, query_source source) const; + size_t count_tags (nano::block_hash const & hash, query_source source) const; + + // Calculates a lookback size based on the size of the ledger where larger ledgers have a larger sample count + std::size_t compute_throttle_size () const; + +private: + nano::bootstrap::account_sets accounts; + nano::bootstrap::database_scan database_scan; + nano::bootstrap::throttle throttle; + nano::bootstrap::peer_scoring scoring; + nano::bootstrap::frontier_scan frontiers; + + // clang-format off + class tag_sequenced {}; + class tag_id {}; + class tag_account {}; + class tag_hash {}; + + using ordered_tags = boost::multi_index_container>, + mi::hashed_unique, + mi::member>, + mi::hashed_non_unique, + mi::member>, + mi::hashed_non_unique, + mi::member> + >>; + // clang-format on + ordered_tags tags; + + // Rate limiter for all types of requests + nano::rate_limiter limiter; + // Requests for accounts from database have much lower hitrate and could introduce strain on the network + // A separate (lower) limiter ensures that we always reserve resources for querying accounts from priority queue + nano::rate_limiter database_limiter; + // Rate limiter for frontier requests + nano::rate_limiter frontiers_limiter; + + nano::interval sync_dependencies_interval; + + bool stopped{ false }; + mutable nano::mutex mutex; + mutable nano::condition_variable condition; + std::thread priorities_thread; + std::thread database_thread; + std::thread dependencies_thread; + std::thread frontiers_thread; + std::thread cleanup_thread; + + nano::thread_pool workers; + nano::random_generator_mt rng; +}; + +nano::stat::detail to_stat_detail (bootstrap_service::query_type); +} diff --git a/nano/node/bootstrap_ascending/common.hpp b/nano/node/bootstrap/common.hpp similarity index 53% rename from nano/node/bootstrap_ascending/common.hpp rename to nano/node/bootstrap/common.hpp index f59071e7a0..2cb654fc50 100644 --- a/nano/node/bootstrap_ascending/common.hpp +++ b/nano/node/bootstrap/common.hpp @@ -2,13 +2,13 @@ #include -namespace nano::bootstrap_ascending +namespace nano::bootstrap { using id_t = uint64_t; -static nano::bootstrap_ascending::id_t generate_id () +static nano::bootstrap::id_t generate_id () { - nano::bootstrap_ascending::id_t id; + nano::bootstrap::id_t id; nano::random_pool::generate_block (reinterpret_cast (&id), sizeof (id)); return id; } -} // nano::bootstrap_ascending +} diff --git a/nano/node/bootstrap/crawlers.hpp b/nano/node/bootstrap/crawlers.hpp new file mode 100644 index 0000000000..dec7ac8de1 --- /dev/null +++ b/nano/node/bootstrap/crawlers.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace nano::bootstrap +{ +struct account_database_crawler +{ + using value_type = std::pair; + + static constexpr size_t sequential_attempts = 10; + + account_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) : + store{ store }, + transaction{ transaction }, + it{ store.account.end (transaction) }, + end{ store.account.end (transaction) } + { + seek (start); + } + + void seek (nano::account const & account) + { + it = store.account.begin (transaction, account); + update_current (); + } + + void advance () + { + if (it == end) + { + debug_assert (!current); + return; + } + + ++it; + update_current (); + } + + void advance_to (nano::account const & account) + { + if (it == end) + { + debug_assert (!current); + return; + } + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached or overshoot the target account + if (it->first.number () >= account.number ()) + { + update_current (); + return; + } + } + + // If that fails, perform a fresh lookup + seek (account); + } + + std::optional current{}; + +private: + void update_current () + { + if (it != end) + { + current = *it; + } + else + { + current = std::nullopt; + } + } + + nano::store::component & store; + nano::store::transaction const & transaction; + + nano::store::account::iterator it; + nano::store::account::iterator const end; +}; + +struct pending_database_crawler +{ + using value_type = std::pair; + + static constexpr size_t sequential_attempts = 10; + + pending_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) : + store{ store }, + transaction{ transaction }, + it{ store.pending.end (transaction) }, + end{ store.pending.end (transaction) } + { + seek (start); + } + + void seek (nano::account const & account) + { + it = store.pending.begin (transaction, { account, 0 }); + update_current (); + } + + // Advance to the next account + void advance () + { + if (it == end) + { + debug_assert (!current); + return; + } + + auto const starting_account = it->first.account; + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached the next account + if (it->first.account != starting_account) + { + update_current (); + return; + } + } + + if (it != end) + { + // If that fails, perform a fresh lookup + seek (inc_sat (starting_account.number ())); + } + + update_current (); + } + + void advance_to (nano::account const & account) + { + if (it == end) + { + debug_assert (!current); + return; + } + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached or overshoot the target account + if (it->first.account.number () >= account.number ()) + { + update_current (); + return; + } + } + + // If that fails, perform a fresh lookup + seek (account); + } + + std::optional current{}; + +private: + void update_current () + { + if (it != end) + { + current = *it; + } + else + { + current = std::nullopt; + } + } + + nano::store::component & store; + nano::store::transaction const & transaction; + + nano::store::pending::iterator it; + nano::store::pending::iterator const end; +}; +} \ No newline at end of file diff --git a/nano/node/bootstrap/database_scan.cpp b/nano/node/bootstrap/database_scan.cpp new file mode 100644 index 0000000000..0135129392 --- /dev/null +++ b/nano/node/bootstrap/database_scan.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * database_scan + */ + +nano::bootstrap::database_scan::database_scan (nano::ledger & ledger_a) : + ledger{ ledger_a }, + account_scanner{ ledger }, + pending_scanner{ ledger } +{ +} + +nano::account nano::bootstrap::database_scan::next (std::function const & filter) +{ + if (queue.empty ()) + { + fill (); + } + + while (!queue.empty ()) + { + auto result = queue.front (); + queue.pop_front (); + + if (filter (result)) + { + return result; + } + } + + return { 0 }; +} + +void nano::bootstrap::database_scan::fill () +{ + auto transaction = ledger.store.tx_begin_read (); + + auto set1 = account_scanner.next_batch (transaction, batch_size); + auto set2 = pending_scanner.next_batch (transaction, batch_size); + + queue.insert (queue.end (), set1.begin (), set1.end ()); + queue.insert (queue.end (), set2.begin (), set2.end ()); +} + +bool nano::bootstrap::database_scan::warmed_up () const +{ + return account_scanner.completed > 0 && pending_scanner.completed > 0; +} + +nano::container_info nano::bootstrap::database_scan::container_info () const +{ + nano::container_info info; + info.put ("accounts_iterator", account_scanner.completed); + info.put ("pending_iterator", pending_scanner.completed); + return info; +} + +/* + * account_database_scanner + */ + +std::deque nano::bootstrap::account_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) +{ + std::deque result; + + nano::bootstrap::account_database_crawler crawler{ ledger.store, transaction, next }; + + for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count) + { + auto const & [account, info] = crawler.current.value (); + result.push_back (account); + next = inc_sat (account.number ()); + } + + // Empty current value indicates the end of the table + if (!crawler.current) + { + // Reset for the next ledger iteration + next = { 0 }; + ++completed; + } + + return result; +} + +/* + * pending_database_scanner + */ + +std::deque nano::bootstrap::pending_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) +{ + std::deque result; + + nano::bootstrap::pending_database_crawler crawler{ ledger.store, transaction, next }; + + for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count) + { + auto const & [key, info] = crawler.current.value (); + result.push_back (key.account); + next = inc_sat (key.account.number ()); + } + + // Empty current value indicates the end of the table + if (!crawler.current) + { + // Reset for the next ledger iteration + next = { 0 }; + ++completed; + } + + return result; +} diff --git a/nano/node/bootstrap_ascending/database_scan.hpp b/nano/node/bootstrap/database_scan.hpp similarity index 71% rename from nano/node/bootstrap_ascending/database_scan.hpp rename to nano/node/bootstrap/database_scan.hpp index eb22b46170..758ad3efce 100644 --- a/nano/node/bootstrap_ascending/database_scan.hpp +++ b/nano/node/bootstrap/database_scan.hpp @@ -7,29 +7,25 @@ #include -namespace nano::bootstrap_ascending +namespace nano::bootstrap { -struct account_database_iterator +struct account_database_scanner { - explicit account_database_iterator (nano::ledger &); + nano::ledger & ledger; std::deque next_batch (nano::store::transaction &, size_t batch_size); - bool warmed_up () const; - nano::ledger & ledger; nano::account next{ 0 }; size_t completed{ 0 }; }; -struct pending_database_iterator +struct pending_database_scanner { - explicit pending_database_iterator (nano::ledger &); + nano::ledger & ledger; std::deque next_batch (nano::store::transaction &, size_t batch_size); - bool warmed_up () const; - nano::ledger & ledger; - nano::pending_key next{ 0, 0 }; + nano::account next{ 0 }; size_t completed{ 0 }; }; @@ -52,8 +48,8 @@ class database_scan void fill (); private: - account_database_iterator accounts_iterator; - pending_database_iterator pending_iterator; + account_database_scanner account_scanner; + pending_database_scanner pending_scanner; std::deque queue; diff --git a/nano/node/bootstrap/frontier_scan.cpp b/nano/node/bootstrap/frontier_scan.cpp new file mode 100644 index 0000000000..a65d64fa5e --- /dev/null +++ b/nano/node/bootstrap/frontier_scan.cpp @@ -0,0 +1,188 @@ +#include + +#include +#include + +nano::bootstrap::frontier_scan::frontier_scan (frontier_scan_config const & config_a, nano::stats & stats_a) : + config{ config_a }, + stats{ stats_a } +{ + // Divide nano::account numeric range into consecutive and equal ranges + nano::uint256_t max_account = std::numeric_limits::max (); + nano::uint256_t range_size = max_account / config.head_parallelistm; + + for (unsigned i = 0; i < config.head_parallelistm; ++i) + { + // Start at 1 to avoid the burn account + nano::uint256_t start = (i == 0) ? 1 : i * range_size; + nano::uint256_t end = (i == config.head_parallelistm - 1) ? max_account : start + range_size; + + heads.emplace_back (frontier_head{ nano::account{ start }, nano::account{ end } }); + } + + release_assert (!heads.empty ()); +} + +nano::account nano::bootstrap::frontier_scan::next () +{ + auto const cutoff = std::chrono::steady_clock::now () - config.cooldown; + + auto & heads_by_timestamp = heads.get (); + for (auto it = heads_by_timestamp.begin (); it != heads_by_timestamp.end (); ++it) + { + auto const & head = *it; + + if (head.requests < config.consideration_count || head.timestamp < cutoff) + { + stats.inc (nano::stat::type::bootstrap_frontier_scan, (head.requests < config.consideration_count) ? nano::stat::detail::next_by_requests : nano::stat::detail::next_by_timestamp); + + debug_assert (head.next.number () >= head.start.number ()); + debug_assert (head.next.number () < head.end.number ()); + + auto result = head.next; + + heads_by_timestamp.modify (it, [this] (auto & entry) { + entry.requests += 1; + entry.timestamp = std::chrono::steady_clock::now (); + }); + + return result; + } + } + + stats.inc (nano::stat::type::bootstrap_frontier_scan, nano::stat::detail::next_none); + return { 0 }; +} + +bool nano::bootstrap::frontier_scan::process (nano::account start, std::deque> const & response) +{ + debug_assert (std::all_of (response.begin (), response.end (), [&] (auto const & pair) { return pair.first.number () >= start.number (); })); + + stats.inc (nano::stat::type::bootstrap_frontier_scan, nano::stat::detail::process); + + // Find the first head with head.start <= start + auto & heads_by_start = heads.get (); + auto it = heads_by_start.upper_bound (start); + release_assert (it != heads_by_start.begin ()); + it = std::prev (it); + + bool done = false; + heads_by_start.modify (it, [this, &response, &done] (frontier_head & entry) { + entry.completed += 1; + + for (auto const & [account, _] : response) + { + // Only consider candidates that actually advance the current frontier + if (account.number () > entry.next.number ()) + { + entry.candidates.insert (account); + } + } + + // Trim the candidates + while (entry.candidates.size () > config.candidates) + { + release_assert (!entry.candidates.empty ()); + entry.candidates.erase (std::prev (entry.candidates.end ())); + } + + // Special case for the last frontier head that won't receive larger than max frontier + if (entry.completed >= config.consideration_count * 2 && entry.candidates.empty ()) + { + stats.inc (nano::stat::type::bootstrap_frontier_scan, nano::stat::detail::done_empty); + entry.candidates.insert (entry.end); + } + + // Check if done + if (entry.completed >= config.consideration_count && !entry.candidates.empty ()) + { + stats.inc (nano::stat::type::bootstrap_frontier_scan, nano::stat::detail::done); + + // Take the last candidate as the next frontier + release_assert (!entry.candidates.empty ()); + auto it = std::prev (entry.candidates.end ()); + + debug_assert (entry.next.number () < it->number ()); + entry.next = *it; + entry.processed += entry.candidates.size (); + entry.candidates.clear (); + entry.requests = 0; + entry.completed = 0; + entry.timestamp = {}; + + // Bound the search range + if (entry.next.number () >= entry.end.number ()) + { + stats.inc (nano::stat::type::bootstrap_frontier_scan, nano::stat::detail::done_range); + entry.next = entry.start; + } + + done = true; + } + }); + + return done; +} + +nano::container_info nano::bootstrap::frontier_scan::container_info () const +{ + auto collect_progress = [&] () { + nano::container_info info; + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + + boost::multiprecision::cpp_dec_float_50 start{ head.start.number ().str () }; + boost::multiprecision::cpp_dec_float_50 next{ head.next.number ().str () }; + boost::multiprecision::cpp_dec_float_50 end{ head.end.number ().str () }; + + // Progress in the range [0, 1000000] since we can only represent `size_t` integers in the container_info data + boost::multiprecision::cpp_dec_float_50 progress = (next - start) * boost::multiprecision::cpp_dec_float_50 (1000000) / (end - start); + + info.put (std::to_string (n), progress.convert_to ()); + } + return info; + }; + + auto collect_candidates = [&] () { + nano::container_info info; + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + info.put (std::to_string (n), head.candidates.size ()); + } + return info; + }; + + auto collect_responses = [&] () { + nano::container_info info; + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + info.put (std::to_string (n), head.completed); + } + return info; + }; + + auto collect_processed = [&] () { + nano::container_info info; + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + info.put (std::to_string (n), head.processed); + } + return info; + }; + + auto total_processed = std::accumulate (heads.begin (), heads.end (), std::size_t{ 0 }, [] (auto total, auto const & head) { + return total + head.processed; + }); + + nano::container_info info; + info.put ("total_processed", total_processed); + info.add ("progress", collect_progress ()); + info.add ("candidates", collect_candidates ()); + info.add ("responses", collect_responses ()); + info.add ("processed", collect_processed ()); + return info; +} \ No newline at end of file diff --git a/nano/node/bootstrap/frontier_scan.hpp b/nano/node/bootstrap/frontier_scan.hpp new file mode 100644 index 0000000000..fe7682ff8e --- /dev/null +++ b/nano/node/bootstrap/frontier_scan.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mi = boost::multi_index; + +namespace nano::bootstrap +{ +/* + * Frontier scan divides the account space into ranges and scans each range for outdated frontiers in parallel. + * This class is used to track the progress of each range. + */ +class frontier_scan +{ +public: + frontier_scan (frontier_scan_config const &, nano::stats &); + + nano::account next (); + bool process (nano::account start, std::deque> const & response); + + nano::container_info container_info () const; + +private: // Dependencies + frontier_scan_config const & config; + nano::stats & stats; + +private: + // Represents a range of accounts to scan, once the full range is scanned (goes past `end`) the head wraps around (to the `start`) + struct frontier_head + { + frontier_head (nano::account start_a, nano::account end_a) : + start{ start_a }, + end{ end_a }, + next{ start_a } + { + } + + // The range of accounts to scan is [start, end) + nano::account const start; + nano::account const end; + + // We scan the range by querying frontiers starting at 'next' and gathering candidates + nano::account next; + std::set candidates; + + unsigned requests{ 0 }; + unsigned completed{ 0 }; + std::chrono::steady_clock::time_point timestamp{}; + size_t processed{ 0 }; // Total number of accounts processed + + nano::account index () const + { + return start; + } + }; + + // clang-format off + class tag_sequenced {}; + class tag_start {}; + class tag_timestamp {}; + + using ordered_heads = boost::multi_index_container>, + mi::ordered_unique, + mi::const_mem_fun>, + mi::ordered_non_unique, + mi::member> + >>; + // clang-format on + + ordered_heads heads; +}; +} \ No newline at end of file diff --git a/nano/node/bootstrap/peer_scoring.cpp b/nano/node/bootstrap/peer_scoring.cpp new file mode 100644 index 0000000000..1e406ab962 --- /dev/null +++ b/nano/node/bootstrap/peer_scoring.cpp @@ -0,0 +1,140 @@ +#include +#include +#include + +/* + * peer_scoring + */ + +nano::bootstrap::peer_scoring::peer_scoring (bootstrap_config const & config_a, nano::network_constants const & network_constants_a) : + config{ config_a }, + network_constants{ network_constants_a } +{ +} + +bool nano::bootstrap::peer_scoring::limit_exceeded (std::shared_ptr const & channel) const +{ + auto & index = scoring.get (); + if (auto existing = index.find (channel.get ()); existing != index.end ()) + { + return existing->outstanding >= config.channel_limit; + } + return false; +} + +bool nano::bootstrap::peer_scoring::try_send_message (std::shared_ptr const & channel) +{ + auto & index = scoring.get (); + auto existing = index.find (channel.get ()); + if (existing == index.end ()) + { + index.emplace (channel, 1, 1, 0); + } + else + { + if (existing->outstanding < config.channel_limit) + { + [[maybe_unused]] auto success = index.modify (existing, [] (auto & score) { + ++score.outstanding; + ++score.request_count_total; + }); + debug_assert (success); + } + else + { + return true; + } + } + return false; +} + +void nano::bootstrap::peer_scoring::received_message (std::shared_ptr const & channel) +{ + auto & index = scoring.get (); + if (auto existing = index.find (channel.get ()); existing != index.end ()) + { + if (existing->outstanding > 1) + { + [[maybe_unused]] auto success = index.modify (existing, [] (auto & score) { + --score.outstanding; + ++score.response_count_total; + }); + debug_assert (success); + } + } +} + +std::shared_ptr nano::bootstrap::peer_scoring::channel () +{ + for (auto const & channel : channels) + { + if (!channel->max (traffic_type)) + { + if (!try_send_message (channel)) + { + return channel; + } + } + } + return nullptr; +} + +std::size_t nano::bootstrap::peer_scoring::size () const +{ + return scoring.size (); +} + +std::size_t nano::bootstrap::peer_scoring::available () const +{ + return std::count_if (channels.begin (), channels.end (), [this] (auto const & channel) { + return !limit_exceeded (channel); + }); +} + +void nano::bootstrap::peer_scoring::timeout () +{ + erase_if (scoring, [] (auto const & score) { + if (auto channel = score.shared ()) + { + if (channel->alive ()) + { + return false; // Keep + } + } + return true; + }); + + for (auto score = scoring.begin (), n = scoring.end (); score != n; ++score) + { + scoring.modify (score, [] (auto & score_a) { + score_a.decay (); + }); + } +} + +void nano::bootstrap::peer_scoring::sync (std::deque> const & list) +{ + channels = list; +} + +nano::container_info nano::bootstrap::peer_scoring::container_info () const +{ + nano::container_info info; + info.put ("scores", size ()); + info.put ("available", available ()); + info.put ("channels", channels.size ()); + return info; +} + +/* + * peer_score + */ + +nano::bootstrap::peer_scoring::peer_score::peer_score (std::shared_ptr const & channel_a, uint64_t outstanding_a, uint64_t request_count_total_a, uint64_t response_count_total_a) : + channel{ channel_a }, + channel_ptr{ channel_a.get () }, + outstanding{ outstanding_a }, + request_count_total{ request_count_total_a }, + response_count_total{ response_count_total_a } +{ +} diff --git a/nano/node/bootstrap_ascending/peer_scoring.hpp b/nano/node/bootstrap/peer_scoring.hpp similarity index 74% rename from nano/node/bootstrap_ascending/peer_scoring.hpp rename to nano/node/bootstrap/peer_scoring.hpp index 84fd68e128..0bae9255c3 100644 --- a/nano/node/bootstrap_ascending/peer_scoring.hpp +++ b/nano/node/bootstrap/peer_scoring.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include #include @@ -12,32 +15,38 @@ namespace mi = boost::multi_index; namespace nano { -class bootstrap_ascending_config; -class network_constants; -namespace transport -{ - class channel; -} -namespace bootstrap_ascending +namespace bootstrap { // Container for tracking and scoring peers with respect to bootstrapping class peer_scoring { public: - peer_scoring (bootstrap_ascending_config const &, nano::network_constants const &); + static nano::transport::traffic_type constexpr traffic_type = nano::transport::traffic_type::bootstrap_requests; + + public: + peer_scoring (bootstrap_config const &, nano::network_constants const &); // Returns true if channel limit has been exceeded - bool try_send_message (std::shared_ptr channel); - void received_message (std::shared_ptr channel); + bool limit_exceeded (std::shared_ptr const & channel) const; + bool try_send_message (std::shared_ptr const & channel); + void received_message (std::shared_ptr const & channel); + std::shared_ptr channel (); - [[nodiscard]] std::size_t size () const; + + // Synchronize channels with the network, passed channels should be shuffled + void sync (std::deque> const & list); + // Cleans up scores for closed channels // Decays scores which become inaccurate over time due to message drops void timeout (); - void sync (std::deque> const & list); + + std::size_t size () const; + std::size_t available () const; + + nano::container_info container_info () const; private: - bootstrap_ascending_config const & config; + bootstrap_config const & config; nano::network_constants const & network_constants; private: @@ -75,14 +84,16 @@ namespace bootstrap_ascending // Indexes scores by the number of outstanding requests in ascending order class tag_outstanding {}; - using scoring_t = boost::multi_index_container, mi::member>, mi::ordered_non_unique, mi::member>>>; // clang-format on - scoring_t scoring; + ordered_scoring scoring; + + std::deque> channels; }; } } diff --git a/nano/node/bootstrap_ascending/throttle.cpp b/nano/node/bootstrap/throttle.cpp similarity index 52% rename from nano/node/bootstrap_ascending/throttle.cpp rename to nano/node/bootstrap/throttle.cpp index 715641a21a..422773d3f5 100644 --- a/nano/node/bootstrap_ascending/throttle.cpp +++ b/nano/node/bootstrap/throttle.cpp @@ -1,19 +1,19 @@ #include -#include +#include -nano::bootstrap_ascending::throttle::throttle (std::size_t size) : +nano::bootstrap::throttle::throttle (std::size_t size) : successes_m{ size } { samples.insert (samples.end (), size, true); debug_assert (size > 0); } -bool nano::bootstrap_ascending::throttle::throttled () const +bool nano::bootstrap::throttle::throttled () const { return successes_m == 0; } -void nano::bootstrap_ascending::throttle::add (bool sample) +void nano::bootstrap::throttle::add (bool sample) { debug_assert (!samples.empty ()); pop (); @@ -24,7 +24,7 @@ void nano::bootstrap_ascending::throttle::add (bool sample) } } -void nano::bootstrap_ascending::throttle::resize (std::size_t size) +void nano::bootstrap::throttle::resize (std::size_t size) { debug_assert (size > 0); while (size < samples.size ()) @@ -37,17 +37,17 @@ void nano::bootstrap_ascending::throttle::resize (std::size_t size) } } -std::size_t nano::bootstrap_ascending::throttle::size () const +std::size_t nano::bootstrap::throttle::size () const { return samples.size (); } -std::size_t nano::bootstrap_ascending::throttle::successes () const +std::size_t nano::bootstrap::throttle::successes () const { return successes_m; } -void nano::bootstrap_ascending::throttle::pop () +void nano::bootstrap::throttle::pop () { if (samples.front ()) { diff --git a/nano/node/bootstrap_ascending/throttle.hpp b/nano/node/bootstrap/throttle.hpp similarity index 92% rename from nano/node/bootstrap_ascending/throttle.hpp rename to nano/node/bootstrap/throttle.hpp index 99bb14fae6..a688e54d55 100644 --- a/nano/node/bootstrap_ascending/throttle.hpp +++ b/nano/node/bootstrap/throttle.hpp @@ -2,7 +2,7 @@ #include -namespace nano::bootstrap_ascending +namespace nano::bootstrap { // Class used to throttle the ascending bootstrapper once it reaches a steady state // Tracks verify_result samples and signals throttling if no tracked samples have gotten results @@ -26,4 +26,4 @@ class throttle std::deque samples; std::size_t successes_m; }; -} // nano::boostrap_ascending +} diff --git a/nano/node/bootstrap_ascending/account_sets.cpp b/nano/node/bootstrap_ascending/account_sets.cpp deleted file mode 100644 index 4d0a276dfc..0000000000 --- a/nano/node/bootstrap_ascending/account_sets.cpp +++ /dev/null @@ -1,357 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -/* - * account_sets - */ - -nano::bootstrap_ascending::account_sets::account_sets (nano::account_sets_config const & config_a, nano::stats & stats_a) : - config{ config_a }, - stats{ stats_a } -{ -} - -void nano::bootstrap_ascending::account_sets::priority_up (nano::account const & account) -{ - if (account.is_zero ()) - { - return; - } - - if (!blocked (account)) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::prioritize); - - auto iter = priorities.get ().find (account); - if (iter != priorities.get ().end ()) - { - priorities.get ().modify (iter, [] (auto & val) { - val.priority = std::min ((val.priority + account_sets::priority_increase), account_sets::priority_max); - }); - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::priority_insert); - priorities.get ().insert ({ account, account_sets::priority_initial }); - trim_overflow (); - } - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::prioritize_failed); - } -} - -void nano::bootstrap_ascending::account_sets::priority_down (nano::account const & account) -{ - if (account.is_zero ()) - { - return; - } - - auto iter = priorities.get ().find (account); - if (iter != priorities.get ().end ()) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::deprioritize); - - auto priority_new = iter->priority / account_sets::priority_divide; - if (priority_new <= account_sets::priority_cutoff) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::priority_erase_by_threshold); - priorities.get ().erase (iter); - } - else - { - priorities.get ().modify (iter, [priority_new] (auto & val) { - val.priority = priority_new; - }); - } - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::deprioritize_failed); - } -} - -void nano::bootstrap_ascending::account_sets::priority_set (nano::account const & account) -{ - if (account.is_zero ()) - { - return; - } - - if (!blocked (account)) - { - auto iter = priorities.get ().find (account); - if (iter == priorities.get ().end ()) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::priority_insert); - priorities.get ().insert ({ account, account_sets::priority_initial }); - trim_overflow (); - } - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::prioritize_failed); - } -} - -void nano::bootstrap_ascending::account_sets::block (nano::account const & account, nano::block_hash const & dependency) -{ - debug_assert (!account.is_zero ()); - - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::block); - - auto existing = priorities.get ().find (account); - auto entry = (existing == priorities.get ().end ()) ? priority_entry{ account, 0 } : *existing; - - priorities.get ().erase (account); - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::priority_erase_by_blocking); - - blocking.get ().insert ({ entry, dependency }); - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::blocking_insert); - - trim_overflow (); -} - -void nano::bootstrap_ascending::account_sets::unblock (nano::account const & account, std::optional const & hash) -{ - if (account.is_zero ()) - { - return; - } - - // Unblock only if the dependency is fulfilled - auto existing = blocking.get ().find (account); - if (existing != blocking.get ().end () && (!hash || existing->dependency == *hash)) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::unblock); - - debug_assert (priorities.get ().count (account) == 0); - if (!existing->original_entry.account.is_zero ()) - { - debug_assert (existing->original_entry.account == account); - priorities.get ().insert (existing->original_entry); - } - else - { - priorities.get ().insert ({ account, account_sets::priority_initial }); - } - blocking.get ().erase (account); - - trim_overflow (); - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::unblock_failed); - } -} - -void nano::bootstrap_ascending::account_sets::timestamp_set (const nano::account & account) -{ - debug_assert (!account.is_zero ()); - - auto iter = priorities.get ().find (account); - if (iter != priorities.get ().end ()) - { - priorities.get ().modify (iter, [] (auto & entry) { - entry.timestamp = std::chrono::steady_clock::now (); - }); - } -} - -void nano::bootstrap_ascending::account_sets::timestamp_reset (const nano::account & account) -{ - debug_assert (!account.is_zero ()); - - auto iter = priorities.get ().find (account); - if (iter != priorities.get ().end ()) - { - priorities.get ().modify (iter, [] (auto & entry) { - entry.timestamp = {}; - }); - } -} - -void nano::bootstrap_ascending::account_sets::dependency_update (nano::block_hash const & hash, nano::account const & dependency_account) -{ - debug_assert (!dependency_account.is_zero ()); - - auto [it, end] = blocking.get ().equal_range (hash); - if (it != end) - { - while (it != end) - { - if (it->dependency_account != dependency_account) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::dependency_update); - - blocking.get ().modify (it++, [dependency_account] (auto & entry) { - entry.dependency_account = dependency_account; - }); - } - else - { - ++it; - } - } - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::dependency_update_failed); - } -} - -void nano::bootstrap_ascending::account_sets::trim_overflow () -{ - while (priorities.size () > config.priorities_max) - { - // Erase the oldest entry - priorities.pop_front (); - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::priority_erase_overflow); - } - while (blocking.size () > config.blocking_max) - { - // Erase the oldest entry - blocking.pop_front (); - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::blocking_erase_overflow); - } -} - -nano::account nano::bootstrap_ascending::account_sets::next_priority (std::function const & filter) -{ - if (priorities.empty ()) - { - return { 0 }; - } - - auto const cutoff = std::chrono::steady_clock::now () - config.cooldown; - - for (auto const & entry : priorities.get ()) - { - if (entry.timestamp > cutoff) - { - continue; - } - if (!filter (entry.account)) - { - continue; - } - return entry.account; - } - - return { 0 }; -} - -nano::block_hash nano::bootstrap_ascending::account_sets::next_blocking (std::function const & filter) -{ - if (blocking.empty ()) - { - return { 0 }; - } - - // Scan all entries with unknown dependency account - auto [begin, end] = blocking.get ().equal_range (nano::account{ 0 }); - for (auto const & entry : boost::make_iterator_range (begin, end)) - { - debug_assert (entry.dependency_account.is_zero ()); - if (!filter (entry.dependency)) - { - continue; - } - return entry.dependency; - } - - return { 0 }; -} - -void nano::bootstrap_ascending::account_sets::sync_dependencies () -{ - // Sample all accounts with a known dependency account (> account 0) - auto begin = blocking.get ().upper_bound (nano::account{ 0 }); - auto end = blocking.get ().end (); - - for (auto const & entry : boost::make_iterator_range (begin, end)) - { - debug_assert (!entry.dependency_account.is_zero ()); - - if (priorities.size () >= config.priorities_max) - { - break; - } - - if (!blocked (entry.dependency_account) && !prioritized (entry.dependency_account)) - { - stats.inc (nano::stat::type::bootstrap_ascending_accounts, nano::stat::detail::sync_dependencies); - priority_set (entry.dependency_account); - } - } - - trim_overflow (); -} - -bool nano::bootstrap_ascending::account_sets::blocked (nano::account const & account) const -{ - return blocking.get ().contains (account); -} - -bool nano::bootstrap_ascending::account_sets::prioritized (nano::account const & account) const -{ - return priorities.get ().contains (account); -} - -std::size_t nano::bootstrap_ascending::account_sets::priority_size () const -{ - return priorities.size (); -} - -std::size_t nano::bootstrap_ascending::account_sets::blocked_size () const -{ - return blocking.size (); -} - -bool nano::bootstrap_ascending::account_sets::priority_half_full () const -{ - return priorities.size () > config.priorities_max / 2; -} - -bool nano::bootstrap_ascending::account_sets::blocked_half_full () const -{ - return blocking.size () > config.blocking_max / 2; -} - -double nano::bootstrap_ascending::account_sets::priority (nano::account const & account) const -{ - if (!blocked (account)) - { - auto existing = priorities.get ().find (account); - if (existing != priorities.get ().end ()) - { - return existing->priority; - } - } - return 0.0; -} - -auto nano::bootstrap_ascending::account_sets::info () const -> nano::bootstrap_ascending::account_sets::info_t -{ - return { blocking, priorities }; -} - -nano::container_info nano::bootstrap_ascending::account_sets::container_info () const -{ - // Count blocking entries with their dependency account unknown - auto blocking_unknown = blocking.get ().count (nano::account{ 0 }); - - nano::container_info info; - info.put ("priorities", priorities); - info.put ("blocking", blocking); - info.put ("blocking_unknown", blocking_unknown); - return info; -} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/database_scan.cpp b/nano/node/bootstrap_ascending/database_scan.cpp deleted file mode 100644 index acd2024315..0000000000 --- a/nano/node/bootstrap_ascending/database_scan.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * database_scan - */ - -nano::bootstrap_ascending::database_scan::database_scan (nano::ledger & ledger_a) : - ledger{ ledger_a }, - accounts_iterator{ ledger }, - pending_iterator{ ledger } -{ -} - -nano::account nano::bootstrap_ascending::database_scan::next (std::function const & filter) -{ - if (queue.empty ()) - { - fill (); - } - - while (!queue.empty ()) - { - auto result = queue.front (); - queue.pop_front (); - - if (filter (result)) - { - return result; - } - } - - return { 0 }; -} - -void nano::bootstrap_ascending::database_scan::fill () -{ - auto transaction = ledger.store.tx_begin_read (); - - auto set1 = accounts_iterator.next_batch (transaction, batch_size); - auto set2 = pending_iterator.next_batch (transaction, batch_size); - - queue.insert (queue.end (), set1.begin (), set1.end ()); - queue.insert (queue.end (), set2.begin (), set2.end ()); -} - -bool nano::bootstrap_ascending::database_scan::warmed_up () const -{ - return accounts_iterator.warmed_up () && pending_iterator.warmed_up (); -} - -nano::container_info nano::bootstrap_ascending::database_scan::container_info () const -{ - nano::container_info info; - info.put ("accounts_iterator", accounts_iterator.completed); - info.put ("pending_iterator", pending_iterator.completed); - return info; -} - -/* - * account_database_iterator - */ - -nano::bootstrap_ascending::account_database_iterator::account_database_iterator (nano::ledger & ledger_a) : - ledger{ ledger_a } -{ -} - -std::deque nano::bootstrap_ascending::account_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size) -{ - std::deque result; - - auto it = ledger.store.account.begin (transaction, next); - auto const end = ledger.store.account.end (transaction); - - for (size_t count = 0; it != end && count < batch_size; ++it, ++count) - { - auto const & account = it->first; - result.push_back (account); - next = account.number () + 1; - } - - if (it == end) - { - // Reset for the next ledger iteration - next = { 0 }; - ++completed; - } - - return result; -} - -bool nano::bootstrap_ascending::account_database_iterator::warmed_up () const -{ - return completed > 0; -} - -/* - * pending_database_iterator - */ - -nano::bootstrap_ascending::pending_database_iterator::pending_database_iterator (nano::ledger & ledger_a) : - ledger{ ledger_a } -{ -} - -std::deque nano::bootstrap_ascending::pending_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size) -{ - std::deque result; - - auto it = ledger.store.pending.begin (transaction, next); - auto const end = ledger.store.pending.end (transaction); - - // TODO: This pending iteration heuristic should be encapsulated in a pending_iterator class and reused across other components - // The heuristic is to advance the iterator sequentially until we reach a new account or perform a fresh lookup if the account has too many pending blocks - // This is to avoid the overhead of performing a fresh lookup for every pending account as majority of accounts have only a few pending blocks - auto advance_iterator = [&] () { - auto const starting_account = it->first.account; - - // For RocksDB, sequential access is ~10x faster than performing a fresh lookup (tested on my machine) - const size_t sequential_attempts = 10; - - // First try advancing sequentially - for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) - { - if (it->first.account != starting_account) - { - break; - } - } - - // If we didn't advance to the next account, perform a fresh lookup - if (it != end && it->first.account == starting_account) - { - it = ledger.store.pending.begin (transaction, { starting_account.number () + 1, 0 }); - } - - debug_assert (it == end || it->first.account != starting_account); - }; - - for (size_t count = 0; it != end && count < batch_size; advance_iterator (), ++count) - { - auto const & account = it->first.account; - result.push_back (account); - next = { account.number () + 1, 0 }; - } - - if (it == end) - { - // Reset for the next ledger iteration - next = { 0, 0 }; - ++completed; - } - - return result; -} - -bool nano::bootstrap_ascending::pending_database_iterator::warmed_up () const -{ - return completed > 0; -} diff --git a/nano/node/bootstrap_ascending/peer_scoring.cpp b/nano/node/bootstrap_ascending/peer_scoring.cpp deleted file mode 100644 index 9fc2be605e..0000000000 --- a/nano/node/bootstrap_ascending/peer_scoring.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include -#include -#include - -/* - * peer_scoring - */ - -nano::bootstrap_ascending::peer_scoring::peer_scoring (bootstrap_ascending_config const & config_a, nano::network_constants const & network_constants_a) : - config{ config_a }, - network_constants{ network_constants_a } -{ -} - -bool nano::bootstrap_ascending::peer_scoring::try_send_message (std::shared_ptr channel) -{ - auto & index = scoring.get (); - auto existing = index.find (channel.get ()); - if (existing == index.end ()) - { - index.emplace (channel, 1, 1, 0); - } - else - { - if (existing->outstanding < config.channel_limit) - { - [[maybe_unused]] auto success = index.modify (existing, [] (auto & score) { - ++score.outstanding; - ++score.request_count_total; - }); - debug_assert (success); - } - else - { - return true; - } - } - return false; -} - -void nano::bootstrap_ascending::peer_scoring::received_message (std::shared_ptr channel) -{ - auto & index = scoring.get (); - auto existing = index.find (channel.get ()); - if (existing != index.end ()) - { - if (existing->outstanding > 1) - { - [[maybe_unused]] auto success = index.modify (existing, [] (auto & score) { - --score.outstanding; - ++score.response_count_total; - }); - debug_assert (success); - } - } -} - -std::shared_ptr nano::bootstrap_ascending::peer_scoring::channel () -{ - auto & index = scoring.get (); - for (auto const & score : index) - { - if (auto channel = score.shared ()) - { - if (!channel->max ()) - { - if (!try_send_message (channel)) - { - return channel; - } - } - } - } - return nullptr; -} - -std::size_t nano::bootstrap_ascending::peer_scoring::size () const -{ - return scoring.size (); -} - -void nano::bootstrap_ascending::peer_scoring::timeout () -{ - auto & index = scoring.get (); - - erase_if (index, [] (auto const & score) { - if (auto channel = score.shared ()) - { - if (channel->alive ()) - { - return false; // Keep - } - } - return true; - }); - - for (auto score = scoring.begin (), n = scoring.end (); score != n; ++score) - { - scoring.modify (score, [] (auto & score_a) { - score_a.decay (); - }); - } -} - -void nano::bootstrap_ascending::peer_scoring::sync (std::deque> const & list) -{ - auto & index = scoring.get (); - for (auto const & channel : list) - { - if (channel->get_network_version () >= network_constants.bootstrap_protocol_version_min) - { - if (index.find (channel.get ()) == index.end ()) - { - if (!channel->max (nano::transport::traffic_type::bootstrap)) - { - index.emplace (channel, 1, 1, 0); - } - } - } - } -} - -/* - * peer_score - */ - -nano::bootstrap_ascending::peer_scoring::peer_score::peer_score ( -std::shared_ptr const & channel_a, uint64_t outstanding_a, uint64_t request_count_total_a, uint64_t response_count_total_a) : - channel{ channel_a }, - channel_ptr{ channel_a.get () }, - outstanding{ outstanding_a }, - request_count_total{ request_count_total_a }, - response_count_total{ response_count_total_a } -{ -} diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp deleted file mode 100644 index 661072b448..0000000000 --- a/nano/node/bootstrap_ascending/service.cpp +++ /dev/null @@ -1,853 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -/* - * bootstrap_ascending - */ - -nano::bootstrap_ascending::service::service (nano::node_config const & node_config_a, nano::block_processor & block_processor_a, nano::ledger & ledger_a, nano::network & network_a, nano::stats & stat_a, nano::logger & logger_a) : - config{ node_config_a.bootstrap_ascending }, - network_constants{ node_config_a.network_params.network }, - block_processor{ block_processor_a }, - ledger{ ledger_a }, - network{ network_a }, - stats{ stat_a }, - logger{ logger_a }, - accounts{ config.account_sets, stats }, - database_scan{ ledger }, - throttle{ compute_throttle_size () }, - scoring{ config, node_config_a.network_params.network }, - database_limiter{ config.database_rate_limit, 1.0 } -{ - // TODO: This is called from a very congested blockprocessor thread. Offload this work to a dedicated processing thread - block_processor.batch_processed.add ([this] (auto const & batch) { - { - nano::lock_guard lock{ mutex }; - - auto transaction = ledger.tx_begin_read (); - for (auto const & [result, context] : batch) - { - debug_assert (context.block != nullptr); - inspect (transaction, result, *context.block, context.source); - } - } - condition.notify_all (); - }); - - accounts.priority_set (node_config_a.network_params.ledger.genesis->account_field ().value ()); -} - -nano::bootstrap_ascending::service::~service () -{ - // All threads must be stopped before destruction - debug_assert (!priorities_thread.joinable ()); - debug_assert (!database_thread.joinable ()); - debug_assert (!dependencies_thread.joinable ()); - debug_assert (!timeout_thread.joinable ()); -} - -void nano::bootstrap_ascending::service::start () -{ - debug_assert (!priorities_thread.joinable ()); - debug_assert (!database_thread.joinable ()); - debug_assert (!dependencies_thread.joinable ()); - debug_assert (!timeout_thread.joinable ()); - - if (!config.enable) - { - logger.warn (nano::log::type::bootstrap, "Ascending bootstrap is disabled"); - return; - } - - priorities_thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); - run_priorities (); - }); - - if (config.enable_database_scan) - { - database_thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); - run_database (); - }); - } - - if (config.enable_dependency_walker) - { - dependencies_thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); - run_dependencies (); - }); - } - - timeout_thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); - run_timeouts (); - }); -} - -void nano::bootstrap_ascending::service::stop () -{ - { - nano::lock_guard lock{ mutex }; - stopped = true; - } - condition.notify_all (); - - nano::join_or_pass (priorities_thread); - nano::join_or_pass (database_thread); - nano::join_or_pass (dependencies_thread); - nano::join_or_pass (timeout_thread); -} - -void nano::bootstrap_ascending::service::send (std::shared_ptr const & channel, async_tag tag) -{ - debug_assert (tag.type != query_type::invalid); - debug_assert (tag.source != query_source::invalid); - - { - nano::lock_guard lock{ mutex }; - debug_assert (tags.get ().count (tag.id) == 0); - tags.get ().insert (tag); - } - - nano::asc_pull_req request{ network_constants }; - request.id = tag.id; - - switch (tag.type) - { - case query_type::blocks_by_hash: - case query_type::blocks_by_account: - { - request.type = nano::asc_pull_type::blocks; - - nano::asc_pull_req::blocks_payload pld; - pld.start = tag.start; - pld.count = tag.count; - pld.start_type = tag.type == query_type::blocks_by_hash ? nano::asc_pull_req::hash_type::block : nano::asc_pull_req::hash_type::account; - request.payload = pld; - } - break; - case query_type::account_info_by_hash: - { - request.type = nano::asc_pull_type::account_info; - - nano::asc_pull_req::account_info_payload pld; - pld.target_type = nano::asc_pull_req::hash_type::block; // Query account info by block hash - pld.target = tag.start; - request.payload = pld; - } - break; - default: - debug_assert (false); - } - - request.update_header (); - - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::request, nano::stat::dir::out); - stats.inc (nano::stat::type::bootstrap_ascending_request, to_stat_detail (tag.type)); - - // TODO: There is no feedback mechanism if bandwidth limiter starts dropping our requests - channel->send ( - request, nullptr, - nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type::bootstrap); -} - -std::size_t nano::bootstrap_ascending::service::priority_size () const -{ - nano::lock_guard lock{ mutex }; - return accounts.priority_size (); -} - -std::size_t nano::bootstrap_ascending::service::blocked_size () const -{ - nano::lock_guard lock{ mutex }; - return accounts.blocked_size (); -} - -std::size_t nano::bootstrap_ascending::service::score_size () const -{ - nano::lock_guard lock{ mutex }; - return scoring.size (); -} - -/** Inspects a block that has been processed by the block processor -- Marks an account as blocked if the result code is gap source as there is no reason request additional blocks for this account until the dependency is resolved -- Marks an account as forwarded if it has been recently referenced by a block that has been inserted. - */ -void nano::bootstrap_ascending::service::inspect (secure::transaction const & tx, nano::block_status const & result, nano::block const & block, nano::block_source source) -{ - debug_assert (!mutex.try_lock ()); - - auto const hash = block.hash (); - - switch (result) - { - case nano::block_status::progress: - { - const auto account = block.account (); - - // If we've inserted any block in to an account, unmark it as blocked - accounts.unblock (account); - accounts.priority_up (account); - - if (block.is_send ()) - { - auto destination = block.destination (); - accounts.unblock (destination, hash); // Unblocking automatically inserts account into priority set - accounts.priority_set (destination); - } - } - break; - case nano::block_status::gap_source: - { - if (source == nano::block_source::bootstrap) - { - const auto account = block.previous ().is_zero () ? block.account_field ().value () : ledger.any.block_account (tx, block.previous ()).value (); - const auto source_hash = block.source_field ().value_or (block.link_field ().value_or (0).as_block_hash ()); - - // Mark account as blocked because it is missing the source block - accounts.block (account, source_hash); - } - } - break; - case nano::block_status::gap_previous: - { - // Prevent live traffic from evicting accounts from the priority list - if (source == nano::block_source::live && !accounts.priority_half_full () && !accounts.blocked_half_full ()) - { - if (block.type () == block_type::state) - { - const auto account = block.account_field ().value (); - accounts.priority_set (account); - } - } - } - break; - default: // No need to handle other cases - break; - } -} - -void nano::bootstrap_ascending::service::wait (std::function const & predicate) const -{ - std::unique_lock lock{ mutex }; - - std::chrono::milliseconds interval = 5ms; - while (!stopped && !predicate ()) - { - condition.wait_for (lock, interval); - interval = std::min (interval * 2, config.throttle_wait); - } -} - -void nano::bootstrap_ascending::service::wait_tags () const -{ - wait ([this] () { - debug_assert (!mutex.try_lock ()); - return tags.size () < config.max_requests; - }); -} - -void nano::bootstrap_ascending::service::wait_blockprocessor () const -{ - wait ([this] () { - return block_processor.size (nano::block_source::bootstrap) < config.block_processor_threshold; - }); -} - -std::shared_ptr nano::bootstrap_ascending::service::wait_channel () -{ - std::shared_ptr channel; - - wait ([this, &channel] () { - debug_assert (!mutex.try_lock ()); - channel = scoring.channel (); - return channel != nullptr; // Wait until a channel is available - }); - - return channel; -} - -size_t nano::bootstrap_ascending::service::count_tags (nano::account const & account, query_source source) const -{ - debug_assert (!mutex.try_lock ()); - auto [begin, end] = tags.get ().equal_range (account); - return std::count_if (begin, end, [source] (auto const & tag) { return tag.source == source; }); -} - -size_t nano::bootstrap_ascending::service::count_tags (nano::block_hash const & hash, query_source source) const -{ - debug_assert (!mutex.try_lock ()); - auto [begin, end] = tags.get ().equal_range (hash); - return std::count_if (begin, end, [source] (auto const & tag) { return tag.source == source; }); -} - -std::pair nano::bootstrap_ascending::service::next_priority () -{ - debug_assert (!mutex.try_lock ()); - - auto account = accounts.next_priority ([this] (nano::account const & account) { - return count_tags (account, query_source::priority) < 4; - }); - - if (account.is_zero ()) - { - return {}; - } - - stats.inc (nano::stat::type::bootstrap_ascending_next, nano::stat::detail::next_priority); - accounts.timestamp_set (account); - - // TODO: Priority could be returned by the accounts.next_priority() call - return { account, accounts.priority (account) }; -} - -std::pair nano::bootstrap_ascending::service::wait_priority () -{ - std::pair result{ 0, 0 }; - - wait ([this, &result] () { - debug_assert (!mutex.try_lock ()); - result = next_priority (); - if (!result.first.is_zero ()) - { - return true; - } - return false; - }); - - return result; -} - -nano::account nano::bootstrap_ascending::service::next_database (bool should_throttle) -{ - debug_assert (!mutex.try_lock ()); - debug_assert (config.database_warmup_ratio > 0); - - // Throttling increases the weight of database requests - if (!database_limiter.should_pass (should_throttle ? config.database_warmup_ratio : 1)) - { - return { 0 }; - } - - auto account = database_scan.next ([this] (nano::account const & account) { - return count_tags (account, query_source::database) == 0; - }); - - if (account.is_zero ()) - { - return { 0 }; - } - - stats.inc (nano::stat::type::bootstrap_ascending_next, nano::stat::detail::next_database); - return account; -} - -nano::account nano::bootstrap_ascending::service::wait_database (bool should_throttle) -{ - nano::account result{ 0 }; - - wait ([this, &result, should_throttle] () { - debug_assert (!mutex.try_lock ()); - result = next_database (should_throttle); - if (!result.is_zero ()) - { - return true; - } - return false; - }); - - return result; -} - -nano::block_hash nano::bootstrap_ascending::service::next_blocking () -{ - debug_assert (!mutex.try_lock ()); - - auto blocking = accounts.next_blocking ([this] (nano::block_hash const & hash) { - return count_tags (hash, query_source::blocking) == 0; - }); - - if (blocking.is_zero ()) - { - return { 0 }; - } - - stats.inc (nano::stat::type::bootstrap_ascending_next, nano::stat::detail::next_blocking); - return blocking; -} - -nano::block_hash nano::bootstrap_ascending::service::wait_blocking () -{ - nano::block_hash result{ 0 }; - - wait ([this, &result] () { - debug_assert (!mutex.try_lock ()); - result = next_blocking (); - if (!result.is_zero ()) - { - return true; - } - return false; - }); - - return result; -} - -bool nano::bootstrap_ascending::service::request (nano::account account, size_t count, std::shared_ptr const & channel, query_source source) -{ - debug_assert (count > 0); - debug_assert (count <= nano::bootstrap_server::max_blocks); - - // Limit the max number of blocks to pull - count = std::min (count, config.max_pull_count); - - async_tag tag{}; - tag.source = source; - tag.account = account; - tag.count = count; - - // Check if the account picked has blocks, if it does, start the pull from the highest block - auto info = ledger.store.account.get (ledger.store.tx_begin_read (), account); - if (info) - { - tag.type = query_type::blocks_by_hash; - tag.start = info->head; - tag.hash = info->head; - } - else - { - tag.type = query_type::blocks_by_account; - tag.start = account; - } - - on_request.notify (tag, channel); - - send (channel, tag); - - return true; // Request sent -} - -bool nano::bootstrap_ascending::service::request_info (nano::block_hash hash, std::shared_ptr const & channel, query_source source) -{ - async_tag tag{}; - tag.type = query_type::account_info_by_hash; - tag.source = source; - tag.start = hash; - tag.hash = hash; - - on_request.notify (tag, channel); - - send (channel, tag); - - return true; // Request sent -} - -void nano::bootstrap_ascending::service::run_one_priority () -{ - wait_tags (); - wait_blockprocessor (); - auto channel = wait_channel (); - if (!channel) - { - return; - } - auto [account, priority] = wait_priority (); - if (account.is_zero ()) - { - return; - } - size_t const min_pull_count = 2; - auto count = std::clamp (static_cast (priority), min_pull_count, nano::bootstrap_server::max_blocks); - request (account, count, channel, query_source::priority); -} - -void nano::bootstrap_ascending::service::run_priorities () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - lock.unlock (); - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop); - run_one_priority (); - lock.lock (); - } -} - -void nano::bootstrap_ascending::service::run_one_database (bool should_throttle) -{ - wait_tags (); - wait_blockprocessor (); - auto channel = wait_channel (); - if (!channel) - { - return; - } - auto account = wait_database (should_throttle); - if (account.is_zero ()) - { - return; - } - request (account, 2, channel, query_source::database); -} - -void nano::bootstrap_ascending::service::run_database () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - // Avoid high churn rate of database requests - bool should_throttle = !database_scan.warmed_up () && throttle.throttled (); - lock.unlock (); - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop_database); - run_one_database (should_throttle); - lock.lock (); - } -} - -void nano::bootstrap_ascending::service::run_one_blocking () -{ - wait_tags (); - wait_blockprocessor (); - auto channel = wait_channel (); - if (!channel) - { - return; - } - auto blocking = wait_blocking (); - if (blocking.is_zero ()) - { - return; - } - request_info (blocking, channel, query_source::blocking); -} - -void nano::bootstrap_ascending::service::run_dependencies () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - lock.unlock (); - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop_dependencies); - run_one_blocking (); - lock.lock (); - } -} - -void nano::bootstrap_ascending::service::cleanup_and_sync () -{ - debug_assert (!mutex.try_lock ()); - - scoring.sync (network.list ()); - scoring.timeout (); - - throttle.resize (compute_throttle_size ()); - - auto const cutoff = std::chrono::steady_clock::now () - config.request_timeout; - auto should_timeout = [cutoff] (async_tag const & tag) { - return tag.timestamp < cutoff; - }; - - auto & tags_by_order = tags.get (); - while (!tags_by_order.empty () && should_timeout (tags_by_order.front ())) - { - auto tag = tags_by_order.front (); - tags_by_order.pop_front (); - on_timeout.notify (tag); - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::timeout); - } - - if (sync_dependencies_interval.elapsed (60s)) - { - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::sync_dependencies); - accounts.sync_dependencies (); - } -} - -void nano::bootstrap_ascending::service::run_timeouts () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop_cleanup); - cleanup_and_sync (); - condition.wait_for (lock, 5s, [this] () { return stopped; }); - } -} - -void nano::bootstrap_ascending::service::process (nano::asc_pull_ack const & message, std::shared_ptr const & channel) -{ - nano::unique_lock lock{ mutex }; - - // Only process messages that have a known tag - auto it = tags.get ().find (message.id); - if (it == tags.get ().end ()) - { - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::missing_tag); - return; - } - - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::reply); - - auto tag = *it; - tags.get ().erase (it); // Iterator is invalid after this point - - // Verifies that response type corresponds to our query - struct payload_verifier - { - query_type type; - - bool operator() (const nano::asc_pull_ack::blocks_payload & response) const - { - return type == query_type::blocks_by_hash || type == query_type::blocks_by_account; - } - bool operator() (const nano::asc_pull_ack::account_info_payload & response) const - { - return type == query_type::account_info_by_hash; - } - bool operator() (const nano::asc_pull_ack::frontiers_payload & response) const - { - return false; // TODO: Handle frontiers - } - bool operator() (const nano::empty_payload & response) const - { - return false; // Should not happen - } - }; - - bool valid = std::visit (payload_verifier{ tag.type }, message.payload); - if (!valid) - { - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::invalid_response_type); - return; - } - - // Track bootstrap request response time - stats.inc (nano::stat::type::bootstrap_ascending_reply, to_stat_detail (tag.type)); - stats.sample (nano::stat::sample::bootstrap_tag_duration, nano::log::milliseconds_delta (tag.timestamp), { 0, config.request_timeout.count () }); - - scoring.received_message (channel); - - lock.unlock (); - - on_reply.notify (tag); - - // Process the response payload - std::visit ([this, &tag] (auto && request) { return process (request, tag); }, message.payload); - - condition.notify_all (); -} - -void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::blocks_payload & response, const async_tag & tag) -{ - debug_assert (tag.type == query_type::blocks_by_hash || tag.type == query_type::blocks_by_account); - - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::blocks); - - auto result = verify (response, tag); - switch (result) - { - case verify_result::ok: - { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::ok); - stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::blocks, nano::stat::dir::in, response.blocks.size ()); - - auto blocks = response.blocks; - - // Avoid re-processing the block we already have - release_assert (blocks.size () >= 1); - if (blocks.front ()->hash () == tag.start.as_block_hash ()) - { - blocks.pop_front (); - } - - for (auto const & block : blocks) - { - if (block == blocks.back ()) - { - // It's the last block submitted for this account chain, reset timestamp to allow more requests - block_processor.add (block, nano::block_source::bootstrap, nullptr, [this, account = tag.account] (auto result) { - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::timestamp_reset); - { - nano::lock_guard guard{ mutex }; - accounts.timestamp_reset (account); - } - condition.notify_all (); - }); - } - else - { - block_processor.add (block, nano::block_source::bootstrap); - } - } - - if (tag.source == query_source::database) - { - nano::lock_guard lock{ mutex }; - throttle.add (true); - } - } - break; - case verify_result::nothing_new: - { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::nothing_new); - - nano::lock_guard lock{ mutex }; - accounts.priority_down (tag.account); - if (tag.source == query_source::database) - { - throttle.add (false); - } - } - break; - case verify_result::invalid: - { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::invalid); - } - break; - } -} - -void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::account_info_payload & response, const async_tag & tag) -{ - debug_assert (tag.type == query_type::account_info_by_hash); - debug_assert (!tag.hash.is_zero ()); - - if (response.account.is_zero ()) - { - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info_empty); - } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info); - - // Prioritize account containing the dependency - { - nano::lock_guard lock{ mutex }; - accounts.dependency_update (tag.hash, response.account); - accounts.priority_set (response.account); - } - } -} - -void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::frontiers_payload & response, const async_tag & tag) -{ - // TODO: Make use of frontiers info - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::frontiers); -} - -void nano::bootstrap_ascending::service::process (const nano::empty_payload & response, const async_tag & tag) -{ - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::empty); - debug_assert (false, "empty payload"); // Should not happen -} - -nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::service::verify (const nano::asc_pull_ack::blocks_payload & response, const nano::bootstrap_ascending::service::async_tag & tag) const -{ - auto const & blocks = response.blocks; - - if (blocks.empty ()) - { - return verify_result::nothing_new; - } - if (blocks.size () == 1 && blocks.front ()->hash () == tag.start.as_block_hash ()) - { - return verify_result::nothing_new; - } - if (blocks.size () > tag.count) - { - return verify_result::invalid; - } - - auto const & first = blocks.front (); - switch (tag.type) - { - case query_type::blocks_by_hash: - { - if (first->hash () != tag.start.as_block_hash ()) - { - // TODO: Stat & log - return verify_result::invalid; - } - } - break; - case query_type::blocks_by_account: - { - // Open & state blocks always contain account field - if (first->account_field ().value_or (0) != tag.start.as_account ()) - { - // TODO: Stat & log - return verify_result::invalid; - } - } - break; - default: - return verify_result::invalid; - } - - // Verify blocks make a valid chain - nano::block_hash previous_hash = blocks.front ()->hash (); - for (int n = 1; n < blocks.size (); ++n) - { - auto & block = blocks[n]; - if (block->previous () != previous_hash) - { - // TODO: Stat & log - return verify_result::invalid; // Blocks do not make a chain - } - previous_hash = block->hash (); - } - - return verify_result::ok; -} - -auto nano::bootstrap_ascending::service::info () const -> nano::bootstrap_ascending::account_sets::info_t -{ - nano::lock_guard lock{ mutex }; - return accounts.info (); -} - -std::size_t nano::bootstrap_ascending::service::compute_throttle_size () const -{ - auto ledger_size = ledger.account_count (); - size_t target = ledger_size > 0 ? config.throttle_coefficient * static_cast (std::log (ledger_size)) : 0; - size_t min_size = 16; - return std::max (target, min_size); -} - -nano::container_info nano::bootstrap_ascending::service::container_info () const -{ - nano::lock_guard lock{ mutex }; - - nano::container_info info; - info.put ("tags", tags); - info.put ("throttle", throttle.size ()); - info.put ("throttle_successes", throttle.successes ()); - info.add ("accounts", accounts.container_info ()); - info.add ("database_scan", database_scan.container_info ()); - return info; -} - -/* - * - */ - -nano::stat::detail nano::bootstrap_ascending::to_stat_detail (nano::bootstrap_ascending::service::query_type type) -{ - return nano::enum_util::cast (type); -} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/service.hpp b/nano/node/bootstrap_ascending/service.hpp deleted file mode 100644 index 1935f69481..0000000000 --- a/nano/node/bootstrap_ascending/service.hpp +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -namespace mi = boost::multi_index; - -namespace nano -{ -namespace bootstrap_ascending -{ - class service - { - public: - service (nano::node_config const &, nano::block_processor &, nano::ledger &, nano::network &, nano::stats &, nano::logger &); - ~service (); - - void start (); - void stop (); - - /** - * Process `asc_pull_ack` message coming from network - */ - void process (nano::asc_pull_ack const & message, std::shared_ptr const &); - - std::size_t blocked_size () const; - std::size_t priority_size () const; - std::size_t score_size () const; - - nano::container_info container_info () const; - - nano::bootstrap_ascending::account_sets::info_t info () const; - - private: // Dependencies - bootstrap_ascending_config const & config; - nano::network_constants const & network_constants; - nano::block_processor & block_processor; - nano::ledger & ledger; - nano::network & network; - nano::stats & stats; - nano::logger & logger; - - public: // Tag - enum class query_type - { - invalid = 0, // Default initialization - blocks_by_hash, - blocks_by_account, - account_info_by_hash, - }; - - enum class query_source - { - invalid, - priority, - database, - blocking, - }; - - struct async_tag - { - query_type type{ query_type::invalid }; - query_source source{ query_source::invalid }; - nano::hash_or_account start{ 0 }; - nano::account account{ 0 }; - nano::block_hash hash{ 0 }; - size_t count{ 0 }; - - id_t id{ generate_id () }; - std::chrono::steady_clock::time_point timestamp{ std::chrono::steady_clock::now () }; - }; - - public: // Events - nano::observer_set const &> on_request; - nano::observer_set on_reply; - nano::observer_set on_timeout; - - private: - /* Inspects a block that has been processed by the block processor */ - void inspect (secure::transaction const &, nano::block_status const & result, nano::block const & block, nano::block_source); - - void run_priorities (); - void run_one_priority (); - void run_database (); - void run_one_database (bool should_throttle); - void run_dependencies (); - void run_one_blocking (); - void run_timeouts (); - void cleanup_and_sync (); - - /* Waits for a condition to be satisfied with incremental backoff */ - void wait (std::function const & predicate) const; - - /* Avoid too many in-flight requests */ - void wait_tags () const; - /* Ensure there is enough space in blockprocessor for queuing new blocks */ - void wait_blockprocessor () const; - /* Waits for a channel that is not full */ - std::shared_ptr wait_channel (); - /* Waits until a suitable account outside of cool down period is available */ - std::pair next_priority (); - std::pair wait_priority (); - /* Gets the next account from the database */ - nano::account next_database (bool should_throttle); - nano::account wait_database (bool should_throttle); - /* Waits for next available blocking block */ - nano::block_hash next_blocking (); - nano::block_hash wait_blocking (); - - bool request (nano::account, size_t count, std::shared_ptr const &, query_source); - bool request_info (nano::block_hash, std::shared_ptr const &, query_source); - void send (std::shared_ptr const &, async_tag tag); - - void process (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag); - void process (nano::asc_pull_ack::account_info_payload const & response, async_tag const & tag); - void process (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag); - void process (nano::empty_payload const & response, async_tag const & tag); - - enum class verify_result - { - ok, - nothing_new, - invalid, - }; - - /** - * Verifies whether the received response is valid. Returns: - * - invalid: when received blocks do not correspond to requested hash/account or they do not make a valid chain - * - nothing_new: when received response indicates that the account chain does not have more blocks - * - ok: otherwise, if all checks pass - */ - verify_result verify (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag) const; - - size_t count_tags (nano::account const & account, query_source source) const; - size_t count_tags (nano::block_hash const & hash, query_source source) const; - - // Calculates a lookback size based on the size of the ledger where larger ledgers have a larger sample count - std::size_t compute_throttle_size () const; - - private: - nano::bootstrap_ascending::account_sets accounts; - nano::bootstrap_ascending::database_scan database_scan; - nano::bootstrap_ascending::throttle throttle; - nano::bootstrap_ascending::peer_scoring scoring; - - // clang-format off - class tag_sequenced {}; - class tag_id {}; - class tag_account {}; - class tag_hash {}; - - using ordered_tags = boost::multi_index_container>, - mi::hashed_unique, - mi::member>, - mi::hashed_non_unique, - mi::member>, - mi::hashed_non_unique, - mi::member> - >>; - // clang-format on - ordered_tags tags; - - // Requests for accounts from database have much lower hitrate and could introduce strain on the network - // A separate (lower) limiter ensures that we always reserve resources for querying accounts from priority queue - nano::rate_limiter database_limiter; - - nano::interval sync_dependencies_interval; - - bool stopped{ false }; - mutable nano::mutex mutex; - mutable nano::condition_variable condition; - std::thread priorities_thread; - std::thread database_thread; - std::thread dependencies_thread; - std::thread timeout_thread; - }; - - nano::stat::detail to_stat_detail (service::query_type); -} -} diff --git a/nano/node/bootstrap_weights_beta.hpp b/nano/node/bootstrap_weights_beta.hpp new file mode 100644 index 0000000000..d976c91a14 --- /dev/null +++ b/nano/node/bootstrap_weights_beta.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +namespace nano::weights +{ +// Bootstrap weights for beta network +std::vector> preconfigured_weights_beta = { + { "nano_3faucet4t1nnru6yra9iioia76jddur6zqg6d3fp7h1soyyd8qhgx6tizrsy", "37999100000000000000000000000000000000" }, + { "nano_1betazh7m3c9gwcsy7w3rzynbqr9gomjwn3cp59xqky48we46eaqptbdskh4", "32981063781291209870813128842298384384" }, + { "nano_3immionim1ypak7xbxe53ozdgk8sarjsu1ae7xbrnc8z9ntb8upnq47eugkx", "30000000000000000000000000000000000000" }, + { "nano_3kedrin3axwpe6jcx5fi8bx6sgjcre7bj4su5gpmfyd4gaijn8ndcyzgxche", "24000100000000000000000000000000000000" }, + { "nano_1robotghjtaub18dmo1ihkzg9jjs53ukthxrpt5x7eie3pg7k4ahb5i1uw64", "22400000000000000000000000000000000000" }, + { "nano_1bnano1dnhc356frb1owg4mhi4r47j1i15yq8nuyyso8fg64ux9kdxzmae5g", "16000000000000000000000000000000000000" }, + { "nano_1kitteh45srbwthaxq11tj54awh1trwuyt6o56ya4ghqinqo3a3jisbjg4dd", "12800000000000000000000000000000000000" }, + { "nano_18cgy87ikc4ruyh5aqwqe6dybe9os1ip3681y9wukypz5j7kgh35uxftss1x", "12000000000000000000000000000000000000" }, + { "nano_1betag7az9wk6rbis38s1d35hdsycz1bi95xg4g4j148p6afjk7embcurda4", "5800000000000000000000000000000000001" }, + { "nano_1rickip5smeeztoxcg9jmjpsmyaeu7wkmkjssettss3firi3kmjq186uf3gb", "4000000000000000000000000000000000000" }, + { "nano_1defau1t9off1ine9rep99999999999999999999999999999999wgmuzxxy", "1009399201843717416503167458269866895" }, +}; +uint64_t max_blocks_beta = 39428700; +} diff --git a/nano/node/bootstrap_weights_live.hpp b/nano/node/bootstrap_weights_live.hpp new file mode 100644 index 0000000000..d216ef39a3 --- /dev/null +++ b/nano/node/bootstrap_weights_live.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +namespace nano::weights +{ +// Bootstrap weights for live network +std::vector> preconfigured_weights_live = { + { "nano_37imps4zk1dfahkqweqa91xpysacb7scqxf3jqhktepeofcxqnpx531b3mnt", "14317221601782257940265246844477761608" }, + { "nano_1natrium1o3z5519ifou7xii8crpxpk8y65qmkih8e8bpsjri651oza8imdd", "9008306765618889147447565195449347236" }, + { "nano_19qo4gtzpoyqf6zzezbcuazcsxtqtdin5qbtk8jkoz4fdmq4ssagn3u1odhz", "8739841068913099957481638583625439629" }, + { "nano_3oxhohaxp9ceobppkhp7wahauxd4zgyz4fhxfniyp4mb9opq4upfnaccswo7", "4203898049152356535934809611421529209" }, + { "nano_1ninja7rh37ehfp9utkor5ixmxyg8kme8fnzc4zty145ibch8kf5jwpnzr3r", "4024762787339309821866196279802590240" }, + { "nano_3g6ue89jij6bxaz3hodne1c7gzgw77xawpdz4p38siu145u3u17c46or4jeu", "3337151397877677561329648108185177534" }, + { "nano_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh", "2874116211611066355866777673649464616" }, + { "nano_3jwrszth46rk1mu7rmb4rhm54us8yg1gw3ipodftqtikf5yqdyr7471nsg1k", "2826000248597673916756231091556482538" }, + { "nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4", "2685447874371585798927260813897439383" }, + { "nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou", "2409113082946438436094993574762321744" }, + { "nano_1x7biz69cem95oo7gxkrw6kzhfywq4x5dupw4z1bdzkb74dk9kpxwzjbdhhs", "2338182349109669974712009280480350867" }, + { "nano_1rs5rtyeo1owjt6cz9ypdkqyydq656kai8t35haiioapts39x96br5u4mbdw", "2257139514323759810052534483561497850" }, + { "nano_1wcxcjbwnnsdpee3d9i365e8bcj1uuyoqg9he5zjpt3r57dnjqe3gdc184ck", "2193280220689674855382211724497160547" }, + { "nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs", "2116783925068090591956066895857906699" }, + { "nano_3kqdiqmqiojr1aqqj51aq8bzz5jtwnkmhb38qwf3ppngo8uhhzkdkn7up7rp", "1921367135034926636887919266356810634" }, + { "nano_3chartsi6ja8ay1qq9xg3xegqnbg1qx76nouw6jedyb8wx3r4wu94rxap7hg", "1907273799369069209045342053988770783" }, + { "nano_3rw4un6ys57hrb39sy1qx8qy5wukst1iiponztrz9qiz6qqa55kxzx4491or", "1859294811826658647497107952298836662" }, + { "nano_1ookerz3adg5rxc4zwwoshim5yyyihf6dpogjihwwq6ksjpq7ea4fuam5mmc", "1708675709399069539343978412465572698" }, + { "nano_1tk8h3yzkibbsti8upkfa69wqafz6mzfzgu8bu5edaay9k7hidqdunpr4tb6", "1548307357160578238104031638442525252" }, + { "nano_1iuz18n4g4wfp9gf7p1s8qkygxw7wx9qfjq6a9aq68uyrdnningdcjontgar", "1536193230228535247903579681119958205" }, + { "nano_3msc38fyn67pgio16dj586pdrceahtn75qgnx7fy19wscixrc8dbb3abhbw6", "1505067175154564405525328895424049474" }, + { "nano_3pg8khw8gs94c1qeq9741n99ubrut8sj3n9kpntim1rm35h4wdzirofazmwt", "1284753294680413281750900949179729362" }, + { "nano_1ec5optppmndqsb3rxu1qa4hpo39957s7mfqycpbd547jga4768o6xz8gfie", "1280974299337937802229858202370845514" }, + { "nano_3patrick68y5btibaujyu7zokw7ctu4onikarddphra6qt688xzrszcg4yuo", "1234606917063903627113233555642524531" }, + { "nano_1zuksmn4e8tjw1ch8m8fbrwy5459bx8645o9euj699rs13qy6ysjhrewioey", "1228945454877289042776069155113658061" }, + { "nano_1wenanoqm7xbypou7x3nue1isaeddamjdnc3z99tekjbfezdbq8fmb659o7t", "1074208866349499832207446685330610139" }, + { "nano_3power3gwb43rs7u9ky3rsjp6fojftejceexfkf845sfczyue4q3r1hfpr3o", "1032377900448607574856495824050651392" }, + { "nano_1jtx5p8141zjtukz4msp1x93st7nh475f74odj8673qqm96xczmtcnanos1o", "966034556899526176448021511730888010" }, + { "nano_35btiz1mgfwp95c3ckazmzbp5gepduxtijuijd9xebeau8u1gsbea41smjca", "932734402605108617158860600988089671" }, + { "nano_35nu7hynfzecjtauskk6yb8pgfeifscqz4hmgtani15s8eiocgsz15axursj", "894001715198160000000000000000000000" }, + { "nano_3ktybzzy14zxgb6osbhcc155pwk7osbmf5gbh5fo73bsfu9wuiz54t1uozi1", "891208781793902574686515301072534699" }, + { "nano_1aqkrayihxzdahoxpjrg8o6mxgxfzq46hhcdm1u48w3qexsakx7pzzhjn3fc", "867529343889764534853752690417963017" }, + { "nano_3u7d5iohy14swyhxhgfm9iq4xa9yibhcgnyj697uwhicp14dhx4woik5e9ek", "787717996825365535053675000254943429" }, + { "nano_3o7uzba8b9e1wqu5ziwpruteyrs3scyqr761x7ke6w1xctohxfh5du75qgaj", "781026512386372824181194105745980103" }, + { "nano_3dmtrrws3pocycmbqwawk6xs7446qxa36fcncush4s1pejk16ksbmakis78m", "731688272783146727778615255382099174" }, + { "nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p", "731442344674266489654030894260483519" }, + { "nano_1my1snode8rwccjxkckjirj65zdxo6g5nhh16fh6sn7hwewxooyyesdsmii3", "706532939040312647596528503828324966" }, + { "nano_1awsn43we17c1oshdru4azeqjz9wii41dy8npubm4rg11so7dx3jtqgoeahy", "697014260553515079799724414701546157" }, + { "nano_15nt4cis8ac184q9mj7bedww9ay9zh5jk5k7sj9ypmz44twjcpz3cn6oijir", "659129576598677155405937398810943741" }, + { "nano_3zapp5z141qpjipsb1jnjdmk49jwqy58i6u6wnyrh6x7woajeyme85shxewt", "656573631896113356831865548834091714" }, + { "nano_33ad5app7jeo6jfe9ure6zsj8yg7knt6c1zrr5yg79ktfzk5ouhmpn6p5d7p", "639455694844539882365191529586886249" }, + { "nano_1hza3f7wiiqa7ig3jczyxj5yo86yegcmqk3criaz838j91sxcckpfhbhhra1", "609241665676551210332374336688413993" }, + { "nano_1xckpezrhg56nuokqh6t1stjca67h37jmrp9qnejjkfgimx1msm9ehuaieuq", "607277651931166531882226035290134503" }, + { "nano_3afmp9hx6pp6fdcjq96f9qnoeh1kiqpqyzp7c18byaipf48t3cpzmfnhc1b7", "578626162098920499928766341604249543" }, + { "nano_3strnmn7h9b7oghxa6h9ckrpf5r454fsobpicixps6xwiwc5q4hat7wjbpqz", "578128507103586281780105652419574280" }, + { "nano_318uu1tsbios3kp4dts5b6zy1y49uyb88jajfjyxwmozht8unaxeb43keork", "574877575934201034223283964352899451" }, + { "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579", "573976448470409630350394615480596497" }, + { "nano_375pi67f4i4ag5rudoziza86z715bepsmp1r6ri4domt7ct6tk67mjsxtebm", "563530755922834831919842970709556703" }, + { "nano_396sch48s3jmzq1bk31pxxpz64rn7joj38emj4ueypkb9p9mzrym34obze6c", "556089766003962618128534604509866734" }, + { "nano_3caprkc56ebsaakn4j4n7g9p8h358mycfjcyzkrfw1nai6prbyk8ihc5yjjk", "550100426761353904022334716632852404" }, + { "nano_16d3mdshcfqayyx8rd9ioimjiicrma743qpd86ohfs9kdzgejmnba1zifo8m", "499691553919320398736118351247200996" }, + { "nano_1oenixj4qtpfcembga9kqwggkb87wooicfy5df8nhdywrjrrqxk7or4gz15b", "491144022712594134192715734880001622" }, + { "nano_1kd4h9nqaxengni43xy9775gcag8ptw8ddjifnm77qes1efuoqikoqy5sjq3", "475402367898168781311027934839922326" }, + { "nano_18shbirtzhmkf7166h39nowj9c9zrpufeg75bkbyoobqwf1iu3srfm9eo3pz", "473484888200612394494596955174427039" }, + { "nano_3n7ky76t4g57o9skjawm8pprooz1bminkbeegsyt694xn6d31c6s744fjzzz", "470772983298051673321076792962966884" }, + { "nano_1isgusmnf1xe45iyjtfxw4qiai36zxdituu7gpni1trtj5ojyujobq13bjah", "459402406358384894037129966948401197" }, + { "nano_34amtofxstsfyqcgphp8piij9u33widykq9wbz6ysjpxhbgmqu8btu1eexer", "457653530646699587582980233117606927" }, + { "nano_1etto78drszxhtb5jhswzzm5m98ffqxwjzwg3gr8ajt5sq4ahdj4bjhni9we", "437567795414962896700152301280696370" }, + { "nano_37ortkby6k68z8tkk8g63ndbp8wjbmofhn56oyxb4rm6s3x51pkpiwcnpgmq", "428403027425840021304386452591466051" }, + { "nano_1f56swb9qtpy3yoxiscq9799nerek153w43yjc9atoaeg3e91cc9zfr89ehj", "422870201797228894543689070440428154" }, + { "nano_1ota8bpwwawmc8ksdz4ezzrb3afbdeipk1n7rbeguhm4muy1r649uzw5moon", "422585916248728379581517331284390201" }, + { "nano_31xitw55kb3ko8yaz3439hqaqpibxa9shx76suaa3no786do3hjuz8dy6izw", "422509558881319407492719361648844939" }, + { "nano_18bpu81x4oyqsjjsyaeb7ek4rag1bw8gerhaiumookzc4t5prrm4d7zg56ww", "421011929914960639796442037269974660" }, + { "nano_1center16ci77qw5w69ww8sy4i4bfmgfhr81ydzpurm91cauj11jn6y3uc5y", "419531315554376009047180777677066994" }, + { "nano_3hd4ezdgsp15iemx7h81in7xz5tpxi43b6b41zn3qmwiuypankocw3awes5k", "406983596358752932306138071853398256" }, + { "nano_3ekb6tp8ixtkibimyygepgkwckzhds9basxd5zfue4efjnxaan77gsnanick", "403726279759135980416863490423676112" }, + { "nano_34zuxqdsucurhjrmpc4aixzbgaa4wjzz6bn5ryn56emc9tmd3pnxjoxfzyb6", "401721644358570040128189073988165666" }, + { "nano_1bj5cf9hkgkcspmn15day8cyn3hyaciufbba4rqmbnkmbdpjdmo9pwyatjoi", "371405290365393564328742107324220123" }, + { "nano_1o5rr464r8bgu4h3jnt5yn4a6tbd5shbbpwk6aj6sunf8dzdpmke6q4tgtkj", "361321659995846670424585898471174756" }, + { "nano_1fe17w13stn8rqos3nxmupoez9sne4pc4njmr1fbz9nci6obnng6jatton5q", "354266195342959707650270816618065878" }, + { "nano_1fnx59bqpx11s1yn7i5hba3ot5no4ypy971zbkp5wtium3yyafpwhhwkq8fc", "346532956942850970130454455250552346" }, + { "nano_3wat6ci5a55s895eec64i4ihfd9ry3bdxnpb776mj1srjedqfdf4k11rcmg4", "339847282852099789826751962823360136" }, + { "nano_1frogs8fqpdk17w17dpsdbgtbcdmm4egnty8oinxppkwykjzedrbt3suh4as", "333860637948103389959056165149230927" }, + { "nano_3ug8jkpbr35qpa1ceyf6kf7za8nirbxyiyh58iapfzrujfsi4dxf4kmbp6nq", "332994208622362746843950000000000000" }, + { "nano_11pb5aa6uirs9hoqsg4swnzyehoiqowj94kdpthwkhwufmtd6a11xx35iron", "325464071311866907692983818652858988" }, + { "nano_15zntj4a8r6bkihei788ciy1jgc5wnskan1gpgn8e8jku3r4qhr7rwifitir", "321433136102182762384873514381838441" }, + { "nano_3mhrc9czyfzzok7xeoeaknq6w5ok9horo7d4a99m8tbtbyogg8apz491pkzt", "301940752316947610717375877367278899" }, + { "nano_3tta9pdxr4djdcm6r3c7969syoirj3dunrtynmmi8n1qtxzk9iksoz1gxdrh", "287457507563612759726789615128810995" }, + { "nano_3uaydiszyup5zwdt93dahp7mri1cwa5ncg9t4657yyn3o4i1pe8sfjbimbas", "274824261191947465447162790953568458" }, + { "nano_1qgkdadcbwn65sp95gr144fuc99tm5tn6gx9y8ow9bgaam6r5ixgtx19tw93", "269175353980745312611780290676177202" }, + { "nano_1j78msn5omp8jrjge8txwxm4x3smusa1cojg7nuk8fdzoux41fqeeogg5aa1", "264341307271795012456916324338235018" }, + { "nano_3hjo1cehsxrssawmpew98u4ug8bxy4ppht5ch647zpuscdgedfy1xh4yga7z", "252284087686411970070836791559970343" }, + { "nano_1banexkcfuieufzxksfrxqf6xy8e57ry1zdtq9yn7jntzhpwu4pg4hajojmq", "248890458504846155585605341588191239" }, + { "nano_3rropjiqfxpmrrkooej4qtmm1pueu36f9ghinpho4esfdor8785a455d16nf", "232006753135270866140936739355907182" }, + { "nano_1bananobjcrqugm87e8p3kxkhy7d1bzkty53n889iyunm83cp14rb9fin78p", "231464969464816163644892875295414156" }, + { "nano_14j1gqkn8pekpsapqd8c3kciapphaysf6mgw1spsojzzr6qrtskd9dxtopo7", "230780511821502720760490285540800404" }, + { "nano_3abuqtbaotp9myn6ihb6mg96hf7jnapuddydf6ytgd174t4phg86nnq4cmxj", "213152011595563147447719482415767279" }, + { "nano_16d45ow3tsj1y3z9n4satwzxgj6qiue1ggxbwbrj3b33qr58bzchkpsffpx4", "212872173277488310244442366713145695" }, + { "nano_3i3dqy5xs98ewtk9ejfpxfwbsscejc6njz9hk5ia1446gdkxpxkjeeia719n", "212323491960013748584645821297315308" }, + { "nano_1wqymgyw3mtmmmtdqbtipdqq93zcsa1qqq8tdxz8ronswxjnp6mnpgg7yzcf", "207068935509700000000000000000000000" }, + { "nano_1gemini56efw4qrfzfcc71cky1wj7a6673fu5ue5afyyz55zb1cxkj8rkr1n", "205552176112166783156225855072229376" }, + { "nano_3pnanopr3d5g7o45zh3nmdkqpaqxhhp3mw14nzr41smjz8xsrfyhtf9xac77", "198245863763999192988435633718625314" }, + { "nano_1111111111111111111111111111111111111111111111111111hifc8npp", "194863328981291288009376920113598877" }, + { "nano_1fuckbtc6p55wt64eo4rz7brq3ubjfd8unhz3it5fbdpta8tww7ywk8p9su7", "194770724680932921970358685857307956" }, + { "nano_16u1uufyoig8777y6r8iqjtrw8sg8maqrm36zzcm95jmbd9i9aj5i8abr8u5", "188132765190942752050703192409432781" }, + { "nano_1mime3jd7dbnshd6zw1gjqax5zit31h6y1x6pczfuz7au33ftacjib5cc1ez", "175727665295938016116141189473158949" }, + { "nano_1cafe95a81ko3mq3oin4wnubsbw9z3w3tw5a95u47897wxy96r1zj9hxu1wb", "159657285204872924588361803828828695" }, + { "nano_1just1zdsnke856mu5pmed1qdkzk6adh3d13iiqr3so66sr8pbcnh15bdjda", "151860246655352030545681996261039160" }, + { "nano_3kc9wsf9y4y9r3k9yj1d5da53ytjepcf993bcto17xh1s691wyc6im9xaodr", "149974184738767890000000101999999999" }, + { "nano_3f1owhubic8wa8rfmj5x6w9ore9btbtju5eampghs3y9ere6q6u96jraoo5s", "149243980231699932417541673214579638" }, + { "nano_1n1hukyqred6yuch1xgtmdofe1bnc68eza733qmb6r19xo9us7qipbjujad1", "144548727886419830000000000000000000" }, + { "nano_1wxwqkagc6byutzhwqwxuaqofigbufkmqsgf9a9k5pqz3f7kr6mbgu9nror3", "140260148943145965568980211200950272" }, + { "nano_1ebq356ex7n5efth49o1p31r4fmuuoara5tmwduarg7b9jphyxsatr3ja6g8", "139022848906009579119746606534967146" }, + { "nano_1k5fqb5q6t44tsd13ziny66w6mxbya6x397g7tkz7hnkcpppofuojzs7qmik", "134850575048709037899730669200010113" }, + { "nano_39jnmbpgqez7d4zdh83u44jcbf9nb5y3s8z495jf6hxb6719ybdew1ph5qk8", "130001958105000000000000000000000000" }, + { "nano_14mp1ua4oi45rxosft3d8qe4g6a1u1srma59jg85ax6s8zuwhi4yzgdnqhz3", "125504057360322476977533338602093886" }, + { "nano_3fg3hi6b4ptj5y5ss4a3cwarbzahaeazzs6mjf18t1cqm3pmetgtgrtafafp", "121766786123567890000000000000000000" }, + { "nano_1sw898hgeexgrsq8x16wdadwdrs3obn418z6x98parb5tymz879mu89qndju", "118174729005214632121806827131857332" }, + { "nano_3o5dcp6kjish9xuu51akx1d8bp4pytk4diput3s8dkt7cktnmcg96aoi1cbw", "116498337036870213160593996795391359" }, + { "nano_3hrppx3sfxoiycjm9iaqsr3odecgarcxxxhsm41s9pbs75ykambxqhu9ys58", "107467825463030087806917544668625310" }, + { "nano_1niabkx3gbxit5j5yyqcpas71dkffggbr6zpd3heui8rpoocm5xqbdwq44oh", "106696502074863777722983018980325183" }, + { "nano_3bsnis6ha3m9cepuaywskn9jykdggxcu8mxsp76yc3oinrt3n7gi77xiggtm", "106499105314102514902754325239415225" }, + { "nano_3o5oeefdnrha7x7styp1tnmefen7fnrooy4jgnfb1otws54yf7uqfuxmojoy", "101396235450651304650656209248815684" }, + { "nano_1f1aman8jkw8fom6fmjgddhiym784mzsspg39xwmt6wmh9pa8awhw39qgaie", "100001852234001000000000000000000000" }, + { "nano_1chbdd5pkj545neroitwc6bpsps7mohgb8kstwnhxrh3dkkqimjytmp3k3uy", "100000004736000000000000000000000000" }, + { "nano_1ba7uuq9kt68jzzw51a5kt577xtzaq37cnj8otqbhhnkgjyhwu91wtsjacez", "92599306388857055454946275512234883" }, + { "nano_37331hiaumyscgh5c5woq8u4ow13bwdtwie94pdz3a6wmkny4bgsnknp3td4", "72689789428000547000000000000018711" }, + { "nano_3kc8wwut3u8g1kwa6x4drkzu346bdbyqzsn14tmabrpeobn8igksfqkzajbb", "69938740104186467375765800378318163" }, + { "nano_1asau6gr8ft5ykynpkauctrq1w37sdasdymuigtxotim6kxoa3rgn3dpenis", "69368294931521238411402287427733619" }, + { "nano_16f43w3bzsws3joz3xtffe5jzgwcijaty9affr718ithrcy64bkgtjag394y", "64747247521135780000000000000000000" }, + { "nano_1ssx76ua5rtp7awrr4td5eecqq9es697i9gnujhqxd19qgzet56of8qrh7ee", "62825092568000000000000000000000000" }, + { "nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg", "61107133085780339646999089988408727" }, + { "nano_1r98z5i14ztss597ajy3y9no5kpm1bm3uj1gam4gstjhhy3othmus1mamxyz", "60081247898330387024674929860415106" }, + { "nano_3cqnodxiojh34fdxsmirendtqh5qfcnyxpc5bhfu1r5ga7mnj36q335whxdt", "59301035173271560000000000000000000" }, + { "nano_1tuc3179gw3qroosjmc71fori1j5x8z1d6dfthubgr8tjkkgf4cc7xfpnzig", "56410000000307872461115185441013760" }, + { "nano_3s41zmdnmkgg6z5gcc748xwg9fkyweefwc9a6zt6gtwejerpdg9cmf3hxfgq", "54345296782640000000000000000000000" }, + { "nano_3ju9wdb8zpzdn3bnhz6ypm5sipccok9urqif9kdyweimj9c9zyi54ceos1qu", "53793573747989279607346668987519934" }, + { "nano_1hispanocq7ow8azrs3mirsccebuh5e3pfk8pkfzfafbyooezpsnwiuizjcu", "52785211800965329416421874045142062" }, + { "nano_1m1afmq54gum53md3dm3o9arctwn8buwqk8kynxszh468qmm3kn7sawmgihz", "49551961005976889634294506863145399" }, + { "nano_1thingspmippfngcrtk1ofd3uwftffnu4qu9xkauo9zkiuep6iknzci3jxa6", "49004877549064408626248259260415222" }, + { "nano_3hps1wndzrxtybfg77agkrmstb6yzdrr3d55ynw6hfogw8gnux94fdwi8owg", "47412799607881472224803493973773430" }, + { "nano_1nk9zdf1otddxhxfqimjdkmbtq17yzf3z6giz1as7x1huyug8er1ukeqpqpe", "47125708533165128619688467734451787" }, + { "nano_3b4tp37mbyecg98gztrc6t8f1soxkiw6xnbcw7jxctkc77xa9zxtixkrn913", "42647546525470000000000000000000000" }, + { "nano_1ipx847tk8o46pwxt5qjdbncjqcbwcc1rrmqnkztrfjy5k7z4imsrata9est", "42317965344222570489914260872593408" }, + { "nano_1cwswatjifmjnmtu5toepkwca64m7qtuukizyjxsghujtpdr9466wjmn89d8", "41567653422928329345790131665184407" }, + { "nano_16k5pimotz9zehjk795wa4qcx54mtusk8hc5mdsjgy57gnhbj3hj6zaib4ic", "40634739896052438945712123408188842" }, + { "nano_1brainb3zz81wmhxndsbrjb94hx3fhr1fyydmg6iresyk76f3k7y7jiazoji", "36883007625206335485926771292197466" }, +}; +uint64_t max_blocks_live = 203134603; +} diff --git a/nano/node/bounded_backlog.cpp b/nano/node/bounded_backlog.cpp new file mode 100644 index 0000000000..b019d8c674 --- /dev/null +++ b/nano/node/bounded_backlog.cpp @@ -0,0 +1,564 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +nano::bounded_backlog::bounded_backlog (nano::node_config const & config_a, nano::node & node_a, nano::ledger & ledger_a, nano::bucketing & bucketing_a, nano::backlog_scan & backlog_scan_a, nano::block_processor & block_processor_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a) : + config{ config_a }, + node{ node_a }, + ledger{ ledger_a }, + bucketing{ bucketing_a }, + backlog_scan{ backlog_scan_a }, + block_processor{ block_processor_a }, + confirming_set{ confirming_set_a }, + stats{ stats_a }, + logger{ logger_a }, + scan_limiter{ config.bounded_backlog.scan_rate }, + workers{ 1, nano::thread_role::name::bounded_backlog_notifications } +{ + // Activate accounts with unconfirmed blocks + backlog_scan.batch_activated.add ([this] (auto const & batch) { + auto transaction = ledger.tx_begin_read (); + for (auto const & info : batch) + { + activate (transaction, info.account, info.account_info, info.conf_info); + } + }); + + // Erase accounts with all confirmed blocks + backlog_scan.batch_scanned.add ([this] (auto const & batch) { + nano::lock_guard guard{ mutex }; + for (auto const & info : batch) + { + if (info.conf_info.height == info.account_info.block_count) + { + index.erase (info.account); + } + } + }); + + // Track unconfirmed blocks + block_processor.batch_processed.add ([this] (auto const & batch) { + auto transaction = ledger.tx_begin_read (); + for (auto const & [result, context] : batch) + { + if (result == nano::block_status::progress) + { + auto const & block = context.block; + insert (transaction, *block); + } + } + }); + + // Remove rolled back blocks from the backlog + block_processor.rolled_back.add ([this] (auto const & blocks, auto const & rollback_root) { + nano::lock_guard guard{ mutex }; + for (auto const & block : blocks) + { + index.erase (block->hash ()); + } + }); + + // Remove cemented blocks from the backlog + confirming_set.batch_cemented.add ([this] (auto const & batch) { + nano::lock_guard guard{ mutex }; + for (auto const & context : batch) + { + index.erase (context.block->hash ()); + } + }); +} + +nano::bounded_backlog::~bounded_backlog () +{ + // Thread must be stopped before destruction + debug_assert (!thread.joinable ()); + debug_assert (!scan_thread.joinable ()); + debug_assert (!workers.alive ()); +} + +void nano::bounded_backlog::start () +{ + debug_assert (!thread.joinable ()); + + if (!config.bounded_backlog.enable) + { + return; + } + + workers.start (); + + thread = std::thread{ [this] () { + nano::thread_role::set (nano::thread_role::name::bounded_backlog); + run (); + } }; + + scan_thread = std::thread{ [this] () { + nano::thread_role::set (nano::thread_role::name::bounded_backlog_scan); + run_scan (); + } }; +} + +void nano::bounded_backlog::stop () +{ + { + nano::lock_guard lock{ mutex }; + stopped = true; + } + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); + } + if (scan_thread.joinable ()) + { + scan_thread.join (); + } + workers.stop (); +} + +size_t nano::bounded_backlog::index_size () const +{ + nano::lock_guard guard{ mutex }; + return index.size (); +} + +void nano::bounded_backlog::activate (nano::secure::transaction & transaction, nano::account const & account, nano::account_info const & account_info, nano::confirmation_height_info const & conf_info) +{ + debug_assert (conf_info.frontier != account_info.head); + + // Insert blocks into the index starting from the account head block + auto block = ledger.any.block_get (transaction, account_info.head); + while (block) + { + // We reached the confirmed frontier, no need to track more blocks + if (block->hash () == conf_info.frontier) + { + break; + } + // Check if the block is already in the backlog, avoids unnecessary ledger lookups + if (contains (block->hash ())) + { + break; + } + + bool inserted = insert (transaction, *block); + + // If the block was not inserted, we already have it in the backlog + if (!inserted) + { + break; + } + + transaction.refresh_if_needed (); + + block = ledger.any.block_get (transaction, block->previous ()); + } +} + +void nano::bounded_backlog::update (nano::secure::transaction const & transaction, nano::block_hash const & hash) +{ + // Erase if the block is either confirmed or missing + if (!ledger.unconfirmed_exists (transaction, hash)) + { + nano::lock_guard guard{ mutex }; + index.erase (hash); + } +} + +bool nano::bounded_backlog::insert (nano::secure::transaction const & transaction, nano::block const & block) +{ + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, block); + auto const bucket_index = bucketing.bucket_index (priority_balance); + + nano::lock_guard guard{ mutex }; + + return index.insert (block, bucket_index, priority_timestamp); +} + +bool nano::bounded_backlog::predicate () const +{ + debug_assert (!mutex.try_lock ()); + // Both ledger and tracked backlog must be over the threshold + return ledger.backlog_count () > config.max_backlog && index.size () > config.max_backlog; +} + +void nano::bounded_backlog::run () +{ + std::unique_lock lock{ mutex }; + while (!stopped) + { + condition.wait_for (lock, 1s, [this] { + return stopped || predicate (); + }); + + if (stopped) + { + return; + } + + // Wait until all notification about the previous rollbacks are processed + while (workers.queued_tasks () >= config.bounded_backlog.max_queued_notifications) + { + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::cooldown); + condition.wait_for (lock, 100ms, [this] { return stopped.load (); }); + if (stopped) + { + return; + } + } + + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::loop); + + // Calculate the number of targets to rollback + uint64_t const backlog = ledger.backlog_count (); + uint64_t const target_count = backlog > config.max_backlog ? backlog - config.max_backlog : 0; + + auto targets = gather_targets (std::min (target_count, static_cast (config.bounded_backlog.batch_size))); + if (!targets.empty ()) + { + lock.unlock (); + + stats.add (nano::stat::type::bounded_backlog, nano::stat::detail::gathered_targets, targets.size ()); + auto processed = perform_rollbacks (targets, target_count); + + lock.lock (); + + // Erase rolled back blocks from the index + for (auto const & hash : processed) + { + index.erase (hash); + } + } + else + { + // Cooldown, this should not happen in normal operation + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::no_targets); + condition.wait_for (lock, 100ms, [this] { + return stopped.load (); + }); + } + } +} + +bool nano::bounded_backlog::should_rollback (nano::block_hash const & hash) const +{ + if (node.vote_cache.contains (hash)) + { + return false; + } + if (node.vote_router.contains (hash)) + { + return false; + } + if (node.active.recently_confirmed.exists (hash)) + { + return false; + } + if (node.scheduler.contains (hash)) + { + return false; + } + if (node.confirming_set.contains (hash)) + { + return false; + } + if (node.local_block_broadcaster.contains (hash)) + { + return false; + } + return true; +} + +std::deque nano::bounded_backlog::perform_rollbacks (std::deque const & targets, size_t max_rollbacks) +{ + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::performing_rollbacks); + + auto transaction = ledger.tx_begin_write (nano::store::writer::bounded_backlog); + + std::deque processed; + for (auto const & hash : targets) + { + // Skip the rollback if the block is being used by the node, this should be race free as it's checked while holding the ledger write lock + if (!should_rollback (hash)) + { + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::rollback_skipped); + continue; + } + + // Here we check that the block is still OK to rollback, there could be a delay between gathering the targets and performing the rollbacks + if (auto block = ledger.any.block_get (transaction, hash)) + { + logger.debug (nano::log::type::bounded_backlog, "Rolling back: {}, account: {}", hash.to_string (), block->account ().to_account ()); + + std::deque> rollback_list; + bool error = ledger.rollback (transaction, hash, rollback_list); + stats.inc (nano::stat::type::bounded_backlog, error ? nano::stat::detail::rollback_failed : nano::stat::detail::rollback); + + for (auto const & rollback : rollback_list) + { + processed.push_back (rollback->hash ()); + } + + // Notify observers of the rolled back blocks on a background thread, avoid dispatching notifications when holding ledger write transaction + workers.post ([this, rollback_list = std::move (rollback_list), root = block->qualified_root ()] { + // TODO: Calling block_processor's event here is not ideal, but duplicating these events is even worse + block_processor.rolled_back.notify (rollback_list, root); + }); + + // Return early if we reached the maximum number of rollbacks + if (processed.size () >= max_rollbacks) + { + break; + } + } + else + { + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::rollback_missing_block); + processed.push_back (hash); + } + } + + return processed; +} + +size_t nano::bounded_backlog::bucket_threshold () const +{ + return config.max_backlog / bucketing.size (); +} + +std::deque nano::bounded_backlog::gather_targets (size_t max_count) const +{ + debug_assert (!mutex.try_lock ()); + + std::deque targets; + + // Start rolling back from lowest index buckets first + for (auto bucket : bucketing.bucket_indices ()) + { + // Only start rolling back if the bucket is over the threshold of unconfirmed blocks + if (index.size (bucket) > bucket_threshold ()) + { + auto const count = std::min (max_count, config.bounded_backlog.batch_size); + + auto const top = index.top (bucket, count, [this] (auto const & hash) { + // Only rollback if the block is not being used by the node + return should_rollback (hash); + }); + + for (auto const & entry : top) + { + targets.push_back (entry); + } + } + } + + return targets; +} + +void nano::bounded_backlog::run_scan () +{ + std::unique_lock lock{ mutex }; + while (!stopped) + { + auto wait = [&] (auto count) { + while (!scan_limiter.should_pass (count)) + { + condition.wait_for (lock, 100ms); + if (stopped) + { + return; + } + } + }; + + nano::block_hash last = 0; + while (!stopped) + { + wait (config.bounded_backlog.batch_size); + + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::loop_scan); + + auto batch = index.next (last, config.bounded_backlog.batch_size); + if (batch.empty ()) // If batch is empty, we iterated over all accounts in the index + { + break; + } + + lock.unlock (); + { + auto transaction = ledger.tx_begin_read (); + for (auto const & hash : batch) + { + stats.inc (nano::stat::type::bounded_backlog, nano::stat::detail::scanned); + update (transaction, hash); + last = hash; + } + } + lock.lock (); + } + } +} + +bool nano::bounded_backlog::contains (nano::block_hash const & hash) const +{ + nano::lock_guard guard{ mutex }; + return index.contains (hash); +} + +nano::container_info nano::bounded_backlog::container_info () const +{ + nano::lock_guard guard{ mutex }; + nano::container_info info; + info.put ("backlog", index.size ()); + info.put ("notifications", workers.queued_tasks ()); + info.add ("index", index.container_info ()); + return info; +} + +/* + * backlog_index + */ + +bool nano::backlog_index::insert (nano::block const & block, nano::bucket_index bucket, nano::priority_timestamp priority) +{ + auto const hash = block.hash (); + auto const account = block.account (); + + entry new_entry{ + .hash = hash, + .account = account, + .bucket = bucket, + .priority = priority, + }; + + auto [it, inserted] = blocks.emplace (new_entry); + if (inserted) + { + size_by_bucket[bucket]++; + return true; + } + return false; +} + +bool nano::backlog_index::erase (nano::account const & account) +{ + auto const [begin, end] = blocks.get ().equal_range (account); + for (auto it = begin; it != end;) + { + size_by_bucket[it->bucket]--; + it = blocks.get ().erase (it); + } + return begin != end; +} + +bool nano::backlog_index::erase (nano::block_hash const & hash) +{ + if (auto existing = blocks.get ().find (hash); existing != blocks.get ().end ()) + { + size_by_bucket[existing->bucket]--; + blocks.get ().erase (existing); + return true; + } + return false; +} + +std::deque nano::backlog_index::top (nano::bucket_index bucket, size_t count, filter_callback const & filter) const +{ + // Highest timestamp, lowest priority, iterate in descending order + priority_key const starting_key{ bucket, std::numeric_limits::max () }; + + std::deque results; + auto begin = blocks.get ().lower_bound (starting_key); + for (auto it = begin; it != blocks.get ().end () && it->bucket == bucket && results.size () < count; ++it) + { + if (filter (it->hash)) + { + results.push_back (it->hash); + } + } + return results; +} + +std::deque nano::backlog_index::next (nano::block_hash last, size_t count) const +{ + std::deque results; + + auto it = blocks.get ().upper_bound (last); + auto end = blocks.get ().end (); + + for (; it != end && results.size () < count; ++it) + { + results.push_back (it->hash); + } + return results; +} + +bool nano::backlog_index::contains (nano::block_hash const & hash) const +{ + return blocks.get ().contains (hash); +} + +size_t nano::backlog_index::size () const +{ + return blocks.size (); +} + +size_t nano::backlog_index::size (nano::bucket_index bucket) const +{ + if (auto it = size_by_bucket.find (bucket); it != size_by_bucket.end ()) + { + return it->second; + } + return 0; +} + +nano::container_info nano::backlog_index::container_info () const +{ + auto collect_bucket_sizes = [&] () { + nano::container_info info; + for (auto [bucket, count] : size_by_bucket) + { + info.put (std::to_string (bucket), count); + } + return info; + }; + + nano::container_info info; + info.put ("blocks", blocks); + info.add ("sizes", collect_bucket_sizes ()); + return info; +} + +/* + * bounded_backlog_config + */ + +nano::error nano::bounded_backlog_config::serialize (nano::tomlconfig & toml) const +{ + toml.put ("enable", enable, "Enable the bounded backlog. \ntype:bool"); + toml.put ("batch_size", batch_size, "Maximum number of blocks to rollback per iteration. \ntype:uint64"); + toml.put ("max_queued_notifications", max_queued_notifications, "Maximum number of queued background tasks before cooldown. \ntype:uint64"); + toml.put ("scan_rate", scan_rate, "Rate limit for refreshing the backlog index. \ntype:uint64"); + + return toml.get_error (); +} + +nano::error nano::bounded_backlog_config::deserialize (nano::tomlconfig & toml) +{ + toml.get ("enable", enable); + toml.get ("batch_size", batch_size); + toml.get ("max_queued_notifications", max_queued_notifications); + toml.get ("scan_rate", scan_rate); + + return toml.get_error (); +} \ No newline at end of file diff --git a/nano/node/bounded_backlog.hpp b/nano/node/bounded_backlog.hpp new file mode 100644 index 0000000000..807bfe69d6 --- /dev/null +++ b/nano/node/bounded_backlog.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace mi = boost::multi_index; + +namespace nano +{ +class backlog_index +{ +public: + struct priority_key + { + nano::bucket_index bucket; + nano::priority_timestamp priority; + + auto operator<=> (priority_key const &) const = default; + }; + + struct entry + { + nano::block_hash hash; + nano::account account; + nano::bucket_index bucket; + nano::priority_timestamp priority; + + backlog_index::priority_key priority_key () const + { + return { bucket, priority }; + } + }; + +public: + backlog_index () = default; + + bool insert (nano::block const & block, nano::bucket_index, nano::priority_timestamp); + + bool erase (nano::account const & account); + bool erase (nano::block_hash const & hash); + + using filter_callback = std::function; + std::deque top (nano::bucket_index, size_t count, filter_callback const &) const; + + std::deque next (nano::block_hash last, size_t count) const; + + bool contains (nano::block_hash const & hash) const; + size_t size () const; + size_t size (nano::bucket_index) const; + + nano::container_info container_info () const; + +private: + // clang-format off + class tag_hash {}; + class tag_hash_ordered {}; + class tag_account {}; + class tag_priority {}; + + using ordered_blocks = boost::multi_index_container, // Allows for fast lookup + mi::member>, + mi::ordered_unique, // Allows for sequential scan + mi::member>, + mi::hashed_non_unique, + mi::member>, + mi::ordered_non_unique, + mi::const_mem_fun, std::greater<>> // DESC order + >>; + // clang-format on + + ordered_blocks blocks; + + // Keep track of the size of the backlog in number of unconfirmed blocks per bucket + std::unordered_map size_by_bucket; +}; + +class bounded_backlog_config +{ +public: + nano::error deserialize (nano::tomlconfig &); + nano::error serialize (nano::tomlconfig &) const; + +public: + bool enable{ true }; + size_t batch_size{ 32 }; + size_t max_queued_notifications{ 128 }; + size_t scan_rate{ 64 }; +}; + +class bounded_backlog +{ +public: + bounded_backlog (nano::node_config const &, nano::node &, nano::ledger &, nano::bucketing &, nano::backlog_scan &, nano::block_processor &, nano::confirming_set &, nano::stats &, nano::logger &); + ~bounded_backlog (); + + void start (); + void stop (); + + size_t index_size () const; + size_t bucket_threshold () const; + bool contains (nano::block_hash const &) const; + + nano::container_info container_info () const; + +private: // Dependencies + nano::node_config const & config; + nano::node & node; + nano::ledger & ledger; + nano::bucketing & bucketing; + nano::backlog_scan & backlog_scan; + nano::block_processor & block_processor; + nano::confirming_set & confirming_set; + nano::stats & stats; + nano::logger & logger; + +private: + void activate (nano::secure::transaction &, nano::account const &, nano::account_info const &, nano::confirmation_height_info const &); + void update (nano::secure::transaction const &, nano::block_hash const &); + bool insert (nano::secure::transaction const &, nano::block const &); + + bool predicate () const; + void run (); + std::deque gather_targets (size_t max_count) const; + bool should_rollback (nano::block_hash const &) const; + + std::deque perform_rollbacks (std::deque const & targets, size_t max_rollbacks); + + void run_scan (); + +private: + nano::backlog_index index; + + nano::rate_limiter scan_limiter; + + std::atomic stopped{ false }; + nano::condition_variable condition; + mutable nano::mutex mutex; + std::thread thread; + std::thread scan_thread; + + nano::thread_pool workers; +}; +} \ No newline at end of file diff --git a/nano/node/bucketing.cpp b/nano/node/bucketing.cpp new file mode 100644 index 0000000000..271c1e1f8b --- /dev/null +++ b/nano/node/bucketing.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +nano::bucketing::bucketing () +{ + auto build_region = [this] (uint128_t const & begin, uint128_t const & end, size_t count) { + auto width = (end - begin) / count; + for (auto i = 0; i < count; ++i) + { + minimums.push_back (begin + i * width); + } + }; + + minimums.push_back (uint128_t{ 0 }); + build_region (uint128_t{ 1 } << 79, uint128_t{ 1 } << 88, 1); + build_region (uint128_t{ 1 } << 88, uint128_t{ 1 } << 92, 2); + build_region (uint128_t{ 1 } << 92, uint128_t{ 1 } << 96, 4); + build_region (uint128_t{ 1 } << 96, uint128_t{ 1 } << 100, 8); + build_region (uint128_t{ 1 } << 100, uint128_t{ 1 } << 104, 16); + build_region (uint128_t{ 1 } << 104, uint128_t{ 1 } << 108, 16); + build_region (uint128_t{ 1 } << 108, uint128_t{ 1 } << 112, 8); + build_region (uint128_t{ 1 } << 112, uint128_t{ 1 } << 116, 4); + build_region (uint128_t{ 1 } << 116, uint128_t{ 1 } << 120, 2); + minimums.push_back (uint128_t{ 1 } << 120); + + for (auto i = 0; i < minimums.size (); ++i) + { + indices.push_back (i); + } +} + +nano::bucket_index nano::bucketing::bucket_index (nano::amount balance) const +{ + release_assert (!minimums.empty ()); + auto it = std::upper_bound (minimums.begin (), minimums.end (), balance); + release_assert (it != minimums.begin ()); // There should always be a bucket with a minimum_balance of 0 + return std::distance (minimums.begin (), std::prev (it)); +} + +std::vector const & nano::bucketing::bucket_indices () const +{ + return indices; +} + +size_t nano::bucketing::size () const +{ + return minimums.size (); +} \ No newline at end of file diff --git a/nano/node/bucketing.hpp b/nano/node/bucketing.hpp new file mode 100644 index 0000000000..09a5bff03d --- /dev/null +++ b/nano/node/bucketing.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace nano +{ +class bucketing +{ +public: + bucketing (); + + nano::bucket_index bucket_index (nano::amount balance) const; + std::vector const & bucket_indices () const; + size_t size () const; + +private: + std::vector minimums; + std::vector indices; +}; +} \ No newline at end of file diff --git a/nano/node/cli.cpp b/nano/node/cli.cpp index 5ebe67bbf1..c5b29888d2 100644 --- a/nano/node/cli.cpp +++ b/nano/node/cli.cpp @@ -1,9 +1,10 @@ #include #include +#include #include #include -#include #include +#include #include #include #include @@ -100,7 +101,6 @@ void nano::add_node_flag_options (boost::program_options::options_description & ("disable_legacy_bootstrap", "Disables legacy bootstrap") ("disable_wallet_bootstrap", "Disables wallet lazy bootstrap") ("disable_ongoing_bootstrap", "Disable ongoing bootstrap") - ("disable_ascending_bootstrap", "Disable ascending bootstrap") ("disable_rep_crawler", "Disable rep crawler") ("disable_request_loop", "Disable request loop") ("disable_bootstrap_listener", "Disables bootstrap processing for TCP listener (not including realtime network TCP connections)") @@ -138,7 +138,6 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o flags_a.disable_legacy_bootstrap = (vm.count ("disable_legacy_bootstrap") > 0); flags_a.disable_wallet_bootstrap = (vm.count ("disable_wallet_bootstrap") > 0); flags_a.disable_ongoing_bootstrap = (vm.count ("disable_ongoing_bootstrap") > 0); - flags_a.disable_ascending_bootstrap = (vm.count ("disable_ascending_bootstrap") > 0); flags_a.disable_rep_crawler = (vm.count ("disable_rep_crawler") > 0); flags_a.disable_request_loop = (vm.count ("disable_request_loop") > 0); flags_a.disable_bootstrap_bulk_pull_server = (vm.count ("disable_bootstrap_bulk_pull_server") > 0); @@ -648,10 +647,10 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map { auto root_str = root_it->second.as (); auto transaction (node.node->store.tx_begin_write ()); - nano::root root; + nano::qualified_root root; if (!root.decode_hex (root_str)) { - node.node->store.final_vote.clear (transaction, root); + node.node->store.final_vote.del (transaction, root); std::cout << "Successfully cleared final votes" << std::endl; } else diff --git a/nano/node/confirmation_solicitor.cpp b/nano/node/confirmation_solicitor.cpp index 5695f5f8b9..a0a71a0327 100644 --- a/nano/node/confirmation_solicitor.cpp +++ b/nano/node/confirmation_solicitor.cpp @@ -44,12 +44,13 @@ bool nano::confirmation_solicitor::broadcast (nano::election const & election_a) bool const different (exists && existing->second.hash != hash); if (!exists || different) { - i->channel->send (winner); + i->channel->send (winner, nano::transport::traffic_type::block_broadcast); count += different ? 0 : 1; } } // Random flood for block propagation - network.flood_message (winner, nano::transport::buffer_drop_policy::limiter, 0.5f); + // TODO: Avoid broadcasting to the same peers that were already broadcasted to + network.flood_message (winner, nano::transport::traffic_type::block_broadcast, 0.5f); error = false; } return error; @@ -71,9 +72,9 @@ bool nano::confirmation_solicitor::add (nano::election const & election_a) bool const different (exists && existing->second.hash != hash); if (!exists || !is_final || different) { - auto & request_queue (requests[rep.channel]); - if (!rep.channel->max ()) + if (!rep.channel->max (nano::transport::traffic_type::confirmation_requests)) { + auto & request_queue (requests[rep.channel]); request_queue.emplace_back (election_a.status.winner->hash (), election_a.status.winner->root ()); count += different ? 0 : 1; error = false; @@ -101,14 +102,14 @@ void nano::confirmation_solicitor::flush () if (roots_hashes_l.size () == nano::network::confirm_req_hashes_max) { nano::confirm_req req{ config.network_params.network, roots_hashes_l }; - channel->send (req); + channel->send (req, nano::transport::traffic_type::confirmation_requests); roots_hashes_l.clear (); } } if (!roots_hashes_l.empty ()) { nano::confirm_req req{ config.network_params.network, roots_hashes_l }; - channel->send (req); + channel->send (req, nano::transport::traffic_type::confirmation_requests); } } prepared = false; diff --git a/nano/node/confirming_set.cpp b/nano/node/confirming_set.cpp index e504b0acae..db7ca3e6a4 100644 --- a/nano/node/confirming_set.cpp +++ b/nano/node/confirming_set.cpp @@ -1,5 +1,8 @@ +#include #include +#include #include +#include #include #include #include @@ -7,12 +10,13 @@ #include #include -nano::confirming_set::confirming_set (confirming_set_config const & config_a, nano::ledger & ledger_a, nano::stats & stats_a, nano::logger & logger_a) : +nano::confirming_set::confirming_set (confirming_set_config const & config_a, nano::ledger & ledger_a, nano::block_processor & block_processor_a, nano::stats & stats_a, nano::logger & logger_a) : config{ config_a }, ledger{ ledger_a }, + block_processor{ block_processor_a }, stats{ stats_a }, logger{ logger_a }, - notification_workers{ 1, nano::thread_role::name::confirmation_height_notifications } + workers{ 1, nano::thread_role::name::confirmation_height_notifications } { batch_cemented.add ([this] (auto const & cemented) { for (auto const & context : cemented) @@ -20,6 +24,28 @@ nano::confirming_set::confirming_set (confirming_set_config const & config_a, na cemented_observers.notify (context.block); } }); + + // Requeue blocks that failed to cement immediately due to missing ledger blocks + block_processor.batch_processed.add ([this] (auto const & batch) { + bool should_notify = false; + { + std::lock_guard lock{ mutex }; + for (auto const & [result, context] : batch) + { + if (auto it = deferred.get ().find (context.block->hash ()); it != deferred.get ().end ()) + { + stats.inc (nano::stat::type::confirming_set, nano::stat::detail::requeued); + set.push_back (*it); + deferred.get ().erase (it); + should_notify = true; + } + } + } + if (should_notify) + { + condition.notify_all (); + } + }); } nano::confirming_set::~confirming_set () @@ -55,7 +81,7 @@ void nano::confirming_set::start () return; } - notification_workers.start (); + workers.start (); thread = std::thread{ [this] () { nano::thread_role::set (nano::thread_role::name::confirmation_height); @@ -74,19 +100,20 @@ void nano::confirming_set::stop () { thread.join (); } - notification_workers.stop (); + workers.stop (); } bool nano::confirming_set::contains (nano::block_hash const & hash) const { std::lock_guard lock{ mutex }; - return set.get ().contains (hash) || current.contains (hash); + return set.get ().contains (hash) || deferred.get ().contains (hash) || current.contains (hash); } std::size_t nano::confirming_set::size () const { + // Do not report deferred blocks, as they are not currently being processed (and might never be requeued) std::lock_guard lock{ mutex }; - return set.size (); + return set.size () + current.size (); } void nano::confirming_set::run () @@ -96,6 +123,9 @@ void nano::confirming_set::run () { stats.inc (nano::stat::type::confirming_set, nano::stat::detail::loop); + cleanup (lock); + debug_assert (lock.owns_lock ()); + if (!set.empty ()) { run_batch (lock); @@ -136,9 +166,9 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) // Keep track of the blocks we're currently cementing, so that the .contains (...) check is accurate debug_assert (current.empty ()); - for (auto const & [hash, election] : batch) + for (auto const & entry : batch) { - current.insert (hash); + current.insert (entry.hash); } lock.unlock (); @@ -150,7 +180,7 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) std::unique_lock lock{ mutex }; // It's possible that ledger cementing happens faster than the notifications can be processed by other components, cooldown here - while (notification_workers.queued_tasks () >= config.max_queued_notifications) + while (workers.queued_tasks () >= config.max_queued_notifications) { stats.inc (nano::stat::type::confirming_set, nano::stat::detail::cooldown); condition.wait_for (lock, 100ms, [this] { return stopped.load (); }); @@ -160,7 +190,7 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) } } - notification_workers.post ([this, batch = std::move (batch)] () { + workers.post ([this, batch = std::move (batch)] () { stats.inc (nano::stat::type::confirming_set, nano::stat::detail::notify); batch_cemented.notify (batch); }); @@ -179,8 +209,11 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) { auto transaction = ledger.tx_begin_write (nano::store::writer::confirmation_height); - for (auto const & [hash, election] : batch) + for (auto const & entry : batch) { + auto const & hash = entry.hash; + auto const & election = entry.election; + size_t cemented_count = 0; bool success = false; do @@ -235,6 +268,12 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) { stats.inc (nano::stat::type::confirming_set, nano::stat::detail::cementing_failed); logger.debug (nano::log::type::confirming_set, "Failed to cement block: {}", hash.to_string ()); + + // Requeue failed blocks for processing later + // Add them to the deferred set while still holding the exclusive database write transaction to avoid block processor races + lock.lock (); + deferred.push_back (entry); + lock.unlock (); } } } @@ -244,17 +283,59 @@ void nano::confirming_set::run_batch (std::unique_lock & lock) already_cemented.notify (already); + // Clear current set only after the transaction is committed lock.lock (); current.clear (); lock.unlock (); } +void nano::confirming_set::cleanup (std::unique_lock & lock) +{ + debug_assert (lock.owns_lock ()); + debug_assert (!mutex.try_lock ()); + + auto const cutoff = std::chrono::steady_clock::now () - config.deferred_age_cutoff; + std::deque evicted; + + auto should_evict = [&] (entry const & entry) { + return entry.timestamp < cutoff; + }; + + // Iterate in sequenced (insertion) order + for (auto it = deferred.begin (), end = deferred.end (); it != end;) + { + if (should_evict (*it) || deferred.size () > config.max_deferred) + { + stats.inc (nano::stat::type::confirming_set, nano::stat::detail::evicted); + debug_assert (ledger.any.block_exists (ledger.tx_begin_read (), it->hash)); + evicted.push_back (*it); + it = deferred.erase (it); + } + else + { + break; // Entries are sequenced, so we can stop here and avoid unnecessary iteration + } + } + + // Notify about evicted blocks so that other components can perform necessary cleanup + if (!evicted.empty ()) + { + lock.unlock (); + for (auto const & entry : evicted) + { + cementing_failed.notify (entry.hash); + } + lock.lock (); + } +} + nano::container_info nano::confirming_set::container_info () const { std::lock_guard guard{ mutex }; nano::container_info info; info.put ("set", set); - info.add ("notification_workers", notification_workers.container_info ()); + info.put ("deferred", deferred); + info.add ("workers", workers.container_info ()); return info; } diff --git a/nano/node/confirming_set.hpp b/nano/node/confirming_set.hpp index 99569ce1e6..908b08c95e 100644 --- a/nano/node/confirming_set.hpp +++ b/nano/node/confirming_set.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include #include #include #include +#include #include #include @@ -34,6 +36,11 @@ class confirming_set_config final /** Maximum number of dependent blocks to be stored in memory during processing */ size_t max_blocks{ 128 * 1024 }; size_t max_queued_notifications{ 8 }; + + /** Maximum number of failed blocks to wait for requeuing */ + size_t max_deferred{ 16 * 1024 }; + /** Max age of deferred blocks before they are dropped */ + std::chrono::seconds deferred_age_cutoff{ 15min }; }; /** @@ -45,7 +52,7 @@ class confirming_set final friend class confirmation_height_pruned_source_Test; public: - confirming_set (confirming_set_config const &, nano::ledger &, nano::stats &, nano::logger &); + confirming_set (confirming_set_config const &, nano::ledger &, nano::block_processor &, nano::stats &, nano::logger &); ~confirming_set (); void start (); @@ -69,12 +76,14 @@ class confirming_set final nano::observer_set const &> batch_cemented; nano::observer_set const &> already_cemented; + nano::observer_set cementing_failed; nano::observer_set> cemented_observers; private: // Dependencies confirming_set_config const & config; nano::ledger & ledger; + nano::block_processor & block_processor; nano::stats & stats; nano::logger & logger; @@ -83,11 +92,13 @@ class confirming_set final { nano::block_hash hash; std::shared_ptr election; + std::chrono::steady_clock::time_point timestamp{ std::chrono::steady_clock::now () }; }; void run (); void run_batch (std::unique_lock &); std::deque next_batch (size_t max_count); + void cleanup (std::unique_lock &); private: // clang-format off @@ -102,14 +113,18 @@ class confirming_set final >>; // clang-format on + // Blocks that are ready to be cemented ordered_entries set; + // Blocks that could not be cemented immediately (e.g. waiting for rollbacks to complete) + ordered_entries deferred; + // Blocks that are being cemented in the current batch std::unordered_set current; - nano::thread_pool notification_workers; - std::atomic stopped{ false }; mutable std::mutex mutex; std::condition_variable condition; std::thread thread; + + nano::thread_pool workers; }; } diff --git a/nano/node/distributed_work.cpp b/nano/node/distributed_work.cpp index 81d771a5e1..a7cb40598e 100644 --- a/nano/node/distributed_work.cpp +++ b/nano/node/distributed_work.cpp @@ -5,6 +5,7 @@ #include #include +#include std::shared_ptr nano::distributed_work::peer_request::get_prepared_json_request (std::string const & request_string_a) const { diff --git a/nano/node/distributed_work.hpp b/nano/node/distributed_work.hpp index 17edecc60b..4d83e0160a 100644 --- a/nano/node/distributed_work.hpp +++ b/nano/node/distributed_work.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include diff --git a/nano/node/distributed_work_factory.hpp b/nano/node/distributed_work_factory.hpp index 8024bb1841..56daf3b853 100644 --- a/nano/node/distributed_work_factory.hpp +++ b/nano/node/distributed_work_factory.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 06ea41350d..6780acfb14 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -6,9 +6,11 @@ #include #include #include +#include #include #include #include +#include using namespace std::chrono; @@ -26,7 +28,7 @@ nano::election::election (nano::node & node_a, std::shared_ptr cons live_vote_action (live_vote_action_a), node (node_a), behavior_m (election_behavior_a), - status ({ block_a, 0, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::ongoing }), + status (block_a), height (block_a->sideband ().height), root (block_a->root ()), qualified_root (block_a->qualified_root ()) @@ -54,19 +56,28 @@ void nano::election::confirm_once (nano::unique_lock & lock) node.active.recently_confirmed.put (qualified_root, status_l.winner->hash ()); + auto const extended_status = current_status_locked (); + node.stats.inc (nano::stat::type::election, nano::stat::detail::confirm_once); node.logger.trace (nano::log::type::election, nano::log::detail::election_confirmed, nano::log::arg{ "id", id }, nano::log::arg{ "qualified_root", qualified_root }, - nano::log::arg{ "status", current_status_locked () }); + nano::log::arg{ "status", extended_status }); - lock.unlock (); + node.logger.debug (nano::log::type::election, "Election confirmed with winner: {} (behavior: {}, state: {}, voters: {}, blocks: {}, duration: {}ms, confirmation requests: {})", + status_l.winner->hash ().to_string (), + to_string (behavior_m), + to_string (state_m), + extended_status.status.voter_count, + extended_status.status.block_count, + extended_status.status.election_duration.count (), + extended_status.status.confirmation_request_count); - node.election_workers.post ([this_l = shared_from_this (), status_l, confirmation_action_l = confirmation_action] () { - // This is necessary if the winner of the election is one of the forks. - // In that case the winning block is not yet in the ledger and cementing needs to wait for rollbacks to complete. - this_l->node.process_confirmed (status_l.winner->hash (), this_l); + node.confirming_set.add (status_l.winner->hash (), shared_from_this ()); + + lock.unlock (); + node.election_workers.post ([status_l, confirmation_action_l = confirmation_action] () { if (confirmation_action_l) { confirmation_action_l (status_l.winner); @@ -142,7 +153,7 @@ bool nano::election::state_change (nano::election_state expected_a, nano::electi std::chrono::milliseconds nano::election::confirm_req_time () const { - switch (behavior ()) + switch (behavior_m) { case election_behavior::manual: case election_behavior::priority: @@ -173,6 +184,25 @@ void nano::election::transition_active () state_change (nano::election_state::passive, nano::election_state::active); } +bool nano::election::transition_priority () +{ + nano::lock_guard guard{ mutex }; + + if (behavior_m == nano::election_behavior::priority || behavior_m == nano::election_behavior::manual) + { + return false; + } + + behavior_m = nano::election_behavior::priority; + last_vote = std::chrono::steady_clock::time_point{}; // allow new outgoing votes immediately + + node.logger.debug (nano::log::type::election, "Transitioned election behavior to priority from {} for root: {}", + to_string (behavior_m), + qualified_root.to_string ()); + + return true; +} + void nano::election::cancel () { nano::lock_guard guard{ mutex }; @@ -304,7 +334,7 @@ bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a std::chrono::milliseconds nano::election::time_to_live () const { - switch (behavior ()) + switch (behavior_m) { case election_behavior::manual: case election_behavior::priority: @@ -389,7 +419,7 @@ void nano::election::confirm_if_quorum (nano::unique_lock & lock_a) { debug_assert (lock_a.owns_lock ()); auto tally_l (tally_impl ()); - debug_assert (!tally_l.empty ()); + release_assert (!tally_l.empty ()); auto winner (tally_l.begin ()); auto block_l (winner->second); auto const & winner_hash_l (block_l->hash ()); @@ -411,10 +441,13 @@ void nano::election::confirm_if_quorum (nano::unique_lock & lock_a) { if (!is_quorum.exchange (true) && node.config.enable_voting && node.wallets.reps ().voting > 0) { + ++vote_broadcast_count; node.final_generator.add (root, status.winner->hash ()); } if (final_weight >= node.online_reps.delta ()) { + // In some edge cases block might get rolled back while the election is confirming, reprocess it to ensure it's present in the ledger + node.block_processor.add (block_l, nano::block_source::election); confirm_once (lock_a); debug_assert (!lock_a.owns_lock ()); } @@ -540,7 +573,7 @@ bool nano::election::publish (std::shared_ptr const & block_a) if (status.winner->hash () == block_a->hash ()) { status.winner = block_a; - node.network.flood_block (block_a, nano::transport::buffer_drop_policy::no_limiter_drop); + node.network.flood_block (block_a, nano::transport::traffic_type::block_broadcast); } } } @@ -565,6 +598,7 @@ nano::election_extended_status nano::election::current_status_locked () const nano::election_status status_l = status; status_l.confirmation_request_count = confirmation_request_count; + status_l.vote_broadcast_count = vote_broadcast_count; status_l.block_count = nano::narrow_cast (last_blocks.size ()); status_l.voter_count = nano::narrow_cast (last_votes.size ()); return nano::election_extended_status{ status_l, last_votes, last_blocks, tally_impl () }; @@ -594,6 +628,7 @@ void nano::election::broadcast_vote_locked (nano::unique_lock & loc if (node.config.enable_voting && node.wallets.reps ().voting > 0) { node.stats.inc (nano::stat::type::election, nano::stat::detail::broadcast_vote); + ++vote_broadcast_count; if (confirmed_locked () || have_quorum (tally_impl ())) { @@ -759,6 +794,7 @@ std::vector nano::election::votes_with_weight () co nano::election_behavior nano::election::behavior () const { + nano::lock_guard guard{ mutex }; return behavior_m; } @@ -790,6 +826,7 @@ void nano::election_extended_status::operator() (nano::object_stream & obs) cons obs.write ("tally_amount", status.tally.to_string_dec ()); obs.write ("final_tally_amount", status.final_tally.to_string_dec ()); obs.write ("confirmation_request_count", status.confirmation_request_count); + obs.write ("vote_broadcast_count", status.vote_broadcast_count); obs.write ("block_count", status.block_count); obs.write ("voter_count", status.voter_count); obs.write ("type", status.type); diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 3ab73a765f..78e6c5d8d9 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -86,6 +87,7 @@ class election final : public std::enable_shared_from_this public: // State transitions bool transition_time (nano::confirmation_solicitor &); void transition_active (); + bool transition_priority (); void cancel (); public: // Status @@ -95,6 +97,7 @@ class election final : public std::enable_shared_from_this std::shared_ptr winner () const; std::chrono::milliseconds duration () const; std::atomic confirmation_request_count{ 0 }; + std::atomic vote_broadcast_count{ 0 }; nano::tally_t tally () const; bool have_quorum (nano::tally_t const &) const; @@ -179,7 +182,7 @@ class election final : public std::enable_shared_from_this mutable nano::uint128_t final_weight{ 0 }; mutable std::unordered_map last_tally; - nano::election_behavior const behavior_m; + nano::election_behavior behavior_m; std::chrono::steady_clock::time_point const election_start{ std::chrono::steady_clock::now () }; mutable nano::mutex mutex; diff --git a/nano/node/election_status.hpp b/nano/node/election_status.hpp index 548bf8e2bf..d75b7e3a99 100644 --- a/nano/node/election_status.hpp +++ b/nano/node/election_status.hpp @@ -35,8 +35,18 @@ class election_status final std::chrono::milliseconds election_end{ std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()) }; std::chrono::milliseconds election_duration{ std::chrono::duration_values::zero () }; unsigned confirmation_request_count{ 0 }; + unsigned vote_broadcast_count{ 0 }; unsigned block_count{ 0 }; unsigned voter_count{ 0 }; election_status_type type{ nano::election_status_type::inactive_confirmation_height }; + + election_status () = default; + + election_status (std::shared_ptr block_a, election_status_type type_a = nano::election_status_type::ongoing) : + winner (block_a), + type (type_a) + { + block_count = 1; + } }; } diff --git a/nano/node/common.cpp b/nano/node/endpoint.cpp similarity index 93% rename from nano/node/common.cpp rename to nano/node/endpoint.cpp index 5edab1f7f9..891f887282 100644 --- a/nano/node/common.cpp +++ b/nano/node/endpoint.cpp @@ -2,10 +2,11 @@ #include #include #include -#include #include +#include #include #include +#include #include @@ -27,6 +28,12 @@ uint64_t nano::ip_address_hash_raw (boost::asio::ip::address const & ip_a, uint1 return result; } +uint64_t nano::endpoint_hash_raw (nano::endpoint const & endpoint_a) +{ + uint64_t result (nano::ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); + return result; +} + bool nano::parse_port (std::string const & string_a, uint16_t & port_a) { bool result = false; diff --git a/nano/node/common.hpp b/nano/node/endpoint.hpp similarity index 53% rename from nano/node/common.hpp rename to nano/node/endpoint.hpp index 5dd98516df..c1ed629ef3 100644 --- a/nano/node/common.hpp +++ b/nano/node/endpoint.hpp @@ -1,13 +1,15 @@ #pragma once -#include -#include -#include -#include #include -#include +#include #include +#include + +namespace boost::asio::ip +{ +class address; +} namespace nano { @@ -18,16 +20,11 @@ bool parse_endpoint (std::string const &, nano::endpoint &); std::optional parse_endpoint (std::string const &); bool parse_tcp_endpoint (std::string const &, nano::tcp_endpoint &); uint64_t ip_address_hash_raw (boost::asio::ip::address const & ip_a, uint16_t port = 0); +uint64_t endpoint_hash_raw (nano::endpoint const & endpoint_a); } namespace { -uint64_t endpoint_hash_raw (nano::endpoint const & endpoint_a) -{ - uint64_t result (nano::ip_address_hash_raw (endpoint_a.address (), endpoint_a.port ())); - return result; -} - template struct endpoint_hash { @@ -38,7 +35,7 @@ struct endpoint_hash<8> { std::size_t operator() (nano::endpoint const & endpoint_a) const { - return endpoint_hash_raw (endpoint_a); + return nano::endpoint_hash_raw (endpoint_a); } }; @@ -47,7 +44,7 @@ struct endpoint_hash<4> { std::size_t operator() (nano::endpoint const & endpoint_a) const { - uint64_t big (endpoint_hash_raw (endpoint_a)); + uint64_t big = nano::endpoint_hash_raw (endpoint_a); uint32_t result (static_cast (big) ^ static_cast (big >> 32)); return result; } @@ -82,60 +79,19 @@ struct ip_address_hash<4> namespace std { template <> -struct hash<::nano::endpoint> -{ - std::size_t operator() (::nano::endpoint const & endpoint_a) const - { - endpoint_hash ehash; - return ehash (endpoint_a); - } -}; +struct hash<::nano::endpoint>; #ifndef BOOST_ASIO_HAS_STD_HASH template <> -struct hash -{ - std::size_t operator() (boost::asio::ip::address const & ip_a) const - { - ip_address_hash ihash; - return ihash (ip_a); - } -}; +struct hash; #endif } namespace boost { template <> -struct hash<::nano::endpoint> -{ - std::size_t operator() (::nano::endpoint const & endpoint_a) const - { - std::hash<::nano::endpoint> hash; - return hash (endpoint_a); - } -}; +struct hash<::nano::endpoint>; template <> -struct hash -{ - std::size_t operator() (boost::asio::ip::address const & ip_a) const - { - std::hash hash; - return hash (ip_a); - } -}; -} - -namespace nano -{ -/** Helper guard which contains all the necessary purge (remove all memory even if used) functions */ -class node_singleton_memory_pool_purge_guard -{ -public: - node_singleton_memory_pool_purge_guard (); - -private: - nano::cleanup_guard cleanup_guard; -}; +struct hash; } diff --git a/nano/node/endpoint_templ.hpp b/nano/node/endpoint_templ.hpp new file mode 100644 index 0000000000..22d17e9630 --- /dev/null +++ b/nano/node/endpoint_templ.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +namespace std +{ +template <> +struct hash<::nano::endpoint> +{ + std::size_t operator() (::nano::endpoint const & endpoint_a) const + { + endpoint_hash ehash; + return ehash (endpoint_a); + } +}; + +#ifndef BOOST_ASIO_HAS_STD_HASH +template <> +struct hash +{ + std::size_t operator() (boost::asio::ip::address const & ip_a) const + { + ip_address_hash ihash; + return ihash (ip_a); + } +}; +#endif +} + +namespace boost +{ +template <> +struct hash<::nano::endpoint> +{ + std::size_t operator() (::nano::endpoint const & endpoint_a) const + { + std::hash<::nano::endpoint> hash; + return hash (endpoint_a); + } +}; + +template <> +struct hash +{ + std::size_t operator() (boost::asio::ip::address const & ip_a) const + { + std::hash hash; + return hash (ip_a); + } +}; +} diff --git a/nano/node/epoch_upgrader.cpp b/nano/node/epoch_upgrader.cpp index 69d2a50657..1a44e9f244 100644 --- a/nano/node/epoch_upgrader.cpp +++ b/nano/node/epoch_upgrader.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/nano/node/epoch_upgrader.hpp b/nano/node/epoch_upgrader.hpp index d9bccad98e..5cbb9b7b88 100644 --- a/nano/node/epoch_upgrader.hpp +++ b/nano/node/epoch_upgrader.hpp @@ -1,22 +1,18 @@ #pragma once -#include +#include #include #include #include +#include +#include +#include +#include #include namespace nano { -class node; -class ledger; -namespace store -{ - class component; -} -class network_params; - class epoch_upgrader final { public: diff --git a/nano/node/fwd.hpp b/nano/node/fwd.hpp index 7bdcb57665..3a8b9a162b 100644 --- a/nano/node/fwd.hpp +++ b/nano/node/fwd.hpp @@ -1,24 +1,24 @@ #pragma once +#include #include #include #include -// TODO: Move to lib/fwd.hpp -namespace nano -{ -class block; -class container_info; -class thread_pool; -} - namespace nano { +class account_sets_config; class active_elections; +class backlog_scan; class block_processor; +class bounded_backlog; +class bucketing; +class bootstrap_config; +class bootstrap_server; +class bootstrap_service; class confirming_set; class election; -class ledger; +class election_status; class local_block_broadcaster; class local_vote_history; class logger; @@ -33,8 +33,12 @@ class recently_cemented_cache; class recently_confirmed_cache; class rep_crawler; class rep_tiers; +class telemetry; +class unchecked_map; class stats; class vote_cache; +enum class vote_code; +enum class vote_source; class vote_generator; class vote_processor; class vote_router; @@ -49,8 +53,9 @@ enum class vote_code; namespace nano::scheduler { +class component; class hinted; class manual; class optimistic; class priority; -} \ No newline at end of file +} diff --git a/nano/node/ipc/flatbuffers_util.cpp b/nano/node/ipc/flatbuffers_util.cpp index f8265733fe..c4d3a0355f 100644 --- a/nano/node/ipc/flatbuffers_util.cpp +++ b/nano/node/ipc/flatbuffers_util.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/nano/node/ipc/ipc_config.cpp b/nano/node/ipc/ipc_config.cpp index 04d4901b3e..7672615bbd 100644 --- a/nano/node/ipc/ipc_config.cpp +++ b/nano/node/ipc/ipc_config.cpp @@ -1,7 +1,14 @@ +#include #include #include #include +nano::ipc::ipc_config_tcp_socket::ipc_config_tcp_socket (nano::network_constants & network_constants) : + network_constants{ network_constants }, + port{ network_constants.default_ipc_port } +{ +} + nano::error nano::ipc::ipc_config::serialize_toml (nano::tomlconfig & toml) const { nano::tomlconfig tcp_l; diff --git a/nano/node/ipc/ipc_config.hpp b/nano/node/ipc/ipc_config.hpp index 4722177da8..3cdb73bef3 100644 --- a/nano/node/ipc/ipc_config.hpp +++ b/nano/node/ipc/ipc_config.hpp @@ -2,12 +2,12 @@ #include #include +#include #include namespace nano { -class tomlconfig; namespace ipc { /** Base class for transport configurations */ @@ -46,11 +46,7 @@ namespace ipc class ipc_config_tcp_socket : public ipc_config_transport { public: - ipc_config_tcp_socket (nano::network_constants & network_constants) : - network_constants{ network_constants }, - port{ network_constants.default_ipc_port } - { - } + ipc_config_tcp_socket (nano::network_constants & network_constants); nano::network_constants & network_constants; /** Listening port */ uint16_t port; diff --git a/nano/node/ipc/ipc_server.cpp b/nano/node/ipc/ipc_server.cpp index ce711cb392..0db3140e19 100644 --- a/nano/node/ipc/ipc_server.cpp +++ b/nano/node/ipc/ipc_server.cpp @@ -483,7 +483,7 @@ class socket_transport : public nano::ipc::transport acceptor->set_option (option_keepalive); accept (); - runner = std::make_unique (io_ctx, server.logger, static_cast (std::max (1, concurrency_a))); + runner = std::make_unique (io_ctx, server.logger, static_cast (std::max (1, concurrency_a)), nano::thread_role::name::io_ipc); } boost::asio::io_context & context () const diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 288296d052..fcdca6a24c 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1,17 +1,20 @@ +#include #include #include #include +#include #include #include +#include #include -#include -#include -#include +#include #include #include +#include #include #include #include +#include #include #include #include @@ -250,7 +253,6 @@ nano::account_info nano::json_handler::account_info_impl (secure::transaction co if (!info) { ec = nano::error_common::account_not_found; - node.bootstrap_initiator.bootstrap_lazy (account_a, false, account_a.to_account ()); } else { @@ -1195,7 +1197,7 @@ void nano::json_handler::block_confirm () else { // Add record in confirmation history for confirmed block - nano::election_status status{ block_l, 0, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::active_confirmation_height }; + nano::election_status status{ block_l, nano::election_status_type::active_confirmation_height }; node.active.recently_cemented.put (status); // Trigger callback for confirmed block auto account = block_l->account (); @@ -1804,16 +1806,7 @@ void nano::json_handler::bootstrap () uint16_t port; if (!nano::parse_port (port_text, port)) { - if (!node.flags.disable_legacy_bootstrap) - { - std::string bootstrap_id (request.get ("id", "")); - node.bootstrap_initiator.bootstrap (nano::endpoint (address, port), true, bootstrap_id); - response_l.put ("success", ""); - } - else - { - ec = nano::error_rpc::disabled_bootstrap_legacy; - } + ec = nano::error_rpc::disabled_bootstrap_legacy; } else { @@ -1830,22 +1823,7 @@ void nano::json_handler::bootstrap () void nano::json_handler::bootstrap_any () { bool const force = request.get ("force", false); - if (!node.flags.disable_legacy_bootstrap) - { - nano::account start_account{}; - boost::optional account_text (request.get_optional ("account")); - if (account_text.is_initialized ()) - { - start_account = account_impl (account_text.get ()); - } - std::string bootstrap_id (request.get ("id", "")); - node.bootstrap_initiator.bootstrap (force, bootstrap_id, std::numeric_limits::max (), start_account); - response_l.put ("success", ""); - } - else - { - ec = nano::error_rpc::disabled_bootstrap_legacy; - } + ec = nano::error_rpc::disabled_bootstrap_legacy; response_errors (); } @@ -1855,19 +1833,7 @@ void nano::json_handler::bootstrap_lazy () bool const force = request.get ("force", false); if (!ec) { - if (!node.flags.disable_lazy_bootstrap) - { - auto existed (node.bootstrap_initiator.current_lazy_attempt () != nullptr); - std::string bootstrap_id (request.get ("id", "")); - auto key_inserted (node.bootstrap_initiator.bootstrap_lazy (hash, force, bootstrap_id)); - bool started = !existed && key_inserted; - response_l.put ("started", started ? "1" : "0"); - response_l.put ("key_inserted", key_inserted ? "1" : "0"); - } - else - { - ec = nano::error_rpc::disabled_bootstrap_lazy; - } + ec = nano::error_rpc::disabled_bootstrap_lazy; } response_errors (); } @@ -1877,39 +1843,7 @@ void nano::json_handler::bootstrap_lazy () */ void nano::json_handler::bootstrap_status () { - auto attempts_count (node.bootstrap_initiator.attempts.size ()); - response_l.put ("bootstrap_threads", std::to_string (node.config.bootstrap_initiator_threads)); - response_l.put ("running_attempts_count", std::to_string (attempts_count)); - response_l.put ("total_attempts_count", std::to_string (node.bootstrap_initiator.attempts.incremental)); - boost::property_tree::ptree connections; - { - nano::lock_guard connections_lock (node.bootstrap_initiator.connections->mutex); - connections.put ("clients", std::to_string (node.bootstrap_initiator.connections->clients.size ())); - connections.put ("connections", std::to_string (node.bootstrap_initiator.connections->connections_count)); - connections.put ("idle", std::to_string (node.bootstrap_initiator.connections->idle.size ())); - connections.put ("target_connections", std::to_string (node.bootstrap_initiator.connections->target_connections (node.bootstrap_initiator.connections->pulls.size (), attempts_count))); - connections.put ("pulls", std::to_string (node.bootstrap_initiator.connections->pulls.size ())); - } - response_l.add_child ("connections", connections); - boost::property_tree::ptree attempts; - { - nano::lock_guard attempts_lock (node.bootstrap_initiator.attempts.bootstrap_attempts_mutex); - for (auto i : node.bootstrap_initiator.attempts.attempts) - { - boost::property_tree::ptree entry; - auto & attempt (i.second); - entry.put ("id", attempt->id); - entry.put ("mode", attempt->mode_text ()); - entry.put ("started", static_cast (attempt->started)); - entry.put ("pulling", std::to_string (attempt->pulling)); - entry.put ("total_blocks", std::to_string (attempt->total_blocks)); - entry.put ("requeued_pulls", std::to_string (attempt->requeued_pulls)); - attempt->get_information (entry); - entry.put ("duration", std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt->attempt_start).count ()); - attempts.push_back (std::make_pair ("", entry)); - } - } - response_l.add_child ("attempts", attempts); + // TODO: Bootstrap status for ascending bootstrap response_errors (); } @@ -2240,7 +2174,7 @@ void nano::json_handler::delegators () { auto transaction (node.ledger.tx_begin_read ()); boost::property_tree::ptree delegators; - for (auto i (node.store.account.begin (transaction, start_account.number () + 1)), n (node.store.account.end (transaction)); i != n && delegators.size () < count; ++i) + for (auto i (node.store.account.begin (transaction, inc_sat (start_account.number ()))), n (node.store.account.end (transaction)); i != n && delegators.size () < count; ++i) { nano::account_info const & info (i->second); if (info.representative == representative) @@ -3699,7 +3633,7 @@ void nano::json_handler::republish () } hash = node.ledger.any.block_successor (transaction, hash).value_or (0); } - node.network.flood_block_many (std::move (republish_bundle), nullptr, 25); + node.network.flood_block_many (std::move (republish_bundle), nano::transport::traffic_type::block_broadcast_rpc, 25ms); response_l.put ("success", ""); // obsolete response_l.add_child ("blocks", blocks); } @@ -4256,7 +4190,7 @@ void nano::json_handler::unopened () break; } // Skip existing accounts - iterator = node.store.pending.begin (transaction, nano::pending_key (account.number () + 1, 0)); + iterator = node.store.pending.begin (transaction, nano::pending_key (inc_sat (account.number ()), 0)); } else { @@ -4615,7 +4549,7 @@ void nano::json_handler::wallet_frontiers () void nano::json_handler::wallet_history () { - uint64_t modified_since (1); + uint64_t modified_since (0); boost::optional modified_since_text (request.get_optional ("modified_since")); if (modified_since_text.is_initialized ()) { @@ -4934,7 +4868,7 @@ void nano::json_handler::wallet_republish () blocks.push_back (std::make_pair ("", entry)); } } - node.network.flood_block_many (std::move (republish_bundle), nullptr, 25); + node.network.flood_block_many (std::move (republish_bundle), nano::transport::traffic_type::keepalive, 25ms); response_l.add_child ("blocks", blocks); } response_errors (); @@ -5221,7 +5155,7 @@ void nano::json_handler::work_peers_clear () void nano::json_handler::populate_backlog () { - node.backlog.trigger (); + node.backlog_scan.trigger (); response_l.put ("success", ""); response_errors (); } @@ -5230,7 +5164,7 @@ void nano::json_handler::debug_bootstrap_priority_info () { if (!ec) { - auto [blocking, priorities] = node.ascendboot.info (); + auto [blocking, priorities] = node.bootstrap.info (); // priorities { @@ -5249,7 +5183,7 @@ void nano::json_handler::debug_bootstrap_priority_info () boost::property_tree::ptree response_blocking; for (auto const & entry : blocking) { - const auto account = entry.account (); + const auto account = entry.account; const auto dependency = entry.dependency; response_blocking.put (account.to_account (), dependency.to_string ()); diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp index c8ed6b6bd2..5780054871 100644 --- a/nano/node/json_handler.hpp +++ b/nano/node/json_handler.hpp @@ -81,8 +81,6 @@ class json_handler : public std::enable_shared_from_this void key_create (); void key_expand (); void ledger (); - void mnano_to_raw (nano::uint128_t = nano::nano_ratio); - void mnano_from_raw (nano::uint128_t = nano::nano_ratio); void nano_to_raw (); void raw_to_nano (); void node_id (); diff --git a/nano/node/local_block_broadcaster.cpp b/nano/node/local_block_broadcaster.cpp index ad1a2201fe..6248de0042 100644 --- a/nano/node/local_block_broadcaster.cpp +++ b/nano/node/local_block_broadcaster.cpp @@ -1,13 +1,15 @@ #include #include #include -#include +#include #include #include #include #include #include +#include + nano::local_block_broadcaster::local_block_broadcaster (local_block_broadcaster_config const & config_a, nano::node & node_a, nano::block_processor & block_processor_a, nano::network & network_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a, bool enabled_a) : config{ config_a }, node{ node_a }, @@ -54,10 +56,13 @@ nano::local_block_broadcaster::local_block_broadcaster (local_block_broadcaster_ } }); - block_processor.rolled_back.add ([this] (auto const & block) { + block_processor.rolled_back.add ([this] (auto const & blocks, auto const & rollback_root) { nano::lock_guard guard{ mutex }; - auto erased = local_blocks.get ().erase (block->hash ()); - stats.add (nano::stat::type::local_block_broadcaster, nano::stat::detail::rollback, erased); + for (auto const & block : blocks) + { + auto erased = local_blocks.get ().erase (block->hash ()); + stats.add (nano::stat::type::local_block_broadcaster, nano::stat::detail::rollback, erased); + } }); confirming_set.cemented_observers.add ([this] (auto const & block) { @@ -98,6 +103,12 @@ void nano::local_block_broadcaster::stop () nano::join_or_pass (thread); } +bool nano::local_block_broadcaster::contains (nano::block_hash const & hash) const +{ + nano::lock_guard lock{ mutex }; + return local_blocks.get ().contains (hash); +} + size_t nano::local_block_broadcaster::size () const { nano::lock_guard lock{ mutex }; @@ -230,4 +241,4 @@ nano::container_info nano::local_block_broadcaster::container_info () const nano::container_info info; info.put ("local", local_blocks); return info; -} \ No newline at end of file +} diff --git a/nano/node/local_block_broadcaster.hpp b/nano/node/local_block_broadcaster.hpp index d2bed33ce3..7353f39fe5 100644 --- a/nano/node/local_block_broadcaster.hpp +++ b/nano/node/local_block_broadcaster.hpp @@ -1,10 +1,11 @@ #pragma once #include +#include #include #include #include -#include +#include #include #include @@ -58,6 +59,7 @@ class local_block_broadcaster final void start (); void stop (); + bool contains (nano::block_hash const &) const; size_t size () const; nano::container_info container_info () const; diff --git a/nano/node/local_vote_history.hpp b/nano/node/local_vote_history.hpp index 311867e21a..9ee4e38c64 100644 --- a/nano/node/local_vote_history.hpp +++ b/nano/node/local_vote_history.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/nano/node/message_processor.cpp b/nano/node/message_processor.cpp index 469c55bd18..b6828f812e 100644 --- a/nano/node/message_processor.cpp +++ b/nano/node/message_processor.cpp @@ -1,8 +1,9 @@ #include -#include +#include #include #include #include +#include nano::message_processor::message_processor (message_processor_config const & config_a, nano::node & node_a) : config{ config_a }, @@ -170,16 +171,13 @@ class process_visitor : public nano::message_visitor void keepalive (nano::keepalive const & message) override { - // Check for special node port data - auto peer0 (message.peers[0]); - if (peer0.address () == boost::asio::ip::address_v6{} && peer0.port () != 0) + // Check for self reported peering port + auto self_report = message.peers[0]; + if (self_report.address () == boost::asio::ip::address_v6{} && self_report.port () != 0) { - // TODO: Remove this as we do not need to establish a second connection to the same peer - nano::endpoint new_endpoint (channel->get_remote_endpoint ().address (), peer0.port ()); - node.network.merge_peer (new_endpoint); - // Remember this for future forwarding to other peers - channel->set_peering_endpoint (new_endpoint); + nano::endpoint peering_endpoint{ channel->get_remote_endpoint ().address (), self_report.port () }; + channel->set_peering_endpoint (peering_endpoint); } } @@ -267,7 +265,7 @@ class process_visitor : public nano::message_visitor void asc_pull_ack (nano::asc_pull_ack const & message) override { - node.ascendboot.process (message, channel); + node.bootstrap.process (message, channel); } private: @@ -317,4 +315,4 @@ nano::error nano::message_processor_config::deserialize (nano::tomlconfig & toml toml.get ("max_queue", max_queue); return toml.get_error (); -} \ No newline at end of file +} diff --git a/nano/node/messages.cpp b/nano/node/messages.cpp index a2e819cc37..d665ee3f86 100644 --- a/nano/node/messages.cpp +++ b/nano/node/messages.cpp @@ -1,15 +1,18 @@ +#include #include #include #include +#include #include #include #include #include #include -#include #include +#include #include #include +#include #include #include @@ -1873,7 +1876,7 @@ void nano::asc_pull_ack::blocks_payload::serialize (nano::stream & stream) const nano::serialize_block (stream, *block); } // For convenience, end with null block terminator - nano::serialize_block_type (stream, nano::block_type::not_a_block); + nano::write (stream, nano::block_type::not_a_block); } void nano::asc_pull_ack::blocks_payload::deserialize (nano::stream & stream) diff --git a/nano/node/messages.hpp b/nano/node/messages.hpp index 00f1070b0e..2a0e4d0a3c 100644 --- a/nano/node/messages.hpp +++ b/nano/node/messages.hpp @@ -1,19 +1,19 @@ #pragma once +#include #include -#include #include #include -#include +#include #include #include #include #include #include #include -#include -#include +#include #include +#include #include #include @@ -22,6 +22,12 @@ #include #include +namespace nano +{ +using block_uniquer = uniquer; +using vote_uniquer = uniquer; +} + namespace nano { /** @@ -724,7 +730,7 @@ class asc_pull_ack final : public message static frontier deserialize_frontier (nano::stream &); public: // Payload - std::vector frontiers; + std::deque frontiers; public: // Logging void operator() (nano::object_stream &) const; diff --git a/nano/node/monitor.cpp b/nano/node/monitor.cpp index 47b9f8905a..44ce038bbb 100644 --- a/nano/node/monitor.cpp +++ b/nano/node/monitor.cpp @@ -19,7 +19,7 @@ nano::monitor::~monitor () void nano::monitor::start () { - if (!config.enabled) + if (!config.enable) { return; } @@ -115,7 +115,7 @@ void nano::monitor::run_one () nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable periodic node status logging\ntype:bool"); + toml.put ("enable", enable, "Enable or disable periodic node status logging\ntype:bool"); toml.put ("interval", interval.count (), "Interval between status logs\ntype:seconds"); return toml.get_error (); @@ -123,7 +123,7 @@ nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const nano::error nano::monitor_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); auto interval_l = interval.count (); toml.get ("interval", interval_l); interval = std::chrono::seconds{ interval_l }; diff --git a/nano/node/monitor.hpp b/nano/node/monitor.hpp index a76f753bbc..a351297d23 100644 --- a/nano/node/monitor.hpp +++ b/nano/node/monitor.hpp @@ -17,7 +17,7 @@ class monitor_config final nano::error serialize (nano::tomlconfig &) const; public: - bool enabled{ true }; + bool enable{ true }; std::chrono::seconds interval{ 60s }; }; diff --git a/nano/node/network.cpp b/nano/node/network.cpp index b8f95f471c..12ee9718d6 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -19,16 +18,20 @@ std::size_t nano::network::confirm_ack_hashes_max{ 255 }; * network */ -nano::network::network (nano::node & node, uint16_t port) : - config{ node.config.network }, - node{ node }, +nano::network::network (nano::node & node_a, uint16_t port_a) : + config{ node_a.config.network }, + node{ node_a }, id{ nano::network_constants::active_network }, syn_cookies{ node.config.network.max_peers_per_ip, node.logger }, resolver{ node.io_ctx }, filter{ node.config.network.duplicate_filter_size, node.config.network.duplicate_filter_cutoff }, tcp_channels{ node }, - port{ port } + port{ port_a } { + node.observers.channel_connected.add ([this] (std::shared_ptr const & channel) { + node.stats.inc (nano::stat::type::network, nano::stat::detail::connected); + node.logger.debug (nano::log::type::network, "Connected to: {}", channel->to_string ()); + }); } nano::network::~network () @@ -59,6 +62,11 @@ void nano::network::start () run_reachout (); }); } + else + { + node.logger.warn (nano::log::type::network, "Peer reachout is disabled"); + } + if (config.cached_peer_reachout.count () > 0) { reachout_cached_thread = std::thread ([this] () { @@ -66,11 +74,19 @@ void nano::network::start () run_reachout_cached (); }); } + else + { + node.logger.warn (nano::log::type::network, "Cached peer reachout is disabled"); + } if (!node.flags.disable_tcp_realtime) { tcp_channels.start (); } + else + { + node.logger.warn (nano::log::type::network, "Realtime TCP is disabled"); + } } void nano::network::stop () @@ -217,109 +233,113 @@ void nano::network::run_reachout_cached () } } -void nano::network::send_keepalive (std::shared_ptr const & channel_a) +void nano::network::send_keepalive (std::shared_ptr const & channel) const { nano::keepalive message{ node.network_params.network }; random_fill (message.peers); - channel_a->send (message); + channel->send (message, nano::transport::traffic_type::keepalive); } -void nano::network::send_keepalive_self (std::shared_ptr const & channel_a) +void nano::network::send_keepalive_self (std::shared_ptr const & channel) const { nano::keepalive message{ node.network_params.network }; fill_keepalive_self (message.peers); - channel_a->send (message); + channel->send (message, nano::transport::traffic_type::keepalive); } -void nano::network::flood_message (nano::message & message_a, nano::transport::buffer_drop_policy const drop_policy_a, float const scale_a) +void nano::network::flood_message (nano::message const & message, nano::transport::traffic_type type, float scale) const { - for (auto & i : list (fanout (scale_a))) + for (auto const & channel : list (fanout (scale))) { - i->send (message_a, nullptr, drop_policy_a); + channel->send (message, type); } } -void nano::network::flood_keepalive (float const scale_a) +void nano::network::flood_keepalive (float scale) const { nano::keepalive message{ node.network_params.network }; random_fill (message.peers); - flood_message (message, nano::transport::buffer_drop_policy::limiter, scale_a); + flood_message (message, nano::transport::traffic_type::keepalive, scale); } -void nano::network::flood_keepalive_self (float const scale_a) +void nano::network::flood_keepalive_self (float scale) const { nano::keepalive message{ node.network_params.network }; fill_keepalive_self (message.peers); - flood_message (message, nano::transport::buffer_drop_policy::limiter, scale_a); + flood_message (message, nano::transport::traffic_type::keepalive, scale); } -void nano::network::flood_block (std::shared_ptr const & block, nano::transport::buffer_drop_policy const drop_policy) +void nano::network::flood_block (std::shared_ptr const & block, nano::transport::traffic_type type) const { nano::publish message{ node.network_params.network, block }; - flood_message (message, drop_policy); + flood_message (message, type); } -void nano::network::flood_block_initial (std::shared_ptr const & block) +void nano::network::flood_block_initial (std::shared_ptr const & block) const { nano::publish message{ node.network_params.network, block, /* is_originator */ true }; for (auto const & rep : node.rep_crawler.principal_representatives ()) { - rep.channel->send (message, nullptr, nano::transport::buffer_drop_policy::no_limiter_drop); + rep.channel->send (message, nano::transport::traffic_type::block_broadcast_initial); } for (auto & peer : list_non_pr (fanout (1.0))) { - peer->send (message, nullptr, nano::transport::buffer_drop_policy::no_limiter_drop); + peer->send (message, nano::transport::traffic_type::block_broadcast_initial); } } -void nano::network::flood_vote (std::shared_ptr const & vote, float scale, bool rebroadcasted) +void nano::network::flood_vote (std::shared_ptr const & vote, float scale, bool rebroadcasted) const { nano::confirm_ack message{ node.network_params.network, vote, rebroadcasted }; - for (auto & i : list (fanout (scale))) + for (auto & channel : list (fanout (scale))) { - i->send (message, nullptr); + channel->send (message, rebroadcasted ? nano::transport::traffic_type::vote_rebroadcast : nano::transport::traffic_type::vote); } } -void nano::network::flood_vote_non_pr (std::shared_ptr const & vote, float scale, bool rebroadcasted) +void nano::network::flood_vote_non_pr (std::shared_ptr const & vote, float scale, bool rebroadcasted) const { nano::confirm_ack message{ node.network_params.network, vote, rebroadcasted }; - for (auto & i : list_non_pr (fanout (scale))) + for (auto & channel : list_non_pr (fanout (scale))) { - i->send (message, nullptr); + channel->send (message, rebroadcasted ? nano::transport::traffic_type::vote_rebroadcast : nano::transport::traffic_type::vote); } } -void nano::network::flood_vote_pr (std::shared_ptr const & vote, bool rebroadcasted) +void nano::network::flood_vote_pr (std::shared_ptr const & vote, bool rebroadcasted) const { nano::confirm_ack message{ node.network_params.network, vote, rebroadcasted }; - for (auto const & i : node.rep_crawler.principal_representatives ()) + for (auto const & channel : node.rep_crawler.principal_representatives ()) { - i.channel->send (message, nullptr, nano::transport::buffer_drop_policy::no_limiter_drop); + channel.channel->send (message, rebroadcasted ? nano::transport::traffic_type::vote_rebroadcast : nano::transport::traffic_type::vote); } } -void nano::network::flood_block_many (std::deque> blocks_a, std::function callback_a, unsigned delay_a) +void nano::network::flood_block_many (std::deque> blocks, nano::transport::traffic_type type, std::chrono::milliseconds delay, std::function callback) const { - if (!blocks_a.empty ()) + if (blocks.empty ()) { - auto block_l (blocks_a.front ()); - blocks_a.pop_front (); - flood_block (block_l); - if (!blocks_a.empty ()) - { - std::weak_ptr node_w (node.shared ()); - node.workers.post_delayed (std::chrono::milliseconds (delay_a + std::rand () % delay_a), [node_w, blocks (std::move (blocks_a)), callback_a, delay_a] () { - if (auto node_l = node_w.lock ()) - { - node_l->network.flood_block_many (std::move (blocks), callback_a, delay_a); - } - }); - } - else if (callback_a) - { - callback_a (); - } + return; + } + + auto block = blocks.front (); + blocks.pop_front (); + + flood_block (block, type); + + if (!blocks.empty ()) + { + std::weak_ptr node_w (node.shared ()); + node.workers.post_delayed (delay, [node_w, type, blocks = std::move (blocks), delay, callback] () mutable { + if (auto node_l = node_w.lock ()) + { + node_l->network.flood_block_many (std::move (blocks), type, delay, callback); + } + }); + } + else if (callback) + { + callback (); } } @@ -332,17 +352,24 @@ void nano::network::merge_peers (std::array const & peers_a) } } -void nano::network::merge_peer (nano::endpoint const & peer_a) +bool nano::network::merge_peer (nano::endpoint const & peer) { - if (track_reachout (peer_a)) + if (track_reachout (peer)) { node.stats.inc (nano::stat::type::network, nano::stat::detail::merge_peer); - - tcp_channels.start_tcp (peer_a); + node.logger.debug (nano::log::type::network, "Initiating peer merge: {}", fmt::streamed (peer)); + bool started = tcp_channels.start_tcp (peer); + if (!started) + { + node.stats.inc (nano::stat::type::tcp, nano::stat::detail::merge_peer_failed); + node.logger.debug (nano::log::type::network, "Peer merge failed: {}", fmt::streamed (peer)); + } + return started; } + return false; // Not initiated } -bool nano::network::not_a_peer (nano::endpoint const & endpoint_a, bool allow_local_peers) +bool nano::network::not_a_peer (nano::endpoint const & endpoint_a, bool allow_local_peers) const { bool result (false); if (endpoint_a.address ().to_v6 ().is_unspecified ()) @@ -370,32 +397,32 @@ bool nano::network::track_reachout (nano::endpoint const & endpoint_a) return tcp_channels.track_reachout (endpoint_a); } -std::deque> nano::network::list (std::size_t count_a, uint8_t minimum_version_a, bool include_tcp_temporary_channels_a) +std::deque> nano::network::list (std::size_t max_count, uint8_t minimum_version) const { - std::deque> result; - tcp_channels.list (result, minimum_version_a, include_tcp_temporary_channels_a); - nano::random_pool_shuffle (result.begin (), result.end ()); - if (count_a > 0 && result.size () > count_a) + auto result = tcp_channels.list (minimum_version); + nano::random_pool_shuffle (result.begin (), result.end ()); // Randomize returned peer order + if (max_count > 0 && result.size () > max_count) { - result.resize (count_a, nullptr); + result.resize (max_count, nullptr); } return result; } -std::deque> nano::network::list_non_pr (std::size_t count_a) +std::deque> nano::network::list_non_pr (std::size_t max_count, uint8_t minimum_version) const { - std::deque> result; - tcp_channels.list (result); + auto result = tcp_channels.list (minimum_version); auto partition_point = std::partition (result.begin (), result.end (), [this] (std::shared_ptr const & channel) { return !node.rep_crawler.is_pr (channel); }); result.resize (std::distance (result.begin (), partition_point)); - nano::random_pool_shuffle (result.begin (), result.end ()); - if (result.size () > count_a) + + nano::random_pool_shuffle (result.begin (), result.end ()); // Randomize returned peer order + + if (result.size () > max_count) { - result.resize (count_a, nullptr); + result.resize (max_count, nullptr); } return result; } @@ -406,14 +433,14 @@ std::size_t nano::network::fanout (float scale) const return static_cast (std::ceil (scale * size_sqrt ())); } -std::unordered_set> nano::network::random_set (std::size_t count_a, uint8_t min_version_a, bool include_temporary_channels_a) const +std::unordered_set> nano::network::random_set (std::size_t max_count, uint8_t minimum_version) const { - return tcp_channels.random_set (count_a, min_version_a, include_temporary_channels_a); + return tcp_channels.random_set (max_count, minimum_version); } void nano::network::random_fill (std::array & target_a) const { - auto peers (random_set (target_a.size (), 0, false)); // Don't include channels with ephemeral remote ports + auto peers (random_set (target_a.size (), 0)); debug_assert (peers.size () <= target_a.size ()); auto endpoint (nano::endpoint (boost::asio::ip::address_v6{}, 0)); debug_assert (endpoint.address ().is_v6 ()); diff --git a/nano/node/network.hpp b/nano/node/network.hpp index 0ab498ed2b..621e7fdd93 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -90,37 +90,48 @@ class network final void start (); void stop (); - void flood_message (nano::message &, nano::transport::buffer_drop_policy const = nano::transport::buffer_drop_policy::limiter, float const = 1.0f); - void flood_keepalive (float const scale_a = 1.0f); - void flood_keepalive_self (float const scale_a = 0.5f); - void flood_vote (std::shared_ptr const &, float scale, bool rebroadcasted = false); - void flood_vote_pr (std::shared_ptr const &, bool rebroadcasted = false); - void flood_vote_non_pr (std::shared_ptr const &, float scale, bool rebroadcasted = false); + nano::endpoint endpoint () const; + + void flood_message (nano::message const &, nano::transport::traffic_type, float scale = 1.0f) const; + void flood_keepalive (float scale = 1.0f) const; + void flood_keepalive_self (float scale = 0.5f) const; + void flood_vote (std::shared_ptr const &, float scale, bool rebroadcasted = false) const; + void flood_vote_pr (std::shared_ptr const &, bool rebroadcasted = false) const; + void flood_vote_non_pr (std::shared_ptr const &, float scale, bool rebroadcasted = false) const; // Flood block to all PRs and a random selection of non-PRs - void flood_block_initial (std::shared_ptr const &); + void flood_block_initial (std::shared_ptr const &) const; // Flood block to a random selection of peers - void flood_block (std::shared_ptr const &, nano::transport::buffer_drop_policy const = nano::transport::buffer_drop_policy::limiter); - void flood_block_many (std::deque>, std::function = nullptr, unsigned = broadcast_interval_ms); - void merge_peers (std::array const &); - void merge_peer (nano::endpoint const &); - void send_keepalive (std::shared_ptr const &); - void send_keepalive_self (std::shared_ptr const &); + void flood_block (std::shared_ptr const &, nano::transport::traffic_type) const; + void flood_block_many (std::deque>, nano::transport::traffic_type, std::chrono::milliseconds delay = 10ms, std::function callback = nullptr) const; + + void send_keepalive (std::shared_ptr const &) const; + void send_keepalive_self (std::shared_ptr const &) const; + + void merge_peers (std::array const & ips); + bool merge_peer (nano::endpoint const & ip); + std::shared_ptr find_node_id (nano::account const &); std::shared_ptr find_channel (nano::endpoint const &); - bool not_a_peer (nano::endpoint const &, bool allow_local_peers); + + // Check if the endpoint address looks OK + bool not_a_peer (nano::endpoint const &, bool allow_local_peers) const; // Should we reach out to this endpoint with a keepalive message? If yes, register a new reachout attempt bool track_reachout (nano::endpoint const &); - std::deque> list (std::size_t max_count = 0, uint8_t = 0, bool = true); - std::deque> list_non_pr (std::size_t); + + std::deque> list (std::size_t max_count = 0, uint8_t minimum_version = 0) const; + std::deque> list_non_pr (std::size_t max_count, uint8_t minimum_version = 0) const; + // Desired fanout for a given scale std::size_t fanout (float scale = 1.0f) const; + void random_fill (std::array &) const; void fill_keepalive_self (std::array &) const; + // Note: The minimum protocol version is used after the random selection, so number of peers can be less than expected. - std::unordered_set> random_set (std::size_t count, uint8_t min_version = 0, bool include_temporary_channels = false) const; + std::unordered_set> random_set (std::size_t max_count, uint8_t minimum_version = 0) const; + // Get the next peer for attempting a tcp bootstrap connection nano::tcp_endpoint bootstrap_peer (); - nano::endpoint endpoint () const; void cleanup (std::chrono::steady_clock::time_point const & cutoff); std::size_t size () const; float size_sqrt () const; @@ -158,8 +169,6 @@ class network final public: // Callbacks std::function disconnect_observer{ [] () {} }; - // Called when a new channel is observed - std::function)> channel_observer{ [] (auto) {} }; private: std::atomic stopped{ false }; @@ -171,7 +180,6 @@ class network final std::thread reachout_cached_thread; public: - static unsigned const broadcast_interval_ms = 10; static std::size_t const buffer_size = 512; static std::size_t confirm_req_hashes_max; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 42985173d5..b6838515fa 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -1,23 +1,32 @@ +#include #include +#include #include #include #include #include #include +#include #include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -35,6 +44,7 @@ #include #include #include +#include #include #include @@ -50,12 +60,12 @@ double constexpr nano::node::price_max; double constexpr nano::node::free_cutoff; -namespace nano +namespace nano::weights { -extern unsigned char nano_bootstrap_weights_live[]; -extern std::size_t nano_bootstrap_weights_live_size; -extern unsigned char nano_bootstrap_weights_beta[]; -extern std::size_t nano_bootstrap_weights_beta_size; +extern std::vector> preconfigured_weights_live; +extern uint64_t max_blocks_live; +extern std::vector> preconfigured_weights_beta; +extern uint64_t max_blocks_beta; } /* @@ -68,17 +78,22 @@ nano::node::node (std::shared_ptr io_ctx_a, uint16_t pe } nano::node::node (std::shared_ptr io_ctx_a, std::filesystem::path const & application_path_a, nano::node_config const & config_a, nano::work_pool & work_a, nano::node_flags flags_a, unsigned seq) : + application_path{ application_path_a }, node_id{ load_or_create_node_id (application_path_a) }, + node_initialized_latch{ 1 }, config{ config_a }, flags{ flags_a }, + network_params{ config.network_params }, io_ctx_shared{ std::make_shared () }, io_ctx{ *io_ctx_shared }, - logger{ make_logger_identifier (node_id) }, + logger_impl{ std::make_unique (make_logger_identifier (node_id)) }, + logger{ *logger_impl }, + stats_impl{ std::make_unique (logger, config.stats_config) }, + stats{ *stats_impl }, runner_impl{ std::make_unique (io_ctx_shared, logger, config.io_threads) }, runner{ *runner_impl }, - node_initialized_latch (1), - network_params{ config.network_params }, - stats{ logger, config.stats_config }, + observers_impl{ std::make_unique () }, + observers{ *observers_impl }, workers_impl{ std::make_unique (config.background_threads, nano::thread_role::name::worker, /* start immediately */ true) }, workers{ *workers_impl }, bootstrap_workers_impl{ std::make_unique (config.bootstrap_serving_threads, nano::thread_role::name::bootstrap_worker, /* start immediately */ true) }, @@ -87,13 +102,17 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy wallet_workers{ *wallet_workers_impl }, election_workers_impl{ std::make_unique (1, nano::thread_role::name::election_worker, /* start immediately */ true) }, election_workers{ *election_workers_impl }, - work (work_a), - distributed_work (*this), - store_impl (nano::make_store (logger, application_path_a, network_params.ledger, flags.read_only, true, config_a.rocksdb_config, config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_config, config_a.backup_before_upgrade)), - store (*store_impl), - unchecked{ config.max_unchecked_blocks, stats, flags.disable_block_processor_unchecked_deletion }, - wallets_store_impl (std::make_unique (application_path_a / "wallets.ldb", config_a.lmdb_config)), - wallets_store (*wallets_store_impl), + work{ work_a }, + distributed_work_impl{ std::make_unique (*this) }, + distributed_work{ *distributed_work_impl }, + store_impl{ nano::make_store (logger, application_path_a, network_params.ledger, flags.read_only, true, config_a.rocksdb_config, config_a.diagnostics_config.txn_tracking, config_a.block_processor_batch_max_time, config_a.lmdb_config, config_a.backup_before_upgrade) }, + store{ *store_impl }, + unchecked_impl{ std::make_unique (config.max_unchecked_blocks, stats, flags.disable_block_processor_unchecked_deletion) }, + unchecked{ *unchecked_impl }, + wallets_store_impl{ std::make_unique (application_path_a / "wallets.ldb", config_a.lmdb_config) }, + wallets_store{ *wallets_store_impl }, + wallets_impl{ std::make_unique (wallets_store.init_error (), *this) }, + wallets{ *wallets_impl }, ledger_impl{ std::make_unique (store, stats, network_params.ledger, flags_a.generate_cache, config_a.representative_vote_weight_minimum.number ()) }, ledger{ *ledger_impl }, outbound_limiter_impl{ std::make_unique (config) }, @@ -103,11 +122,10 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy // empty `config.peering_port` means the user made no port choice at all; // otherwise, any value is considered, with `0` having the special meaning of 'let the OS pick a port instead' // - network (*this, config.peering_port.has_value () ? *config.peering_port : 0), + network_impl{ std::make_unique (*this, config.peering_port.has_value () ? *config.peering_port : 0) }, + network{ *network_impl }, telemetry_impl{ std::make_unique (flags, *this, network, observers, network_params, stats) }, telemetry{ *telemetry_impl }, - bootstrap_initiator (*this), - bootstrap_server{ config.bootstrap_server, store, ledger, network_params.network, stats }, // BEWARE: `bootstrap` takes `network.port` instead of `config.peering_port` because when the user doesn't specify // a peering port and wants the OS to pick one, the picking happens when `network` gets initialized // (if UDP is active, otherwise it happens when `bootstrap` gets initialized), so then for TCP traffic @@ -117,22 +135,30 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy // tcp_listener_impl{ std::make_unique (network.port, config.tcp, *this) }, tcp_listener{ *tcp_listener_impl }, - application_path (application_path_a), port_mapping_impl{ std::make_unique (*this) }, port_mapping{ *port_mapping_impl }, - block_processor (*this), - confirming_set_impl{ std::make_unique (config.confirming_set, ledger, stats, logger) }, + block_processor_impl{ std::make_unique (config, ledger, unchecked, stats, logger) }, + block_processor{ *block_processor_impl }, + confirming_set_impl{ std::make_unique (config.confirming_set, ledger, block_processor, stats, logger) }, confirming_set{ *confirming_set_impl }, + bucketing_impl{ std::make_unique () }, + bucketing{ *bucketing_impl }, active_impl{ std::make_unique (*this, confirming_set, block_processor) }, active{ *active_impl }, - rep_crawler (config.rep_crawler, *this), - rep_tiers{ ledger, network_params, online_reps, stats, logger }, - warmed_up (0), - online_reps (ledger, config), + online_reps_impl{ std::make_unique (config, ledger, stats, logger) }, + online_reps{ *online_reps_impl }, + rep_crawler_impl{ std::make_unique (config.rep_crawler, *this) }, + rep_crawler{ *rep_crawler_impl }, + rep_tiers_impl{ std::make_unique (ledger, network_params, online_reps, stats, logger) }, + rep_tiers{ *rep_tiers_impl }, history_impl{ std::make_unique (config.network_params.voting) }, history{ *history_impl }, - vote_uniquer{}, - vote_cache{ config.vote_cache, stats }, + block_uniquer_impl{ std::make_unique () }, + block_uniquer{ *block_uniquer_impl }, + vote_uniquer_impl{ std::make_unique () }, + vote_uniquer{ *vote_uniquer_impl }, + vote_cache_impl{ std::make_unique (config.vote_cache, stats) }, + vote_cache{ *vote_cache_impl }, vote_router_impl{ std::make_unique (vote_cache, active.recently_confirmed) }, vote_router{ *vote_router_impl }, vote_processor_impl{ std::make_unique (config.vote_processor, vote_router, observers, stats, flags, logger, online_reps, rep_crawler, ledger, network_params, rep_tiers) }, @@ -143,39 +169,51 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy generator{ *generator_impl }, final_generator_impl{ std::make_unique (config, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* final */ true) }, final_generator{ *final_generator_impl }, - scheduler_impl{ std::make_unique (config, *this, ledger, block_processor, active, online_reps, vote_cache, confirming_set, stats, logger) }, + scheduler_impl{ std::make_unique (config, *this, ledger, bucketing, block_processor, active, online_reps, vote_cache, confirming_set, stats, logger) }, scheduler{ *scheduler_impl }, aggregator_impl{ std::make_unique (config.request_aggregator, *this, stats, generator, final_generator, history, ledger, wallets, vote_router) }, aggregator{ *aggregator_impl }, - wallets (wallets_store.init_error (), *this), - backlog_impl{ std::make_unique (config.backlog_population, scheduler, ledger, stats) }, + backlog_scan_impl{ std::make_unique (config.backlog_scan, ledger, stats) }, + backlog_scan{ *backlog_scan_impl }, + backlog_impl{ std::make_unique (config, *this, ledger, bucketing, backlog_scan, block_processor, confirming_set, stats, logger) }, backlog{ *backlog_impl }, - ascendboot_impl{ std::make_unique (config, block_processor, ledger, network, stats, logger) }, - ascendboot{ *ascendboot_impl }, - websocket{ config.websocket_config, observers, wallets, ledger, io_ctx, logger }, - epoch_upgrader{ *this, ledger, store, network_params, logger }, + bootstrap_server_impl{ std::make_unique (config.bootstrap_server, store, ledger, network_params.network, stats) }, + bootstrap_server{ *bootstrap_server_impl }, + bootstrap_impl{ std::make_unique (config, block_processor, ledger, network, stats, logger) }, + bootstrap{ *bootstrap_impl }, + websocket_impl{ std::make_unique (config.websocket_config, observers, wallets, ledger, io_ctx, logger) }, + websocket{ *websocket_impl }, + epoch_upgrader_impl{ std::make_unique (*this, ledger, store, network_params, logger) }, + epoch_upgrader{ *epoch_upgrader_impl }, local_block_broadcaster_impl{ std::make_unique (config.local_block_broadcaster, *this, block_processor, network, confirming_set, stats, logger, !flags.disable_block_processor_republishing) }, local_block_broadcaster{ *local_block_broadcaster_impl }, - process_live_dispatcher{ ledger, scheduler.priority, vote_cache, websocket }, + process_live_dispatcher_impl{ std::make_unique (ledger, scheduler.priority, vote_cache, websocket) }, + process_live_dispatcher{ *process_live_dispatcher_impl }, peer_history_impl{ std::make_unique (config.peer_history, store, network, logger, stats) }, peer_history{ *peer_history_impl }, monitor_impl{ std::make_unique (config.monitor, *this) }, monitor{ *monitor_impl }, - startup_time (std::chrono::steady_clock::now ()), - node_seq (seq) + startup_time{ std::chrono::steady_clock::now () }, + node_seq{ seq } { logger.debug (nano::log::type::node, "Constructing node..."); process_live_dispatcher.connect (block_processor); - unchecked.satisfied.add ([this] (nano::unchecked_info const & info) { - block_processor.add (info.block, nano::block_source::unchecked); - }); - vote_cache.rep_weight_query = [this] (nano::account const & rep) { return ledger.weight (rep); }; + // TODO: Hook this direclty in the schedulers + backlog_scan.batch_activated.add ([this] (auto const & batch) { + auto transaction = ledger.tx_begin_read (); + for (auto const & info : batch) + { + scheduler.optimistic.activate (info.account, info.account_info, info.conf_info); + scheduler.priority.activate (transaction, info.account, info.account_info, info.conf_info); + } + }); + // Republish vote if it is new and the node does not host a principal representative (or close to) vote_router.vote_processed.add ([this] (std::shared_ptr const & vote, nano::vote_source source, std::unordered_map const & results) { bool processed = std::any_of (results.begin (), results.end (), [] (auto const & result) { @@ -191,15 +229,24 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy } }); + // Do some cleanup due to this block never being processed by confirmation height processor + confirming_set.cementing_failed.add ([this] (auto const & hash) { + active.recently_confirmed.erase (hash); + }); + + // Do some cleanup of rolled back blocks + block_processor.rolled_back.add ([this] (auto const & blocks, auto const & rollback_root) { + for (auto const & block : blocks) + { + history.erase (block->root ()); + } + }); + if (!init_error ()) { wallets.observer = [this] (bool active) { observers.wallet.notify (active); }; - network.channel_observer = [this] (std::shared_ptr const & channel_a) { - debug_assert (channel_a != nullptr); - observers.endpoint.notify (channel_a); - }; network.disconnect_observer = [this] () { observers.disconnect.notify (); }; @@ -210,7 +257,7 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy if ((status_a.type == nano::election_status_type::active_confirmed_quorum || status_a.type == nano::election_status_type::active_confirmation_height)) { auto node_l (shared_from_this ()); - background ([node_l, block_a, account_a, amount_a, is_state_send_a, is_state_epoch_a] () { + io_ctx.post ([node_l, block_a, account_a, amount_a, is_state_send_a, is_state_epoch_a] () { boost::property_tree::ptree event; event.add ("account", account_a.to_account ()); event.add ("hash", block_a->hash ().to_string ()); @@ -264,8 +311,8 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy }); } - observers.endpoint.add ([this] (std::shared_ptr const & channel_a) { - this->network.send_keepalive_self (channel_a); + observers.channel_connected.add ([this] (std::shared_ptr const & channel) { + network.send_keepalive_self (channel); }); observers.vote.add ([this] (std::shared_ptr vote, std::shared_ptr const & channel, nano::vote_source source, nano::vote_code code) { @@ -303,10 +350,13 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy logger.info (nano::log::type::node, "Work pool threads: {} ({})", work.threads.size (), (work.opencl ? "OpenCL" : "CPU")); logger.info (nano::log::type::node, "Work peers: {}", config.work_peers.size ()); logger.info (nano::log::type::node, "Node ID: {}", node_id.pub.to_node_id ()); + logger.info (nano::log::type::node, "Number of buckets: {}", bucketing.size ()); + logger.info (nano::log::type::node, "Genesis block: {}", config.network_params.ledger.genesis->hash ().to_string ()); + logger.info (nano::log::type::node, "Genesis account: {}", config.network_params.ledger.genesis->account ().to_account ()); if (!work_generation_enabled ()) { - logger.info (nano::log::type::node, "Work generation is disabled"); + logger.warn (nano::log::type::node, "Work generation is disabled"); } logger.info (nano::log::type::node, "Outbound bandwidth limit: {} bytes/s, burst ratio: {}", @@ -557,15 +607,9 @@ void nano::node::process_local_async (std::shared_ptr const & block void nano::node::start () { - long_inactivity_cleanup (); - network.start (); message_processor.start (); - if (!flags.disable_legacy_bootstrap && !flags.disable_ongoing_bootstrap) - { - ongoing_bootstrap (); - } if (flags.enable_pruning) { auto this_l (shared ()); @@ -578,10 +622,8 @@ void nano::node::start () rep_crawler.start (); } - ongoing_online_weight_calculation_queue (); - bool tcp_enabled = false; - if (config.tcp_incoming_connections_max > 0 && !(flags.disable_bootstrap_listener && flags.disable_tcp_realtime)) + if (!(flags.disable_bootstrap_listener && flags.disable_tcp_realtime)) { tcp_listener.start (); tcp_enabled = true; @@ -606,14 +648,6 @@ void nano::node::start () { search_receivable_all (); } - if (!flags.disable_wallet_bootstrap) - { - // Delay to start wallet lazy bootstrap - auto this_l (shared ()); - workers.post_delayed (std::chrono::minutes (1), [this_l] () { - this_l->bootstrap_wallet (); - }); - } // Start port mapping if external address is not defined and TCP ports are enabled if (config.external_address == boost::asio::ip::address_v6::any ().to_string () && tcp_enabled) { @@ -631,18 +665,17 @@ void nano::node::start () confirming_set.start (); scheduler.start (); aggregator.start (); + backlog_scan.start (); backlog.start (); bootstrap_server.start (); - if (!flags.disable_ascending_bootstrap) - { - ascendboot.start (); - } + bootstrap.start (); websocket.start (); telemetry.start (); stats.start (); local_block_broadcaster.start (); peer_history.start (); vote_router.start (); + online_reps.start (); monitor.start (); add_initial_peers (); @@ -659,14 +692,15 @@ void nano::node::stop () logger.info (nano::log::type::node, "Node stopping..."); tcp_listener.stop (); - + online_reps.stop (); vote_router.stop (); peer_history.stop (); // Cancels ongoing work generation tasks, which may be blocking other threads // No tasks may wait for work generation in I/O threads, or termination signal capturing will be unable to call node::stop() distributed_work.stop (); + backlog_scan.stop (); + bootstrap.stop (); backlog.stop (); - ascendboot.stop (); rep_crawler.stop (); unchecked.stop (); block_processor.stop (); @@ -682,14 +716,13 @@ void nano::node::stop () telemetry.stop (); websocket.stop (); bootstrap_server.stop (); - bootstrap_initiator.stop (); port_mapping.stop (); wallets.stop (); stats.stop (); epoch_upgrader.stop (); local_block_broadcaster.stop (); message_processor.stop (); - network.stop (); // Stop network last to avoid killing in-use sockets + network.stop (); monitor.stop (); bootstrap_workers.stop (); @@ -700,6 +733,7 @@ void nano::node::stop () // work pool is not stopped on purpose due to testing setup // Stop the IO runner last + runner.abort (); runner.join (); debug_assert (io_ctx_shared.use_count () == 1); // Node should be the last user of the io_context } @@ -755,89 +789,6 @@ nano::uint128_t nano::node::minimum_principal_weight () return online_reps.trended () / network_params.network.principal_weight_factor; } -void nano::node::long_inactivity_cleanup () -{ - bool perform_cleanup = false; - auto const transaction = store.tx_begin_write (); - if (store.online_weight.count (transaction) > 0) - { - auto sample (store.online_weight.rbegin (transaction)); - auto n (store.online_weight.rend (transaction)); - debug_assert (sample != n); - auto const one_week_ago = static_cast ((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.info (nano::log::type::node, "Removed records of peers and online weight after a long period of inactivity"); - } -} - -void nano::node::ongoing_bootstrap () -{ - auto next_wakeup = network_params.network.bootstrap_interval; - if (warmed_up < 3) - { - // Re-attempt bootstrapping more aggressively on startup - next_wakeup = std::chrono::seconds (5); - if (!bootstrap_initiator.in_progress () && !network.empty ()) - { - ++warmed_up; - } - } - if (network_params.network.is_dev_network () && flags.bootstrap_interval != 0) - { - // For test purposes allow faster automatic bootstraps - next_wakeup = std::chrono::seconds (flags.bootstrap_interval); - ++warmed_up; - } - // Differential bootstrap with max age (75% of all legacy attempts) - uint32_t frontiers_age (std::numeric_limits::max ()); - auto bootstrap_weight_reached (ledger.block_count () >= ledger.bootstrap_weight_max_blocks); - auto previous_bootstrap_count (stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out) + stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out)); - /* - - Maximum value for 25% of attempts or if block count is below preconfigured value (initial bootstrap not finished) - - Node shutdown time minus 1 hour for start attempts (warm up) - - Default age value otherwise (1 day for live network, 1 hour for beta) - */ - if (bootstrap_weight_reached) - { - if (warmed_up < 3) - { - // Find last online weight sample (last active time for node) - uint64_t last_sample_time (0); - { - auto transaction = store.tx_begin_read (); - auto last_record = store.online_weight.rbegin (transaction); - if (last_record != store.online_weight.end (transaction)) - { - last_sample_time = last_record->first; - } - } - uint64_t time_since_last_sample = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()).count () - static_cast (last_sample_time / std::pow (10, 9)); // Nanoseconds to seconds - if (time_since_last_sample + 60 * 60 < std::numeric_limits::max ()) - { - frontiers_age = std::max (static_cast (time_since_last_sample + 60 * 60), network_params.bootstrap.default_frontiers_age_seconds); - } - } - else if (previous_bootstrap_count % 4 != 0) - { - frontiers_age = network_params.bootstrap.default_frontiers_age_seconds; - } - } - // Bootstrap and schedule for next attempt - bootstrap_initiator.bootstrap (false, boost::str (boost::format ("auto_bootstrap_%1%") % previous_bootstrap_count), frontiers_age); - std::weak_ptr node_w (shared_from_this ()); - workers.post_delayed (next_wakeup, [node_w] () { - if (auto node_l = node_w.lock ()) - { - node_l->ongoing_bootstrap (); - } - }); -} - void nano::node::backup_wallet () { auto transaction (wallets.tx_begin_read ()); @@ -868,29 +819,6 @@ void nano::node::search_receivable_all () }); } -void nano::node::bootstrap_wallet () -{ - std::deque accounts; - { - nano::lock_guard lock{ wallets.mutex }; - auto const transaction (wallets.tx_begin_read ()); - for (auto i (wallets.items.begin ()), n (wallets.items.end ()); i != n && accounts.size () < 128; ++i) - { - auto & wallet (*i->second); - nano::lock_guard wallet_lock{ wallet.store.mutex }; - for (auto j (wallet.store.begin (transaction)), m (wallet.store.end (transaction)); j != m && accounts.size () < 128; ++j) - { - nano::account account (j->first); - accounts.push_back (account); - } - } - } - if (!accounts.empty ()) - { - bootstrap_initiator.bootstrap_wallet (accounts); - } -} - bool nano::node::collect_ledger_pruning_targets (std::deque & pruning_targets_a, nano::account & last_account_a, uint64_t const batch_read_size_a, uint64_t const max_depth_a, uint64_t const cutoff_time_a) { uint64_t read_operations (0); @@ -934,7 +862,7 @@ bool nano::node::collect_ledger_pruning_targets (std::deque & read_operations += depth; if (read_operations >= batch_read_size_a) { - last_account_a = account.number () + 1; + last_account_a = inc_sat (account.number ()); finish_transaction = true; } else @@ -1130,61 +1058,11 @@ bool nano::node::block_confirmed_or_being_confirmed (nano::block_hash const & ha return block_confirmed_or_being_confirmed (ledger.tx_begin_read (), hash_a); } -void nano::node::ongoing_online_weight_calculation_queue () -{ - std::weak_ptr node_w (shared_from_this ()); - workers.post_delayed ((std::chrono::seconds (network_params.node.weight_period)), [node_w] () { - if (auto node_l = node_w.lock ()) - { - node_l->ongoing_online_weight_calculation (); - } - }); -} - bool nano::node::online () const { return rep_crawler.total_weight () > online_reps.delta (); } -void nano::node::ongoing_online_weight_calculation () -{ - online_reps.sample (); - ongoing_online_weight_calculation_queue (); -} - -// TODO: Replace this with a queue of some sort. Blocks submitted here could be in a limbo for a while: neither part of an active election nor cemented -void nano::node::process_confirmed (nano::block_hash hash, std::shared_ptr election, uint64_t iteration) -{ - stats.inc (nano::stat::type::process_confirmed, nano::stat::detail::initiate); - - // Limit the maximum number of iterations to avoid getting stuck - uint64_t const max_iterations = (config.block_processor_batch_max_time / network_params.node.process_confirmed_interval) * 4; - - if (auto block = ledger.any.block_get (ledger.tx_begin_read (), hash)) - { - stats.inc (nano::stat::type::process_confirmed, nano::stat::detail::done); - logger.trace (nano::log::type::node, nano::log::detail::process_confirmed, nano::log::arg{ "block", block }); - - confirming_set.add (block->hash (), election); - } - else if (iteration < max_iterations) - { - stats.inc (nano::stat::type::process_confirmed, nano::stat::detail::retry); - - // Try again later - election_workers.post_delayed (network_params.node.process_confirmed_interval, [this, hash, election, iteration] () { - process_confirmed (hash, election, iteration + 1); - }); - } - else - { - stats.inc (nano::stat::type::process_confirmed, nano::stat::detail::timeout); - - // Do some cleanup due to this block never being processed by confirmation height processor - active.recently_confirmed.erase (hash); - } -} - std::shared_ptr nano::node::shared () { return shared_from_this (); @@ -1203,30 +1081,17 @@ bool nano::node::init_error () const std::pair> nano::node::get_bootstrap_weights () const { + std::vector> preconfigured_weights = network_params.network.is_live_network () ? nano::weights::preconfigured_weights_live : nano::weights::preconfigured_weights_beta; + uint64_t max_blocks = network_params.network.is_live_network () ? nano::weights::max_blocks_live : nano::weights::max_blocks_beta; std::unordered_map weights; - uint8_t const * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta; - std::size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size; - nano::bufferstream weight_stream ((uint8_t const *)weight_buffer, weight_size); - nano::uint128_union block_height; - uint64_t max_blocks = 0; - if (!nano::try_read (weight_stream, block_height)) + + for (const auto & entry : preconfigured_weights) { - max_blocks = nano::narrow_cast (block_height.number ()); - while (true) - { - nano::account account; - if (nano::try_read (weight_stream, account.bytes)) - { - break; - } - nano::amount weight; - if (nano::try_read (weight_stream, weight.bytes)) - { - break; - } - weights[account] = weight.number (); - } + nano::account account; + account.decode_account (entry.first); + weights[account] = nano::uint128_t (entry.second); } + return { max_blocks, weights }; } @@ -1296,7 +1161,6 @@ nano::container_info nano::node::container_info () const info.add ("work", work.container_info ()); info.add ("ledger", ledger.container_info ()); info.add ("active", active.container_info ()); - info.add ("bootstrap_initiator", bootstrap_initiator.container_info ()); info.add ("tcp_listener", tcp_listener.container_info ()); info.add ("network", network.container_info ()); info.add ("telemetry", telemetry.container_info ()); @@ -1322,11 +1186,14 @@ nano::container_info nano::node::container_info () const info.add ("vote_router", vote_router.container_info ()); info.add ("generator", generator.container_info ()); info.add ("final_generator", final_generator.container_info ()); - info.add ("bootstrap_ascending", ascendboot.container_info ()); + info.add ("bootstrap", bootstrap.container_info ()); info.add ("unchecked", unchecked.container_info ()); info.add ("local_block_broadcaster", local_block_broadcaster.container_info ()); info.add ("rep_tiers", rep_tiers.container_info ()); info.add ("message_processor", message_processor.container_info ()); + info.add ("bandwidth", outbound_limiter.container_info ()); + info.add ("backlog_scan", backlog_scan.container_info ()); + info.add ("bounded_backlog", backlog.container_info ()); return info; } diff --git a/nano/node/node.hpp b/nano/node/node.hpp index ac9a4bb25b..fc6581829a 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -5,10 +5,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -16,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -34,41 +31,6 @@ #include #include -namespace nano -{ -class active_elections; -class backlog_population; -class bandwidth_limiter; -class confirming_set; -class message_processor; -class monitor; -class node; -class telemetry; -class vote_processor; -class vote_cache_processor; -class vote_router; -class work_pool; -class peer_history; -class port_mapping; -class thread_runner; - -namespace scheduler -{ - class component; -} -namespace transport -{ - class tcp_listener; -} -namespace bootstrap_ascending -{ - class service; -} -namespace rocksdb -{ -} // Declare a namespace rocksdb inside nano so all references to the rocksdb library need to be globally scoped e.g. ::rocksdb::Slice -} - namespace nano { class node final : public std::enable_shared_from_this @@ -84,17 +46,10 @@ class node final : public std::enable_shared_from_this std::shared_ptr shared (); - template - void background (T action_a) - { - io_ctx.post (action_a); - } - bool copy_with_compaction (std::filesystem::path const &); void keepalive (std::string const &, uint16_t); int store_version (); void inbound (nano::message const &, std::shared_ptr const &); - void process_confirmed (nano::block_hash, std::shared_ptr = nullptr, uint64_t iteration = 0); void process_active (std::shared_ptr const &); std::optional process_local (std::shared_ptr const &); void process_local_async (std::shared_ptr const &); @@ -104,10 +59,8 @@ class node final : public std::enable_shared_from_this std::pair balance_pending (nano::account const &, bool only_confirmed); nano::uint128_t weight (nano::account const &); nano::uint128_t minimum_principal_weight (); - void ongoing_bootstrap (); void backup_wallet (); void search_receivable_all (); - void bootstrap_wallet (); bool collect_ledger_pruning_targets (std::deque &, nano::account &, uint64_t const, uint64_t const, uint64_t const); void ledger_pruning (uint64_t const, bool); void ongoing_ledger_pruning (); @@ -130,8 +83,6 @@ class node final : public std::enable_shared_from_this bool block_confirmed_or_being_confirmed (nano::block_hash const &); void do_rpc_callback (boost::asio::ip::tcp::resolver::iterator i_a, std::string const &, uint16_t, std::shared_ptr const &, std::shared_ptr const &, std::shared_ptr const &); - void ongoing_online_weight_calculation (); - void ongoing_online_weight_calculation_queue (); bool online () const; bool init_error () const; std::pair> get_bootstrap_weights () const; @@ -145,17 +96,22 @@ class node final : public std::enable_shared_from_this nano::container_info container_info () const; public: + const std::filesystem::path application_path; const nano::keypair node_id; + boost::latch node_initialized_latch; nano::node_config config; nano::node_flags flags; + nano::network_params & network_params; std::shared_ptr io_ctx_shared; boost::asio::io_context & io_ctx; - nano::logger logger; + std::unique_ptr logger_impl; + nano::logger & logger; + std::unique_ptr stats_impl; + nano::stats & stats; std::unique_ptr runner_impl; nano::thread_runner & runner; - boost::latch node_initialized_latch; - nano::network_params & network_params; - nano::stats stats; + std::unique_ptr observers_impl; + nano::node_observers & observers; std::unique_ptr workers_impl; nano::thread_pool & workers; std::unique_ptr bootstrap_workers_impl; @@ -165,43 +121,52 @@ class node final : public std::enable_shared_from_this std::unique_ptr election_workers_impl; nano::thread_pool & election_workers; nano::work_pool & work; - nano::distributed_work_factory distributed_work; + std::unique_ptr distributed_work_impl; + nano::distributed_work_factory & distributed_work; std::unique_ptr store_impl; nano::store::component & store; - nano::unchecked_map unchecked; + std::unique_ptr unchecked_impl; + nano::unchecked_map & unchecked; std::unique_ptr wallets_store_impl; nano::wallets_store & wallets_store; + std::unique_ptr wallets_impl; + nano::wallets & wallets; std::unique_ptr ledger_impl; nano::ledger & ledger; std::unique_ptr outbound_limiter_impl; nano::bandwidth_limiter & outbound_limiter; std::unique_ptr message_processor_impl; nano::message_processor & message_processor; - nano::network network; + std::unique_ptr network_impl; + nano::network & network; std::unique_ptr telemetry_impl; nano::telemetry & telemetry; - nano::bootstrap_initiator bootstrap_initiator; - nano::bootstrap_server bootstrap_server; std::unique_ptr tcp_listener_impl; nano::transport::tcp_listener & tcp_listener; - std::filesystem::path application_path; - nano::node_observers observers; std::unique_ptr port_mapping_impl; nano::port_mapping & port_mapping; - nano::block_processor block_processor; + std::unique_ptr block_processor_impl; + nano::block_processor & block_processor; std::unique_ptr confirming_set_impl; nano::confirming_set & confirming_set; + std::unique_ptr bucketing_impl; + nano::bucketing & bucketing; std::unique_ptr active_impl; nano::active_elections & active; - nano::online_reps online_reps; - nano::rep_crawler rep_crawler; - nano::rep_tiers rep_tiers; - unsigned warmed_up; + std::unique_ptr online_reps_impl; + nano::online_reps & online_reps; + std::unique_ptr rep_crawler_impl; + nano::rep_crawler & rep_crawler; + std::unique_ptr rep_tiers_impl; + nano::rep_tiers & rep_tiers; std::unique_ptr history_impl; nano::local_vote_history & history; - nano::block_uniquer block_uniquer; - nano::vote_uniquer vote_uniquer; - nano::vote_cache vote_cache; + std::unique_ptr block_uniquer_impl; + nano::block_uniquer & block_uniquer; + std::unique_ptr vote_uniquer_impl; + nano::vote_uniquer & vote_uniquer; + std::unique_ptr vote_cache_impl; + nano::vote_cache & vote_cache; std::unique_ptr vote_router_impl; nano::vote_router & vote_router; std::unique_ptr vote_processor_impl; @@ -216,16 +181,22 @@ class node final : public std::enable_shared_from_this nano::scheduler::component & scheduler; std::unique_ptr aggregator_impl; nano::request_aggregator & aggregator; - nano::wallets wallets; - std::unique_ptr backlog_impl; - nano::backlog_population & backlog; - std::unique_ptr ascendboot_impl; - nano::bootstrap_ascending::service & ascendboot; - nano::websocket_server websocket; - nano::epoch_upgrader epoch_upgrader; + std::unique_ptr backlog_scan_impl; + nano::backlog_scan & backlog_scan; + std::unique_ptr backlog_impl; + nano::bounded_backlog & backlog; + std::unique_ptr bootstrap_server_impl; + nano::bootstrap_server & bootstrap_server; + std::unique_ptr bootstrap_impl; + nano::bootstrap_service & bootstrap; + std::unique_ptr websocket_impl; + nano::websocket_server & websocket; + std::unique_ptr epoch_upgrader_impl; + nano::epoch_upgrader & epoch_upgrader; std::unique_ptr local_block_broadcaster_impl; nano::local_block_broadcaster & local_block_broadcaster; - nano::process_live_dispatcher process_live_dispatcher; + std::unique_ptr process_live_dispatcher_impl; + nano::process_live_dispatcher & process_live_dispatcher; std::unique_ptr peer_history_impl; nano::peer_history & peer_history; std::unique_ptr monitor_impl; @@ -238,28 +209,20 @@ class node final : public std::enable_shared_from_this std::atomic stopped{ false }; static double constexpr price_max = 16.0; static double constexpr free_cutoff = 1024.0; - // For tests only - unsigned node_seq; - // For tests only + +public: // For tests only + const unsigned node_seq; std::optional work_generate_blocking (nano::block &); - // For tests only std::optional work_generate_blocking (nano::root const &, uint64_t); - // For tests only std::optional work_generate_blocking (nano::root const &); public: // Testing convenience functions - /** - Creates a new write transaction and inserts `block' and returns result - Transaction is comitted before function return - */ [[nodiscard]] nano::block_status process (std::shared_ptr block); [[nodiscard]] nano::block_status process (secure::write_transaction const &, std::shared_ptr block); nano::block_hash latest (nano::account const &); nano::uint128_t balance (nano::account const &); private: - void long_inactivity_cleanup (); - static std::string make_logger_identifier (nano::keypair const & node_id); }; diff --git a/nano/node/node_observers.cpp b/nano/node/node_observers.cpp index a0bbd03cd7..ed437dd7d5 100644 --- a/nano/node/node_observers.cpp +++ b/nano/node/node_observers.cpp @@ -9,10 +9,10 @@ nano::container_info nano::node_observers::container_info () const info.put ("active_started", active_started.size ()); info.put ("active_stopped", active_stopped.size ()); info.put ("account_balance", account_balance.size ()); - info.put ("endpoint", endpoint.size ()); info.put ("disconnect", disconnect.size ()); info.put ("work_cancel", work_cancel.size ()); info.put ("telemetry", telemetry.size ()); info.put ("socket_connected", socket_connected.size ()); + info.put ("channel_connected", channel_connected.size ()); return info; } diff --git a/nano/node/node_observers.hpp b/nano/node/node_observers.hpp index 1c6d36ed40..af9df9a074 100644 --- a/nano/node/node_observers.hpp +++ b/nano/node/node_observers.hpp @@ -2,21 +2,10 @@ #include #include +#include #include #include -namespace nano -{ -enum class vote_source; -class election_status; -class telemetry; -enum class vote_code; -} -namespace nano::transport -{ -class channel; -} - namespace nano { class node_observers final @@ -29,11 +18,11 @@ class node_observers final nano::observer_set active_started; nano::observer_set active_stopped; nano::observer_set account_balance; - nano::observer_set> endpoint; nano::observer_set<> disconnect; nano::observer_set work_cancel; nano::observer_set const &> telemetry; - nano::observer_set socket_connected; + nano::observer_set> socket_connected; + nano::observer_set> channel_connected; nano::container_info container_info () const; }; diff --git a/nano/node/node_wrapper.cpp b/nano/node/node_wrapper.cpp index 5f4f1a17b9..a49fd759bb 100644 --- a/nano/node/node_wrapper.cpp +++ b/nano/node/node_wrapper.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 5ceb3a6fa4..a6314d9d76 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -125,7 +125,6 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const toml.put ("pow_sleep_interval", pow_sleep_interval.count (), "Time to sleep between batch work generation attempts. Reduces max CPU usage at the expense of a longer generation time.\ntype:nanoseconds"); toml.put ("external_address", external_address, "The external address of this node (NAT). If not set, the node will request this information via UPnP.\ntype:string,ip"); toml.put ("external_port", external_port, "The external port number of this node (NAT). Only used if external_address is set.\ntype:uint16"); - toml.put ("tcp_incoming_connections_max", tcp_incoming_connections_max, "Maximum number of incoming TCP connections.\ntype:uint64"); toml.put ("use_memory_pools", use_memory_pools, "If true, allocate memory from memory pools. Enabling this may improve performance. Memory is never released to the OS.\ntype:bool"); toml.put ("bandwidth_limit", bandwidth_limit, "Outbound traffic limit in bytes/sec after which messages will be dropped.\nNote: changing to unlimited bandwidth (0) is not recommended for limited connections.\ntype:uint64"); @@ -140,6 +139,7 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const 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"); toml.put ("request_aggregator_threads", request_aggregator_threads, "Number of threads to dedicate to request aggregator. Defaults to using all cpu threads, up to a maximum of 4"); toml.put ("max_unchecked_blocks", max_unchecked_blocks, "Maximum number of unchecked blocks to store in memory. Defaults to 65536. \ntype:uint64,[0..]"); + toml.put ("max_backlog", max_backlog, "Maximum number of unconfirmed blocks to keep in the ledger. If this limit is exceeded, the node will start dropping low-priority unconfirmed blocks.\ntype:uint64"); toml.put ("rep_crawler_weight_minimum", rep_crawler_weight_minimum.to_string_dec (), "Rep crawler minimum weight, if this is less than minimum principal weight then this is taken as the minimum weight a rep must have to be tracked. If you want to track all reps set this to 0. If you do not want this to influence anything then set it to max value. This is only useful for debugging or for people who really know what they are doing.\ntype:string,amount,raw"); toml.put ("enable_upnp", enable_upnp, "Enable or disable automatic UPnP port forwarding. This feature only works if the node is directly connected to a router (not inside a docker container, etc.).\ntype:bool"); @@ -214,9 +214,9 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const priority_bucket.serialize (priority_bucket_l); toml.put_child ("priority_bucket", priority_bucket_l); - nano::tomlconfig bootstrap_ascending_l; - bootstrap_ascending.serialize (bootstrap_ascending_l); - toml.put_child ("bootstrap_ascending", bootstrap_ascending_l); + nano::tomlconfig bootstrap_l; + bootstrap.serialize (bootstrap_l); + toml.put_child ("bootstrap", bootstrap_l); nano::tomlconfig bootstrap_server_l; bootstrap_server.serialize (bootstrap_server_l); @@ -246,6 +246,10 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const peer_history.serialize (peer_history_l); toml.put_child ("peer_history", peer_history_l); + nano::tomlconfig tcp_l; + tcp.serialize (tcp_l); + toml.put_child ("tcp", tcp_l); + nano::tomlconfig request_aggregator_l; request_aggregator.serialize (request_aggregator_l); toml.put_child ("request_aggregator", request_aggregator_l); @@ -258,9 +262,13 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const monitor.serialize (monitor_l); toml.put_child ("monitor", monitor_l); - nano::tomlconfig backlog_population_l; - backlog_population.serialize (backlog_population_l); - toml.put_child ("backlog_population", backlog_population_l); + nano::tomlconfig backlog_scan_l; + backlog_scan.serialize (backlog_scan_l); + toml.put_child ("backlog_scan", backlog_scan_l); + + nano::tomlconfig bounded_backlog_l; + bounded_backlog.serialize (bounded_backlog_l); + toml.put_child ("bounded_backlog", bounded_backlog_l); return toml.get_error (); } @@ -329,10 +337,10 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) priority_bucket.deserialize (config_l); } - if (toml.has_key ("bootstrap_ascending")) + if (toml.has_key ("bootstrap")) { - auto config_l = toml.get_required_child ("bootstrap_ascending"); - bootstrap_ascending.deserialize (config_l); + auto config_l = toml.get_required_child ("bootstrap"); + bootstrap.deserialize (config_l); } if (toml.has_key ("bootstrap_server")) @@ -377,6 +385,12 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) peer_history.deserialize (config_l); } + if (toml.has_key ("tcp")) + { + auto config_l = toml.get_required_child ("tcp"); + tcp.deserialize (config_l); + } + if (toml.has_key ("request_aggregator")) { auto config_l = toml.get_required_child ("request_aggregator"); @@ -395,10 +409,16 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) monitor.deserialize (config_l); } - if (toml.has_key ("backlog_population")) + if (toml.has_key ("backlog_scan")) + { + auto config_l = toml.get_required_child ("backlog_scan"); + backlog_scan.deserialize (config_l); + } + + if (toml.has_key ("bounded_backlog")) { - auto config_l = toml.get_required_child ("backlog_population"); - backlog_population.deserialize (config_l); + auto config_l = toml.get_required_child ("bounded_backlog"); + bounded_backlog.deserialize (config_l); } /* @@ -527,7 +547,6 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) toml.get ("external_address", external_address_l); external_address = external_address_l.to_string (); toml.get ("external_port", external_port); - toml.get ("tcp_incoming_connections_max", tcp_incoming_connections_max); auto pow_sleep_interval_l (pow_sleep_interval.count ()); toml.get (pow_sleep_interval_key, pow_sleep_interval_l); @@ -552,6 +571,7 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml) toml.get ("request_aggregator_threads", request_aggregator_threads); toml.get ("max_unchecked_blocks", max_unchecked_blocks); + toml.get ("max_backlog", max_backlog); auto rep_crawler_weight_minimum_l (rep_crawler_weight_minimum.to_string_dec ()); if (toml.has_key ("rep_crawler_weight_minimum")) diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index c25700d852..a981b00ca5 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -9,10 +9,11 @@ #include #include #include -#include -#include +#include +#include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -108,19 +110,17 @@ class node_config std::chrono::seconds tcp_io_timeout{ (network_params.network.is_dev_network () && !is_sanitizer_build ()) ? std::chrono::seconds (5) : std::chrono::seconds (15) }; std::chrono::nanoseconds pow_sleep_interval{ 0 }; - /** Default maximum incoming TCP connections, including realtime network & bootstrap */ - unsigned tcp_incoming_connections_max{ 2048 }; bool use_memory_pools{ true }; static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5); /** Default outbound traffic shaping is 10MB/s */ std::size_t bandwidth_limit{ 10 * 1024 * 1024 }; /** By default, allow bursts of 15MB/s (not sustainable) */ double bandwidth_limit_burst_ratio{ 3. }; - /** Default boostrap outbound traffic limit is 5MB/s */ + /** Default bootstrap outbound traffic limit is 5MB/s */ std::size_t bootstrap_bandwidth_limit{ 5 * 1024 * 1024 }; /** Bootstrap traffic does not need bursts */ double bootstrap_bandwidth_burst_ratio{ 1. }; - nano::bootstrap_ascending_config bootstrap_ascending; + nano::bootstrap_config bootstrap; nano::bootstrap_server_config bootstrap_server; std::chrono::milliseconds confirming_set_batch_time{ 250 }; bool backup_before_upgrade{ false }; @@ -128,11 +128,14 @@ class node_config uint32_t max_queued_requests{ 512 }; unsigned request_aggregator_threads{ std::min (nano::hardware_concurrency (), 4u) }; // Max 4 threads if available unsigned max_unchecked_blocks{ 65536 }; + std::size_t max_backlog{ 100000 }; std::chrono::seconds max_pruning_age{ !network_params.network.is_beta_network () ? std::chrono::seconds (24 * 60 * 60) : std::chrono::seconds (5 * 60) }; // 1 day; 5 minutes for beta network uint64_t max_pruning_depth{ 0 }; nano::rocksdb_config rocksdb_config; nano::lmdb_config lmdb_config; bool enable_upnp{ true }; + +public: nano::vote_cache_config vote_cache; nano::rep_crawler_config rep_crawler; nano::block_processor_config block_processor; @@ -146,7 +149,8 @@ class node_config nano::local_block_broadcaster_config local_block_broadcaster; nano::confirming_set_config confirming_set; nano::monitor_config monitor; - nano::backlog_population_config backlog_population; + nano::backlog_scan_config backlog_scan; + nano::bounded_backlog_config bounded_backlog; public: /** Entry is ignored if it cannot be parsed as a valid address:port */ @@ -171,7 +175,6 @@ class node_flags final bool disable_bootstrap_bulk_pull_server{ false }; bool disable_bootstrap_bulk_push_client{ false }; bool disable_ongoing_bootstrap{ false }; // For testing only - bool disable_ascending_bootstrap{ false }; bool disable_rep_crawler{ false }; bool disable_request_loop{ false }; // For testing only bool disable_tcp_realtime{ false }; diff --git a/nano/node/online_reps.cpp b/nano/node/online_reps.cpp index b049128204..1de0e27c2a 100644 --- a/nano/node/online_reps.cpp +++ b/nano/node/online_reps.cpp @@ -1,105 +1,264 @@ +#include +#include +#include #include #include #include #include #include -nano::online_reps::online_reps (nano::ledger & ledger_a, nano::node_config const & config_a) : +nano::online_reps::online_reps (nano::node_config const & config_a, nano::ledger & ledger_a, nano::stats & stats_a, nano::logger & logger_a) : + config{ config_a }, ledger{ ledger_a }, - config{ config_a } + stats{ stats_a }, + logger{ logger_a } +{ +} + +nano::online_reps::~online_reps () +{ + debug_assert (!thread.joinable ()); +} + +void nano::online_reps::start () +{ + debug_assert (!thread.joinable ()); + + { + auto transaction = ledger.tx_begin_write (nano::store::writer::online_weight); + sanitize_trended (transaction); + + auto trended_l = calculate_trended (transaction); + nano::lock_guard lock{ mutex }; + cached_trended = trended_l; + + logger.info (nano::log::type::online_reps, "Initial trended weight: {}", fmt::streamed (cached_trended)); + } + + thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::online_reps); + run (); + }); +} + +void nano::online_reps::stop () { - if (!ledger.store.init_error ()) { - auto transaction (ledger.store.tx_begin_read ()); - trended_m = calculate_trend (transaction); + nano::lock_guard lock{ mutex }; + stopped = true; + } + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); } } -void nano::online_reps::observe (nano::account const & rep_a) +void nano::online_reps::observe (nano::account const & rep) { - if (ledger.weight (rep_a) > 0) + if (ledger.weight (rep) > config.representative_vote_weight_minimum) { nano::lock_guard lock{ mutex }; + auto now = std::chrono::steady_clock::now (); - auto new_insert = reps.get ().erase (rep_a) == 0; - reps.insert ({ now, rep_a }); - auto cutoff = reps.get ().lower_bound (now - std::chrono::seconds (config.network_params.node.weight_period)); - auto trimmed = reps.get ().begin () != cutoff; - reps.get ().erase (reps.get ().begin (), cutoff); + auto new_insert = reps.get ().erase (rep) == 0; + reps.insert ({ now, rep }); + + stats.inc (nano::stat::type::online_reps, new_insert ? nano::stat::detail::rep_new : nano::stat::detail::rep_update); + + bool trimmed = trim (); + + // Update current online weight if anything changed if (new_insert || trimmed) { - online_m = calculate_online (); + stats.inc (nano::stat::type::online_reps, nano::stat::detail::update_online); + cached_online = calculate_online (); } } } -void nano::online_reps::sample () +bool nano::online_reps::trim () +{ + debug_assert (!mutex.try_lock ()); + + auto now = std::chrono::steady_clock::now (); + auto cutoff = reps.get ().lower_bound (now - config.network_params.node.weight_interval); + auto trimmed = reps.get ().begin () != cutoff; + reps.get ().erase (reps.get ().begin (), cutoff); + return trimmed; +} + +void nano::online_reps::run () { nano::unique_lock lock{ mutex }; - nano::uint128_t online_l = online_m; - lock.unlock (); - nano::uint128_t trend_l; + while (!stopped) { - auto transaction = ledger.store.tx_begin_write (); - // Discard oldest entries - while (ledger.store.online_weight.count (transaction) >= config.network_params.node.max_weight_samples) + // Set next time point explicitly to ensure that we don't sample too early + auto next = std::chrono::steady_clock::now () + config.network_params.node.weight_interval; + condition.wait_until (lock, next, [this, next] { + return stopped || std::chrono::steady_clock::now () >= next; + }); + if (!stopped) { - auto oldest (ledger.store.online_weight.begin (transaction)); - debug_assert (oldest != ledger.store.online_weight.end (transaction)); - ledger.store.online_weight.del (transaction, oldest->first); + lock.unlock (); + sample (); + lock.lock (); } - ledger.store.online_weight.put (transaction, std::chrono::system_clock::now ().time_since_epoch ().count (), online_l); - trend_l = calculate_trend (transaction); } - lock.lock (); - trended_m = trend_l; +} + +void nano::online_reps::sample () +{ + stats.inc (nano::stat::type::online_reps, nano::stat::detail::sample); + + auto transaction = ledger.tx_begin_write (nano::store::writer::online_weight); + + // Remove old records from the database + trim_trended (transaction); + + // Put current online weight sample into the database + ledger.store.online_weight.put (transaction, nano::seconds_since_epoch (), online ()); + + // Update current trended weight + auto trended_l = calculate_trended (transaction); + { + nano::lock_guard lock{ mutex }; + cached_trended = trended_l; + } + logger.info (nano::log::type::online_reps, "Updated trended weight: {}", fmt::streamed (trended_l)); } nano::uint128_t nano::online_reps::calculate_online () const { - nano::uint128_t current; - for (auto & i : reps) + debug_assert (!mutex.try_lock ()); + return std::accumulate (reps.begin (), reps.end (), nano::uint128_t{ 0 }, [this] (nano::uint128_t current, rep_info const & info) { + return current + ledger.weight (info.account); + }); +} + +void nano::online_reps::trim_trended (nano::store::write_transaction const & transaction) +{ + auto const now = std::chrono::system_clock::now (); + auto const cutoff = now - config.network_params.node.weight_cutoff; + + std::deque to_remove; + + for (auto it = ledger.store.online_weight.begin (transaction); it != ledger.store.online_weight.end (transaction); ++it) + { + auto tstamp = nano::from_seconds_since_epoch (it->first); + if (tstamp < cutoff) + { + stats.inc (nano::stat::type::online_reps, nano::stat::detail::trim_trend); + to_remove.push_back (*it); + } + else + { + break; // Entries are ordered by timestamp, so break early + } + } + + // Remove entries after iterating to avoid iterator invalidation + for (auto const & entry : to_remove) + { + ledger.store.online_weight.del (transaction, entry.first); + } + + // Ensure that all remaining entries are within the expected range + debug_assert (verify_consistency (transaction, now, cutoff)); +} + +void nano::online_reps::sanitize_trended (nano::store::write_transaction const & transaction) +{ + auto const now = std::chrono::system_clock::now (); + auto const cutoff = now - config.network_params.node.weight_cutoff; + + size_t removed_old = 0, removed_future = 0; + std::deque to_remove; + + for (auto it = ledger.store.online_weight.begin (transaction); it != ledger.store.online_weight.end (transaction); ++it) { - current += ledger.weight (i.account); + auto tstamp = nano::from_seconds_since_epoch (it->first); + if (tstamp < cutoff) + { + stats.inc (nano::stat::type::online_reps, nano::stat::detail::sanitize_old); + to_remove.push_back (*it); + ++removed_old; + } + else if (tstamp > now) + { + stats.inc (nano::stat::type::online_reps, nano::stat::detail::sanitize_future); + to_remove.push_back (*it); + ++removed_future; + } } - return current; + + // Remove entries after iterating to avoid iterator invalidation + for (auto const & entry : to_remove) + { + ledger.store.online_weight.del (transaction, entry.first); + } + + logger.debug (nano::log::type::online_reps, "Sanitized online weight trend, remaining entries: {}, removed: {} (old: {}, future: {})", + ledger.store.online_weight.count (transaction), + removed_old + removed_future, + removed_old, + removed_future); + + // Ensure that all remaining entries are within the expected range + debug_assert (verify_consistency (transaction, now, cutoff)); } -nano::uint128_t nano::online_reps::calculate_trend (store::transaction & transaction_a) const +bool nano::online_reps::verify_consistency (nano::store::write_transaction const & transaction, std::chrono::system_clock::time_point now, std::chrono::system_clock::time_point cutoff) const +{ + for (auto it = ledger.store.online_weight.begin (transaction); it != ledger.store.online_weight.end (transaction); ++it) + { + auto tstamp = nano::from_seconds_since_epoch (it->first); + if (tstamp < cutoff || tstamp > now) + { + return false; + } + } + return true; +} + +nano::uint128_t nano::online_reps::calculate_trended (nano::store::transaction const & transaction) const { std::vector items; - items.reserve (config.network_params.node.max_weight_samples + 1); - items.push_back (config.online_weight_minimum.number ()); - for (auto i (ledger.store.online_weight.begin (transaction_a)), n (ledger.store.online_weight.end (transaction_a)); i != n; ++i) + for (auto it = ledger.store.online_weight.begin (transaction); it != ledger.store.online_weight.end (transaction); ++it) { - items.push_back (i->second.number ()); + items.push_back (it->second.number ()); } - nano::uint128_t result; - // Pick median value for our target vote weight - auto median_idx = items.size () / 2; - nth_element (items.begin (), items.begin () + median_idx, items.end ()); - result = items[median_idx]; - return result; + if (!items.empty ()) + { + // Pick median value for our target vote weight + auto median_idx = items.size () / 2; + std::nth_element (items.begin (), items.begin () + median_idx, items.end ()); + return items[median_idx]; + } + return 0; } nano::uint128_t nano::online_reps::trended () const { nano::lock_guard lock{ mutex }; - return trended_m; + return std::max (cached_trended, config.online_weight_minimum.number ()); } nano::uint128_t nano::online_reps::online () const { nano::lock_guard lock{ mutex }; - return online_m; + return cached_online; } nano::uint128_t nano::online_reps::delta () const { nano::lock_guard lock{ mutex }; + // Using a larger container to ensure maximum precision - auto weight = static_cast (std::max ({ online_m, trended_m, config.online_weight_minimum.number () })); - return ((weight * online_weight_quorum) / 100).convert_to (); + auto weight = static_cast (std::max ({ cached_online, cached_trended, config.online_weight_minimum.number () })); + auto delta = ((weight * online_weight_quorum) / 100).convert_to (); + release_assert (delta >= config.online_weight_minimum.number () / 100 * online_weight_quorum); + return delta; } std::vector nano::online_reps::list () @@ -114,7 +273,20 @@ void nano::online_reps::clear () { nano::lock_guard lock{ mutex }; reps.clear (); - online_m = 0; + cached_online = 0; +} + +void nano::online_reps::force_online_weight (nano::uint128_t const & online_weight) +{ + release_assert (nano::is_dev_run ()); + nano::lock_guard lock{ mutex }; + cached_online = online_weight; +} + +void nano::online_reps::force_sample () +{ + release_assert (nano::is_dev_run ()); + sample (); } nano::container_info nano::online_reps::container_info () const diff --git a/nano/node/online_reps.hpp b/nano/node/online_reps.hpp index 2838346f3b..2e1d467b52 100644 --- a/nano/node/online_reps.hpp +++ b/nano/node/online_reps.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include @@ -10,26 +12,26 @@ #include #include +#include #include +namespace mi = boost::multi_index; + namespace nano { -class ledger; -class node_config; -namespace store -{ - class transaction; -} - /** Track online representatives and trend online weight */ class online_reps final { public: - online_reps (nano::ledger & ledger_a, nano::node_config const & config_a); + online_reps (nano::node_config const &, nano::ledger &, nano::stats &, nano::logger &); + ~online_reps (); + + void start (); + void stop (); + /** Add voting account \p rep_account to the set of online representatives */ void observe (nano::account const & rep_account); - /** Called periodically to sample online weight */ - void sample (); + /** Returns the trended online stake */ nano::uint128_t trended () const; /** Returns the current online stake */ @@ -42,37 +44,61 @@ class online_reps final nano::container_info container_info () const; public: + // TODO: This should be in the network constants static unsigned constexpr online_weight_quorum = 67; +private: // Dependencies + nano::node_config const & config; + nano::ledger & ledger; + nano::stats & stats; + nano::logger & logger; + private: - class rep_info + void run (); + /** Called periodically to sample online weight */ + void sample (); + bool trim (); + /** Remove old records from the database */ + void trim_trended (nano::store::write_transaction const &); + /** Iterate over all database samples and remove invalid records. This is meant to clean potential leftovers from previous versions. */ + void sanitize_trended (nano::store::write_transaction const &); + + nano::uint128_t calculate_trended (nano::store::transaction const &) const; + nano::uint128_t calculate_online () const; + + bool verify_consistency (nano::store::write_transaction const &, std::chrono::system_clock::time_point now, std::chrono::system_clock::time_point cutoff) const; + +private: + struct rep_info { - public: std::chrono::steady_clock::time_point time; nano::account account; }; - class tag_time - { - }; - class tag_account - { - }; - nano::uint128_t calculate_trend (store::transaction &) const; - nano::uint128_t calculate_online () const; + + // clang-format off + class tag_time {}; + class tag_account {}; + + using ordered_reps = boost::multi_index_container, + mi::member>, + mi::hashed_unique, + mi::member> + >>; + // clang-format off + ordered_reps reps; + + nano::uint128_t cached_trended{0}; + nano::uint128_t cached_online{0}; + + bool stopped{ false }; + nano::condition_variable condition; mutable nano::mutex mutex; - nano::ledger & ledger; - nano::node_config const & config; - boost::multi_index_container, - boost::multi_index::member>, - boost::multi_index::hashed_unique, - boost::multi_index::member>>> - reps; - nano::uint128_t trended_m; - nano::uint128_t online_m; - nano::uint128_t minimum; - - friend class election_quorum_minimum_update_weight_before_quorum_checks_Test; + std::thread thread; + +public: // Only for tests + void force_online_weight (nano::uint128_t const & online_weight); + void force_sample (); }; } diff --git a/nano/node/openclwork.hpp b/nano/node/openclwork.hpp index c73b988440..27660f3c8b 100644 --- a/nano/node/openclwork.hpp +++ b/nano/node/openclwork.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/nano/node/peer_exclusion.hpp b/nano/node/peer_exclusion.hpp index 3100bfe09c..e209d5e638 100644 --- a/nano/node/peer_exclusion.hpp +++ b/nano/node/peer_exclusion.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include #include diff --git a/nano/node/peer_history.cpp b/nano/node/peer_history.cpp index 99b96ed1a8..0065a0057a 100644 --- a/nano/node/peer_history.cpp +++ b/nano/node/peer_history.cpp @@ -104,13 +104,15 @@ void nano::peer_history::run_one () auto const now = std::chrono::system_clock::now (); auto const cutoff = now - config.erase_cutoff; + std::deque to_remove; + for (auto it = store.peer.begin (transaction); it != store.peer.end (transaction); ++it) { auto const [endpoint, timestamp_millis] = *it; auto timestamp = nano::from_milliseconds_since_epoch (timestamp_millis); if (timestamp > now || timestamp < cutoff) { - store.peer.del (transaction, endpoint); + to_remove.push_back (*it); stats.inc (nano::stat::type::peer_history, nano::stat::detail::erased); logger.debug (nano::log::type::peer_history, "Erased peer: {} (not seen for {}s)", @@ -118,6 +120,12 @@ void nano::peer_history::run_one () nano::log::seconds_delta (timestamp)); } } + + // Remove entries after iterating to avoid iterator invalidation + for (auto const & entry : to_remove) + { + store.peer.del (transaction, entry.first); + } } std::vector nano::peer_history::peers () const diff --git a/nano/node/peer_history.hpp b/nano/node/peer_history.hpp index 15d13ffdde..6200446b57 100644 --- a/nano/node/peer_history.hpp +++ b/nano/node/peer_history.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -54,4 +54,4 @@ class peer_history final nano::condition_variable condition; std::thread thread; }; -} \ No newline at end of file +} diff --git a/nano/node/portmapping.cpp b/nano/node/portmapping.cpp index 0dd1605bda..6e5db0329b 100644 --- a/nano/node/portmapping.cpp +++ b/nano/node/portmapping.cpp @@ -7,6 +7,8 @@ #include +#include + std::string nano::mapping_protocol::to_string () { std::stringstream ss; @@ -179,7 +181,7 @@ void nano::port_mapping::refresh_mapping () // We don't map the RPC port because, unless RPC authentication was added, this would almost always be a security risk for (auto & protocol : protocols | boost::adaptors::filtered ([] (auto const & p) { return p.enabled; })) { - auto upnp_description = std::string ("Nano Node (") + node.network_params.network.get_current_network_as_string () + ")"; + auto upnp_description = fmt::format ("Nano Node ({})", node.network_params.network.get_current_network_as_string ()); std::string address_str = address.to_string (); std::string lease_duration_str = std::to_string (node.network_params.portmapping.lease_duration.count ()); diff --git a/nano/node/portmapping.hpp b/nano/node/portmapping.hpp index cc542896ad..437852e945 100644 --- a/nano/node/portmapping.hpp +++ b/nano/node/portmapping.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/nano/node/process_live_dispatcher.cpp b/nano/node/process_live_dispatcher.cpp index 1f00b23249..9e4775bc82 100644 --- a/nano/node/process_live_dispatcher.cpp +++ b/nano/node/process_live_dispatcher.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/nano/node/recently_confirmed_cache.hpp b/nano/node/recently_confirmed_cache.hpp index 803039275e..bdfc95611a 100644 --- a/nano/node/recently_confirmed_cache.hpp +++ b/nano/node/recently_confirmed_cache.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/nano/node/rep_tiers.cpp b/nano/node/rep_tiers.cpp index 6bc532787e..a0eb4fe433 100644 --- a/nano/node/rep_tiers.cpp +++ b/nano/node/rep_tiers.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -149,4 +150,4 @@ nano::container_info nano::rep_tiers::container_info () const nano::stat::detail nano::to_stat_detail (nano::rep_tier tier) { return nano::enum_util::cast (tier); -} \ No newline at end of file +} diff --git a/nano/node/rep_tiers.hpp b/nano/node/rep_tiers.hpp index 71c63ab0df..3c4b54ee11 100644 --- a/nano/node/rep_tiers.hpp +++ b/nano/node/rep_tiers.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -64,4 +65,4 @@ class rep_tiers final mutable nano::mutex mutex; std::thread thread; }; -} \ No newline at end of file +} diff --git a/nano/node/repcrawler.cpp b/nano/node/repcrawler.cpp index b3dc58c405..18523cc951 100644 --- a/nano/node/repcrawler.cpp +++ b/nano/node/repcrawler.cpp @@ -1,7 +1,9 @@ #include #include +#include #include #include +#include #include @@ -13,12 +15,16 @@ nano::rep_crawler::rep_crawler (nano::rep_crawler_config const & config_a, nano: network_constants{ node_a.network_params.network }, active{ node_a.active } { - if (!node.flags.disable_rep_crawler) - { - node.observers.endpoint.add ([this] (std::shared_ptr const & channel) { - query (channel); - }); - } + node.observers.channel_connected.add ([this] (std::shared_ptr const & channel) { + if (!node.flags.disable_rep_crawler) + { + { + nano::lock_guard lock{ mutex }; + prioritized.push_back (channel); + } + condition.notify_all (); + } + }); } nano::rep_crawler::~rep_crawler () @@ -77,7 +83,8 @@ void nano::rep_crawler::validate_and_process (nano::unique_lock & l if (channel->get_type () == nano::transport::transport_type::loopback) { logger.debug (nano::log::type::rep_crawler, "Ignoring vote from loopback channel: {}", channel->to_string ()); - continue; + + continue; // Skip this vote } nano::uint128_t const rep_weight = node.ledger.weight (vote->account); @@ -85,8 +92,9 @@ void nano::rep_crawler::validate_and_process (nano::unique_lock & l { logger.debug (nano::log::type::rep_crawler, "Ignoring vote from account: {} with too little voting weight: {}", vote->account.to_account (), - nano::util::to_str (rep_weight)); - continue; + fmt::streamed (rep_weight)); + + continue; // Skip this vote } // temporary data used for logging after dropping the lock @@ -160,7 +168,7 @@ void nano::rep_crawler::run () lock.lock (); condition.wait_for (lock, query_interval (sufficient_weight), [this, sufficient_weight] { - return stopped || query_predicate (sufficient_weight) || !responses.empty (); + return stopped || query_predicate (sufficient_weight) || !responses.empty () || !prioritized.empty (); }); if (stopped) @@ -179,6 +187,16 @@ void nano::rep_crawler::run () cleanup (); + if (!prioritized.empty ()) + { + decltype (prioritized) prioritized_l; + prioritized_l.swap (prioritized); + + lock.unlock (); + query (prioritized_l); + lock.lock (); + } + if (query_predicate (sufficient_weight)) { last_query = std::chrono::steady_clock::now (); @@ -229,7 +247,7 @@ void nano::rep_crawler::cleanup () }); } -std::vector> nano::rep_crawler::prepare_crawl_targets (bool sufficient_weight) const +std::deque> nano::rep_crawler::prepare_crawl_targets (bool sufficient_weight) const { debug_assert (!mutex.try_lock ()); @@ -245,7 +263,7 @@ std::vector> nano::rep_crawler::prepar // Crawl more aggressively if we lack sufficient total peer weight. auto const required_peer_count = sufficient_weight ? conservative_count : aggressive_count; - auto random_peers = node.network.random_set (required_peer_count, 0, /* include channels with ephemeral remote ports */ true); + auto random_peers = node.network.random_set (required_peer_count); auto should_query = [&, this] (std::shared_ptr const & channel) { if (auto rep = reps.get ().find (channel); rep != reps.get ().end ()) @@ -268,43 +286,22 @@ std::vector> nano::rep_crawler::prepar return { random_peers.begin (), random_peers.end () }; } -auto nano::rep_crawler::prepare_query_target () -> std::optional +auto nano::rep_crawler::prepare_query_target () const -> std::optional { - constexpr int max_attempts = 4; + constexpr int max_attempts = 10; auto transaction = node.ledger.tx_begin_read (); - std::optional> hash_root; - - // Randomly select a block from ledger to request votes for - for (auto i = 0; i < max_attempts && !hash_root; ++i) - { - hash_root = node.ledger.hash_root_random (transaction); - - // Rebroadcasted votes for recently confirmed blocks might confuse the rep crawler - if (active.recently_confirmed.exists (hash_root->first)) - { - hash_root = std::nullopt; - } - } - - if (!hash_root) - { - return std::nullopt; - } - - // Don't send same block multiple times in tests - if (node.network_params.network.is_dev_network ()) + auto random_blocks = node.ledger.random_blocks (transaction, max_attempts); + for (auto const & block : random_blocks) { - nano::lock_guard lock{ mutex }; - - for (auto i = 0; queries.get ().count (hash_root->first) != 0 && i < max_attempts; ++i) + if (!active.recently_confirmed.exists (block->hash ())) { - hash_root = node.ledger.hash_root_random (transaction); + return std::make_pair (block->hash (), block->root ()); } } - return hash_root; + return std::nullopt; } bool nano::rep_crawler::track_rep_request (hash_root_t hash_root, std::shared_ptr const & channel) @@ -330,7 +327,7 @@ bool nano::rep_crawler::track_rep_request (hash_root_t hash_root, std::shared_pt return true; } -void nano::rep_crawler::query (std::vector> const & target_channels) +void nano::rep_crawler::query (std::deque> const & target_channels) { auto maybe_hash_root = prepare_query_target (); if (!maybe_hash_root) @@ -345,8 +342,6 @@ void nano::rep_crawler::query (std::vectorsend ( - req, - [this] (auto & ec, auto size) { - if (ec) - { - stats.inc (nano::stat::type::rep_crawler, nano::stat::detail::write_error, nano::stat::dir::out); - } - }, - nano::transport::buffer_drop_policy::no_socket_drop); + channel->send (req, nano::transport::traffic_type::rep_crawler, [this] (auto & ec, auto size) { + stats.inc (nano::stat::type::rep_crawler_ec, to_stat_detail (ec), nano::stat::dir::out); + }); } else { @@ -376,7 +365,7 @@ void nano::rep_crawler::query (std::vector const & target_channel) { - query (std::vector{ target_channel }); + query (std::deque{ target_channel }); } bool nano::rep_crawler::is_pr (std::shared_ptr const & channel) const @@ -452,6 +441,7 @@ std::vector nano::rep_crawler::representatives (std::size_ } std::vector result; + result.reserve (ordered.size ()); for (auto i = ordered.begin (), n = ordered.end (); i != n && result.size () < count; ++i) { auto const & [weight, rep] = *i; @@ -503,6 +493,7 @@ nano::container_info nano::rep_crawler::container_info () const info.put ("reps", reps); info.put ("queries", queries); info.put ("responses", responses); + info.put ("prioritized", prioritized); return info; } diff --git a/nano/node/repcrawler.hpp b/nano/node/repcrawler.hpp index c7892399e1..070ed95ee4 100644 --- a/nano/node/repcrawler.hpp +++ b/nano/node/repcrawler.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -63,7 +64,7 @@ class rep_crawler final bool process (std::shared_ptr const &, std::shared_ptr const &); /** Attempt to determine if the peer manages one or more representative accounts */ - void query (std::vector> const & target_channels); + void query (std::deque> const & target_channels); /** Attempt to determine if the peer manages one or more representative accounts */ void query (std::shared_ptr const & target_channel); @@ -74,12 +75,10 @@ class rep_crawler final /** Get total available weight from representatives */ nano::uint128_t total_weight () const; - /** Request a list of the top \p count known representatives in descending order of weight, with at least \p weight_a voting weight, and optionally with a minimum version \p minimum_protocol_version - */ + /** Request a list of the top \p count known representatives in descending order of weight, with at least \p weight_a voting weight, and optionally with a minimum version \p minimum_protocol_version */ std::vector representatives (std::size_t count = std::numeric_limits::max (), nano::uint128_t minimum_weight = 0, std::optional const & minimum_protocol_version = {}) const; - /** Request a list of the top \p count known principal representatives in descending order of weight, optionally with a minimum version \p minimum_protocol_version - */ + /** Request a list of the top \p count known principal representatives in descending order of weight, optionally with a minimum version \p minimum_protocol_version */ std::vector principal_representatives (std::size_t count = std::numeric_limits::max (), std::optional const & minimum_protocol_version = {}) const; /** Total number of representatives */ @@ -105,8 +104,9 @@ class rep_crawler final using hash_root_t = std::pair; /** Returns a list of endpoints to crawl. The total weight is passed in to avoid computing it twice. */ - std::vector> prepare_crawl_targets (bool sufficient_weight) const; - std::optional prepare_query_target (); + + std::deque> prepare_crawl_targets (bool sufficient_weight) const; + std::optional prepare_query_target () const; bool track_rep_request (hash_root_t hash_root, std::shared_ptr const & channel); private: @@ -172,9 +172,13 @@ class rep_crawler final private: static size_t constexpr max_responses{ 1024 * 4 }; + using response_t = std::pair, std::shared_ptr>; boost::circular_buffer responses{ max_responses }; + // Freshly established connections that should be queried asap + std::deque> prioritized; + std::chrono::steady_clock::time_point last_query{}; std::atomic stopped{ false }; diff --git a/nano/node/request_aggregator.cpp b/nano/node/request_aggregator.cpp index 8465b7d2b0..8a0c9224f4 100644 --- a/nano/node/request_aggregator.cpp +++ b/nano/node/request_aggregator.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #include #include #include @@ -12,6 +12,7 @@ #include #include #include +#include #include nano::request_aggregator::request_aggregator (request_aggregator_config const & config_a, nano::node & node_a, nano::stats & stats_a, nano::vote_generator & generator_a, nano::vote_generator & final_generator_a, nano::local_vote_history & history_a, nano::ledger & ledger_a, nano::wallets & wallets_a, nano::vote_router & vote_router_a) : @@ -25,13 +26,6 @@ nano::request_aggregator::request_aggregator (request_aggregator_config const & generator (generator_a), final_generator (final_generator_a) { - generator.set_reply_action ([this] (std::shared_ptr const & vote_a, std::shared_ptr const & channel_a) { - this->reply_action (vote_a, channel_a); - }); - final_generator.set_reply_action ([this] (std::shared_ptr const & vote_a, std::shared_ptr const & channel_a) { - this->reply_action (vote_a, channel_a); - }); - queue.max_size_query = [this] (auto const & origin) { return config.max_queue; }; @@ -158,7 +152,7 @@ void nano::request_aggregator::run_batch (nano::unique_lock & lock) transaction.refresh_if_needed (); - if (!channel->max ()) + if (!channel->max (nano::transport::traffic_type::vote_reply)) { process (transaction, request, channel); } @@ -191,12 +185,6 @@ void nano::request_aggregator::process (nano::secure::transaction const & transa } } -void nano::request_aggregator::reply_action (std::shared_ptr const & vote_a, std::shared_ptr const & channel_a) const -{ - nano::confirm_ack confirm{ network_constants, vote_a }; - channel_a->send (confirm); -} - void nano::request_aggregator::erase_duplicates (std::vector> & requests_a) const { std::sort (requests_a.begin (), requests_a.end (), [] (auto const & pair1, auto const & pair2) { @@ -208,68 +196,45 @@ void nano::request_aggregator::erase_duplicates (std::vector const & channel_a) const -> aggregate_result { std::vector> to_generate; std::vector> to_generate_final; for (auto const & [hash, root] : requests_a) { - bool generate_final_vote (false); - std::shared_ptr block; + // Ledger by hash + std::shared_ptr block = ledger.any.block_get (transaction, hash); - // 2. Final votes - auto final_vote_hashes (ledger.store.final_vote.get (transaction, root)); - if (!final_vote_hashes.empty ()) + // Ledger by root + if (!block && !root.is_zero ()) { - generate_final_vote = true; - block = ledger.any.block_get (transaction, final_vote_hashes[0]); - // Allow same root vote - if (block != nullptr && final_vote_hashes.size () > 1) + // Search for block root + if (auto successor = ledger.any.block_successor (transaction, root.as_block_hash ())) { - // WTF? This shouldn't be done like this - to_generate_final.push_back (block); - block = ledger.any.block_get (transaction, final_vote_hashes[1]); - debug_assert (final_vote_hashes.size () == 2); + block = ledger.any.block_get (transaction, successor.value ()); + release_assert (block); } } - // 4. Ledger by hash - if (block == nullptr) - { - block = ledger.any.block_get (transaction, hash); - // Confirmation status. Generate final votes for confirmed - if (block != nullptr) + auto should_generate_final_vote = [&] (auto const & block) { + release_assert (block); + + // Check if final vote is set for this block + if (auto final_hash = ledger.store.final_vote.get (transaction, block->qualified_root ())) { - nano::confirmation_height_info confirmation_height_info; - ledger.store.confirmation_height.get (transaction, block->account (), confirmation_height_info); - generate_final_vote = (confirmation_height_info.height >= block->sideband ().height); + return final_hash == block->hash (); } - } - - // 5. Ledger by root - if (block == nullptr && !root.is_zero ()) - { - // Search for block root - auto successor = ledger.any.block_successor (transaction, root.as_block_hash ()); - if (successor) + // If the final vote is not set, generate vote if the block is confirmed + else { - auto successor_block = ledger.any.block_get (transaction, successor.value ()); - release_assert (successor_block != nullptr); - block = std::move (successor_block); - - // Confirmation status. Generate final votes for confirmed successor - if (block != nullptr) - { - nano::confirmation_height_info confirmation_height_info; - ledger.store.confirmation_height.get (transaction, block->account (), confirmation_height_info); - generate_final_vote = (confirmation_height_info.height >= block->sideband ().height); - } + return ledger.confirmed.block_exists (transaction, block->hash ()); } - } + }; if (block) { - if (generate_final_vote) + if (should_generate_final_vote (block)) { to_generate_final.push_back (block); stats.inc (nano::stat::type::requests, nano::stat::detail::requests_final); diff --git a/nano/node/scheduler/bucket.cpp b/nano/node/scheduler/bucket.cpp index f86fd4bf55..de9083b34f 100644 --- a/nano/node/scheduler/bucket.cpp +++ b/nano/node/scheduler/bucket.cpp @@ -8,9 +8,9 @@ * bucket */ -nano::scheduler::bucket::bucket (nano::uint128_t minimum_balance_a, priority_bucket_config const & config_a, nano::active_elections & active_a, nano::stats & stats_a) : +nano::scheduler::bucket::bucket (nano::bucket_index index_a, priority_bucket_config const & config_a, nano::active_elections & active_a, nano::stats & stats_a) : + index{ index_a }, config{ config_a }, - minimum_balance{ minimum_balance_a }, active{ active_a }, stats{ stats_a } { @@ -34,7 +34,7 @@ bool nano::scheduler::bucket::available () const } } -bool nano::scheduler::bucket::election_vacancy (priority_t candidate) const +bool nano::scheduler::bucket::election_vacancy (nano::priority_timestamp candidate) const { debug_assert (!mutex.try_lock ()); @@ -133,6 +133,12 @@ bool nano::scheduler::bucket::push (uint64_t time, std::shared_ptr return inserted; } +bool nano::scheduler::bucket::contains (nano::block_hash const & hash) const +{ + nano::lock_guard lock{ mutex }; + return queue.get ().contains (hash); +} + size_t nano::scheduler::bucket::size () const { nano::lock_guard lock{ mutex }; @@ -183,20 +189,6 @@ void nano::scheduler::bucket::dump () const } } -/* - * block_entry - */ - -bool nano::scheduler::bucket::block_entry::operator< (block_entry const & other_a) const -{ - return time < other_a.time || (time == other_a.time && block->hash () < other_a.block->hash ()); -} - -bool nano::scheduler::bucket::block_entry::operator== (block_entry const & other_a) const -{ - return time == other_a.time && block->hash () == other_a.block->hash (); -} - /* * priority_bucket_config */ diff --git a/nano/node/scheduler/bucket.hpp b/nano/node/scheduler/bucket.hpp index 668bbec6d6..be82aecf84 100644 --- a/nano/node/scheduler/bucket.hpp +++ b/nano/node/scheduler/bucket.hpp @@ -1,9 +1,13 @@ #pragma once +#include +#include +#include #include #include #include +#include #include #include #include @@ -17,13 +21,6 @@ namespace mi = boost::multi_index; -namespace nano -{ -class election; -class active_elections; -class block; -} - namespace nano::scheduler { class priority_bucket_config final @@ -50,20 +47,19 @@ class priority_bucket_config final class bucket final { public: - using priority_t = uint64_t; + nano::bucket_index const index; public: - bucket (nano::uint128_t minimum_balance, priority_bucket_config const &, nano::active_elections &, nano::stats &); + bucket (nano::bucket_index, priority_bucket_config const &, nano::active_elections &, nano::stats &); ~bucket (); - nano::uint128_t const minimum_balance; - bool available () const; bool activate (); void update (); bool push (uint64_t time, std::shared_ptr block); + bool contains (nano::block_hash const &) const; size_t size () const; size_t election_count () const; bool empty () const; @@ -72,7 +68,7 @@ class bucket final void dump () const; private: - bool election_vacancy (priority_t candidate) const; + bool election_vacancy (nano::priority_timestamp candidate) const; bool election_overfill () const; void cancel_lowest_election (); @@ -87,32 +83,57 @@ class bucket final uint64_t time; std::shared_ptr block; - bool operator< (block_entry const & other_a) const; - bool operator== (block_entry const & other_a) const; + nano::block_hash hash () const + { + return block->hash (); + } + + // Keep operators inlined + bool operator< (block_entry const & other) const + { + return time < other.time || (time == other.time && hash () < other.hash ()); + } + bool operator== (block_entry const & other) const + { + return time == other.time && hash () == other.hash (); + } }; - std::set queue; + // clang-format off + class tag_sequenced {}; + class tag_root {}; + class tag_priority {}; + class tag_hash {}; + // clang-format on + + // clang-format off + using ordered_blocks = boost::multi_index_container, + mi::identity>, + mi::hashed_unique, + mi::const_mem_fun> + >>; + // clang-format on + + ordered_blocks queue; private: // Elections struct election_entry { std::shared_ptr election; nano::qualified_root root; - priority_t priority; + nano::priority_timestamp priority; }; // clang-format off - class tag_sequenced {}; - class tag_root {}; - class tag_priority {}; - using ordered_elections = boost::multi_index_container>, mi::hashed_unique, mi::member>, mi::ordered_non_unique, - mi::member> + mi::member> >>; // clang-format on @@ -121,4 +142,4 @@ class bucket final private: mutable nano::mutex mutex; }; -} // namespace nano::scheduler +} diff --git a/nano/node/scheduler/component.cpp b/nano/node/scheduler/component.cpp index 2d8cf5633a..82b034bd1b 100644 --- a/nano/node/scheduler/component.cpp +++ b/nano/node/scheduler/component.cpp @@ -5,11 +5,11 @@ #include #include -nano::scheduler::component::component (nano::node_config & node_config, nano::node & node, nano::ledger & ledger, nano::block_processor & block_processor, nano::active_elections & active, nano::online_reps & online_reps, nano::vote_cache & vote_cache, nano::confirming_set & confirming_set, nano::stats & stats, nano::logger & logger) : +nano::scheduler::component::component (nano::node_config & node_config, nano::node & node, nano::ledger & ledger, nano::bucketing & bucketing, nano::block_processor & block_processor, nano::active_elections & active, nano::online_reps & online_reps, nano::vote_cache & vote_cache, nano::confirming_set & confirming_set, nano::stats & stats, nano::logger & logger) : hinted_impl{ std::make_unique (node_config.hinted_scheduler, node, vote_cache, active, online_reps, stats) }, manual_impl{ std::make_unique (node) }, optimistic_impl{ std::make_unique (node_config.optimistic_scheduler, node, ledger, active, node_config.network_params.network, stats) }, - priority_impl{ std::make_unique (node_config, node, ledger, block_processor, active, confirming_set, stats, logger) }, + priority_impl{ std::make_unique (node_config, node, ledger, bucketing, block_processor, active, confirming_set, stats, logger) }, hinted{ *hinted_impl }, manual{ *manual_impl }, optimistic{ *optimistic_impl }, @@ -43,6 +43,11 @@ void nano::scheduler::component::stop () priority.stop (); } +bool nano::scheduler::component::contains (nano::block_hash const & hash) const +{ + return manual.contains (hash) || priority.contains (hash); +} + nano::container_info nano::scheduler::component::container_info () const { nano::container_info info; diff --git a/nano/node/scheduler/component.hpp b/nano/node/scheduler/component.hpp index 96de3d94ae..97373577d2 100644 --- a/nano/node/scheduler/component.hpp +++ b/nano/node/scheduler/component.hpp @@ -10,12 +10,15 @@ namespace nano::scheduler class component final { public: - component (nano::node_config &, nano::node &, nano::ledger &, nano::block_processor &, nano::active_elections &, nano::online_reps &, nano::vote_cache &, nano::confirming_set &, nano::stats &, nano::logger &); + component (nano::node_config &, nano::node &, nano::ledger &, nano::bucketing &, nano::block_processor &, nano::active_elections &, nano::online_reps &, nano::vote_cache &, nano::confirming_set &, nano::stats &, nano::logger &); ~component (); void start (); void stop (); + /// Does the block exist in any of the schedulers + bool contains (nano::block_hash const & hash) const; + nano::container_info container_info () const; private: diff --git a/nano/node/scheduler/hinted.cpp b/nano/node/scheduler/hinted.cpp index 47e1ff5ae8..15b8fa5301 100644 --- a/nano/node/scheduler/hinted.cpp +++ b/nano/node/scheduler/hinted.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,7 @@ void nano::scheduler::hinted::start () { debug_assert (!thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } @@ -260,7 +261,7 @@ nano::scheduler::hinted_config::hinted_config (nano::network_constants const & n nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable hinted elections\ntype:bool"); + toml.put ("enable", enable, "Enable or disable hinted elections\ntype:bool"); toml.put ("hinting_threshold", hinting_threshold_percent, "Percentage of online weight needed to start a hinted election. \ntype:uint32,[0,100]"); toml.put ("check_interval", check_interval.count (), "Interval between scans of the vote cache for possible hinted elections. \ntype:milliseconds"); toml.put ("block_cooldown", block_cooldown.count (), "Cooldown period for blocks that failed to start an election. \ntype:milliseconds"); @@ -271,7 +272,7 @@ nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml) nano::error nano::scheduler::hinted_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); toml.get ("hinting_threshold", hinting_threshold_percent); auto check_interval_l = check_interval.count (); diff --git a/nano/node/scheduler/hinted.hpp b/nano/node/scheduler/hinted.hpp index 7cae2f999e..0976472494 100644 --- a/nano/node/scheduler/hinted.hpp +++ b/nano/node/scheduler/hinted.hpp @@ -28,7 +28,7 @@ class hinted_config final nano::error serialize (nano::tomlconfig & toml) const; public: - bool enabled{ true }; + bool enable{ true }; std::chrono::milliseconds check_interval{ 1000 }; std::chrono::milliseconds block_cooldown{ 10000 }; unsigned hinting_threshold_percent{ 10 }; diff --git a/nano/node/scheduler/manual.cpp b/nano/node/scheduler/manual.cpp index 5b1bec4f03..f2fa3d1e29 100644 --- a/nano/node/scheduler/manual.cpp +++ b/nano/node/scheduler/manual.cpp @@ -46,6 +46,14 @@ void nano::scheduler::manual::push (std::shared_ptr const & block_a notify (); } +bool nano::scheduler::manual::contains (nano::block_hash const & hash) const +{ + nano::lock_guard lock{ mutex }; + return std::any_of (queue.cbegin (), queue.cend (), [&hash] (auto const & item) { + return std::get<0> (item)->hash () == hash; + }); +} + bool nano::scheduler::manual::predicate () const { return !queue.empty (); diff --git a/nano/node/scheduler/manual.hpp b/nano/node/scheduler/manual.hpp index 9fe527e50f..c62f215bd5 100644 --- a/nano/node/scheduler/manual.hpp +++ b/nano/node/scheduler/manual.hpp @@ -37,6 +37,8 @@ class manual final // Call action with confirmed block, may be different than what we started with void push (std::shared_ptr const &, boost::optional const & = boost::none); + bool contains (nano::block_hash const &) const; + nano::container_info container_info () const; }; } diff --git a/nano/node/scheduler/optimistic.cpp b/nano/node/scheduler/optimistic.cpp index e2d95b0211..caab43c70e 100644 --- a/nano/node/scheduler/optimistic.cpp +++ b/nano/node/scheduler/optimistic.cpp @@ -29,7 +29,7 @@ void nano::scheduler::optimistic::start () { debug_assert (!thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } @@ -72,7 +72,7 @@ bool nano::scheduler::optimistic::activate_predicate (const nano::account_info & bool nano::scheduler::optimistic::activate (const nano::account & account, const nano::account_info & account_info, const nano::confirmation_height_info & conf_info) { - if (!config.enabled) + if (!config.enable) { return false; } @@ -183,7 +183,7 @@ nano::container_info nano::scheduler::optimistic::container_info () const nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); toml.get ("gap_threshold", gap_threshold); toml.get ("max_size", max_size); @@ -192,7 +192,7 @@ nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & nano::error nano::scheduler::optimistic_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable optimistic elections\ntype:bool"); + toml.put ("enable", enable, "Enable or disable optimistic elections\ntype:bool"); toml.put ("gap_threshold", gap_threshold, "Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation\ntype:uint64"); toml.put ("max_size", max_size, "Maximum number of candidates stored in memory\ntype:uint64"); diff --git a/nano/node/scheduler/optimistic.hpp b/nano/node/scheduler/optimistic.hpp index bcf3e3ca67..24edd1e73e 100644 --- a/nano/node/scheduler/optimistic.hpp +++ b/nano/node/scheduler/optimistic.hpp @@ -30,7 +30,7 @@ class optimistic_config final nano::error serialize (nano::tomlconfig & toml) const; public: - bool enabled{ true }; + bool enable{ true }; /** Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation */ std::size_t gap_threshold{ 32 }; diff --git a/nano/node/scheduler/priority.cpp b/nano/node/scheduler/priority.cpp index 4063ce60d3..566c9511f3 100644 --- a/nano/node/scheduler/priority.cpp +++ b/nano/node/scheduler/priority.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,44 +8,20 @@ #include #include -nano::scheduler::priority::priority (nano::node_config & node_config, nano::node & node_a, nano::ledger & ledger_a, nano::block_processor & block_processor_a, nano::active_elections & active_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a) : +nano::scheduler::priority::priority (nano::node_config & node_config, nano::node & node_a, nano::ledger & ledger_a, nano::bucketing & bucketing_a, nano::block_processor & block_processor_a, nano::active_elections & active_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a) : config{ node_config.priority_scheduler }, node{ node_a }, ledger{ ledger_a }, + bucketing{ bucketing_a }, block_processor{ block_processor_a }, active{ active_a }, confirming_set{ confirming_set_a }, stats{ stats_a }, logger{ logger_a } { - std::vector minimums; - - auto build_region = [&minimums] (uint128_t const & begin, uint128_t const & end, size_t count) { - auto width = (end - begin) / count; - for (auto i = 0; i < count; ++i) - { - minimums.push_back (begin + i * width); - } - }; - - minimums.push_back (uint128_t{ 0 }); - build_region (uint128_t{ 1 } << 79, uint128_t{ 1 } << 88, 1); - build_region (uint128_t{ 1 } << 88, uint128_t{ 1 } << 92, 2); - build_region (uint128_t{ 1 } << 92, uint128_t{ 1 } << 96, 4); - build_region (uint128_t{ 1 } << 96, uint128_t{ 1 } << 100, 8); - build_region (uint128_t{ 1 } << 100, uint128_t{ 1 } << 104, 16); - build_region (uint128_t{ 1 } << 104, uint128_t{ 1 } << 108, 16); - build_region (uint128_t{ 1 } << 108, uint128_t{ 1 } << 112, 8); - build_region (uint128_t{ 1 } << 112, uint128_t{ 1 } << 116, 4); - build_region (uint128_t{ 1 } << 116, uint128_t{ 1 } << 120, 2); - minimums.push_back (uint128_t{ 1 } << 120); - - logger.debug (nano::log::type::election_scheduler, "Number of buckets: {}", minimums.size ()); - - for (size_t i = 0u, n = minimums.size (); i < n; ++i) + for (auto const & index : bucketing.bucket_indices ()) { - auto bucket = std::make_unique (minimums[i], node_config.priority_bucket, active, stats); - buckets.emplace_back (std::move (bucket)); + buckets[index] = std::make_unique (index, node_config.priority_bucket, active, stats); } // Activate accounts with fresh blocks @@ -88,7 +65,7 @@ void nano::scheduler::priority::start () debug_assert (!thread.joinable ()); debug_assert (!cleanup_thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } @@ -135,20 +112,23 @@ bool nano::scheduler::priority::activate (secure::transaction const & transactio { debug_assert (conf_info.frontier != account_info.head); - auto hash = conf_info.height == 0 ? account_info.open_block : ledger.any.block_successor (transaction, conf_info.frontier).value (); - auto block = ledger.any.block_get (transaction, hash); - release_assert (block != nullptr); + auto const hash = conf_info.height == 0 ? account_info.open_block : ledger.any.block_successor (transaction, conf_info.frontier).value_or (0); + auto const block = ledger.any.block_get (transaction, hash); + if (!block) + { + return false; // Not activated + } if (ledger.dependents_confirmed (transaction, *block)) { - auto const balance = block->balance (); - auto const previous_balance = ledger.any.block_balance (transaction, conf_info.frontier).value_or (0); - auto const balance_priority = std::max (balance, previous_balance); + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *block); + auto const bucket_index = bucketing.bucket_index (priority_balance); bool added = false; { - auto & bucket = find_bucket (balance_priority); - added = bucket.push (account_info.modified, block); + auto const & bucket = buckets.at (bucket_index); + release_assert (bucket); + added = bucket->push (priority_timestamp, block); } if (added) { @@ -157,7 +137,8 @@ bool nano::scheduler::priority::activate (secure::transaction const & transactio nano::log::arg{ "account", account.to_account () }, // TODO: Convert to lazy eval nano::log::arg{ "block", block }, nano::log::arg{ "time", account_info.modified }, - nano::log::arg{ "priority", balance_priority }); + nano::log::arg{ "priority_balance", priority_balance }, + nano::log::arg{ "priority_timestamp", priority_timestamp }); notify (); } @@ -184,6 +165,13 @@ bool nano::scheduler::priority::activate_successors (secure::transaction const & return result; } +bool nano::scheduler::priority::contains (nano::block_hash const & hash) const +{ + return std::any_of (buckets.begin (), buckets.end (), [&hash] (auto const & bucket) { + return bucket.second->contains (hash); + }); +} + void nano::scheduler::priority::notify () { condition.notify_all (); @@ -192,21 +180,21 @@ void nano::scheduler::priority::notify () std::size_t nano::scheduler::priority::size () const { return std::accumulate (buckets.begin (), buckets.end (), std::size_t{ 0 }, [] (auto const & sum, auto const & bucket) { - return sum + bucket->size (); + return sum + bucket.second->size (); }); } bool nano::scheduler::priority::empty () const { return std::all_of (buckets.begin (), buckets.end (), [] (auto const & bucket) { - return bucket->empty (); + return bucket.second->empty (); }); } bool nano::scheduler::priority::predicate () const { return std::any_of (buckets.begin (), buckets.end (), [] (auto const & bucket) { - return bucket->available (); + return bucket.second->available (); }); } @@ -225,7 +213,7 @@ void nano::scheduler::priority::run () lock.unlock (); - for (auto & bucket : buckets) + for (auto const & [index, bucket] : buckets) { if (bucket->available ()) { @@ -252,7 +240,7 @@ void nano::scheduler::priority::run_cleanup () lock.unlock (); - for (auto & bucket : buckets) + for (auto const & [index, bucket] : buckets) { bucket->update (); } @@ -262,34 +250,22 @@ void nano::scheduler::priority::run_cleanup () } } -auto nano::scheduler::priority::find_bucket (nano::uint128_t priority) -> bucket & -{ - auto it = std::upper_bound (buckets.begin (), buckets.end (), priority, [] (nano::uint128_t const & priority, std::unique_ptr const & bucket) { - return priority < bucket->minimum_balance; - }); - release_assert (it != buckets.begin ()); // There should always be a bucket with a minimum_balance of 0 - it = std::prev (it); - return **it; -} - nano::container_info nano::scheduler::priority::container_info () const { auto collect_blocks = [&] () { nano::container_info info; - for (auto i = 0; i < buckets.size (); ++i) + for (auto const & [index, bucket] : buckets) { - auto const & bucket = buckets[i]; - info.put (std::to_string (i), bucket->size ()); + info.put (std::to_string (index), bucket->size ()); } return info; }; auto collect_elections = [&] () { nano::container_info info; - for (auto i = 0; i < buckets.size (); ++i) + for (auto const & [index, bucket] : buckets) { - auto const & bucket = buckets[i]; - info.put (std::to_string (i), bucket->election_count ()); + info.put (std::to_string (index), bucket->election_count ()); } return info; }; diff --git a/nano/node/scheduler/priority.hpp b/nano/node/scheduler/priority.hpp index 0e901de991..6f0e3badd2 100644 --- a/nano/node/scheduler/priority.hpp +++ b/nano/node/scheduler/priority.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -20,13 +21,13 @@ class priority_config // TODO: Serialization & deserialization public: - bool enabled{ true }; + bool enable{ true }; }; class priority final { public: - priority (nano::node_config &, nano::node &, nano::ledger &, nano::block_processor &, nano::active_elections &, nano::confirming_set &, nano::stats &, nano::logger &); + priority (nano::node_config &, nano::node &, nano::ledger &, nano::bucketing &, nano::block_processor &, nano::active_elections &, nano::confirming_set &, nano::stats &, nano::logger &); ~priority (); void start (); @@ -40,6 +41,7 @@ class priority final bool activate (nano::secure::transaction const &, nano::account const &, nano::account_info const &, nano::confirmation_height_info const &); bool activate_successors (nano::secure::transaction const &, nano::block const &); + bool contains (nano::block_hash const &) const; void notify (); std::size_t size () const; bool empty () const; @@ -50,6 +52,7 @@ class priority final priority_config const & config; nano::node & node; nano::ledger & ledger; + nano::bucketing & bucketing; nano::block_processor & block_processor; nano::active_elections & active; nano::confirming_set & confirming_set; @@ -60,10 +63,9 @@ class priority final void run (); void run_cleanup (); bool predicate () const; - bucket & find_bucket (nano::uint128_t priority); private: - std::vector> buckets; + std::map> buckets; bool stopped{ false }; nano::condition_variable condition; diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp index 7a349ff6b4..cadc3a7d80 100644 --- a/nano/node/telemetry.cpp +++ b/nano/node/telemetry.cpp @@ -214,7 +214,7 @@ void nano::telemetry::request (std::shared_ptr const & stats.inc (nano::stat::type::telemetry, nano::stat::detail::request); nano::telemetry_req message{ network_params.network }; - channel->send (message); + channel->send (message, nano::transport::traffic_type::telemetry); } void nano::telemetry::run_broadcasts () @@ -233,7 +233,7 @@ void nano::telemetry::broadcast (std::shared_ptr const stats.inc (nano::stat::type::telemetry, nano::stat::detail::broadcast); nano::telemetry_ack message{ network_params.network, telemetry }; - channel->send (message); + channel->send (message, nano::transport::traffic_type::telemetry); } void nano::telemetry::cleanup () diff --git a/nano/node/telemetry.hpp b/nano/node/telemetry.hpp index cb9c337a91..8e0fe8c314 100644 --- a/nano/node/telemetry.hpp +++ b/nano/node/telemetry.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/nano/node/bootstrap/block_deserializer.cpp b/nano/node/transport/block_deserializer.cpp similarity index 84% rename from nano/node/bootstrap/block_deserializer.cpp rename to nano/node/transport/block_deserializer.cpp index ef318b98fb..6584d1b457 100644 --- a/nano/node/bootstrap/block_deserializer.cpp +++ b/nano/node/transport/block_deserializer.cpp @@ -1,14 +1,14 @@ #include #include -#include +#include #include -nano::bootstrap::block_deserializer::block_deserializer () : +nano::transport::block_deserializer::block_deserializer () : read_buffer{ std::make_shared> () } { } -void nano::bootstrap::block_deserializer::read (nano::transport::tcp_socket & socket, callback_type const && callback) +void nano::transport::block_deserializer::read (nano::transport::tcp_socket & socket, callback_type const && callback) { debug_assert (callback); read_buffer->resize (1); @@ -27,7 +27,7 @@ void nano::bootstrap::block_deserializer::read (nano::transport::tcp_socket & so }); } -void nano::bootstrap::block_deserializer::received_type (nano::transport::tcp_socket & socket, callback_type const && callback) +void nano::transport::block_deserializer::received_type (nano::transport::tcp_socket & socket, callback_type const && callback) { nano::block_type type = static_cast (read_buffer->data ()[0]); if (type == nano::block_type::not_a_block) @@ -57,7 +57,7 @@ void nano::bootstrap::block_deserializer::received_type (nano::transport::tcp_so }); } -void nano::bootstrap::block_deserializer::received_block (nano::block_type type, callback_type const && callback) +void nano::transport::block_deserializer::received_block (nano::block_type type, callback_type const && callback) { nano::bufferstream stream{ read_buffer->data (), read_buffer->size () }; auto block = nano::deserialize_block (stream, type); diff --git a/nano/node/transport/block_deserializer.hpp b/nano/node/transport/block_deserializer.hpp new file mode 100644 index 0000000000..6280a78538 --- /dev/null +++ b/nano/node/transport/block_deserializer.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace nano::transport +{ +/** + * Class to read a block-type byte followed by a serialised block from a stream. + * It is typically used to read a series of block-types and blocks terminated by a not-a-block type. + */ +class block_deserializer : public std::enable_shared_from_this +{ +public: + using callback_type = std::function)>; + + block_deserializer (); + /** + * Read a type-prefixed block from 'socket' and pass the result, or an error, to 'callback' + * A normal end to series of blocks is a marked by return no error and a nullptr for block. + */ + void read (nano::transport::tcp_socket & socket, callback_type const && callback); + +private: + /** + * Called by read method on receipt of a block type byte. + * The type byte will be in the read_buffer. + */ + void received_type (nano::transport::tcp_socket & socket, callback_type const && callback); + + /** + * Called by received_type when a block is received, it parses the block and calls the callback. + */ + void received_block (nano::block_type type, callback_type const && callback); + + std::shared_ptr> read_buffer; +}; +} diff --git a/nano/node/transport/channel.cpp b/nano/node/transport/channel.cpp index 6a4c6663e8..ea470f2ec1 100644 --- a/nano/node/transport/channel.cpp +++ b/nano/node/transport/channel.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -14,33 +14,12 @@ nano::transport::channel::channel (nano::node & node_a) : set_network_version (node_a.network_params.network.protocol_version); } -void nano::transport::channel::send (nano::message & message_a, std::function const & callback_a, nano::transport::buffer_drop_policy drop_policy_a, nano::transport::traffic_type traffic_type) +bool nano::transport::channel::send (nano::message const & message, nano::transport::traffic_type traffic_type, callback_t callback) { - auto buffer = message_a.to_shared_const_buffer (); - - bool is_droppable_by_limiter = (drop_policy_a == nano::transport::buffer_drop_policy::limiter); - bool should_pass = node.outbound_limiter.should_pass (buffer.size (), traffic_type); - bool pass = !is_droppable_by_limiter || should_pass; - - node.stats.inc (pass ? nano::stat::type::message : nano::stat::type::drop, to_stat_detail (message_a.type ()), nano::stat::dir::out, /* aggregate all */ true); - node.logger.trace (nano::log::type::channel_sent, to_log_detail (message_a.type ()), - nano::log::arg{ "message", message_a }, - nano::log::arg{ "channel", *this }, - nano::log::arg{ "dropped", !pass }); - - if (pass) - { - send_buffer (buffer, callback_a, drop_policy_a, traffic_type); - } - else - { - if (callback_a) - { - node.background ([callback_a] () { - callback_a (boost::system::errc::make_error_code (boost::system::errc::not_supported), 0); - }); - } - } + auto buffer = message.to_shared_const_buffer (); + bool sent = send_buffer (buffer, traffic_type, std::move (callback)); + node.stats.inc (sent ? nano::stat::type::message : nano::stat::type::drop, to_stat_detail (message.type ()), nano::stat::dir::out, /* aggregate all */ true); + return sent; } void nano::transport::channel::set_peering_endpoint (nano::endpoint endpoint) diff --git a/nano/node/transport/channel.hpp b/nano/node/transport/channel.hpp index 212dc1c373..5911f1249c 100644 --- a/nano/node/transport/channel.hpp +++ b/nano/node/transport/channel.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -22,21 +22,15 @@ enum class transport_type : uint8_t class channel { +public: + using callback_t = std::function; + public: explicit channel (nano::node &); virtual ~channel () = default; - void send (nano::message & message_a, - std::function const & callback_a = nullptr, - nano::transport::buffer_drop_policy policy_a = nano::transport::buffer_drop_policy::limiter, - nano::transport::traffic_type = nano::transport::traffic_type::generic); - - // TODO: investigate clang-tidy warning about default parameters on virtual/override functions - virtual void send_buffer (nano::shared_const_buffer const &, - std::function const & = nullptr, - nano::transport::buffer_drop_policy = nano::transport::buffer_drop_policy::limiter, - nano::transport::traffic_type = nano::transport::traffic_type::generic) - = 0; + /// @returns true if the message was sent (or queued to be sent), false if it was immediately dropped + bool send (nano::message const &, nano::transport::traffic_type, callback_t = nullptr); virtual void close () = 0; @@ -46,7 +40,7 @@ class channel virtual std::string to_string () const = 0; virtual nano::transport::transport_type get_type () const = 0; - virtual bool max (nano::transport::traffic_type = nano::transport::traffic_type::generic) + virtual bool max (nano::transport::traffic_type) { return false; } @@ -125,6 +119,9 @@ class channel std::shared_ptr owner () const; +protected: + virtual bool send_buffer (nano::shared_const_buffer const &, nano::transport::traffic_type, callback_t) = 0; + protected: nano::node & node; mutable nano::mutex mutex; @@ -140,4 +137,4 @@ class channel public: // Logging virtual void operator() (nano::object_stream &) const; }; -} \ No newline at end of file +} diff --git a/nano/node/transport/fake.cpp b/nano/node/transport/fake.cpp index aec913adf9..81d3fec7aa 100644 --- a/nano/node/transport/fake.cpp +++ b/nano/node/transport/fake.cpp @@ -14,16 +14,16 @@ nano::transport::fake::channel::channel (nano::node & node) : /** * The send function behaves like a null device, it throws the data away and returns success. */ -void nano::transport::fake::channel::send_buffer (nano::shared_const_buffer const & buffer_a, std::function const & callback_a, nano::transport::buffer_drop_policy drop_policy_a, nano::transport::traffic_type traffic_type) +bool nano::transport::fake::channel::send_buffer (nano::shared_const_buffer const & buffer, nano::transport::traffic_type traffic_type, nano::transport::channel::callback_t callback) { - // auto bytes = buffer_a.to_bytes (); - auto size = buffer_a.size (); - if (callback_a) + auto size = buffer.size (); + if (callback) { - node.background ([callback_a, size] () { - callback_a (boost::system::errc::make_error_code (boost::system::errc::success), size); + node.io_ctx.post ([callback, size] () { + callback (boost::system::errc::make_error_code (boost::system::errc::success), size); }); } + return true; } std::string nano::transport::fake::channel::to_string () const diff --git a/nano/node/transport/fake.hpp b/nano/node/transport/fake.hpp index d9ce585cbb..f503b2bd58 100644 --- a/nano/node/transport/fake.hpp +++ b/nano/node/transport/fake.hpp @@ -19,12 +19,6 @@ namespace transport std::string to_string () const override; - void send_buffer ( - nano::shared_const_buffer const &, - std::function const & = nullptr, - nano::transport::buffer_drop_policy = nano::transport::buffer_drop_policy::limiter, - nano::transport::traffic_type = nano::transport::traffic_type::generic) override; - void set_endpoint (nano::endpoint const & endpoint_a) { endpoint = endpoint_a; @@ -55,6 +49,9 @@ namespace transport return !closed; } + protected: + bool send_buffer (nano::shared_const_buffer const &, nano::transport::traffic_type, nano::transport::channel::callback_t) override; + private: nano::endpoint endpoint; diff --git a/nano/node/transport/fwd.hpp b/nano/node/transport/fwd.hpp index c62c470304..4be374eba9 100644 --- a/nano/node/transport/fwd.hpp +++ b/nano/node/transport/fwd.hpp @@ -7,4 +7,8 @@ class tcp_channel; class tcp_channels; class tcp_server; class tcp_socket; -} \ No newline at end of file +} +namespace nano::transport::fake +{ +class channel; +} diff --git a/nano/node/transport/inproc.cpp b/nano/node/transport/inproc.cpp index abeca40067..78a4be9904 100644 --- a/nano/node/transport/inproc.cpp +++ b/nano/node/transport/inproc.cpp @@ -18,10 +18,10 @@ nano::transport::inproc::channel::channel (nano::node & node, nano::node & desti * Send the buffer to the peer and call the callback function when done. The call never fails. * Note that the inbound message visitor will be called before the callback because it is called directly whereas the callback is spawned in the background. */ -void nano::transport::inproc::channel::send_buffer (nano::shared_const_buffer const & buffer_a, std::function const & callback_a, nano::transport::buffer_drop_policy drop_policy_a, nano::transport::traffic_type traffic_type) +bool nano::transport::inproc::channel::send_buffer (nano::shared_const_buffer const & buffer, nano::transport::traffic_type traffic_type, nano::transport::channel::callback_t callback) { std::size_t offset{ 0 }; - auto const buffer_read_fn = [&offset, buffer_v = buffer_a.to_bytes ()] (std::shared_ptr> const & data_a, std::size_t size_a, std::function callback_a) { + auto const buffer_read_fn = [&offset, buffer_v = buffer.to_bytes ()] (std::shared_ptr> const & data_a, std::size_t size_a, std::function callback_a) { debug_assert (buffer_v.size () >= (offset + size_a)); data_a->resize (size_a); auto const copy_start = buffer_v.begin () + offset; @@ -48,12 +48,14 @@ void nano::transport::inproc::channel::send_buffer (nano::shared_const_buffer co } }); - if (callback_a) + if (callback) { - node.background ([callback_l = std::move (callback_a), buffer_size = buffer_a.size ()] () { + node.io_ctx.post ([callback_l = std::move (callback), buffer_size = buffer.size ()] () { callback_l (boost::system::errc::make_error_code (boost::system::errc::success), buffer_size); }); } + + return true; } std::string nano::transport::inproc::channel::to_string () const diff --git a/nano/node/transport/inproc.hpp b/nano/node/transport/inproc.hpp index d93fbed2d5..dfe932e777 100644 --- a/nano/node/transport/inproc.hpp +++ b/nano/node/transport/inproc.hpp @@ -17,9 +17,6 @@ namespace transport public: explicit channel (nano::node & node, nano::node & destination); - // TODO: investigate clang-tidy warning about default parameters on virtual/override functions - void send_buffer (nano::shared_const_buffer const &, std::function const & = nullptr, nano::transport::buffer_drop_policy = nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type = nano::transport::traffic_type::generic) override; - std::string to_string () const override; nano::endpoint get_remote_endpoint () const override @@ -42,6 +39,9 @@ namespace transport // Can't be closed } + protected: + bool send_buffer (nano::shared_const_buffer const &, nano::transport::traffic_type, nano::transport::channel::callback_t) override; + private: nano::node & destination; nano::endpoint const endpoint; diff --git a/nano/node/transport/message_deserializer.hpp b/nano/node/transport/message_deserializer.hpp index 4c4d62c229..ca8d923f37 100644 --- a/nano/node/transport/message_deserializer.hpp +++ b/nano/node/transport/message_deserializer.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include diff --git a/nano/node/transport/tcp_channel.cpp b/nano/node/transport/tcp_channel.cpp index cd0f3645bc..47ed452fe6 100644 --- a/nano/node/transport/tcp_channel.cpp +++ b/nano/node/transport/tcp_channel.cpp @@ -1,89 +1,195 @@ +#include #include #include #include #include #include +#include /* * tcp_channel */ -nano::transport::tcp_channel::tcp_channel (nano::node & node_a, std::weak_ptr socket_a) : +nano::transport::tcp_channel::tcp_channel (nano::node & node_a, std::shared_ptr socket_a) : channel (node_a), - socket (std::move (socket_a)) + socket{ socket_a }, + strand{ node_a.io_ctx.get_executor () }, + sending_task{ strand } { + stacktrace = nano::generate_stacktrace (); + remote_endpoint = socket_a->remote_endpoint (); + local_endpoint = socket_a->local_endpoint (); + start (); } nano::transport::tcp_channel::~tcp_channel () { - if (auto socket_l = socket.lock ()) + close (); + release_assert (!sending_task.joinable ()); +} + +void nano::transport::tcp_channel::close () +{ + stop (); + socket->close (); + closed = true; +} + +void nano::transport::tcp_channel::start () +{ + sending_task = nano::async::task (strand, [this] (nano::async::condition & condition) { + return start_sending (condition); // This is not a coroutine, but a corotuine factory + }); +} + +asio::awaitable nano::transport::tcp_channel::start_sending (nano::async::condition & condition) +{ + debug_assert (strand.running_in_this_thread ()); + try + { + co_await run_sending (condition); + } + catch (boost::system::system_error const & ex) { - socket_l->close (); + // Operation aborted is expected when cancelling the acceptor + debug_assert (ex.code () == asio::error::operation_aborted); } + debug_assert (strand.running_in_this_thread ()); } -void nano::transport::tcp_channel::update_endpoints () +void nano::transport::tcp_channel::stop () { - nano::lock_guard lock{ mutex }; + if (sending_task.joinable ()) + { + // Node context must be running to gracefully stop async tasks + debug_assert (!node.io_ctx.stopped ()); + // Ensure that we are not trying to await the task while running on the same thread / io_context + debug_assert (!node.io_ctx.get_executor ().running_in_this_thread ()); + sending_task.cancel (); + sending_task.join (); + } +} - debug_assert (remote_endpoint == nano::endpoint{}); // Not initialized endpoint value - debug_assert (local_endpoint == nano::endpoint{}); // Not initialized endpoint value +bool nano::transport::tcp_channel::max (nano::transport::traffic_type traffic_type) +{ + nano::lock_guard guard{ mutex }; + return queue.max (traffic_type); +} - if (auto socket_l = socket.lock ()) +bool nano::transport::tcp_channel::send_buffer (nano::shared_const_buffer const & buffer, nano::transport::traffic_type type, nano::transport::channel::callback_t callback) +{ + nano::unique_lock lock{ mutex }; + if (!queue.full (type)) + { + queue.push (type, { buffer, callback }); + lock.unlock (); + node.stats.inc (nano::stat::type::tcp_channel, nano::stat::detail::queued, nano::stat::dir::out); + node.stats.inc (nano::stat::type::tcp_channel_queued, to_stat_detail (type), nano::stat::dir::out); + sending_task.notify (); + return true; + } + else { - remote_endpoint = socket_l->remote_endpoint (); - local_endpoint = socket_l->local_endpoint (); + node.stats.inc (nano::stat::type::tcp_channel, nano::stat::detail::drop, nano::stat::dir::out); + node.stats.inc (nano::stat::type::tcp_channel_drop, to_stat_detail (type), nano::stat::dir::out); } + return false; } -void nano::transport::tcp_channel::send_buffer (nano::shared_const_buffer const & buffer_a, std::function const & callback_a, nano::transport::buffer_drop_policy policy_a, nano::transport::traffic_type traffic_type) +asio::awaitable nano::transport::tcp_channel::run_sending (nano::async::condition & condition) { - if (auto socket_l = socket.lock ()) + while (!co_await nano::async::cancelled ()) { - if (!socket_l->max (traffic_type) || (policy_a == nano::transport::buffer_drop_policy::no_socket_drop && !socket_l->full (traffic_type))) + debug_assert (strand.running_in_this_thread ()); + + auto next_batch = [this] () { + const size_t max_batch = 8; // TODO: Make this configurable + nano::lock_guard lock{ mutex }; + return queue.next_batch (max_batch); + }; + + if (auto batch = next_batch (); !batch.empty ()) { - socket_l->async_write ( - buffer_a, [this_s = shared_from_this (), endpoint_a = socket_l->remote_endpoint (), node = std::weak_ptr{ node.shared () }, callback_a] (boost::system::error_code const & ec, std::size_t size_a) { - if (auto node_l = node.lock ()) - { - if (!ec) - { - this_s->set_last_packet_sent (std::chrono::steady_clock::now ()); - } - if (ec == boost::system::errc::host_unreachable) - { - node_l->stats.inc (nano::stat::type::error, nano::stat::detail::unreachable_host, nano::stat::dir::out); - } - if (callback_a) - { - callback_a (ec, size_a); - } - } - }, - traffic_type); + for (auto const & [type, item] : batch) + { + co_await send_one (type, item); + } } else { - if (policy_a == nano::transport::buffer_drop_policy::no_socket_drop) - { - node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_write_no_socket_drop, nano::stat::dir::out); - } - else - { - node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_write_drop, nano::stat::dir::out); - } - if (callback_a) - { - callback_a (boost::system::errc::make_error_code (boost::system::errc::no_buffer_space), 0); - } + co_await condition.wait (); } } - else if (callback_a) +} + +asio::awaitable nano::transport::tcp_channel::send_one (traffic_type type, tcp_channel_queue::entry_t const & item) +{ + debug_assert (strand.running_in_this_thread ()); + + auto const & [buffer, callback] = item; + auto const size = buffer.size (); + + // Wait for socket + while (socket->full ()) + { + node.stats.inc (nano::stat::type::tcp_channel_wait, nano::stat::detail::wait_socket, nano::stat::dir::out); + co_await nano::async::sleep_for (100ms); // TODO: Exponential backoff + } + + // Wait for bandwidth + // This is somewhat inefficient + // The performance impact *should* be mitigated by the fact that we allocate it in larger chunks, so this happens relatively infrequently + const size_t bandwidth_chunk = 128 * 1024; // TODO: Make this configurable + while (allocated_bandwidth < size) { - node.background ([callback_a] () { - callback_a (boost::system::errc::make_error_code (boost::system::errc::not_supported), 0); - }); + // TODO: Consider implementing a subsribe/notification mechanism for bandwidth allocation + if (node.outbound_limiter.should_pass (bandwidth_chunk, type)) // Allocate bandwidth in larger chunks + { + allocated_bandwidth += bandwidth_chunk; + } + else + { + node.stats.inc (nano::stat::type::tcp_channel_wait, nano::stat::detail::wait_bandwidth, nano::stat::dir::out); + co_await nano::async::sleep_for (100ms); // TODO: Exponential backoff + } } + allocated_bandwidth -= size; + + node.stats.inc (nano::stat::type::tcp_channel, nano::stat::detail::send, nano::stat::dir::out); + node.stats.inc (nano::stat::type::tcp_channel_send, to_stat_detail (type), nano::stat::dir::out); + + socket->async_write (buffer, [this_w = weak_from_this (), callback, type] (boost::system::error_code const & ec, std::size_t size) { + if (auto this_l = this_w.lock ()) + { + this_l->node.stats.inc (nano::stat::type::tcp_channel_ec, nano::to_stat_detail (ec), nano::stat::dir::out); + if (!ec) + { + this_l->node.stats.add (nano::stat::type::traffic_tcp_type, to_stat_detail (type), nano::stat::dir::out, size); + this_l->set_last_packet_sent (std::chrono::steady_clock::now ()); + } + } + if (callback) + { + callback (ec, size); + } + }); +} + +bool nano::transport::tcp_channel::alive () const +{ + return socket->alive (); +} + +nano::endpoint nano::transport::tcp_channel::get_remote_endpoint () const +{ + nano::lock_guard lock{ mutex }; + return remote_endpoint; +} + +nano::endpoint nano::transport::tcp_channel::get_local_endpoint () const +{ + nano::lock_guard lock{ mutex }; + return local_endpoint; } std::string nano::transport::tcp_channel::to_string () const @@ -94,6 +200,132 @@ std::string nano::transport::tcp_channel::to_string () const void nano::transport::tcp_channel::operator() (nano::object_stream & obs) const { nano::transport::channel::operator() (obs); // Write common data - obs.write ("socket", socket); } + +/* + * tcp_channel_queue + */ + +nano::transport::tcp_channel_queue::tcp_channel_queue () +{ + for (auto type : all_traffic_types ()) + { + queues.at (type) = { type, {} }; + } +} + +bool nano::transport::tcp_channel_queue::empty () const +{ + return std::all_of (queues.begin (), queues.end (), [] (auto const & queue) { + return queue.second.empty (); + }); +} + +size_t nano::transport::tcp_channel_queue::size () const +{ + return std::accumulate (queues.begin (), queues.end (), size_t{ 0 }, [] (size_t acc, auto const & queue) { + return acc + queue.second.size (); + }); +} + +size_t nano::transport::tcp_channel_queue::size (traffic_type type) const +{ + return queues.at (type).second.size (); +} + +bool nano::transport::tcp_channel_queue::max (traffic_type type) const +{ + return size (type) >= max_size; +} + +bool nano::transport::tcp_channel_queue::full (traffic_type type) const +{ + return size (type) >= full_size; +} + +void nano::transport::tcp_channel_queue::push (traffic_type type, entry_t entry) +{ + debug_assert (!full (type)); // Should be checked before calling this function + queues.at (type).second.push_back (entry); +} + +auto nano::transport::tcp_channel_queue::next () -> value_t +{ + debug_assert (!empty ()); // Should be checked before calling next + + auto should_seek = [&, this] () { + if (current == queues.end ()) + { + return true; + } + auto & queue = current->second; + if (queue.empty ()) + { + return true; + } + // Allow up to `priority` requests to be processed before moving to the next queue + if (counter >= priority (current->first)) + { + return true; + } + return false; + }; + + if (should_seek ()) + { + seek_next (); + } + + release_assert (current != queues.end ()); + + auto & source = current->first; + auto & queue = current->second; + + ++counter; + + release_assert (!queue.empty ()); + auto entry = queue.front (); + queue.pop_front (); + return { source, entry }; +} + +auto nano::transport::tcp_channel_queue::next_batch (size_t max_count) -> batch_t +{ + // TODO: Naive implementation, could be optimized + std::deque result; + while (!empty () && result.size () < max_count) + { + result.emplace_back (next ()); + } + return result; +} + +size_t nano::transport::tcp_channel_queue::priority (traffic_type type) const +{ + switch (type) + { + case traffic_type::block_broadcast: + case traffic_type::vote_rebroadcast: + return 1; + default: + return 4; + } +} + +void nano::transport::tcp_channel_queue::seek_next () +{ + counter = 0; + do + { + if (current != queues.end ()) + { + ++current; + } + if (current == queues.end ()) + { + current = queues.begin (); + } + release_assert (current != queues.end ()); + } while (current->second.empty ()); +} diff --git a/nano/node/transport/tcp_channel.hpp b/nano/node/transport/tcp_channel.hpp index 3936e3e9ac..fc64ed9021 100644 --- a/nano/node/transport/tcp_channel.hpp +++ b/nano/node/transport/tcp_channel.hpp @@ -1,81 +1,100 @@ #pragma once +#include +#include #include +#include #include namespace nano::transport { -class tcp_server; -class tcp_channels; -class tcp_channel; +class tcp_channel_queue final +{ +public: + explicit tcp_channel_queue (); + + using callback_t = std::function; + using entry_t = std::pair; + using value_t = std::pair; + using batch_t = std::deque; + + bool empty () const; + size_t size () const; + size_t size (traffic_type) const; + void push (traffic_type, entry_t); + value_t next (); + batch_t next_batch (size_t max_count); -class tcp_channel : public nano::transport::channel, public std::enable_shared_from_this + bool max (traffic_type) const; + bool full (traffic_type) const; + +public: + constexpr static size_t max_size = 32; + constexpr static size_t full_size = 4 * max_size; + +private: + void seek_next (); + size_t priority (traffic_type) const; + + using queue_t = std::pair>; + nano::enum_array queues{}; + nano::enum_array::iterator current{ queues.end () }; + size_t counter{ 0 }; +}; + +class tcp_channel final : public nano::transport::channel, public std::enable_shared_from_this { friend class nano::transport::tcp_channels; public: - tcp_channel (nano::node &, std::weak_ptr); + tcp_channel (nano::node &, std::shared_ptr); ~tcp_channel () override; - void update_endpoints (); - - // TODO: investigate clang-tidy warning about default parameters on virtual/override functions// - void send_buffer (nano::shared_const_buffer const &, std::function const & = nullptr, nano::transport::buffer_drop_policy = nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type = nano::transport::traffic_type::generic) override; + void close () override; - std::string to_string () const override; - - nano::endpoint get_remote_endpoint () const override - { - nano::lock_guard lock{ mutex }; - return remote_endpoint; - } + bool max (nano::transport::traffic_type traffic_type) override; + bool alive () const override; - nano::endpoint get_local_endpoint () const override - { - nano::lock_guard lock{ mutex }; - return local_endpoint; - } + nano::endpoint get_remote_endpoint () const override; + nano::endpoint get_local_endpoint () const override; nano::transport::transport_type get_type () const override { return nano::transport::transport_type::tcp; } - bool max (nano::transport::traffic_type traffic_type) override - { - bool result = true; - if (auto socket_l = socket.lock ()) - { - result = socket_l->max (traffic_type); - } - return result; - } + std::string to_string () const override; - bool alive () const override - { - if (auto socket_l = socket.lock ()) - { - return socket_l->alive (); - } - return false; - } +protected: + bool send_buffer (nano::shared_const_buffer const &, nano::transport::traffic_type, nano::transport::channel::callback_t) override; - void close () override - { - if (auto socket_l = socket.lock ()) - { - socket_l->close (); - } - } +private: + void start (); + void stop (); + + asio::awaitable start_sending (nano::async::condition &); + asio::awaitable run_sending (nano::async::condition &); + asio::awaitable send_one (traffic_type, tcp_channel_queue::entry_t const &); public: - std::weak_ptr socket; + std::shared_ptr socket; private: nano::endpoint remote_endpoint; nano::endpoint local_endpoint; + nano::async::strand strand; + nano::async::task sending_task; + + mutable nano::mutex mutex; + tcp_channel_queue queue; + std::atomic allocated_bandwidth{ 0 }; + + // Debugging + std::atomic closed{ false }; + std::string stacktrace; + public: // Logging void operator() (nano::object_stream &) const override; }; -} \ No newline at end of file +} diff --git a/nano/node/transport/tcp_channels.cpp b/nano/node/transport/tcp_channels.cpp index 4b983cb7bb..62cc25b65b 100644 --- a/nano/node/transport/tcp_channels.cpp +++ b/nano/node/transport/tcp_channels.cpp @@ -34,17 +34,11 @@ void nano::transport::tcp_channels::close () { nano::lock_guard lock{ mutex }; - for (auto const & channel : channels) + for (auto const & entry : channels) { - if (channel.socket) - { - channel.socket->close (); - } - // Remove response server - if (channel.response_server) - { - channel.response_server->stop (); - } + entry.socket->close (); + entry.server->stop (); + entry.channel->close (); } channels.clear (); @@ -62,7 +56,7 @@ bool nano::transport::tcp_channels::check (const nano::tcp_endpoint & endpoint, if (node.network.not_a_peer (nano::transport::map_tcp_to_endpoint (endpoint), node.config.allow_local_peers)) { node.stats.inc (nano::stat::type::tcp_channels_rejected, nano::stat::detail::not_a_peer); - node.logger.debug (nano::log::type::tcp_channels, "Rejected invalid endpoint channel from: {}", fmt::streamed (endpoint)); + node.logger.debug (nano::log::type::tcp_channels, "Rejected invalid endpoint channel: {}", fmt::streamed (endpoint)); return false; // Reject } @@ -82,7 +76,7 @@ bool nano::transport::tcp_channels::check (const nano::tcp_endpoint & endpoint, if (has_duplicate) { node.stats.inc (nano::stat::type::tcp_channels_rejected, nano::stat::detail::channel_duplicate); - node.logger.debug (nano::log::type::tcp_channels, "Duplicate channel rejected from: {} ({})", fmt::streamed (endpoint), node_id.to_node_id ()); + node.logger.debug (nano::log::type::tcp_channels, "Rejected duplicate channel: {} ({})", fmt::streamed (endpoint), node_id.to_node_id ()); return false; // Reject } @@ -106,19 +100,19 @@ std::shared_ptr nano::transport::tcp_channels::cre if (!check (endpoint, node_id)) { node.stats.inc (nano::stat::type::tcp_channels, nano::stat::detail::channel_rejected); - node.logger.debug (nano::log::type::tcp_channels, "Rejected new channel from: {} ({})", fmt::streamed (endpoint), node_id.to_node_id ()); + node.logger.debug (nano::log::type::tcp_channels, "Rejected channel: {} ({})", fmt::streamed (endpoint), node_id.to_node_id ()); // Rejection reason should be logged earlier return nullptr; } node.stats.inc (nano::stat::type::tcp_channels, nano::stat::detail::channel_accepted); - node.logger.debug (nano::log::type::tcp_channels, "Accepted new channel from: {} ({})", + node.logger.debug (nano::log::type::tcp_channels, "Accepted channel: {} ({}) ({})", fmt::streamed (socket->remote_endpoint ()), + to_string (socket->endpoint_type ()), node_id.to_node_id ()); auto channel = std::make_shared (node, socket); - channel->update_endpoints (); channel->set_node_id (node_id); attempts.get ().erase (endpoint); @@ -128,7 +122,7 @@ std::shared_ptr nano::transport::tcp_channels::cre lock.unlock (); - node.network.channel_observer (channel); + node.observers.channel_connected.notify (channel); return channel; } @@ -157,7 +151,7 @@ std::shared_ptr nano::transport::tcp_channels::fin return result; } -std::unordered_set> nano::transport::tcp_channels::random_set (std::size_t count_a, uint8_t min_version, bool include_temporary_channels_a) const +std::unordered_set> nano::transport::tcp_channels::random_set (std::size_t count_a, uint8_t min_version) const { std::unordered_set> result; result.reserve (count_a); @@ -350,6 +344,7 @@ void nano::transport::tcp_channels::purge (std::chrono::steady_clock::time_point if (!entry.channel->alive ()) { node.logger.debug (nano::log::type::tcp_channels, "Removing dead channel: {}", entry.channel->to_string ()); + entry.channel->close (); return true; // Erase } return false; @@ -383,7 +378,7 @@ void nano::transport::tcp_channels::keepalive () for (auto & channel : to_wakeup) { - channel->send (message); + channel->send (message, nano::transport::traffic_type::keepalive); } } @@ -395,7 +390,7 @@ std::optional nano::transport::tcp_channels::sample_keepalive ( while (counter++ < channels.size ()) { auto index = rng.random (channels.size ()); - if (auto server = channels.get ()[index].response_server) + if (auto server = channels.get ()[index].server) { if (auto keepalive = server->pop_last_keepalive ()) { @@ -407,19 +402,24 @@ std::optional nano::transport::tcp_channels::sample_keepalive ( return std::nullopt; } -void nano::transport::tcp_channels::list (std::deque> & deque_a, uint8_t minimum_version_a, bool include_temporary_channels_a) +std::deque> nano::transport::tcp_channels::list (uint8_t minimum_version) const { nano::lock_guard lock{ mutex }; - // clang-format off - nano::transform_if (channels.get ().begin (), channels.get ().end (), std::back_inserter (deque_a), - [include_temporary_channels_a, minimum_version_a](auto & channel_a) { return channel_a.channel->get_network_version () >= minimum_version_a; }, - [](auto const & channel) { return channel.channel; }); - // clang-format on + + std::deque> result; + for (auto const & entry : channels) + { + if (entry.channel->get_network_version () >= minimum_version) + { + result.push_back (entry.channel); + } + } + return result; } -void nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint) +bool nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint) { - node.tcp_listener.connect (endpoint.address (), endpoint.port ()); + return node.tcp_listener.connect (endpoint.address (), endpoint.port ()); } nano::container_info nano::transport::tcp_channels::container_info () const diff --git a/nano/node/transport/tcp_channels.hpp b/nano/node/transport/tcp_channels.hpp index 1b6f1e363d..939d2040fa 100644 --- a/nano/node/transport/tcp_channels.hpp +++ b/nano/node/transport/tcp_channels.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include -#include +#include #include +#include #include #include @@ -39,22 +41,22 @@ class tcp_channels final std::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 (std::size_t, uint8_t = 0, bool = false) const; std::shared_ptr find_node_id (nano::account const &); // Get the next peer for attempting a tcp connection nano::tcp_endpoint bootstrap_peer (); - bool max_ip_connections (nano::tcp_endpoint const & endpoint_a); - bool max_subnetwork_connections (nano::tcp_endpoint const & endpoint_a); - bool max_ip_or_subnetwork_connections (nano::tcp_endpoint const & endpoint_a); + bool max_ip_connections (nano::tcp_endpoint const & endpoint); + bool max_subnetwork_connections (nano::tcp_endpoint const & endpoint); + bool max_ip_or_subnetwork_connections (nano::tcp_endpoint const & endpoint); // Should we reach out to this endpoint with a keepalive message? If yes, register a new reachout attempt bool track_reachout (nano::endpoint const &); void purge (std::chrono::steady_clock::time_point cutoff_deadline); - void list (std::deque> &, uint8_t = 0, bool = true); + std::deque> list (uint8_t minimum_version = 0) const; + std::unordered_set> random_set (std::size_t max_count, uint8_t minimum_version = 0) const; void keepalive (); std::optional sample_keepalive (); // Connection start - void start_tcp (nano::endpoint const &); + bool start_tcp (nano::endpoint const &); nano::container_info container_info () const; @@ -69,14 +71,19 @@ class tcp_channels final class channel_entry final { public: - std::shared_ptr channel; - std::shared_ptr socket; - std::shared_ptr response_server; + std::shared_ptr channel; + std::shared_ptr socket; + std::shared_ptr server; public: - channel_entry (std::shared_ptr channel_a, std::shared_ptr socket_a, std::shared_ptr server_a) : - channel (std::move (channel_a)), socket (std::move (socket_a)), response_server (std::move (server_a)) + channel_entry (std::shared_ptr channel_a, std::shared_ptr socket_a, std::shared_ptr server_a) : + channel (std::move (channel_a)), + socket (std::move (socket_a)), + server (std::move (server_a)) { + release_assert (socket); + release_assert (server); + release_assert (channel); } nano::tcp_endpoint endpoint () const { @@ -170,4 +177,4 @@ class tcp_channels final mutable nano::random_generator rng; }; -} \ No newline at end of file +} diff --git a/nano/node/transport/tcp_config.cpp b/nano/node/transport/tcp_config.cpp new file mode 100644 index 0000000000..b85d0beb62 --- /dev/null +++ b/nano/node/transport/tcp_config.cpp @@ -0,0 +1,29 @@ +#include + +nano::error nano::transport::tcp_config::serialize (nano::tomlconfig & toml) const +{ + toml.put ("max_inbound_connections", max_inbound_connections, "Maximum number of incoming TCP connections. \ntype:uint64"); + toml.put ("max_outbound_connections", max_outbound_connections, "Maximum number of outgoing TCP connections. \ntype:uint64"); + toml.put ("max_attempts", max_attempts, "Maximum connection attempts. \ntype:uint64"); + toml.put ("max_attempts_per_ip", max_attempts_per_ip, "Maximum connection attempts per IP. \ntype:uint64"); + + toml.put ("connect_timeout", connect_timeout.count (), "Timeout for establishing TCP connection in seconds. \ntype:uint64"); + toml.put ("handshake_timeout", handshake_timeout.count (), "Timeout for completing handshake in seconds. \ntype:uint64"); + toml.put ("io_timeout", io_timeout.count (), "Timeout for TCP I/O operations in seconds. \ntype:uint64"); + + return toml.get_error (); +} + +nano::error nano::transport::tcp_config::deserialize (nano::tomlconfig & toml) +{ + toml.get ("max_inbound_connections", max_inbound_connections); + toml.get ("max_outbound_connections", max_outbound_connections); + toml.get ("max_attempts", max_attempts); + toml.get ("max_attempts_per_ip", max_attempts_per_ip); + + toml.get_duration ("connect_timeout", connect_timeout); + toml.get_duration ("handshake_timeout", handshake_timeout); + toml.get_duration ("io_timeout", io_timeout); + + return toml.get_error (); +} \ No newline at end of file diff --git a/nano/node/transport/tcp_config.hpp b/nano/node/transport/tcp_config.hpp new file mode 100644 index 0000000000..1a8a99d6bd --- /dev/null +++ b/nano/node/transport/tcp_config.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +namespace nano::transport +{ +class tcp_config +{ +public: + explicit tcp_config (nano::network_constants const & network) + { + if (network.is_dev_network ()) + { + max_inbound_connections = 128; + max_outbound_connections = 128; + max_attempts = 128; + max_attempts_per_ip = 128; + connect_timeout = std::chrono::seconds{ 5 }; + } + } + +public: + nano::error deserialize (nano::tomlconfig &); + nano::error serialize (nano::tomlconfig &) const; + +public: + size_t max_inbound_connections{ 2048 }; + size_t max_outbound_connections{ 2048 }; + size_t max_attempts{ 60 }; + size_t max_attempts_per_ip{ 1 }; + std::chrono::seconds connect_timeout{ 60 }; + std::chrono::seconds handshake_timeout{ 30 }; + std::chrono::seconds io_timeout{ 30 }; +}; +} \ No newline at end of file diff --git a/nano/node/transport/tcp_listener.cpp b/nano/node/transport/tcp_listener.cpp index ad3fc63329..372d9bbbc6 100644 --- a/nano/node/transport/tcp_listener.cpp +++ b/nano/node/transport/tcp_listener.cpp @@ -27,7 +27,7 @@ nano::transport::tcp_listener::tcp_listener (uint16_t port_a, tcp_config const & task{ strand } { connection_accepted.add ([this] (auto const & socket, auto const & server) { - node.observers.socket_connected.notify (*socket); + node.observers.socket_connected.notify (socket); }); } @@ -66,40 +66,43 @@ void nano::transport::tcp_listener::start () throw; } - task = nano::async::task (strand, [this] () -> asio::awaitable { - try - { - logger.debug (nano::log::type::tcp_listener, "Starting acceptor"); + task = nano::async::task (strand, start_impl ()); - try - { - co_await run (); - } - catch (boost::system::system_error const & ex) - { - // Operation aborted is expected when cancelling the acceptor - debug_assert (ex.code () == asio::error::operation_aborted); - } - debug_assert (strand.running_in_this_thread ()); + cleanup_thread = std::thread ([this] { + nano::thread_role::set (nano::thread_role::name::tcp_listener); + run_cleanup (); + }); +} - logger.debug (nano::log::type::tcp_listener, "Stopped acceptor"); - } - catch (std::exception const & ex) +asio::awaitable nano::transport::tcp_listener::start_impl () +{ + try + { + logger.debug (nano::log::type::tcp_listener, "Starting acceptor"); + + try { - logger.critical (nano::log::type::tcp_listener, "Error: {}", ex.what ()); - release_assert (false); // Unexpected error + co_await run (); } - catch (...) + catch (boost::system::system_error const & ex) { - logger.critical (nano::log::type::tcp_listener, "Unknown error"); - release_assert (false); // Unexpected error + // Operation aborted is expected when cancelling the acceptor + debug_assert (ex.code () == asio::error::operation_aborted); } - }); + debug_assert (strand.running_in_this_thread ()); - cleanup_thread = std::thread ([this] { - nano::thread_role::set (nano::thread_role::name::tcp_listener); - run_cleanup (); - }); + logger.debug (nano::log::type::tcp_listener, "Stopped acceptor"); + } + catch (std::exception const & ex) + { + logger.critical (nano::log::type::tcp_listener, "Error: {}", ex.what ()); + release_assert (false); // Unexpected error + } + catch (...) + { + logger.critical (nano::log::type::tcp_listener, "Unknown error"); + release_assert (false); // Unexpected error + } } void nano::transport::tcp_listener::stop () @@ -408,7 +411,7 @@ auto nano::transport::tcp_listener::accept_one (asio::ip::tcp::socket raw_socket auto socket = std::make_shared (node, std::move (raw_socket), remote_endpoint, local_endpoint, to_socket_endpoint (type)); auto server = std::make_shared (socket, node.shared (), true); - connections.emplace_back (connection{ remote_endpoint, socket, server }); + connections.emplace_back (connection{ type, remote_endpoint, socket, server }); lock.unlock (); @@ -435,7 +438,9 @@ auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, if (node.network.excluded_peers.check (ip)) // true => error { stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::excluded, to_stat_dir (type)); - logger.debug (nano::log::type::tcp_listener, "Rejected connection from excluded peer: {}", ip.to_string ()); + logger.debug (nano::log::type::tcp_listener, "Rejected connection from excluded peer: {} ({})", + ip.to_string (), + to_string (type)); return accept_result::rejected; } @@ -445,21 +450,25 @@ auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, if (auto count = count_per_ip (ip); count >= node.config.network.max_peers_per_ip) { stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_ip, to_stat_dir (type)); - logger.debug (nano::log::type::tcp_listener, "Max connections per IP reached ({}), unable to open new connection: {}", - count, ip.to_string ()); + logger.debug (nano::log::type::tcp_listener, "Max connections: {} per IP: {} reached, unable to open a new connection ({})", + count, + ip.to_string (), + to_string (type)); return accept_result::rejected; } } - // If the address is IPv4 we don't check for a network limit, since its address space isn't big as IPv6/64. + // If the address is IPv4 we don't check for a subnetwork limit, since its address space isn't big as IPv6/64. if (!node.flags.disable_max_peers_per_subnetwork && !nano::transport::is_ipv4_or_v4_mapped_address (ip)) { if (auto count = count_per_subnetwork (ip); count >= node.config.network.max_peers_per_subnetwork) { stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_per_subnetwork, to_stat_dir (type)); - logger.debug (nano::log::type::tcp_listener, "Max connections per subnetwork reached ({}), unable to open new connection: {}", - count, ip.to_string ()); + logger.debug (nano::log::type::tcp_listener, "Max connections: {} per subnetwork of IP: {} reached, unable to open a new connection ({})", + count, + ip.to_string (), + to_string (type)); return accept_result::rejected; } @@ -472,7 +481,7 @@ auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, if (auto count = count_per_type (connection_type::inbound); count >= config.max_inbound_connections) { stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_attempts, to_stat_dir (type)); - logger.debug (nano::log::type::tcp_listener, "Max inbound connections reached ({}), unable to accept new connection: {}", + logger.debug (nano::log::type::tcp_listener, "Max inbound connections reached: {}, unable to accept new connection: {}", count, ip.to_string ()); return accept_result::rejected; @@ -483,7 +492,7 @@ auto nano::transport::tcp_listener::check_limits (asio::ip::address const & ip, if (auto count = count_per_type (connection_type::outbound); count >= config.max_outbound_connections) { stats.inc (nano::stat::type::tcp_listener_rejected, nano::stat::detail::max_attempts, to_stat_dir (type)); - logger.debug (nano::log::type::tcp_listener, "Max outbound connections reached ({}), unable to initiate new connection: {}", + logger.debug (nano::log::type::tcp_listener, "Max outbound connections reached: {}, unable to initiate new connection: {}", count, ip.to_string ()); return accept_result::rejected; @@ -540,21 +549,15 @@ size_t nano::transport::tcp_listener::bootstrap_count () const size_t nano::transport::tcp_listener::count_per_type (connection_type type) const { debug_assert (!mutex.try_lock ()); - - return std::count_if (connections.begin (), connections.end (), [type] (auto const & connection) { - if (auto socket = connection.socket.lock ()) - { - return socket->endpoint_type () == to_socket_endpoint (type); - } - return false; + return std::count_if (connections.begin (), connections.end (), [&] (auto const & connection) { + return connection.type == type; }); } size_t nano::transport::tcp_listener::count_per_ip (asio::ip::address const & ip) const { debug_assert (!mutex.try_lock ()); - - return std::count_if (connections.begin (), connections.end (), [&ip] (auto const & connection) { + return std::count_if (connections.begin (), connections.end (), [&] (auto const & connection) { return nano::transport::is_same_ip (connection.address (), ip); }); } @@ -562,8 +565,7 @@ size_t nano::transport::tcp_listener::count_per_ip (asio::ip::address const & ip size_t nano::transport::tcp_listener::count_per_subnetwork (asio::ip::address const & ip) const { debug_assert (!mutex.try_lock ()); - - return std::count_if (connections.begin (), connections.end (), [this, &ip] (auto const & connection) { + return std::count_if (connections.begin (), connections.end (), [&] (auto const & connection) { return nano::transport::is_same_subnetwork (connection.address (), ip); }); } @@ -571,8 +573,7 @@ size_t nano::transport::tcp_listener::count_per_subnetwork (asio::ip::address co size_t nano::transport::tcp_listener::count_attempts (asio::ip::address const & ip) const { debug_assert (!mutex.try_lock ()); - - return std::count_if (attempts.begin (), attempts.end (), [&ip] (auto const & attempt) { + return std::count_if (attempts.begin (), attempts.end (), [&] (auto const & attempt) { return nano::transport::is_same_ip (attempt.address (), ip); }); } diff --git a/nano/node/transport/tcp_listener.hpp b/nano/node/transport/tcp_listener.hpp index 55c63da64a..5503d47f70 100644 --- a/nano/node/transport/tcp_listener.hpp +++ b/nano/node/transport/tcp_listener.hpp @@ -1,9 +1,12 @@ #pragma once #include -#include +#include +#include +#include #include #include +#include #include #include @@ -22,29 +25,6 @@ namespace asio = boost::asio; namespace nano::transport { -class tcp_config -{ -public: - explicit tcp_config (nano::network_constants const & network) - { - if (network.is_dev_network ()) - { - max_inbound_connections = 128; - max_outbound_connections = 128; - max_attempts = 128; - max_attempts_per_ip = 128; - connect_timeout = std::chrono::seconds{ 5 }; - } - } - -public: - size_t max_inbound_connections{ 2048 }; - size_t max_outbound_connections{ 2048 }; - size_t max_attempts{ 60 }; - size_t max_attempts_per_ip{ 1 }; - std::chrono::seconds connect_timeout{ 60 }; -}; - /** * Server side portion of tcp sessions. Listens for new socket connections and spawns tcp_server objects when connected. */ @@ -78,13 +58,13 @@ class tcp_listener final size_t realtime_count () const; size_t bootstrap_count () const; - std::vector> sockets () const; - std::vector> servers () const; + std::vector> sockets () const; + std::vector> servers () const; nano::container_info container_info () const; public: // Events - using connection_accepted_event_t = nano::observer_set const &, std::shared_ptr>; + using connection_accepted_event_t = nano::observer_set, std::shared_ptr>; connection_accepted_event_t connection_accepted; private: // Dependencies @@ -94,6 +74,7 @@ class tcp_listener final nano::logger & logger; private: + asio::awaitable start_impl (); asio::awaitable run (); asio::awaitable wait_available_slots () const; @@ -131,9 +112,10 @@ class tcp_listener final private: struct connection { + connection_type type; asio::ip::tcp::endpoint endpoint; - std::weak_ptr socket; - std::weak_ptr server; + std::weak_ptr socket; + std::weak_ptr server; asio::ip::address address () const { @@ -176,4 +158,4 @@ class tcp_listener final static std::string_view to_string (connection_type); static nano::transport::socket_endpoint to_socket_endpoint (connection_type); }; -} \ No newline at end of file +} diff --git a/nano/node/transport/tcp_server.cpp b/nano/node/transport/tcp_server.cpp index 881da00c50..1097301cb8 100644 --- a/nano/node/transport/tcp_server.cpp +++ b/nano/node/transport/tcp_server.cpp @@ -1,5 +1,3 @@ -#include -#include #include #include #include @@ -516,79 +514,26 @@ nano::transport::tcp_server::bootstrap_message_visitor::bootstrap_message_visito void nano::transport::tcp_server::bootstrap_message_visitor::bulk_pull (const nano::bulk_pull & message) { - auto node = server->node.lock (); - if (!node) - { - return; - } - if (node->flags.disable_bootstrap_bulk_pull_server) - { - return; - } - - node->bootstrap_workers.post ([server = server, message = message] () { - // TODO: Add completion callback to bulk pull server - // TODO: There should be no need to re-copy message as unique pointer, refactor those bulk/frontier pull/push servers - auto bulk_pull_server = std::make_shared (server, std::make_unique (message)); - bulk_pull_server->send_next (); - }); - - processed = true; + // Ignored since V28 + // TODO: Abort connection? } void nano::transport::tcp_server::bootstrap_message_visitor::bulk_pull_account (const nano::bulk_pull_account & message) { - auto node = server->node.lock (); - if (!node) - { - return; - } - if (node->flags.disable_bootstrap_bulk_pull_server) - { - return; - } - - node->bootstrap_workers.post ([server = server, message = message] () { - // TODO: Add completion callback to bulk pull server - // TODO: There should be no need to re-copy message as unique pointer, refactor those bulk/frontier pull/push servers - auto bulk_pull_account_server = std::make_shared (server, std::make_unique (message)); - bulk_pull_account_server->send_frontier (); - }); - - processed = true; + // Ignored since V28 + // TODO: Abort connection? } void nano::transport::tcp_server::bootstrap_message_visitor::bulk_push (const nano::bulk_push &) { - auto node = server->node.lock (); - if (!node) - { - return; - } - node->bootstrap_workers.post ([server = server] () { - // TODO: Add completion callback to bulk pull server - auto bulk_push_server = std::make_shared (server); - bulk_push_server->throttled_receive (); - }); - - processed = true; + // Ignored since V28 + // TODO: Abort connection? } void nano::transport::tcp_server::bootstrap_message_visitor::frontier_req (const nano::frontier_req & message) { - auto node = server->node.lock (); - if (!node) - { - return; - } - - node->bootstrap_workers.post ([server = server, message = message] () { - // TODO: There should be no need to re-copy message as unique pointer, refactor those bulk/frontier pull/push servers - auto response = std::make_shared (server, std::make_unique (message)); - response->send_next (); - }); - - processed = true; + // Ignored since V28 + // TODO: Abort connection? } /* diff --git a/nano/node/transport/tcp_server.hpp b/nano/node/transport/tcp_server.hpp index 96085ae8b1..c4971464d7 100644 --- a/nano/node/transport/tcp_server.hpp +++ b/nano/node/transport/tcp_server.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include diff --git a/nano/node/transport/tcp_socket.cpp b/nano/node/transport/tcp_socket.cpp index e85ee7a894..07bc9a4ec8 100644 --- a/nano/node/transport/tcp_socket.cpp +++ b/nano/node/transport/tcp_socket.cpp @@ -5,8 +5,6 @@ #include #include -#include - #include #include #include @@ -18,13 +16,14 @@ * socket */ -nano::transport::tcp_socket::tcp_socket (nano::node & node_a, nano::transport::socket_endpoint endpoint_type_a, std::size_t max_queue_size_a) : - tcp_socket{ node_a, boost::asio::ip::tcp::socket{ node_a.io_ctx }, {}, {}, endpoint_type_a, max_queue_size_a } +nano::transport::tcp_socket::tcp_socket (nano::node & node_a, nano::transport::socket_endpoint endpoint_type_a, size_t queue_size_a) : + tcp_socket{ node_a, boost::asio::ip::tcp::socket{ node_a.io_ctx }, {}, {}, endpoint_type_a, queue_size_a } { } -nano::transport::tcp_socket::tcp_socket (nano::node & node_a, boost::asio::ip::tcp::socket raw_socket_a, boost::asio::ip::tcp::endpoint remote_endpoint_a, boost::asio::ip::tcp::endpoint local_endpoint_a, nano::transport::socket_endpoint endpoint_type_a, std::size_t max_queue_size_a) : - send_queue{ max_queue_size_a }, +nano::transport::tcp_socket::tcp_socket (nano::node & node_a, boost::asio::ip::tcp::socket raw_socket_a, boost::asio::ip::tcp::endpoint remote_endpoint_a, boost::asio::ip::tcp::endpoint local_endpoint_a, nano::transport::socket_endpoint endpoint_type_a, size_t queue_size_a) : + queue_size{ queue_size_a }, + send_queue{ queue_size }, node_w{ node_a.shared () }, strand{ node_a.io_ctx.get_executor () }, raw_socket{ std::move (raw_socket_a) }, @@ -35,8 +34,7 @@ nano::transport::tcp_socket::tcp_socket (nano::node & node_a, boost::asio::ip::t last_completion_time_or_init{ nano::seconds_since_epoch () }, last_receive_time_or_init{ nano::seconds_since_epoch () }, default_timeout{ node_a.config.tcp_io_timeout }, - silent_connection_tolerance_time{ node_a.network_params.network.silent_connection_tolerance_time }, - max_queue_size{ max_queue_size_a } + silent_connection_tolerance_time{ node_a.network_params.network.silent_connection_tolerance_time } { } @@ -61,8 +59,7 @@ void nano::transport::tcp_socket::async_connect (nano::tcp_endpoint const & endp boost::asio::post (strand, [this_l = shared_from_this (), endpoint_a, callback = std::move (callback_a)] () { this_l->raw_socket.async_connect (endpoint_a, - boost::asio::bind_executor (this_l->strand, - [this_l, callback = std::move (callback), endpoint_a] (boost::system::error_code const & ec) { + boost::asio::bind_executor (this_l->strand, [this_l, callback = std::move (callback), endpoint_a] (boost::system::error_code const & ec) { debug_assert (this_l->strand.running_in_this_thread ()); auto node_l = this_l->node_w.lock (); @@ -72,6 +69,7 @@ void nano::transport::tcp_socket::async_connect (nano::tcp_endpoint const & endp } this_l->remote = endpoint_a; + if (ec) { node_l->stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_connect_error, nano::stat::dir::in); @@ -85,7 +83,10 @@ void nano::transport::tcp_socket::async_connect (nano::tcp_endpoint const & endp boost::system::error_code ec; this_l->local = this_l->raw_socket.local_endpoint (ec); } - node_l->observers.socket_connected.notify (*this_l); + + node_l->logger.debug (nano::log::type::tcp_socket, "Successfully connected to: {}, local: {}", + fmt::streamed (this_l->remote), + fmt::streamed (this_l->local)); } callback (ec); })); @@ -137,7 +138,7 @@ void nano::transport::tcp_socket::async_read (std::shared_ptr callback_a, nano::transport::traffic_type traffic_type) +void nano::transport::tcp_socket::async_write (nano::shared_const_buffer const & buffer_a, std::function callback_a) { auto node_l = node_w.lock (); if (!node_l) @@ -149,19 +150,19 @@ void nano::transport::tcp_socket::async_write (nano::shared_const_buffer const & { if (callback_a) { - node_l->background ([callback = std::move (callback_a)] () { + node_l->io_ctx.post ([callback = std::move (callback_a)] () { callback (boost::system::errc::make_error_code (boost::system::errc::not_supported), 0); }); } return; } - bool queued = send_queue.insert (buffer_a, callback_a, traffic_type); + bool queued = send_queue.insert (buffer_a, callback_a, traffic_type::generic); if (!queued) { if (callback_a) { - node_l->background ([callback = std::move (callback_a)] () { + node_l->io_ctx.post ([callback = std::move (callback_a)] () { callback (boost::system::errc::make_error_code (boost::system::errc::not_supported), 0); }); } @@ -186,17 +187,18 @@ void nano::transport::tcp_socket::write_queued_messages () return; } - auto next = send_queue.pop (); - if (!next) + auto maybe_next = send_queue.pop (); + if (!maybe_next) { return; } + auto const & [next, type] = *maybe_next; set_default_timeout (); write_in_progress = true; - nano::async_write (raw_socket, next->buffer, - boost::asio::bind_executor (strand, [this_l = shared_from_this (), next /* `next` object keeps buffer in scope */] (boost::system::error_code ec, std::size_t size) { + nano::async_write (raw_socket, next.buffer, + boost::asio::bind_executor (strand, [this_l = shared_from_this (), next /* `next` object keeps buffer in scope */, type] (boost::system::error_code ec, std::size_t size) { debug_assert (this_l->strand.running_in_this_thread ()); auto node_l = this_l->node_w.lock (); @@ -217,9 +219,9 @@ void nano::transport::tcp_socket::write_queued_messages () this_l->set_last_completion (); } - if (next->callback) + if (next.callback) { - next->callback (ec, size); + next.callback (ec, size); } if (!ec) @@ -229,14 +231,14 @@ void nano::transport::tcp_socket::write_queued_messages () })); } -bool nano::transport::tcp_socket::max (nano::transport::traffic_type traffic_type) const +bool nano::transport::tcp_socket::max () const { - return send_queue.size (traffic_type) >= max_queue_size; + return send_queue.size (traffic_type::generic) >= queue_size; } -bool nano::transport::tcp_socket::full (nano::transport::traffic_type traffic_type) const +bool nano::transport::tcp_socket::full () const { - return send_queue.size (traffic_type) >= 2 * max_queue_size; + return send_queue.size (traffic_type::generic) >= 2 * queue_size; } /** Call set_timeout with default_timeout as parameter */ @@ -315,8 +317,8 @@ void nano::transport::tcp_socket::ongoing_checkup () if (condition_to_disconnect) { - node_l->logger.debug (nano::log::type::tcp_server, "Closing socket due to timeout ({})", nano::util::to_str (this_l->remote)); - + // TODO: Stats + node_l->logger.debug (nano::log::type::tcp_socket, "Socket timeout, closing: {}", fmt::streamed (this_l->remote)); this_l->timed_out = true; this_l->close (); } @@ -392,7 +394,14 @@ void nano::transport::tcp_socket::close_internal () if (ec) { node_l->stats.inc (nano::stat::type::socket, nano::stat::detail::error_socket_close); - node_l->logger.error (nano::log::type::socket, "Failed to close socket gracefully: {} ({})", ec.message (), nano::util::to_str (remote)); + node_l->logger.error (nano::log::type::tcp_socket, "Failed to close socket gracefully: {} ({})", + fmt::streamed (remote), + ec.message ()); + } + else + { + // TODO: Stats + node_l->logger.debug (nano::log::type::tcp_socket, "Closed socket: {}", fmt::streamed (remote)); } } @@ -412,7 +421,7 @@ void nano::transport::tcp_socket::operator() (nano::object_stream & obs) const { obs.write ("remote_endpoint", remote_endpoint ()); obs.write ("local_endpoint", local_endpoint ()); - obs.write ("type", type_m); + obs.write ("type", type_m.load ()); obs.write ("endpoint_type", endpoint_type_m); } @@ -436,17 +445,17 @@ bool nano::transport::socket_queue::insert (const buffer_t & buffer, callback_t return false; // Not queued } -std::optional nano::transport::socket_queue::pop () +auto nano::transport::socket_queue::pop () -> std::optional { nano::lock_guard guard{ mutex }; - auto try_pop = [this] (nano::transport::traffic_type type) -> std::optional { + auto try_pop = [this] (nano::transport::traffic_type type) -> std::optional { auto & que = queues[type]; if (!que.empty ()) { auto item = que.front (); que.pop (); - return item; + return std::make_pair (item, type); } return std::nullopt; }; @@ -456,10 +465,6 @@ std::optional nano::transport::socket_queu { return item; } - if (auto item = try_pop (nano::transport::traffic_type::bootstrap)) - { - return item; - } return std::nullopt; } @@ -488,45 +493,6 @@ bool nano::transport::socket_queue::empty () const }); } -/* - * socket_functions - */ - -boost::asio::ip::network_v6 nano::transport::socket_functions::get_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) -{ - return boost::asio::ip::make_network_v6 (ip_address, static_cast (network_prefix)); -} - -boost::asio::ip::address nano::transport::socket_functions::first_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) -{ - auto range = get_ipv6_subnet_address (ip_address, network_prefix).hosts (); - debug_assert (!range.empty ()); - return *(range.begin ()); -} - -boost::asio::ip::address nano::transport::socket_functions::last_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) -{ - auto range = get_ipv6_subnet_address (ip_address, network_prefix).hosts (); - debug_assert (!range.empty ()); - return *(--range.end ()); -} - -std::size_t nano::transport::socket_functions::count_subnetwork_connections ( -nano::transport::address_socket_mmap const & per_address_connections, -boost::asio::ip::address_v6 const & remote_address, -std::size_t network_prefix) -{ - auto range = get_ipv6_subnet_address (remote_address, network_prefix).hosts (); - if (range.empty ()) - { - return 0; - } - auto const first_ip = first_ipv6_subnet_address (remote_address, network_prefix); - auto const last_ip = last_ipv6_subnet_address (remote_address, network_prefix); - auto const counted_connections = std::distance (per_address_connections.lower_bound (first_ip), per_address_connections.upper_bound (last_ip)); - return counted_connections; -} - /* * */ diff --git a/nano/node/transport/tcp_socket.hpp b/nano/node/transport/tcp_socket.hpp index 5eadaa2e36..6e1ba40da4 100644 --- a/nano/node/transport/tcp_socket.hpp +++ b/nano/node/transport/tcp_socket.hpp @@ -42,10 +42,12 @@ class socket_queue final }; public: + using result_t = std::pair; + explicit socket_queue (std::size_t max_size); bool insert (buffer_t const &, callback_t, nano::transport::traffic_type); - std::optional pop (); + std::optional pop (); void clear (); std::size_t size (nano::transport::traffic_type) const; bool empty () const; @@ -65,10 +67,10 @@ class tcp_socket final : public std::enable_shared_from_this friend class tcp_listener; public: - static std::size_t constexpr default_max_queue_size = 128; + static size_t constexpr default_queue_size = 16; public: - explicit tcp_socket (nano::node &, nano::transport::socket_endpoint = socket_endpoint::client, std::size_t max_queue_size = default_max_queue_size); + explicit tcp_socket (nano::node &, nano::transport::socket_endpoint = socket_endpoint::client, size_t queue_size = default_queue_size); // TODO: Accepting remote/local endpoints as a parameter is unnecessary, but is needed for now to keep compatibility with the legacy code tcp_socket ( @@ -77,7 +79,7 @@ class tcp_socket final : public std::enable_shared_from_this boost::asio::ip::tcp::endpoint remote_endpoint, boost::asio::ip::tcp::endpoint local_endpoint, nano::transport::socket_endpoint = socket_endpoint::server, - std::size_t max_queue_size = default_max_queue_size); + size_t queue_size = default_queue_size); ~tcp_socket (); @@ -95,8 +97,7 @@ class tcp_socket final : public std::enable_shared_from_this void async_write ( nano::shared_const_buffer const &, - std::function callback = {}, - traffic_type = traffic_type::generic); + std::function callback = nullptr); boost::asio::ip::tcp::endpoint remote_endpoint () const; boost::asio::ip::tcp::endpoint local_endpoint () const; @@ -108,16 +109,16 @@ class tcp_socket final : public std::enable_shared_from_this std::chrono::seconds get_default_timeout_value () const; void set_timeout (std::chrono::seconds); - bool max (nano::transport::traffic_type = traffic_type::generic) const; - bool full (nano::transport::traffic_type = traffic_type::generic) const; + bool max () const; + bool full () const; nano::transport::socket_type type () const { return type_m; }; - void type_set (nano::transport::socket_type type_a) + void type_set (nano::transport::socket_type type) { - type_m = type_a; + type_m = type; } nano::transport::socket_endpoint endpoint_type () const { @@ -141,6 +142,7 @@ class tcp_socket final : public std::enable_shared_from_this } private: + size_t const queue_size; socket_queue send_queue; protected: @@ -195,23 +197,10 @@ class tcp_socket final : public std::enable_shared_from_this void read_impl (std::shared_ptr> const & data_a, std::size_t size_a, std::function callback_a); private: - nano::transport::socket_type type_m{ socket_type::undefined }; - nano::transport::socket_endpoint endpoint_type_m; - -public: - std::size_t const max_queue_size; + socket_endpoint const endpoint_type_m; + std::atomic type_m{ socket_type::undefined }; public: // Logging virtual void operator() (nano::object_stream &) const; }; - -using address_socket_mmap = std::multimap>; - -namespace socket_functions -{ - boost::asio::ip::network_v6 get_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); - boost::asio::ip::address first_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); - boost::asio::ip::address last_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); - std::size_t count_subnetwork_connections (nano::transport::address_socket_mmap const &, boost::asio::ip::address_v6 const &, std::size_t); -} } diff --git a/nano/node/transport/traffic_type.cpp b/nano/node/transport/traffic_type.cpp new file mode 100644 index 0000000000..9d170c7133 --- /dev/null +++ b/nano/node/transport/traffic_type.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +#include + +std::string_view nano::transport::to_string (nano::transport::traffic_type type) +{ + return nano::enum_util::name (type); +} + +std::vector nano::transport::all_traffic_types () +{ + return nano::enum_util::values (); +} + +nano::stat::detail nano::transport::to_stat_detail (nano::transport::traffic_type type) +{ + return nano::enum_util::cast (type); +} \ No newline at end of file diff --git a/nano/node/transport/traffic_type.hpp b/nano/node/transport/traffic_type.hpp index 308a9768eb..f0f451b3a7 100644 --- a/nano/node/transport/traffic_type.hpp +++ b/nano/node/transport/traffic_type.hpp @@ -1,13 +1,31 @@ #pragma once +#include + +#include +#include + namespace nano::transport { -/** - * Used for message prioritization and bandwidth limits - */ enum class traffic_type { generic, - bootstrap, // Ascending bootstrap (asc_pull_ack, asc_pull_req) traffic + bootstrap_server, + bootstrap_requests, + block_broadcast, + block_broadcast_initial, + block_broadcast_rpc, + confirmation_requests, + keepalive, + vote, + vote_rebroadcast, + vote_reply, + rep_crawler, + telemetry, + test, }; + +std::string_view to_string (traffic_type); +std::vector all_traffic_types (); +nano::stat::detail to_stat_detail (traffic_type); } \ No newline at end of file diff --git a/nano/node/transport/transport.cpp b/nano/node/transport/transport.cpp index 4a90626f9e..2b7d83630b 100644 --- a/nano/node/transport/transport.cpp +++ b/nano/node/transport/transport.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -164,4 +164,62 @@ bool nano::transport::reserved_address (nano::endpoint const & endpoint_a, bool } } return result; +} + +nano::stat::detail nano::to_stat_detail (boost::system::error_code const & ec) +{ + switch (ec.value ()) + { + case boost::system::errc::success: + return nano::stat::detail::success; + case boost::system::errc::no_buffer_space: + return nano::stat::detail::no_buffer_space; + case boost::system::errc::timed_out: + return nano::stat::detail::timed_out; + case boost::system::errc::host_unreachable: + return nano::stat::detail::host_unreachable; + case boost::system::errc::not_supported: + return nano::stat::detail::not_supported; + default: + return nano::stat::detail::other; + } +} + +/* + * socket_functions + */ + +boost::asio::ip::network_v6 nano::transport::socket_functions::get_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) +{ + return boost::asio::ip::make_network_v6 (ip_address, static_cast (network_prefix)); +} + +boost::asio::ip::address nano::transport::socket_functions::first_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) +{ + auto range = get_ipv6_subnet_address (ip_address, network_prefix).hosts (); + debug_assert (!range.empty ()); + return *(range.begin ()); +} + +boost::asio::ip::address nano::transport::socket_functions::last_ipv6_subnet_address (boost::asio::ip::address_v6 const & ip_address, std::size_t network_prefix) +{ + auto range = get_ipv6_subnet_address (ip_address, network_prefix).hosts (); + debug_assert (!range.empty ()); + return *(--range.end ()); +} + +std::size_t nano::transport::socket_functions::count_subnetwork_connections ( +nano::transport::address_socket_mmap const & per_address_connections, +boost::asio::ip::address_v6 const & remote_address, +std::size_t network_prefix) +{ + auto range = get_ipv6_subnet_address (remote_address, network_prefix).hosts (); + if (range.empty ()) + { + return 0; + } + auto const first_ip = first_ipv6_subnet_address (remote_address, network_prefix); + auto const last_ip = last_ipv6_subnet_address (remote_address, network_prefix); + auto const counted_connections = std::distance (per_address_connections.lower_bound (first_ip), per_address_connections.upper_bound (last_ip)); + return counted_connections; } \ No newline at end of file diff --git a/nano/node/transport/transport.hpp b/nano/node/transport/transport.hpp index 3065bbd049..0d87402ba2 100644 --- a/nano/node/transport/transport.hpp +++ b/nano/node/transport/transport.hpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -24,4 +24,19 @@ bool is_same_subnetwork (boost::asio::ip::address const &, boost::asio::ip::addr // Unassigned, reserved, self bool reserved_address (nano::endpoint const &, bool allow_local_peers = false); + +using address_socket_mmap = std::multimap>; + +namespace socket_functions +{ + boost::asio::ip::network_v6 get_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); + boost::asio::ip::address first_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); + boost::asio::ip::address last_ipv6_subnet_address (boost::asio::ip::address_v6 const &, std::size_t); + std::size_t count_subnetwork_connections (nano::transport::address_socket_mmap const &, boost::asio::ip::address_v6 const &, std::size_t); +} +} + +namespace nano +{ +nano::stat::detail to_stat_detail (boost::system::error_code const &); } \ No newline at end of file diff --git a/nano/node/vote_cache.cpp b/nano/node/vote_cache.cpp index 36f9c4adf9..89ddd8e650 100644 --- a/nano/node/vote_cache.cpp +++ b/nano/node/vote_cache.cpp @@ -1,8 +1,10 @@ +#include #include #include #include #include #include +#include #include @@ -200,6 +202,14 @@ std::vector> nano::vote_cache::find (const nano::blo return {}; } +bool nano::vote_cache::contains (const nano::block_hash & hash) const +{ + nano::lock_guard lock{ mutex }; + + auto & cache_by_hash = cache.get (); + return cache_by_hash.find (hash) != cache_by_hash.end (); +} + bool nano::vote_cache::erase (const nano::block_hash & hash) { nano::lock_guard lock{ mutex }; diff --git a/nano/node/vote_cache.hpp b/nano/node/vote_cache.hpp index c70bae9d37..93209a3203 100644 --- a/nano/node/vote_cache.hpp +++ b/nano/node/vote_cache.hpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include #include @@ -13,6 +15,7 @@ #include #include +#include #include #include #include @@ -21,16 +24,6 @@ namespace mi = boost::multi_index; -namespace nano -{ -class node; -class active_elections; -class election; -class vote; -enum class vote_code; -enum class vote_source; -} - namespace nano { class vote_cache_config final @@ -133,6 +126,7 @@ class vote_cache final * Tries to find an entry associated with block hash */ std::vector> find (nano::block_hash const & hash) const; + bool contains (nano::block_hash const & hash) const; /** * Removes an entry associated with block hash, does nothing if entry does not exist diff --git a/nano/node/vote_generator.cpp b/nano/node/vote_generator.cpp index e29c88cc97..c1651a69c1 100644 --- a/nano/node/vote_generator.cpp +++ b/nano/node/vote_generator.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -75,19 +76,20 @@ bool nano::vote_generator::should_vote (transaction_variant_t const & transactio void nano::vote_generator::start () { debug_assert (!thread.joinable ()); - thread = std::thread ([this] () { run (); }); - + thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::voting); + run (); + }); vote_generation_queue.start (); } void nano::vote_generator::stop () { vote_generation_queue.stop (); - - nano::unique_lock lock{ mutex }; - stopped = true; - - lock.unlock (); + { + nano::lock_guard lock{ mutex }; + stopped = true; + } condition.notify_all (); if (thread.joinable ()) @@ -171,12 +173,6 @@ std::size_t nano::vote_generator::generate (std::vector const &, std::shared_ptr const &)> action_a) -{ - release_assert (!reply_action); - reply_action = action_a; -} - void nano::vote_generator::broadcast (nano::unique_lock & lock_a) { debug_assert (lock_a.owns_lock ()); @@ -216,6 +212,10 @@ void nano::vote_generator::broadcast (nano::unique_lock & lock_a) void nano::vote_generator::reply (nano::unique_lock & lock_a, request_t && request_a) { + if (request_a.second->max (nano::transport::traffic_type::vote_reply)) + { + return; + } lock_a.unlock (); auto i (request_a.first.cbegin ()); auto n (request_a.first.cend ()); @@ -244,9 +244,11 @@ void nano::vote_generator::reply (nano::unique_lock & lock_a, reque if (!hashes.empty ()) { stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes, stat::dir::in, hashes.size ()); - vote (hashes, roots, [this, &channel = request_a.second] (std::shared_ptr const & vote_a) { - this->reply_action (vote_a, channel); - this->stats.inc (nano::stat::type::requests, nano::stat::detail::requests_generated_votes, stat::dir::in); + + vote (hashes, roots, [this, channel = request_a.second] (std::shared_ptr const & vote_a) { + nano::confirm_ack confirm{ config.network_params.network, vote_a }; + channel->send (confirm, nano::transport::traffic_type::vote_reply); + stats.inc (nano::stat::type::requests, nano::stat::detail::requests_generated_votes, stat::dir::in); }); } } @@ -283,16 +285,22 @@ void nano::vote_generator::broadcast_action (std::shared_ptr const & void nano::vote_generator::run () { - nano::thread_role::set (nano::thread_role::name::voting); nano::unique_lock lock{ mutex }; while (!stopped) { - condition.wait_for (lock, config.vote_generator_delay, [this] () { return broadcast_predicate () || !requests.empty (); }); + condition.wait_for (lock, config.vote_generator_delay, [this] () { + return stopped || broadcast_predicate () || !requests.empty (); + }); + + if (stopped) + { + return; + } if (broadcast_predicate ()) { broadcast (lock); - next_broadcast = std::chrono::steady_clock::now () + std::chrono::milliseconds (config.vote_generator_delay); + next_broadcast = std::chrono::steady_clock::now () + config.vote_generator_delay; } if (!requests.empty ()) @@ -306,11 +314,13 @@ void nano::vote_generator::run () bool nano::vote_generator::broadcast_predicate () const { + debug_assert (!mutex.try_lock ()); + if (candidates.size () >= nano::network::confirm_ack_hashes_max) { return true; } - if (candidates.size () > 0 && std::chrono::steady_clock::now () > next_broadcast) + if (!candidates.empty () && std::chrono::steady_clock::now () > next_broadcast) { return true; } @@ -326,4 +336,4 @@ nano::container_info nano::vote_generator::container_info () const info.put ("requests", requests.size ()); info.add ("queue", vote_generation_queue.container_info ()); return info; -} \ No newline at end of file +} diff --git a/nano/node/vote_generator.hpp b/nano/node/vote_generator.hpp index 76fc0c583a..dbec5fb25f 100644 --- a/nano/node/vote_generator.hpp +++ b/nano/node/vote_generator.hpp @@ -40,7 +40,6 @@ class vote_generator final void add (nano::root const &, nano::block_hash const &); /** Queue blocks for vote generation, returning the number of successful candidates.*/ std::size_t generate (std::vector> const & blocks_a, std::shared_ptr const & channel_a); - void set_reply_action (std::function const &, std::shared_ptr const &)>); void start (); void stop (); @@ -59,9 +58,6 @@ class vote_generator final bool should_vote (transaction_variant_t const &, nano::root const &, nano::block_hash const &) const; bool broadcast_predicate () const; -private: - std::function const &, std::shared_ptr &)> reply_action; // must be set only during initialization by using set_reply_action - private: // Dependencies nano::node_config const & config; nano::node & node; diff --git a/nano/node/vote_processor.cpp b/nano/node/vote_processor.cpp index 4d0c8ef547..283e78ac90 100644 --- a/nano/node/vote_processor.cpp +++ b/nano/node/vote_processor.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -376,4 +377,4 @@ nano::error nano::vote_processor_config::deserialize (nano::tomlconfig & toml) toml.get ("batch_size", batch_size); return toml.get_error (); -} \ No newline at end of file +} diff --git a/nano/node/vote_router.cpp b/nano/node/vote_router.cpp index acf3d069c3..75d641229b 100644 --- a/nano/node/vote_router.cpp +++ b/nano/node/vote_router.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -155,6 +156,13 @@ std::shared_ptr nano::vote_router::election (nano::block_hash co return nullptr; } +// This is meant to be a fast check and may return false positives if weak pointers have expired, but we don't care about that here +bool nano::vote_router::contains (nano::block_hash const & hash) const +{ + std::shared_lock lock{ mutex }; + return elections.contains (hash); +} + void nano::vote_router::start () { thread = std::thread{ [this] () { @@ -192,4 +200,4 @@ nano::container_info nano::vote_router::container_info () const nano::container_info info; info.put ("elections", elections); return info; -} \ No newline at end of file +} diff --git a/nano/node/vote_router.hpp b/nano/node/vote_router.hpp index ed2d3d09c5..e74f06705d 100644 --- a/nano/node/vote_router.hpp +++ b/nano/node/vote_router.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -39,6 +40,9 @@ class vote_router final vote_router (nano::vote_cache & cache, nano::recently_confirmed_cache & recently_confirmed); ~vote_router (); + void start (); + void stop (); + // Add a route for 'hash' to 'election' // Existing routes will be replaced // Election must hold the block for the hash being passed in @@ -54,9 +58,7 @@ class vote_router final std::unordered_map vote (std::shared_ptr const &, nano::vote_source = nano::vote_source::live, nano::block_hash filter = { 0 }); bool active (nano::block_hash const & hash) const; std::shared_ptr election (nano::block_hash const & hash) const; - - void start (); - void stop (); + bool contains (nano::block_hash const & hash) const; using vote_processed_event_t = nano::observer_set const &, nano::vote_source, std::unordered_map const &>; vote_processed_event_t vote_processed; diff --git a/nano/node/vote_spacing.hpp b/nano/node/vote_spacing.hpp index f46cd6352a..c5ee41827f 100644 --- a/nano/node/vote_spacing.hpp +++ b/nano/node/vote_spacing.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 18dd7df7a8..50c41c6490 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1,7 +1,9 @@ #include #include +#include #include #include +#include #include #include #include @@ -244,6 +246,10 @@ void nano::fan::value_set (nano::raw_key const & value_a) *(values[0]) ^= value_a; } +/* + * wallet_store + */ + // Wallet version number nano::account const nano::wallet_store::version_special{}; // Random number used to salt private key encryption @@ -667,17 +673,23 @@ void nano::kdf::phs (nano::raw_key & result_a, std::string const & password_a, n (void)success; } +/* + * wallet + */ + nano::wallet::wallet (bool & init_a, store::transaction & transaction_a, nano::wallets & wallets_a, std::string const & wallet_a) : lock_observer ([] (bool, bool) {}), store (init_a, wallets_a.kdf, transaction_a, wallets_a.env, wallets_a.node.config.random_representative (), wallets_a.node.config.password_fanout, wallet_a), - wallets (wallets_a) + wallets (wallets_a), + logger (wallets_a.logger) { } nano::wallet::wallet (bool & init_a, store::transaction & transaction_a, nano::wallets & wallets_a, std::string const & wallet_a, std::string const & json) : lock_observer ([] (bool, bool) {}), store (init_a, wallets_a.kdf, transaction_a, wallets_a.env, wallets_a.node.config.random_representative (), wallets_a.node.config.password_fanout, wallet_a, json), - wallets (wallets_a) + wallets (wallets_a), + logger (wallets_a.logger) { } @@ -708,7 +720,7 @@ bool nano::wallet::enter_password (store::transaction const & transaction_a, std auto result (store.attempt_password (transaction_a, password_a)); if (!result) { - wallets.node.logger.info (nano::log::type::wallet, "Wallet unlocked"); + logger.info (nano::log::type::wallet, "Wallet unlocked"); auto this_l = shared_from_this (); wallets.queue_wallet_action (nano::wallets::high_priority, this_l, [this_l] (nano::wallet & wallet) { @@ -718,7 +730,7 @@ bool nano::wallet::enter_password (store::transaction const & transaction_a, std } else { - wallets.node.logger.warn (nano::log::type::wallet, "Invalid password, wallet locked"); + logger.warn (nano::log::type::wallet, "Invalid password, wallet locked"); } lock_observer (result, password_a.empty ()); return result; @@ -730,6 +742,9 @@ nano::public_key nano::wallet::deterministic_insert (store::transaction const & if (store.valid_password (transaction_a)) { key = store.deterministic_insert (transaction_a); + + logger.info (nano::log::type::wallet, "Deterministically inserted new account: {}", key.to_account ()); + if (generate_work_a) { work_ensure (key, key); @@ -737,6 +752,8 @@ nano::public_key nano::wallet::deterministic_insert (store::transaction const & auto half_principal_weight (wallets.node.minimum_principal_weight () / 2); if (wallets.check_rep (key, half_principal_weight)) { + logger.info (nano::log::type::wallet, "New account qualified as a representative: {}", key.to_account ()); + nano::lock_guard lock{ representatives_mutex }; representatives.insert (key); } @@ -751,6 +768,9 @@ nano::public_key nano::wallet::deterministic_insert (uint32_t const index, bool if (store.valid_password (transaction)) { key = store.deterministic_insert (transaction, index); + + logger.info (nano::log::type::wallet, "Deterministically inserted new account: {}", key.to_account ()); + if (generate_work_a) { work_ensure (key, key); @@ -857,6 +877,11 @@ std::shared_ptr nano::wallet::receive_action (nano::block_hash cons nano::raw_key prv; if (!store.fetch (transaction, account_a, prv)) { + logger.info (nano::log::type::wallet, "Receiving block {} from account {}, amount: {}", + send_hash_a.to_string (), + account_a.to_account (), + pending_info->amount.number ().convert_to ()); + if (work_a == 0) { store.work_get (transaction, account_a, work_a); @@ -875,23 +900,27 @@ std::shared_ptr nano::wallet::receive_action (nano::block_hash cons } else { - wallets.node.logger.warn (nano::log::type::wallet, "Unable to receive, wallet locked"); + logger.warn (nano::log::type::wallet, "Unable to receive, wallet locked, block {} to account: {}", + send_hash_a.to_string (), + account_a.to_account ()); } } else { // Ledger doesn't have this marked as available to receive anymore + logger.warn (nano::log::type::wallet, "Not receiving block {}, block already received", send_hash_a.to_string ()); } } else { // Ledger doesn't have this block anymore. + logger.warn (nano::log::type::wallet, "Not receiving block {}, block no longer exists or pruned", send_hash_a.to_string ()); } } else { // Someone sent us something below the threshold of receiving - wallets.node.logger.warn (nano::log::type::wallet, "Not receiving block {} due to minimum receive threshold", send_hash_a.to_string ()); + logger.warn (nano::log::type::wallet, "Not receiving block {} due to minimum receive threshold", send_hash_a.to_string ()); } if (block != nullptr) { @@ -916,6 +945,10 @@ std::shared_ptr nano::wallet::change_action (nano::account const & auto existing (store.find (transaction, source_a)); if (existing != store.end (transaction) && !wallets.node.ledger.any.account_head (block_transaction, source_a).is_zero ()) { + logger.info (nano::log::type::wallet, "Changing representative for account {} to {}", + source_a.to_account (), + representative_a.to_account ()); + auto info = wallets.node.ledger.any.account_get (block_transaction, source_a); debug_assert (info); nano::raw_key prv; @@ -929,6 +962,16 @@ std::shared_ptr nano::wallet::change_action (nano::account const & block = std::make_shared (source_a, info->head, representative_a, info->balance, 0, prv, source_a, work_a); details.epoch = info->epoch (); } + else + { + logger.warn (nano::log::type::wallet, "Changing representative for account {} failed, wallet locked or account not found", + source_a.to_account ()); + } + } + else + { + logger.warn (nano::log::type::wallet, "Changing representative for account {} failed, wallet locked", + source_a.to_account ()); } } if (block != nullptr) @@ -950,7 +993,7 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so id_mdb_val = nano::store::lmdb::db_val (id_a->size (), const_cast (id_a->data ())); } - auto prepare_send = [&id_mdb_val, &wallets = this->wallets, &store = this->store, &source_a, &amount_a, &work_a, &account_a] (auto const & transaction) { + auto prepare_send = [this, &id_mdb_val, &wallets = this->wallets, &store = this->store, &source_a, &amount_a, &work_a, &account_a, &id_a] (auto const & transaction) { auto block_transaction = wallets.node.ledger.tx_begin_read (); auto error (false); auto cached_block (false); @@ -967,8 +1010,18 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so block = wallets.node.ledger.any.block_get (block_transaction, hash); if (block != nullptr) { + logger.warn (nano::log::type::wallet, "Block already exists for send action with id: {}, existing hash: {}", + id_a.value (), + hash.to_string ()); + cached_block = true; - wallets.node.network.flood_block (block, nano::transport::buffer_drop_policy::no_limiter_drop); + wallets.node.network.flood_block (block, nano::transport::traffic_type::block_broadcast_initial); + } + else + { + logger.warn (nano::log::type::wallet, "Block was not found in ledger for send action with id: {}, hash: {}", + id_a.value (), + hash.to_string ()); } } else if (status != MDB_NOTFOUND) @@ -986,6 +1039,11 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so auto balance (wallets.node.ledger.any.account_balance (block_transaction, source_a)); if (balance && balance.value ().number () >= amount_a) { + logger.info (nano::log::type::wallet, "Sending from account: {} to: {}, amount: {}", + source_a.to_account (), + account_a.to_account (), + amount_a.convert_to ()); + auto info = wallets.node.ledger.any.account_get (block_transaction, source_a); debug_assert (info); nano::raw_key prv; @@ -1008,6 +1066,13 @@ std::shared_ptr nano::wallet::send_action (nano::account const & so } } } + else + { + logger.warn (nano::log::type::wallet, "Insufficient balance for send from: {}, required: {} but available: {}", + account_a.to_account (), + amount_a.convert_to (), + balance ? balance.value ().number ().convert_to () : "unknown"); + } } } } @@ -1053,7 +1118,7 @@ bool nano::wallet::action_complete (std::shared_ptr const & block_a auto required_difficulty{ wallets.node.network_params.work.threshold (block_a->work_version (), details_a) }; if (wallets.node.network_params.work.difficulty (*block_a) < required_difficulty) { - wallets.node.logger.info (nano::log::type::wallet, "Cached or provided work for block {} account {} is invalid, regenerating...", + logger.info (nano::log::type::wallet, "Cached or provided work for block {} account {} is invalid, regenerating...", block_a->hash ().to_string (), account_a.to_account ()); @@ -1151,7 +1216,7 @@ void nano::wallet::work_update (store::transaction const & transaction_a, nano:: } else { - wallets.node.logger.warn (nano::log::type::wallet, "Cached work no longer valid, discarding"); + logger.warn (nano::log::type::wallet, "Cached work no longer valid, discarding"); } } @@ -1180,7 +1245,7 @@ bool nano::wallet::search_receivable (store::transaction const & wallet_transact auto result (!store.valid_password (wallet_transaction_a)); if (!result) { - wallets.node.logger.info (nano::log::type::wallet, "Beginning receivable block search"); + logger.info (nano::log::type::wallet, "Beginning receivable block search"); for (auto i (store.begin (wallet_transaction_a)), n (store.end (wallet_transaction_a)); i != n; ++i) { @@ -1197,7 +1262,7 @@ bool nano::wallet::search_receivable (store::transaction const & wallet_transact auto amount (pending.amount.number ()); if (wallets.node.config.receive_minimum.number () <= amount) { - wallets.node.logger.info (nano::log::type::wallet, "Found a receivable block {} for account {}", hash.to_string (), pending.source.to_account ()); + logger.info (nano::log::type::wallet, "Found a receivable block {} for account {}", hash.to_string (), pending.source.to_account ()); if (wallets.node.ledger.confirmed.block_exists_or_pruned (block_transaction, hash)) { @@ -1219,11 +1284,11 @@ bool nano::wallet::search_receivable (store::transaction const & wallet_transact } } - wallets.node.logger.info (nano::log::type::wallet, "Receivable block search phase complete"); + logger.info (nano::log::type::wallet, "Receivable block search phase complete"); } else { - wallets.node.logger.warn (nano::log::type::wallet, "Stopping search, wallet is locked"); + logger.warn (nano::log::type::wallet, "Unable to search receivable blocks, wallet is locked"); } return result; } @@ -1269,17 +1334,23 @@ uint32_t nano::wallet::deterministic_check (store::transaction const & transacti nano::public_key nano::wallet::change_seed (store::transaction const & transaction_a, nano::raw_key const & prv_a, uint32_t count) { + logger.info (nano::log::type::wallet, "Changing wallet seed"); + store.seed_set (transaction_a, prv_a); auto account = deterministic_insert (transaction_a); if (count == 0) { count = deterministic_check (transaction_a, 0); + logger.info (nano::log::type::wallet, "Auto-detected {} accounts to generate", count); } for (uint32_t i (0); i < count; ++i) { // Disable work generation to prevent weak CPU nodes stuck account = deterministic_insert (transaction_a, false); } + + logger.info (nano::log::type::wallet, "Completed changing wallet seed and generating accounts"); + return account; } @@ -1315,11 +1386,15 @@ void nano::wallet::work_cache_blocking (nano::account const & account_a, nano::r } else if (!wallets.node.stopped) { - wallets.node.logger.warn (nano::log::type::wallet, "Could not precache work for root {} due to work generation failure", root_a.to_string ()); + logger.warn (nano::log::type::wallet, "Could not precache work for root {} due to work generation failure", root_a.to_string ()); } } } +/* + * wallets + */ + void nano::wallets::do_wallet_actions () { nano::unique_lock action_lock{ action_mutex }; @@ -1352,6 +1427,7 @@ nano::wallets::wallets (bool error_a, nano::node & node_a) : observer ([] (bool) {}), kdf{ node_a.config.network_params.kdf_work }, node (node_a), + logger (node_a.logger), env (boost::polymorphic_downcast (node_a.wallets_store_impl.get ())->environment), stopped (false) { @@ -1411,16 +1487,38 @@ nano::wallets::wallets (bool error_a, nano::node & node_a) : { item.second->enter_initial_password (); } - if (node_a.config.enable_voting) +} + +nano::wallets::~wallets () +{ + stop (); +} + +void nano::wallets::start () +{ + thread = std::thread{ [this] () { + nano::thread_role::set (nano::thread_role::name::wallet_actions); + do_wallet_actions (); + } }; + + if (node.config.enable_voting) { - lock.unlock (); ongoing_compute_reps (); } } -nano::wallets::~wallets () +void nano::wallets::stop () { - stop (); + { + nano::lock_guard action_lock{ action_mutex }; + stopped = true; + actions.clear (); + } + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); + } } std::shared_ptr nano::wallets::open (nano::wallet_id const & id_a) @@ -1582,7 +1680,7 @@ void nano::wallets::foreach_representative (std::functionfirst.to_string ()); + logger.warn (nano::log::type::wallet, "Representative locked inside wallet: {}", i->first.to_string ()); } } } @@ -1608,28 +1706,6 @@ bool nano::wallets::exists (store::transaction const & transaction_a, nano::acco return result; } -void nano::wallets::stop () -{ - { - nano::lock_guard action_lock{ action_mutex }; - stopped = true; - actions.clear (); - } - condition.notify_all (); - if (thread.joinable ()) - { - thread.join (); - } -} - -void nano::wallets::start () -{ - thread = std::thread{ [this] () { - nano::thread_role::set (nano::thread_role::name::wallet_actions); - do_wallet_actions (); - } }; -} - nano::store::write_transaction nano::wallets::tx_begin_write () { return env.tx_begin_write (); @@ -1740,12 +1816,12 @@ void nano::wallets::receive_confirmed (nano::block_hash const & hash_a, nano::ac { if (!node.ledger.confirmed.block_exists_or_pruned (node.ledger.tx_begin_read (), hash_a)) { - node.logger.warn (nano::log::type::wallet, "Confirmed block is missing: {}", hash_a.to_string ()); - debug_assert (false, "Confirmed block is missing"); + logger.warn (nano::log::type::wallet, "Confirmed block is missing: {}", hash_a.to_string ()); + debug_assert (false, "confirmed block is missing"); } else { - node.logger.warn (nano::log::type::wallet, "Block has already been received: {}", hash_a.to_string ()); + logger.warn (nano::log::type::wallet, "Block has already been received: {}", hash_a.to_string ()); } } } diff --git a/nano/node/wallet.hpp b/nano/node/wallet.hpp index 9cb7aa50e7..0a7c4baf8a 100644 --- a/nano/node/wallet.hpp +++ b/nano/node/wallet.hpp @@ -172,6 +172,7 @@ class wallet final : public std::enable_shared_from_this std::function lock_observer; nano::wallet_store store; nano::wallets & wallets; + nano::logger & logger; nano::mutex representatives_mutex; std::unordered_set representatives; }; @@ -205,8 +206,12 @@ class wallet_representatives class wallets final { public: - wallets (bool, nano::node &); + wallets (bool error, nano::node &); ~wallets (); + + void start (); + void stop (); + std::shared_ptr open (nano::wallet_id const &); std::shared_ptr create (nano::wallet_id const &); bool search_receivable (nano::wallet_id const &); @@ -217,8 +222,6 @@ class wallets final void queue_wallet_action (nano::uint128_t const &, std::shared_ptr const &, std::function); void foreach_representative (std::function const &); bool exists (store::transaction const &, nano::account const &); - void start (); - void stop (); void clear_send_ids (store::transaction const &); nano::wallet_representatives reps () const; bool check_rep (nano::account const &, nano::uint128_t const &, bool const = true); @@ -240,6 +243,7 @@ class wallets final MDB_dbi handle; MDB_dbi send_action_ids; nano::node & node; + nano::logger & logger; nano::store::lmdb::env & env; std::atomic stopped; std::thread thread; diff --git a/nano/node/websocket.cpp b/nano/node/websocket.cpp index d2b8eab72d..32a68d2bb5 100644 --- a/nano/node/websocket.cpp +++ b/nano/node/websocket.cpp @@ -1,7 +1,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/nano/node/websocket.hpp b/nano/node/websocket.hpp index 2a38956060..1a5eb0dbb8 100644 --- a/nano/node/websocket.hpp +++ b/nano/node/websocket.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/nano/node/websocketconfig.hpp b/nano/node/websocketconfig.hpp index 5dc0e6acd9..9b44e44467 100644 --- a/nano/node/websocketconfig.hpp +++ b/nano/node/websocketconfig.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/nano/qt/qt.cpp b/nano/qt/qt.cpp index 5e23b6cc08..8665f79927 100644 --- a/nano/qt/qt.cpp +++ b/nano/qt/qt.cpp @@ -14,6 +14,8 @@ #include #include +#include + namespace { void show_line_error (QLineEdit & line) @@ -75,12 +77,14 @@ nano_qt::self_pane::self_pane (nano_qt::wallet & wallet_a, nano::account const & wallet (wallet_a) { your_account_label->setStyleSheet ("font-weight: bold;"); - std::string network = wallet.node.network_params.network.get_current_network_as_string (); + + // Capitalize the first letter + std::string network{ wallet.node.network_params.network.get_current_network_as_string () }; if (!network.empty ()) { network[0] = std::toupper (network[0]); } - version = new QLabel (boost::str (boost::format ("%1% %2% network") % NANO_VERSION_STRING % network).c_str ()); + version = new QLabel (fmt::format ("{} {} network", NANO_VERSION_STRING, network).c_str ()); self_layout->addWidget (your_account_label); self_layout->addStretch (); @@ -399,11 +403,6 @@ nano_qt::import::import (nano_qt::wallet & wallet_a) : { this->wallet.account = this->wallet.wallet_m->change_seed (transaction, seed_l); successful = true; - // Pending check for accounts to restore if bootstrap is in progress - if (this->wallet.node.bootstrap_initiator.in_progress ()) - { - this->wallet.needs_deterministic_restore = true; - } } else { @@ -763,7 +762,7 @@ nano_qt::block_viewer::block_viewer (nano_qt::wallet & wallet_a) : if (this->wallet.node.ledger.any.block_exists (transaction, block)) { rebroadcast->setEnabled (false); - this->wallet.node.background ([this, block] () { + this->wallet.node.workers.post ([this, block] () { rebroadcast_action (block); }); } @@ -784,7 +783,7 @@ void nano_qt::block_viewer::rebroadcast_action (nano::block_hash const & hash_a) auto block (wallet.node.ledger.any.block_get (transaction, hash_a)); if (block != nullptr) { - wallet.node.network.flood_block (block); + wallet.node.network.flood_block (block, nano::transport::traffic_type::block_broadcast_initial); auto successor = wallet.node.ledger.any.block_successor (transaction, hash_a); if (successor) { @@ -1243,7 +1242,7 @@ void nano_qt::wallet::start () if (this_l->wallet_m->store.valid_password (transaction)) { this_l->send_blocks_send->setEnabled (false); - this_l->node.background ([this_w, account_l, actual] () { + this_l->node.workers.post ([this_w, account_l, actual] () { if (auto this_l = this_w.lock ()) { this_l->wallet_m->send_async (this_l->account, account_l, actual, [this_w] (std::shared_ptr const & block_a) { @@ -1401,7 +1400,7 @@ void nano_qt::wallet::start () })); } }); - node.observers.endpoint.add ([this_w] (std::shared_ptr const &) { + node.observers.channel_connected.add ([this_w] (std::shared_ptr const &) { if (auto this_l = this_w.lock ()) { this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w] () { @@ -1423,31 +1422,6 @@ void nano_qt::wallet::start () })); } }); - node.bootstrap_initiator.add_observer ([this_w] (bool active_a) { - if (auto this_l = this_w.lock ()) - { - this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w, active_a] () { - if (auto this_l = this_w.lock ()) - { - if (active_a) - { - this_l->active_status.insert (nano_qt::status_types::synchronizing); - } - else - { - this_l->active_status.erase (nano_qt::status_types::synchronizing); - // Check for accounts to restore - if (this_l->needs_deterministic_restore) - { - this_l->needs_deterministic_restore = false; - auto transaction (this_l->wallet_m->wallets.tx_begin_write ()); - this_l->wallet_m->deterministic_restore (transaction); - } - } - } - })); - } - }); node.work.work_observers.add ([this_w] (bool working) { if (auto this_l = this_w.lock ()) { @@ -1829,7 +1803,6 @@ nano_qt::advanced_actions::advanced_actions (nano_qt::wallet & wallet_a) : bootstrap_label (new QLabel ("IPV6:port \"::ffff:192.168.0.1:7075\"")), peer_count_label (new QLabel ("")), bootstrap_line (new QLineEdit), - peers_bootstrap (new QPushButton ("Initiate Bootstrap")), peers_refresh (new QPushButton ("Refresh")), peers_back (new QPushButton ("Back")), wallet (wallet_a) @@ -1871,7 +1844,6 @@ nano_qt::advanced_actions::advanced_actions (nano_qt::wallet & wallet_a) : peer_summary_layout->addWidget (peer_count_label); peers_layout->addLayout (peer_summary_layout); peers_layout->addWidget (bootstrap_line); - peers_layout->addWidget (peers_bootstrap); peers_layout->addWidget (peers_refresh); peers_layout->addWidget (peers_back); peers_layout->setContentsMargins (0, 0, 0, 0); @@ -1931,20 +1903,6 @@ nano_qt::advanced_actions::advanced_actions (nano_qt::wallet & wallet_a) : QObject::connect (peers_back, &QPushButton::released, [this] () { this->wallet.pop_main_stack (); }); - QObject::connect (peers_bootstrap, &QPushButton::released, [this] () { - nano::endpoint endpoint; - auto error (nano::parse_endpoint (bootstrap_line->text ().toStdString (), endpoint)); - if (!error) - { - show_line_ok (*bootstrap_line); - bootstrap_line->clear (); - this->wallet.node.bootstrap_initiator.bootstrap (endpoint); - } - else - { - show_line_error (*bootstrap_line); - } - }); QObject::connect (peers_refresh, &QPushButton::released, [this] () { refresh_peers (); }); @@ -1958,7 +1916,6 @@ nano_qt::advanced_actions::advanced_actions (nano_qt::wallet & wallet_a) : std::thread ([this] { this->wallet.wallet_m->search_receivable (this->wallet.wallet_m->wallets.tx_begin_read ()); }).detach (); }); QObject::connect (bootstrap, &QPushButton::released, [this] () { - std::thread ([this] { this->wallet.node.bootstrap_initiator.bootstrap (); }).detach (); }); QObject::connect (create_block, &QPushButton::released, [this] () { this->wallet.push_main_stack (this->wallet.block_creation.window); diff --git a/nano/qt/qt.hpp b/nano/qt/qt.hpp index fd94052bbd..3b1c870d61 100644 --- a/nano/qt/qt.hpp +++ b/nano/qt/qt.hpp @@ -85,7 +85,6 @@ class advanced_actions QLabel * bootstrap_label; QLabel * peer_count_label; QLineEdit * bootstrap_line; - QPushButton * peers_bootstrap; QPushButton * peers_refresh; QPushButton * peers_back; diff --git a/nano/qt_test/entry.cpp b/nano/qt_test/entry.cpp index b3ce57fd8f..e0a4255078 100644 --- a/nano/qt_test/entry.cpp +++ b/nano/qt_test/entry.cpp @@ -1,5 +1,6 @@ +#include #include -#include +#include #include diff --git a/nano/qt_test/qt.cpp b/nano/qt_test/qt.cpp index 6e843e60dd..1d09b81ae3 100644 --- a/nano/qt_test/qt.cpp +++ b/nano/qt_test/qt.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -1032,38 +1033,6 @@ TEST (wallet, import_locked) system.wallet (0)->store.seed (seed3, transaction); ASSERT_EQ (seed1, seed3); } -// DISABLED: this always fails -TEST (wallet, DISABLED_synchronizing) -{ - nano_qt::eventloop_processor processor; - nano::test::system system0 (1); - nano::test::system system1 (1); - auto key1 (system0.wallet (0)->deterministic_insert ()); - auto wallet (std::make_shared (*test_application, processor, *system0.nodes[0], system0.wallet (0), key1)); - wallet->start (); - { - auto transaction = system1.nodes[0]->ledger.tx_begin_write (); - auto latest (system1.nodes[0]->ledger.any.account_head (transaction, nano::dev::genesis_key.pub)); - auto send = std::make_shared (latest, key1, 0, nano::dev::genesis_key.prv, nano::dev::genesis_key.pub, *system1.work.generate (latest)); - system1.nodes[0]->ledger.process (transaction, send); - } - ASSERT_EQ (0, wallet->active_status.active.count (nano_qt::status_types::synchronizing)); - system0.nodes[0]->bootstrap_initiator.bootstrap (system1.nodes[0]->network.endpoint ()); - system1.deadline_set (10s); - while (wallet->active_status.active.count (nano_qt::status_types::synchronizing) == 0) - { - ASSERT_NO_ERROR (system0.poll ()); - ASSERT_NO_ERROR (system1.poll ()); - test_application->processEvents (); - } - system1.deadline_set (25s); - while (wallet->active_status.active.count (nano_qt::status_types::synchronizing) == 1) - { - ASSERT_NO_ERROR (system0.poll ()); - ASSERT_NO_ERROR (system1.poll ()); - test_application->processEvents (); - } -} TEST (wallet, epoch_2_validation) { diff --git a/nano/rpc_test/entry.cpp b/nano/rpc_test/entry.cpp index 806f07645b..4cfa6e0b3d 100644 --- a/nano/rpc_test/entry.cpp +++ b/nano/rpc_test/entry.cpp @@ -1,6 +1,7 @@ +#include #include #include -#include +#include #include diff --git a/nano/rpc_test/receivable.cpp b/nano/rpc_test/receivable.cpp index 9199a14619..672a8f2584 100644 --- a/nano/rpc_test/receivable.cpp +++ b/nano/rpc_test/receivable.cpp @@ -133,7 +133,7 @@ TEST (rpc, receivable_unconfirmed) { nano::test::system system; nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, config); auto chain = nano::test::setup_chain (system, *node, 1, nano::dev::genesis_key, false); auto block1 = chain[0]; @@ -530,7 +530,7 @@ TEST (rpc, accounts_receivable_confirmed) { nano::test::system system; nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, config); auto chain = nano::test::setup_chain (system, *node, 1, nano::dev::genesis_key, false); auto block1 = chain[0]; diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index ce050df029..82c9837d5b 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -1,15 +1,19 @@ #include #include +#include #include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -22,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2482,44 +2487,6 @@ TEST (rpc, account_representative_set_epoch_2_insufficient_work) } } -TEST (rpc, bootstrap) -{ - nano::test::system system0; - auto node = add_ipc_enabled_node (system0); - nano::test::system system1 (1); - auto node1 = system1.nodes[0]; - auto latest (node1->latest (nano::dev::genesis_key.pub)); - nano::block_builder builder; - auto send = builder - .send () - .previous (latest) - .destination (nano::dev::genesis_key.pub) - .balance (100) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node1->work_generate_blocking (latest)) - .build (); - { - auto transaction = node1->ledger.tx_begin_write (); - ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction, send)); - } - auto const rpc_ctx = add_rpc (system0, node); - boost::property_tree::ptree request; - request.put ("action", "bootstrap"); - request.put ("address", "::ffff:127.0.0.1"); - request.put ("port", node1->network.endpoint ().port ()); - test_response response (request, rpc_ctx.rpc->listening_port (), *system0.io_ctx); - while (response.status == 0) - { - system0.poll (); - } - system1.deadline_set (10s); - while (node->latest (nano::dev::genesis_key.pub) != node1->latest (nano::dev::genesis_key.pub)) - { - ASSERT_NO_ERROR (system0.poll ()); - ASSERT_NO_ERROR (system1.poll ()); - } -} - TEST (rpc, account_remove) { nano::test::system system0; @@ -2768,33 +2735,6 @@ TEST (rpc, successors) ASSERT_EQ (response, response2); } -TEST (rpc, bootstrap_any) -{ - nano::test::system system0; - auto node = add_ipc_enabled_node (system0); - nano::test::system system1 (1); - auto latest (system1.nodes[0]->latest (nano::dev::genesis_key.pub)); - nano::block_builder builder; - auto send = builder - .send () - .previous (latest) - .destination (nano::dev::genesis_key.pub) - .balance (100) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system1.nodes[0]->work_generate_blocking (latest)) - .build (); - { - auto transaction = system1.nodes[0]->ledger.tx_begin_write (); - ASSERT_EQ (nano::block_status::progress, system1.nodes[0]->ledger.process (transaction, send)); - } - auto const rpc_ctx = add_rpc (system0, node); - boost::property_tree::ptree request; - request.put ("action", "bootstrap_any"); - auto response (wait_response (system0, rpc_ctx, request)); - std::string success (response.get ("success")); - ASSERT_TRUE (success.empty ()); -} - TEST (rpc, republish) { nano::test::system system; @@ -2985,7 +2925,7 @@ TEST (rpc, accounts_balances_unopened_account_with_receivables) { nano::test::system system; nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, config); // send a 1 raw to the unopened account which will have receivables @@ -3289,7 +3229,7 @@ TEST (rpc, pending_exists) { nano::test::system system; nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, config); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -3348,7 +3288,7 @@ TEST (rpc, wallet_receivable) { nano::test::system system; nano::node_config config; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, config); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -4412,7 +4352,7 @@ TEST (rpc, populate_backlog) nano::test::system system; nano::node_config node_config = system.default_config (); // Disable automatic backlog population - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = add_ipc_enabled_node (system, node_config); // Create and process a block that won't get automatically scheduled for confirmation @@ -6471,59 +6411,6 @@ TEST (rpc, epoch_upgrade_multithreaded) } } -// FIXME: This test is testing legacy bootstrap, the current behavior is different -TEST (rpc, DISABLED_account_lazy_start) -{ - nano::test::system system{}; - nano::node_flags node_flags{}; - node_flags.disable_legacy_bootstrap = true; - auto node1 = system.add_node (node_flags); - nano::keypair key{}; - nano::block_builder builder; - // Generating test chain - auto send1 = builder - .state () - .account (nano::dev::genesis_key.pub) - .previous (nano::dev::genesis->hash ()) - .representative (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) - .link (key.pub) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*system.work.generate (nano::dev::genesis->hash ())) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (send1)); - auto open = builder - .open () - .source (send1->hash ()) - .representative (key.pub) - .account (key.pub) - .sign (key.prv, key.pub) - .work (*system.work.generate (key.pub)) - .build (); - ASSERT_EQ (nano::block_status::progress, node1->process (open)); - - // Start lazy bootstrap with account - nano::node_config node_config = system.default_config (); - node_config.ipc_config.transport_tcp.enabled = true; - node_config.ipc_config.transport_tcp.port = system.get_available_port (); - auto node2 = system.add_node (node_config, node_flags); - nano::test::establish_tcp (system, *node2, node1->network.endpoint ()); - auto const rpc_ctx = add_rpc (system, node2); - boost::property_tree::ptree request; - request.put ("action", "account_info"); - request.put ("account", key.pub.to_account ()); - auto response = wait_response (system, rpc_ctx, request); - boost::optional account_error{ response.get_optional ("error") }; - ASSERT_TRUE (account_error.is_initialized ()); - - // Check processed blocks - ASSERT_TIMELY (10s, !node2->bootstrap_initiator.in_progress ()); - - // needs timed assert because the writing (put) operation is done by a different - // thread, it might not get done before DB get operation. - ASSERT_TIMELY (15s, nano::test::block_or_pruned_all_exists (*node2, { send1, open })); -} - TEST (rpc, receive) { nano::test::system system; diff --git a/nano/secure/CMakeLists.txt b/nano/secure/CMakeLists.txt index f11d6e85d2..a0cbea2a44 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -10,35 +10,9 @@ else() error("Unknown platform: ${CMAKE_SYSTEM_NAME}") endif() -# Embed bootstrap representative weights in executable. Both live and beta -# weights are added to accommodate switching networks from the command line. -file(READ ${CMAKE_SOURCE_DIR}/rep_weights_live.bin liveweights HEX) -string(REGEX REPLACE "(..)" "0x\\1," liveweights ${liveweights}) -file( - WRITE ${CMAKE_BINARY_DIR}/bootstrap_weights_live.cpp - "#include \n" - "namespace nano {\n" - " unsigned char nano_bootstrap_weights_live[] = {${liveweights} 0x00};\n" - " size_t nano_bootstrap_weights_live_size = sizeof(nano_bootstrap_weights_live) - 1;\n" - "}\n") - -if(EXISTS ${CMAKE_SOURCE_DIR}/rep_weights_beta.bin) - file(READ ${CMAKE_SOURCE_DIR}/rep_weights_beta.bin betaweights HEX) - string(REGEX REPLACE "(..)" "0x\\1," betaweights ${betaweights}) -endif() -file( - WRITE ${CMAKE_BINARY_DIR}/bootstrap_weights_beta.cpp - "#include \n" - "namespace nano {\n" - " unsigned char nano_bootstrap_weights_beta[] = {${betaweights} 0x00};\n" - " size_t nano_bootstrap_weights_beta_size = sizeof(nano_bootstrap_weights_beta) - 1;\n" - "}\n") - add_library( secure ${PLATFORM_SECURE_SOURCE} - ${CMAKE_BINARY_DIR}/bootstrap_weights_live.cpp - ${CMAKE_BINARY_DIR}/bootstrap_weights_beta.cpp account_info.hpp account_info.cpp account_iterator.cpp @@ -46,6 +20,7 @@ add_library( account_iterator_impl.hpp common.hpp common.cpp + fwd.hpp generate_cache_flags.hpp generate_cache_flags.cpp ledger.hpp diff --git a/nano/secure/account_info.cpp b/nano/secure/account_info.cpp index 1b18ca6024..81bcec0c6b 100644 --- a/nano/secure/account_info.cpp +++ b/nano/secure/account_info.cpp @@ -1,3 +1,4 @@ +#include #include nano::account_info::account_info (nano::block_hash const & head_a, nano::account const & representative_a, nano::block_hash const & open_block_a, nano::amount const & balance_a, nano::seconds_t modified_a, uint64_t block_count_a, nano::epoch epoch_a) : @@ -90,4 +91,4 @@ bool nano::account_info_v22::deserialize (nano::stream & stream_a) } return error; -} \ No newline at end of file +} diff --git a/nano/secure/account_info.hpp b/nano/secure/account_info.hpp index ee850d8d2f..a40136f64f 100644 --- a/nano/secure/account_info.hpp +++ b/nano/secure/account_info.hpp @@ -1,8 +1,8 @@ #pragma once #include +#include #include -#include #include namespace nano diff --git a/nano/secure/account_iterator_impl.hpp b/nano/secure/account_iterator_impl.hpp index a68f70b352..af8d0a7a19 100644 --- a/nano/secure/account_iterator_impl.hpp +++ b/nano/secure/account_iterator_impl.hpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index f5fd8e44fc..9b4c7f5ddb 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -1,9 +1,12 @@ +#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -74,17 +77,40 @@ std::shared_ptr parse_block_from_genesis_data (std::string const & } } +/* + * nano::dev constants + */ + nano::keypair nano::dev::genesis_key{ dev_private_key_data }; nano::network_params nano::dev::network_params{ nano::networks::nano_dev_network }; nano::ledger_constants & nano::dev::constants{ nano::dev::network_params.ledger }; std::shared_ptr & nano::dev::genesis = nano::dev::constants.genesis; -nano::network_params::network_params (nano::networks network_a) : - work{ network_a == nano::networks::nano_live_network ? nano::work_thresholds::publish_full : network_a == nano::networks::nano_beta_network ? nano::work_thresholds::publish_beta - : network_a == nano::networks::nano_test_network ? nano::work_thresholds::publish_test - : nano::work_thresholds::publish_dev }, - network{ work, network_a }, - ledger{ work, network_a }, +/* + * + */ + +nano::work_thresholds const & nano::work_thresholds_for_network (nano::networks network_type) +{ + switch (network_type) + { + case nano::networks::nano_live_network: + return nano::work_thresholds::publish_full; + case nano::networks::nano_beta_network: + return nano::work_thresholds::publish_beta; + case nano::networks::nano_dev_network: + return nano::work_thresholds::publish_dev; + case nano::networks::nano_test_network: + return nano::work_thresholds::publish_test; + default: + release_assert (false, "invalid network"); + } +} + +nano::network_params::network_params (nano::networks network_type) : + work{ work_thresholds_for_network (network_type) }, + network{ work, network_type }, + ledger{ work, network_type }, voting{ network }, node{ network }, portmapping{ network }, @@ -95,44 +121,116 @@ nano::network_params::network_params (nano::networks network_a) : kdf_work = network.is_dev_network () ? kdf_dev_work : kdf_full_work; } -nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::networks network_a) : +/* + * + */ + +nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::networks network_type) : work{ work }, - zero_key ("0"), - nano_beta_account (beta_public_key_data), - nano_live_account (live_public_key_data), - nano_test_account (test_public_key_data), - nano_dev_genesis (parse_block_from_genesis_data (dev_genesis_data)), - nano_beta_genesis (parse_block_from_genesis_data (beta_genesis_data)), - nano_live_genesis (parse_block_from_genesis_data (live_genesis_data)), - nano_test_genesis (parse_block_from_genesis_data (test_genesis_data)), - genesis (network_a == nano::networks::nano_dev_network ? nano_dev_genesis : network_a == nano::networks::nano_beta_network ? nano_beta_genesis - : network_a == nano::networks::nano_test_network ? nano_test_genesis - : nano_live_genesis), + zero_key{ "0" }, + nano_beta_account{ beta_public_key_data }, + nano_live_account{ live_public_key_data }, + nano_test_account{ test_public_key_data }, + nano_dev_genesis{ parse_block_from_genesis_data (dev_genesis_data) }, + nano_beta_genesis{ parse_block_from_genesis_data (beta_genesis_data) }, + nano_live_genesis{ parse_block_from_genesis_data (live_genesis_data) }, + nano_test_genesis{ parse_block_from_genesis_data (test_genesis_data) }, genesis_amount{ std::numeric_limits::max () }, - burn_account{} -{ - nano_beta_genesis->sideband_set (nano::block_sideband (nano_beta_genesis->account_field ().value (), 0, std::numeric_limits::max (), 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false, nano::epoch::epoch_0)); - nano_dev_genesis->sideband_set (nano::block_sideband (nano_dev_genesis->account_field ().value (), 0, std::numeric_limits::max (), 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false, nano::epoch::epoch_0)); - nano_live_genesis->sideband_set (nano::block_sideband (nano_live_genesis->account_field ().value (), 0, std::numeric_limits::max (), 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false, nano::epoch::epoch_0)); - nano_test_genesis->sideband_set (nano::block_sideband (nano_test_genesis->account_field ().value (), 0, std::numeric_limits::max (), 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false, nano::epoch::epoch_0)); + burn_account{ nano::account{ 0 } } +{ + nano_beta_genesis->sideband_set (nano::block_sideband{ + /* account */ nano_beta_genesis->account_field ().value (), + /* successor (block_hash) */ nano::block_hash{ 0 }, + /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, + /* height */ uint64_t{ 1 }, + /* local_timestamp */ 0, + /* epoch */ nano::epoch::epoch_0, + /* is_send */ false, + /* is_receive */ false, + /* is_epoch */ false, + /* source_epoch */ nano::epoch::epoch_0 }); + + nano_dev_genesis->sideband_set (nano::block_sideband{ + /* account */ nano_dev_genesis->account_field ().value (), + /* successor (block_hash) */ nano::block_hash{ 0 }, + /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, + /* height */ uint64_t{ 1 }, + /* local_timestamp */ 0, + /* epoch */ nano::epoch::epoch_0, + /* is_send */ false, + /* is_receive */ false, + /* is_epoch */ false, + /* source_epoch */ nano::epoch::epoch_0 }); + + nano_live_genesis->sideband_set (nano::block_sideband{ + /* account */ nano_live_genesis->account_field ().value (), + /* successor (block_hash) */ nano::block_hash{ 0 }, + /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, + /* height */ uint64_t{ 1 }, + /* local_timestamp */ 0, + /* epoch */ nano::epoch::epoch_0, + /* is_send */ false, + /* is_receive */ false, + /* is_epoch */ false, + /* source_epoch */ nano::epoch::epoch_0 }); + + nano_test_genesis->sideband_set (nano::block_sideband{ + /* account */ nano_test_genesis->account_field ().value (), + /* successor (block_hash) */ nano::block_hash{ 0 }, + /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, + /* height */ uint64_t{ 1 }, + /* local_timestamp */ 0, + /* epoch */ nano::epoch::epoch_0, + /* is_send */ false, + /* is_receive */ false, + /* is_epoch */ false, + /* source_epoch */ nano::epoch::epoch_0 }); + + nano::account epoch_v2_signer; + switch (network_type) + { + case networks::nano_dev_network: + { + genesis = nano_dev_genesis; + epoch_v2_signer = nano::dev::genesis_key.pub; + } + break; + case networks::nano_live_network: + { + genesis = nano_live_genesis; + epoch_v2_signer = nano::account::from_account ("nano_3qb6o6i1tkzr6jwr5s7eehfxwg9x6eemitdinbpi7u8bjjwsgqfj4wzser3x"); + } + break; + case networks::nano_beta_network: + { + genesis = nano_beta_genesis; + epoch_v2_signer = nano_beta_account; + } + break; + case networks::nano_test_network: + { + genesis = nano_test_genesis; + epoch_v2_signer = nano_test_account; + } + break; + default: + release_assert (false, "invalid network"); + break; + } + release_assert (genesis != nullptr); + release_assert (!epoch_v2_signer.is_zero ()); - nano::link epoch_link_v1; - char const * epoch_message_v1 ("epoch v1 block"); - strncpy ((char *)epoch_link_v1.bytes.data (), epoch_message_v1, epoch_link_v1.bytes.size ()); + nano::link const epoch_link_v1{ "epoch v1 block" }; epochs.add (nano::epoch::epoch_1, genesis->account (), epoch_link_v1); - nano::link epoch_link_v2; - nano::account nano_live_epoch_v2_signer; - auto error (nano_live_epoch_v2_signer.decode_account ("nano_3qb6o6i1tkzr6jwr5s7eehfxwg9x6eemitdinbpi7u8bjjwsgqfj4wzser3x")); - debug_assert (!error); - auto epoch_v2_signer (network_a == nano::networks::nano_dev_network ? nano::dev::genesis_key.pub : network_a == nano::networks::nano_beta_network ? nano_beta_account - : network_a == nano::networks::nano_test_network ? nano_test_account - : nano_live_epoch_v2_signer); - char const * epoch_message_v2 ("epoch v2 block"); - strncpy ((char *)epoch_link_v2.bytes.data (), epoch_message_v2, epoch_link_v2.bytes.size ()); + nano::link const epoch_link_v2{ "epoch v2 block" }; epochs.add (nano::epoch::epoch_2, epoch_v2_signer, epoch_link_v2); } +/* + * + */ + nano::hardened_constants & nano::hardened_constants::get () { static hardened_constants instance{}; @@ -147,28 +245,44 @@ nano::hardened_constants::hardened_constants () : nano::random_pool::generate_block (random_128.bytes.data (), random_128.bytes.size ()); } +/* + * + */ + nano::node_constants::node_constants (nano::network_constants & network_constants) { backup_interval = std::chrono::minutes (5); search_pending_interval = network_constants.is_dev_network () ? std::chrono::seconds (1) : std::chrono::seconds (5 * 60); unchecked_cleaning_interval = std::chrono::minutes (30); process_confirmed_interval = network_constants.is_dev_network () ? std::chrono::milliseconds (50) : std::chrono::milliseconds (500); - max_weight_samples = (network_constants.is_live_network () || network_constants.is_test_network ()) ? 4032 : 288; - weight_period = 5 * 60; // 5 minutes + weight_interval = network_constants.is_dev_network () ? std::chrono::seconds (1) : std::chrono::minutes (5); + weight_cutoff = (network_constants.is_live_network () || network_constants.is_test_network ()) ? std::chrono::weeks (2) : std::chrono::days (1); } +/* + * + */ + nano::voting_constants::voting_constants (nano::network_constants & network_constants) : max_cache{ network_constants.is_dev_network () ? 256U : 128U * 1024 }, delay{ network_constants.is_dev_network () ? 1 : 15 } { } +/* + * + */ + nano::portmapping_constants::portmapping_constants (nano::network_constants & network_constants) { lease_duration = std::chrono::seconds (1787); // ~30 minutes health_check_period = std::chrono::seconds (53); } +/* + * + */ + nano::bootstrap_constants::bootstrap_constants (nano::network_constants & network_constants) { lazy_max_pull_blocks = network_constants.is_dev_network () ? 2 : 512; @@ -180,6 +294,10 @@ nano::bootstrap_constants::bootstrap_constants (nano::network_constants & networ default_frontiers_age_seconds = network_constants.is_dev_network () ? 1 : 24 * 60 * 60; // 1 second for dev network, 24 hours for live/beta } +/* + * keypair + */ + // Create a new random keypair nano::keypair::keypair () { @@ -202,6 +320,10 @@ nano::keypair::keypair (std::string const & prv_a) ed25519_publickey (prv.bytes.data (), pub.bytes.data ()); } +/* + * unchecked_info + */ + nano::unchecked_info::unchecked_info (std::shared_ptr const & block_a) : block (block_a), modified_m (nano::seconds_since_epoch ()) @@ -314,6 +436,10 @@ nano::wallet_id nano::random_wallet_id () return wallet_id; } +/* + * unchecked_key + */ + nano::unchecked_key::unchecked_key (nano::hash_or_account const & dependency) : unchecked_key{ dependency, 0 } { @@ -362,6 +488,10 @@ nano::block_hash const & nano::unchecked_key::key () const return previous; } +/* + * + */ + std::string_view nano::to_string (nano::block_status code) { return nano::enum_util::name (code); diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index e1639bd265..60b7ef31ac 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -4,19 +4,13 @@ #include #include #include -#include +#include +#include +#include #include #include -#include #include #include -#include -#include - -#include -#include -#include -#include #include #include @@ -170,7 +164,7 @@ class network_params; class ledger_constants { public: - ledger_constants (nano::work_thresholds & work, nano::networks network_a); + ledger_constants (nano::work_thresholds &, nano::networks); nano::work_thresholds & work; nano::keypair zero_key; nano::account nano_beta_account; @@ -217,9 +211,10 @@ class node_constants std::chrono::minutes unchecked_cleaning_interval; std::chrono::milliseconds process_confirmed_interval; - /** The maximum amount of samples for a 2 week period on live or 1 day on beta */ - uint64_t max_weight_samples; - uint64_t weight_period; + /** Time between collecting online representative samples */ + std::chrono::seconds weight_interval; + /** The maximum time to keep online weight samples: 2 weeks on live or 1 day on beta */ + std::chrono::seconds weight_cutoff; }; /** Voting related constants whose value depends on the active network */ @@ -255,12 +250,13 @@ class bootstrap_constants uint32_t default_frontiers_age_seconds; }; +nano::work_thresholds const & work_thresholds_for_network (nano::networks); + /** Constants whose value depends on the active network */ class network_params { public: - /** Populate values based on \p network_a */ - network_params (nano::networks network_a); + explicit network_params (nano::networks); unsigned kdf_work; nano::work_thresholds work; diff --git a/nano/secure/fwd.hpp b/nano/secure/fwd.hpp index 1d57ae0cfc..bf0de82c8c 100644 --- a/nano/secure/fwd.hpp +++ b/nano/secure/fwd.hpp @@ -1,14 +1,18 @@ #pragma once +namespace nano +{ +class account_info; +class keypair; +class ledger; +class ledger_cache; +class ledger_constants; +class network_params; +class vote; +} namespace nano::secure { +class read_transaction; class transaction; class write_transaction; -class read_transaction; } - -namespace nano -{ -class account_info; -class vote; -} \ No newline at end of file diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index d2857974de..60d39ab1c3 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include @@ -34,7 +36,7 @@ namespace class rollback_visitor : public nano::block_visitor { public: - rollback_visitor (nano::secure::write_transaction const & transaction_a, nano::ledger & ledger_a, std::vector> & list_a) : + rollback_visitor (nano::secure::write_transaction const & transaction_a, nano::ledger & ledger_a, std::deque> & list_a) : transaction (transaction_a), ledger (ledger_a), list (list_a) @@ -177,7 +179,7 @@ class rollback_visitor : public nano::block_visitor } nano::secure::write_transaction const & transaction; nano::ledger & ledger; - std::vector> & list; + std::deque> & list; bool error{ false }; }; @@ -790,6 +792,11 @@ void nano::ledger::initialize (nano::generate_cache_flags const & generate_cache cache.pruned_count = store.pruned.count (transaction); } +bool nano::ledger::unconfirmed_exists (secure::transaction const & transaction, nano::block_hash const & hash) +{ + return any.block_exists (transaction, hash) && !confirmed.block_exists (transaction, hash); +} + nano::uint128_t nano::ledger::account_receivable (secure::transaction const & transaction_a, nano::account const & account_a, bool only_confirmed_a) { nano::uint128_t result (0); @@ -934,33 +941,25 @@ std::string nano::ledger::block_text (nano::block_hash const & hash_a) return result; } -std::pair nano::ledger::hash_root_random (secure::transaction const & transaction_a) const +std::deque> nano::ledger::random_blocks (secure::transaction const & transaction, size_t count) const { - nano::block_hash hash (0); - nano::root root (0); - if (!pruning) - { - auto block (store.block.random (transaction_a)); - hash = block->hash (); - root = block->root (); - } - else + std::deque> result; + + auto const starting_hash = nano::random_pool::generate (); + + // It is more efficient to choose a random starting point and pick a few sequential blocks from there + auto it = store.block.begin (transaction, starting_hash); + auto const end = store.block.end (transaction); + while (result.size () < count) { - uint64_t count (cache.block_count); - auto region = nano::random_pool::generate_word64 (0, count - 1); - // Pruned cache cannot guarantee that pruned blocks are already commited - if (region < cache.pruned_count) - { - hash = store.pruned.random (transaction_a); - } - if (hash.is_zero ()) + if (it != end) { - auto block (store.block.random (transaction_a)); - hash = block->hash (); - root = block->root (); + result.push_back (it->second.block); } + ++it; // Store iterators wrap around when reaching the end } - return std::make_pair (hash, root.as_block_hash ()); + + return result; } // Vote weight of an account @@ -990,7 +989,7 @@ nano::uint128_t nano::ledger::weight_exact (secure::transaction const & txn_a, n } // Rollback blocks until `block_a' doesn't exist or it tries to penetrate the confirmation height -bool nano::ledger::rollback (secure::write_transaction const & transaction_a, nano::block_hash const & block_a, std::vector> & list_a) +bool nano::ledger::rollback (secure::write_transaction const & transaction_a, nano::block_hash const & block_a, std::deque> & list_a) { debug_assert (any.block_exists (transaction_a, block_a)); auto account_l = any.block_account (transaction_a, block_a).value (); @@ -1024,7 +1023,7 @@ bool nano::ledger::rollback (secure::write_transaction const & transaction_a, na bool nano::ledger::rollback (secure::write_transaction const & transaction_a, nano::block_hash const & block_a) { - std::vector> rollback_list; + std::deque> rollback_list; return rollback (transaction_a, block_a, rollback_list); } @@ -1260,6 +1259,21 @@ uint64_t nano::ledger::pruning_action (secure::write_transaction & transaction_a return pruned_count; } +auto nano::ledger::block_priority (nano::secure::transaction const & transaction, nano::block const & block) const -> block_priority_result +{ + auto const balance = block.balance (); + auto const previous_block = !block.previous ().is_zero () ? any.block_get (transaction, block.previous ()) : nullptr; + auto const previous_balance = previous_block ? previous_block->balance () : 0; + + // Handle full send case nicely where the balance would otherwise be 0 + auto const priority_balance = std::max (balance, block.is_send () ? previous_balance : 0); + + // Use previous block timestamp as priority timestamp for least recently used prioritization within the same bucket + // Account info timestamp is not used here because it will get out of sync when rollbacks happen + auto const priority_timestamp = previous_block ? previous_block->sideband ().timestamp : block.sideband ().timestamp; + return { priority_balance, priority_timestamp }; +} + // A precondition is that the store is an LMDB store bool nano::ledger::migrate_lmdb_to_rocksdb (std::filesystem::path const & data_path_a) const { @@ -1279,7 +1293,12 @@ bool nano::ledger::migrate_lmdb_to_rocksdb (std::filesystem::path const & data_p boost::system::error_code error_chmod; nano::set_secure_perm_directory (data_path_a, error_chmod); auto rockdb_data_path = data_path_a / "rocksdb"; - std::filesystem::remove_all (rockdb_data_path); + + if (std::filesystem::exists (rockdb_data_path)) + { + logger.error (nano::log::type::ledger, "Existing RocksDB folder found in '{}'. Please remove it and try again.", rockdb_data_path.string ()); + return true; + } auto error (false); @@ -1424,7 +1443,8 @@ bool nano::ledger::migrate_lmdb_to_rocksdb (std::filesystem::path const & data_p logger.info (nano::log::type::ledger, "{} entries converted ({}%)", count.load (), table_size > 0 ? count.load () * 100 / table_size : 100); logger.info (nano::log::type::ledger, "Finalizing migration..."); - auto lmdb_transaction (store.tx_begin_read ()); + + auto lmdb_transaction (tx_begin_read ()); auto version = store.version.get (lmdb_transaction); auto rocksdb_transaction (rocksdb_store->tx_begin_write ()); rocksdb_store->version.put (rocksdb_transaction, version); @@ -1448,21 +1468,26 @@ bool nano::ledger::migrate_lmdb_to_rocksdb (std::filesystem::path const & data_p error |= store.version.get (lmdb_transaction) != rocksdb_store->version.get (rocksdb_transaction); // For large tables a random key is used instead and makes sure it exists - auto random_block (store.block.random (lmdb_transaction)); - error |= rocksdb_store->block.get (rocksdb_transaction, random_block->hash ()) == nullptr; + auto blocks = random_blocks (lmdb_transaction, 42); + release_assert (!blocks.empty ()); + for (auto const & block : blocks) + { + auto const account = block->account (); - auto account = random_block->account (); - nano::account_info account_info; - error |= rocksdb_store->account.get (rocksdb_transaction, account, account_info); + error |= rocksdb_store->block.get (rocksdb_transaction, block->hash ()) == nullptr; - // If confirmation height exists in the lmdb ledger for this account it should exist in the rocksdb ledger - nano::confirmation_height_info confirmation_height_info{}; - if (!store.confirmation_height.get (lmdb_transaction, account, confirmation_height_info)) - { - error |= rocksdb_store->confirmation_height.get (rocksdb_transaction, account, confirmation_height_info); + nano::account_info account_info; + error |= rocksdb_store->account.get (rocksdb_transaction, account, account_info); + + // If confirmation height exists in the lmdb ledger for this account it should exist in the rocksdb ledger + nano::confirmation_height_info confirmation_height_info{}; + if (!store.confirmation_height.get (lmdb_transaction, account, confirmation_height_info)) + { + error |= rocksdb_store->confirmation_height.get (rocksdb_transaction, account, confirmation_height_info); + } } - logger.info (nano::log::type::ledger, "Migration completed. Make sure to enable RocksDb in the config file under [node.rocksdb]"); + logger.info (nano::log::type::ledger, "Migration completed. Make sure to enable RocksDB in the config file under [node.rocksdb]"); logger.info (nano::log::type::ledger, "After confirming correct node operation, the data.ldb file can be deleted if no longer required"); } else @@ -1517,6 +1542,13 @@ uint64_t nano::ledger::pruned_count () const return cache.pruned_count; } +uint64_t nano::ledger::backlog_count () const +{ + auto blocks = cache.block_count.load (); + auto cemented = cache.cemented_count.load (); + return (blocks > cemented) ? blocks - cemented : 0; +} + nano::container_info nano::ledger::container_info () const { nano::container_info info; diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 3171e4593c..91e1b8155d 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -43,6 +43,7 @@ class ledger final /** Start read-only transaction */ secure::read_transaction tx_begin_read () const; + bool unconfirmed_exists (secure::transaction const &, nano::block_hash const &); nano::uint128_t account_receivable (secure::transaction const &, nano::account const &, bool = false); /** * Returns the cached vote weight for the given representative. @@ -58,11 +59,11 @@ class ledger final nano::block_hash representative_calculated (secure::transaction const &, nano::block_hash const &); std::string block_text (char const *); std::string block_text (nano::block_hash const &); - std::pair hash_root_random (secure::transaction const &) const; + std::deque> random_blocks (secure::transaction const &, size_t count) const; std::optional pending_info (secure::transaction const &, nano::pending_key const & key) const; std::deque> confirm (secure::write_transaction &, nano::block_hash const & hash, size_t max_blocks = 1024 * 128); nano::block_status process (secure::write_transaction const &, std::shared_ptr block); - bool rollback (secure::write_transaction const &, nano::block_hash const &, std::vector> &); + bool rollback (secure::write_transaction const &, nano::block_hash const &, std::deque> & rollback_list); bool rollback (secure::write_transaction const &, nano::block_hash const &); void update_account (secure::write_transaction const &, nano::account const &, nano::account_info const &, nano::account_info const &); uint64_t pruning_action (secure::write_transaction &, nano::block_hash const &, uint64_t const); @@ -75,12 +76,20 @@ class ledger final nano::link const & epoch_link (nano::epoch) const; bool migrate_lmdb_to_rocksdb (std::filesystem::path const &) const; bool bootstrap_weight_reached () const; + static nano::epoch version (nano::block const & block); nano::epoch version (secure::transaction const &, nano::block_hash const & hash) const; + uint64_t cemented_count () const; uint64_t block_count () const; uint64_t account_count () const; uint64_t pruned_count () const; + uint64_t backlog_count () const; + + // Returned priority balance is maximum of block balance and previous block balance to handle full account balance send cases + // Returned timestamp is the previous block timestamp or the current timestamp if there's no previous block + using block_priority_result = std::pair; + block_priority_result block_priority (secure::transaction const &, nano::block const &) const; nano::container_info container_info () const; diff --git a/nano/secure/ledger_set_any.cpp b/nano/secure/ledger_set_any.cpp index 67b14fda19..4eed6292b0 100644 --- a/nano/secure/ledger_set_any.cpp +++ b/nano/secure/ledger_set_any.cpp @@ -123,11 +123,19 @@ std::optional nano::ledger_set_any::block_balance (secure::transac bool nano::ledger_set_any::block_exists (secure::transaction const & transaction, nano::block_hash const & hash) const { + if (hash.is_zero ()) + { + return false; + } return ledger.store.block.exists (transaction, hash); } bool nano::ledger_set_any::block_exists_or_pruned (secure::transaction const & transaction, nano::block_hash const & hash) const { + if (hash.is_zero ()) + { + return false; + } if (ledger.store.pruned.exists (transaction, hash)) { return true; @@ -137,6 +145,10 @@ bool nano::ledger_set_any::block_exists_or_pruned (secure::transaction const & t std::shared_ptr nano::ledger_set_any::block_get (secure::transaction const & transaction, nano::block_hash const & hash) const { + if (hash.is_zero ()) + { + return nullptr; + } return ledger.store.block.get (transaction, hash); } diff --git a/nano/secure/ledger_set_confirmed.cpp b/nano/secure/ledger_set_confirmed.cpp index 8702d61b4f..655f04edf8 100644 --- a/nano/secure/ledger_set_confirmed.cpp +++ b/nano/secure/ledger_set_confirmed.cpp @@ -45,10 +45,6 @@ uint64_t nano::ledger_set_confirmed::account_height (secure::transaction const & std::optional nano::ledger_set_confirmed::block_balance (secure::transaction const & transaction, nano::block_hash const & hash) const { - if (hash.is_zero ()) - { - return std::nullopt; - } auto block = block_get (transaction, hash); if (!block) { @@ -64,6 +60,10 @@ bool nano::ledger_set_confirmed::block_exists (secure::transaction const & trans bool nano::ledger_set_confirmed::block_exists_or_pruned (secure::transaction const & transaction, nano::block_hash const & hash) const { + if (hash.is_zero ()) + { + return false; + } if (ledger.store.pruned.exists (transaction, hash)) { return true; @@ -73,6 +73,10 @@ bool nano::ledger_set_confirmed::block_exists_or_pruned (secure::transaction con std::shared_ptr nano::ledger_set_confirmed::block_get (secure::transaction const & transaction, nano::block_hash const & hash) const { + if (hash.is_zero ()) + { + return nullptr; + } auto block = ledger.store.block.get (transaction, hash); if (!block) { diff --git a/nano/secure/pending_info.cpp b/nano/secure/pending_info.cpp index a9d52588a6..85a32a8aaa 100644 --- a/nano/secure/pending_info.cpp +++ b/nano/secure/pending_info.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/secure/pending_info.hpp b/nano/secure/pending_info.hpp index c00e1cdb39..43273cab8f 100644 --- a/nano/secure/pending_info.hpp +++ b/nano/secure/pending_info.hpp @@ -1,18 +1,10 @@ #pragma once #include +#include #include -#include - -namespace nano -{ -class ledger; -} - -namespace nano::secure -{ -class transaction; -} +#include +#include namespace nano { diff --git a/nano/secure/receivable_iterator_impl.hpp b/nano/secure/receivable_iterator_impl.hpp index b2cbebef72..f2e1867dfa 100644 --- a/nano/secure/receivable_iterator_impl.hpp +++ b/nano/secure/receivable_iterator_impl.hpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/secure/rep_weights.hpp b/nano/secure/rep_weights.hpp index 9209bbf255..29a2c28044 100644 --- a/nano/secure/rep_weights.hpp +++ b/nano/secure/rep_weights.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/nano/secure/transaction.hpp b/nano/secure/transaction.hpp index 6c4acde3d3..0a1bd90a41 100644 --- a/nano/secure/transaction.hpp +++ b/nano/secure/transaction.hpp @@ -27,19 +27,23 @@ class transaction // Conversion operator to const nano::store::transaction& virtual operator const nano::store::transaction & () const = 0; + + // Certain transactions may need to be refreshed if they are held for a long time + virtual bool refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }) = 0; }; -class write_transaction : public transaction +class write_transaction final : public transaction { + nano::store::write_guard guard; // Guard should be released after the transaction nano::store::write_transaction txn; - nano::store::write_guard guard; std::chrono::steady_clock::time_point start; public: - explicit write_transaction (nano::store::write_transaction && txn, nano::store::write_guard && guard) noexcept : - txn{ std::move (txn) }, - guard{ std::move (guard) } + explicit write_transaction (nano::store::write_transaction && txn_a, nano::store::write_guard && guard_a) noexcept : + guard{ std::move (guard_a) }, + txn{ std::move (txn_a) } { + debug_assert (guard.is_owned ()); start = std::chrono::steady_clock::now (); } @@ -68,7 +72,7 @@ class write_transaction : public transaction renew (); } - bool refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }) + bool refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }) override { auto now = std::chrono::steady_clock::now (); if (now - start > max_age) @@ -97,7 +101,7 @@ class write_transaction : public transaction } }; -class read_transaction : public transaction +class read_transaction final : public transaction { nano::store::read_transaction txn; @@ -118,9 +122,9 @@ class read_transaction : public transaction txn.refresh (); } - void refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }) + bool refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }) override { - txn.refresh_if_needed (max_age); + return txn.refresh_if_needed (max_age); } auto timestamp () const @@ -140,4 +144,4 @@ class read_transaction : public transaction return txn; } }; -} // namespace nano::secure +} diff --git a/nano/secure/utility.hpp b/nano/secure/utility.hpp index 90953eea01..c80f54a037 100644 --- a/nano/secure/utility.hpp +++ b/nano/secure/utility.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include diff --git a/nano/secure/vote.cpp b/nano/secure/vote.cpp index 296886634b..b7b7e6c633 100644 --- a/nano/secure/vote.cpp +++ b/nano/secure/vote.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -193,4 +194,4 @@ void nano::vote::operator() (nano::object_stream & obs) const obs.write ("final", is_final_timestamp (timestamp_m)); obs.write ("timestamp", timestamp_m); obs.write_range ("hashes", hashes); -} \ No newline at end of file +} diff --git a/nano/secure/vote.hpp b/nano/secure/vote.hpp index 0737acf00e..c4e24f149d 100644 --- a/nano/secure/vote.hpp +++ b/nano/secure/vote.hpp @@ -1,7 +1,7 @@ #pragma once +#include #include -#include #include #include @@ -10,11 +10,6 @@ #include -namespace nano -{ -class object_stream; -} - namespace nano { class vote final diff --git a/nano/slow_test/bootstrap.cpp b/nano/slow_test/bootstrap.cpp index 521bff8852..c9f983e13e 100644 --- a/nano/slow_test/bootstrap.cpp +++ b/nano/slow_test/bootstrap.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,7 +60,7 @@ std::unique_ptr start_rpc (nano::test::system & system, nano::node } } -TEST (bootstrap_ascending, profile) +TEST (bootstrap, profile) { nano::test::system system; nano::thread_runner runner{ system.io_ctx, system.logger, 2 }; @@ -71,12 +71,12 @@ TEST (bootstrap_ascending, profile) nano::node_config config_server{ network_params }; config_server.preconfigured_peers.clear (); config_server.bandwidth_limit = 0; // Unlimited server bandwidth + config_server.bootstrap.enable = false; nano::node_flags flags_server; flags_server.disable_legacy_bootstrap = true; flags_server.disable_wallet_bootstrap = true; flags_server.disable_add_initial_peers = true; flags_server.disable_ongoing_bootstrap = true; - flags_server.disable_ascending_bootstrap = true; auto data_path_server = nano::working_path (network); // auto data_path_server = ""; auto server = std::make_shared (system.io_ctx, data_path_server, config_server, system.work, flags_server); @@ -107,67 +107,7 @@ TEST (bootstrap_ascending, profile) auto client_rpc = start_rpc (system, *server, 55000); auto server_rpc = start_rpc (system, *client, 55001); - struct entry - { - nano::bootstrap_ascending::service::async_tag tag; - std::shared_ptr request_channel; - std::shared_ptr reply_channel; - - bool replied{ false }; - bool received{ false }; - }; - nano::mutex mutex; - std::unordered_map requests; - - server->bootstrap_server.on_response.add ([&] (auto & response, auto & channel) { - nano::lock_guard lock{ mutex }; - - if (requests.count (response.id)) - { - requests[response.id].replied = true; - requests[response.id].reply_channel = channel; - } - else - { - std::cerr << "unknown response: " << response.id << std::endl; - } - }); - - client->ascendboot.on_request.add ([&] (auto & tag, auto & channel) { - nano::lock_guard lock{ mutex }; - - requests[tag.id] = { tag, channel }; - }); - - client->ascendboot.on_reply.add ([&] (auto & tag) { - nano::lock_guard lock{ mutex }; - - requests[tag.id].received = true; - }); - - /*client->ascendboot.on_timeout.add ([&] (auto & tag) { - nano::lock_guard lock{ mutex }; - - if (requests.count (tag.id)) - { - auto entry = requests[tag.id]; - - std::cerr << "timeout: " - << "replied: " << entry.replied - << " | " - << "recevied: " << entry.received - << " | " - << "request: " << entry.request_channel->to_string () - << " ||| " - << "reply: " << (entry.reply_channel ? entry.reply_channel->to_string () : "null") - << std::endl; - } - else - { - std::cerr << "unknown timeout: " << tag.id << std::endl; - } - });*/ std::cout << "server count: " << server->ledger.block_count () << std::endl; @@ -175,11 +115,11 @@ TEST (bootstrap_ascending, profile) rate.observe ("count", [&] () { return client->ledger.block_count (); }); rate.observe ("unchecked", [&] () { return client->unchecked.count (); }); rate.observe ("block_processor", [&] () { return client->block_processor.size (); }); - rate.observe ("priority", [&] () { return client->ascendboot.priority_size (); }); - rate.observe ("blocking", [&] () { return client->ascendboot.blocked_size (); }); - rate.observe (*client, nano::stat::type::bootstrap_ascending, nano::stat::detail::request, nano::stat::dir::out); - rate.observe (*client, nano::stat::type::bootstrap_ascending, nano::stat::detail::reply, nano::stat::dir::in); - rate.observe (*client, nano::stat::type::bootstrap_ascending, nano::stat::detail::blocks, nano::stat::dir::in); + rate.observe ("priority", [&] () { return client->bootstrap.priority_size (); }); + rate.observe ("blocking", [&] () { return client->bootstrap.blocked_size (); }); + rate.observe (*client, nano::stat::type::bootstrap, nano::stat::detail::request, nano::stat::dir::out); + rate.observe (*client, nano::stat::type::bootstrap, nano::stat::detail::reply, nano::stat::dir::in); + rate.observe (*client, nano::stat::type::bootstrap, nano::stat::detail::blocks, nano::stat::dir::in); rate.observe (*server, nano::stat::type::bootstrap_server, nano::stat::detail::blocks, nano::stat::dir::out); rate.observe (*client, nano::stat::type::ledger, nano::stat::detail::old, nano::stat::dir::in); rate.observe (*client, nano::stat::type::ledger, nano::stat::detail::gap_epoch_open_pending, nano::stat::dir::in); diff --git a/nano/slow_test/entry.cpp b/nano/slow_test/entry.cpp index 096c7dc6a9..0659075b55 100644 --- a/nano/slow_test/entry.cpp +++ b/nano/slow_test/entry.cpp @@ -1,5 +1,6 @@ +#include #include -#include +#include #include diff --git a/nano/slow_test/node.cpp b/nano/slow_test/node.cpp index b9c3731f69..f25ecff973 100644 --- a/nano/slow_test/node.cpp +++ b/nano/slow_test/node.cpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -298,7 +301,7 @@ TEST (node, fork_storm) auto open_result (node_i->process (open)); ASSERT_EQ (nano::block_status::progress, open_result); auto transaction (node_i->store.tx_begin_read ()); - node_i->network.flood_block (open); + node_i->network.flood_block (open, nano::transport::traffic_type::test); } } auto again (true); @@ -641,7 +644,7 @@ TEST (confirmation_height, many_accounts_single_confirmation) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = 100; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -724,7 +727,7 @@ TEST (confirmation_height, many_accounts_many_confirmations) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = 100; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -797,7 +800,7 @@ TEST (confirmation_height, long_chains) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); nano::keypair key1; system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -942,7 +945,7 @@ TEST (confirmation_height, dynamic_algorithm) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); nano::keypair key; system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); @@ -990,7 +993,7 @@ TEST (confirmation_height, many_accounts_send_receive_self) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.online_weight_minimum = 100; - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; node_config.active_elections.size = 400000; nano::node_flags node_flags; auto node = system.add_node (node_config); @@ -1119,6 +1122,7 @@ TEST (confirmation_height, many_accounts_send_receive_self) // as opposed to active transactions which implicitly calls confirmation height processor. TEST (confirmation_height, many_accounts_send_receive_self_no_elections) { + nano::test::system system; if (nano::rocksdb_config::using_rocksdb_in_tests ()) { // Don't test this in rocksdb mode @@ -1137,8 +1141,12 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections) nano::block_hash block_hash_being_processed{ 0 }; nano::store::write_queue write_queue; + + nano::node_config node_config; + nano::unchecked_map unchecked{ 0, stats, false }; + nano::block_processor block_processor{ node_config, ledger, unchecked, stats, logger }; nano::confirming_set_config confirming_set_config{}; - nano::confirming_set confirming_set{ confirming_set_config, ledger, stats, logger }; + nano::confirming_set confirming_set{ confirming_set_config, ledger, block_processor, stats, logger }; auto const num_accounts = 100000; @@ -1147,7 +1155,6 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections) std::vector> open_blocks; nano::block_builder builder; - nano::test::system system; { auto transaction = ledger.tx_begin_write (); @@ -1405,7 +1412,7 @@ TEST (telemetry, under_load) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; nano::node_flags node_flags; auto node = system.add_node (node_config, node_flags); node_config.peering_port = system.get_available_port (); @@ -1766,7 +1773,7 @@ TEST (node, mass_block_new) { nano::test::system system; nano::node_config node_config = system.default_config (); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); node.network_params.network.aec_loop_interval_ms = 500; @@ -1905,8 +1912,9 @@ TEST (node, aggressive_flooding) node_flags.disable_lazy_bootstrap = true; node_flags.disable_legacy_bootstrap = true; node_flags.disable_wallet_bootstrap = true; - node_flags.disable_ascending_bootstrap = true; - auto & node1 (*system.add_node (node_flags)); + nano::node_config node_config; + node_config.bootstrap.enable = false; + auto & node1 (*system.add_node (node_config, node_flags)); auto & wallet1 (*system.wallet (0)); wallet1.insert_adhoc (nano::dev::genesis_key.prv); std::vector, std::shared_ptr>> nodes_wallets; @@ -2028,7 +2036,7 @@ TEST (node, wallet_create_block_confirm_conflicts) nano::test::system system; nano::block_builder builder; nano::node_config node_config (system.get_available_port ()); - node_config.backlog_population.enable = false; + node_config.backlog_scan.enable = false; auto node = system.add_node (node_config); auto const num_blocks = 10000; @@ -2099,7 +2107,7 @@ TEST (system, block_sequence) config.peering_port = system.get_available_port (); // config.bandwidth_limit = 16 * 1024; config.enable_voting = true; - config.backlog_population.enable = false; + config.backlog_scan.enable = false; nano::node_flags flags; flags.disable_max_peers_per_ip = true; flags.disable_ongoing_bootstrap = true; diff --git a/nano/slow_test/vote_cache.cpp b/nano/slow_test/vote_cache.cpp index f7d8e61bb9..9f70affa9a 100644 --- a/nano/slow_test/vote_cache.cpp +++ b/nano/slow_test/vote_cache.cpp @@ -134,7 +134,7 @@ TEST (vote_cache, perf_singlethreaded) nano::test::system system; nano::node_flags flags; nano::node_config config = system.default_config (); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config, flags); const int rep_count = 50; @@ -193,7 +193,7 @@ TEST (vote_cache, perf_multithreaded) nano::test::system system; nano::node_flags flags; nano::node_config config = system.default_config (); - config.backlog_population.enable = false; + config.backlog_scan.enable = false; auto & node = *system.add_node (config, flags); const int thread_count = 12; diff --git a/nano/slow_test/vote_processor.cpp b/nano/slow_test/vote_processor.cpp index 938aa2566a..b3fcffa9f7 100644 --- a/nano/slow_test/vote_processor.cpp +++ b/nano/slow_test/vote_processor.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include diff --git a/nano/store/CMakeLists.txt b/nano/store/CMakeLists.txt index 0fb509489a..e3f4ef175a 100644 --- a/nano/store/CMakeLists.txt +++ b/nano/store/CMakeLists.txt @@ -9,6 +9,7 @@ add_library( db_val_impl.hpp iterator.hpp final_vote.hpp + fwd.hpp lmdb/account.hpp lmdb/block.hpp lmdb/confirmation_height.hpp diff --git a/nano/store/block.hpp b/nano/store/block.hpp index 10cda6d29d..13a7b18bf4 100644 --- a/nano/store/block.hpp +++ b/nano/store/block.hpp @@ -30,7 +30,6 @@ class block virtual std::optional successor (transaction const & tx, nano::block_hash const &) const = 0; virtual void successor_clear (write_transaction const & tx, nano::block_hash const &) = 0; virtual std::shared_ptr get (transaction const & tx, nano::block_hash const &) const = 0; - virtual std::shared_ptr random (transaction const & tx) = 0; virtual void del (write_transaction const & tx, nano::block_hash const &) = 0; virtual bool exists (transaction const & tx, nano::block_hash const &) = 0; virtual uint64_t count (transaction const & tx) = 0; diff --git a/nano/store/component.hpp b/nano/store/component.hpp index f36f837efe..13f00910f5 100644 --- a/nano/store/component.hpp +++ b/nano/store/component.hpp @@ -2,8 +2,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -11,26 +12,12 @@ #include #include +#include #include namespace nano { -namespace store -{ - class account; - class block; - class confirmation_height; - class final_vote; - class online_weight; - class peer; - class pending; - class pruned; - class version; - class rep_weight; -} -class ledger_cache; - namespace store { /** @@ -90,8 +77,8 @@ namespace store virtual void rebuild_db (write_transaction const & transaction_a) = 0; /** Not applicable to all sub-classes */ - virtual void serialize_mdb_tracker (boost::property_tree::ptree &, std::chrono::milliseconds, std::chrono::milliseconds){}; - virtual void serialize_memory_stats (boost::property_tree::ptree &) = 0; + virtual void serialize_mdb_tracker (::boost::property_tree::ptree &, std::chrono::milliseconds, std::chrono::milliseconds){}; + virtual void serialize_memory_stats (::boost::property_tree::ptree &) = 0; virtual bool init_error () const = 0; diff --git a/nano/store/db_val.hpp b/nano/store/db_val.hpp index 7cc415a5fd..12b81a143f 100644 --- a/nano/store/db_val.hpp +++ b/nano/store/db_val.hpp @@ -16,6 +16,7 @@ class account_info_v22; class block; class pending_info; class pending_key; +class vote; } namespace nano::store diff --git a/nano/store/final_vote.hpp b/nano/store/final_vote.hpp index f6f96f9796..8e692d4045 100644 --- a/nano/store/final_vote.hpp +++ b/nano/store/final_vote.hpp @@ -22,10 +22,9 @@ class final_vote public: virtual bool put (store::write_transaction const & transaction_a, nano::qualified_root const & root_a, nano::block_hash const & hash_a) = 0; - virtual std::vector get (store::transaction const & transaction_a, nano::root const & root_a) = 0; - virtual void del (store::write_transaction const & transaction_a, nano::root const & root_a) = 0; + virtual std::optional get (store::transaction const & transaction_a, nano::qualified_root const & qualified_root_a) = 0; + virtual void del (store::write_transaction const & transaction_a, nano::qualified_root const & root_a) = 0; virtual size_t count (store::transaction const & transaction_a) const = 0; - virtual void clear (store::write_transaction const &, nano::root const &) = 0; virtual void clear (store::write_transaction const &) = 0; virtual iterator begin (store::transaction const & transaction_a, nano::qualified_root const & root_a) const = 0; virtual iterator begin (store::transaction const & transaction_a) const = 0; diff --git a/nano/store/fwd.hpp b/nano/store/fwd.hpp index 9ecd1ff3a5..febe31a892 100644 --- a/nano/store/fwd.hpp +++ b/nano/store/fwd.hpp @@ -1,9 +1,23 @@ #pragma once +namespace nano +{ +enum class tables; +} namespace nano::store { +class account; +class block; class component; +class confirmation_height; +class final_vote; +class online_weight; +class peer; +class pending; +class pruned; +class read_transaction; +class rep_weight; class transaction; +class version; class write_transaction; -class read_transaction; -} \ No newline at end of file +} diff --git a/nano/store/lmdb/block.cpp b/nano/store/lmdb/block.cpp index 218245b310..7343372cb1 100644 --- a/nano/store/lmdb/block.cpp +++ b/nano/store/lmdb/block.cpp @@ -106,19 +106,6 @@ std::shared_ptr nano::store::lmdb::block::get (store::transaction c return result; } -std::shared_ptr nano::store::lmdb::block::random (store::transaction const & transaction) -{ - nano::block_hash hash; - nano::random_pool::generate_block (hash.bytes.data (), hash.bytes.size ()); - auto existing = begin (transaction, hash); - if (existing == end (transaction)) - { - existing = begin (transaction); - } - debug_assert (existing != end (transaction)); - return existing->second.block; -} - void nano::store::lmdb::block::del (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) { auto status = store.del (transaction_a, tables::blocks, hash_a); diff --git a/nano/store/lmdb/block.hpp b/nano/store/lmdb/block.hpp index f1130feaa3..9da50b58bc 100644 --- a/nano/store/lmdb/block.hpp +++ b/nano/store/lmdb/block.hpp @@ -27,7 +27,6 @@ class block : public nano::store::block std::optional successor (store::transaction const & transaction_a, nano::block_hash const & hash_a) const override; void successor_clear (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) override; std::shared_ptr get (store::transaction const & transaction_a, nano::block_hash const & hash_a) const override; - std::shared_ptr random (store::transaction const & transaction_a) override; void del (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) override; bool exists (store::transaction const & transaction_a, nano::block_hash const & hash_a) override; uint64_t count (store::transaction const & transaction_a) override; diff --git a/nano/store/lmdb/final_vote.cpp b/nano/store/lmdb/final_vote.cpp index c7fdf7acad..547dd600b6 100644 --- a/nano/store/lmdb/final_vote.cpp +++ b/nano/store/lmdb/final_vote.cpp @@ -23,30 +23,22 @@ bool nano::store::lmdb::final_vote::put (store::write_transaction const & transa return result; } -std::vector nano::store::lmdb::final_vote::get (store::transaction const & transaction, nano::root const & root_a) +std::optional nano::store::lmdb::final_vote::get (store::transaction const & transaction, nano::qualified_root const & qualified_root_a) { - std::vector result; - nano::qualified_root key_start{ root_a.raw, 0 }; - for (auto i = begin (transaction, key_start), n = end (transaction); i != n && nano::qualified_root{ i->first }.root () == root_a; ++i) + nano::store::lmdb::db_val result; + auto status = store.get (transaction, tables::final_votes, qualified_root_a, result); + std::optional final_vote_hash; + if (store.success (status)) { - result.push_back (i->second); + final_vote_hash = static_cast (result); } - return result; + return final_vote_hash; } -void nano::store::lmdb::final_vote::del (store::write_transaction const & transaction, nano::root const & root) +void nano::store::lmdb::final_vote::del (store::write_transaction const & transaction, nano::qualified_root const & root) { - std::vector final_vote_qualified_roots; - for (auto i = begin (transaction, nano::qualified_root{ root.raw, 0 }), n = end (transaction); i != n && nano::qualified_root{ i->first }.root () == root; ++i) - { - final_vote_qualified_roots.push_back (i->first); - } - - for (auto & final_vote_qualified_root : final_vote_qualified_roots) - { - auto status = store.del (transaction, tables::final_votes, final_vote_qualified_root); - store.release_assert_success (status); - } + auto status = store.del (transaction, tables::final_votes, root); + store.release_assert_success (status); } size_t nano::store::lmdb::final_vote::count (store::transaction const & transaction_a) const @@ -54,11 +46,6 @@ size_t nano::store::lmdb::final_vote::count (store::transaction const & transact return store.count (transaction_a, tables::final_votes); } -void nano::store::lmdb::final_vote::clear (store::write_transaction const & transaction_a, nano::root const & root_a) -{ - del (transaction_a, root_a); -} - void nano::store::lmdb::final_vote::clear (store::write_transaction const & transaction_a) { store.drop (transaction_a, nano::tables::final_votes); diff --git a/nano/store/lmdb/final_vote.hpp b/nano/store/lmdb/final_vote.hpp index c905edc9dd..193cb6e717 100644 --- a/nano/store/lmdb/final_vote.hpp +++ b/nano/store/lmdb/final_vote.hpp @@ -18,10 +18,9 @@ class final_vote : public nano::store::final_vote public: explicit final_vote (nano::store::lmdb::component & store); bool put (store::write_transaction const & transaction_a, nano::qualified_root const & root_a, nano::block_hash const & hash_a) override; - std::vector get (store::transaction const & transaction_a, nano::root const & root_a) override; - void del (store::write_transaction const & transaction_a, nano::root const & root_a) override; + std::optional get (store::transaction const & transaction_a, nano::qualified_root const & qualified_root_a) override; + void del (store::write_transaction const & transaction_a, nano::qualified_root const & root_a) override; size_t count (store::transaction const & transaction_a) const override; - void clear (store::write_transaction const & transaction_a, nano::root const & root_a) override; void clear (store::write_transaction const & transaction_a) override; iterator begin (store::transaction const & transaction_a, nano::qualified_root const & root_a) const override; iterator begin (store::transaction const & transaction_a) const override; diff --git a/nano/store/lmdb/lmdb_env.cpp b/nano/store/lmdb/lmdb_env.cpp index b30718960f..eeae96d584 100644 --- a/nano/store/lmdb/lmdb_env.cpp +++ b/nano/store/lmdb/lmdb_env.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/nano/store/rocksdb/block.cpp b/nano/store/rocksdb/block.cpp index 6385704dae..32ba7b8bb0 100644 --- a/nano/store/rocksdb/block.cpp +++ b/nano/store/rocksdb/block.cpp @@ -106,18 +106,6 @@ std::shared_ptr nano::store::rocksdb::block::get (store::transactio } return result; } -std::shared_ptr nano::store::rocksdb::block::random (store::transaction const & transaction) -{ - nano::block_hash hash; - nano::random_pool::generate_block (hash.bytes.data (), hash.bytes.size ()); - auto existing = begin (transaction, hash); - if (existing == end (transaction)) - { - existing = begin (transaction); - } - debug_assert (existing != end (transaction)); - return existing->second.block; -} void nano::store::rocksdb::block::del (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) { diff --git a/nano/store/rocksdb/block.hpp b/nano/store/rocksdb/block.hpp index 741c862f77..23918d83a8 100644 --- a/nano/store/rocksdb/block.hpp +++ b/nano/store/rocksdb/block.hpp @@ -25,7 +25,6 @@ class block : public nano::store::block std::optional successor (store::transaction const & transaction_a, nano::block_hash const & hash_a) const override; void successor_clear (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) override; std::shared_ptr get (store::transaction const & transaction_a, nano::block_hash const & hash_a) const override; - std::shared_ptr random (store::transaction const & transaction_a) override; void del (store::write_transaction const & transaction_a, nano::block_hash const & hash_a) override; bool exists (store::transaction const & transaction_a, nano::block_hash const & hash_a) override; uint64_t count (store::transaction const & transaction_a) override; diff --git a/nano/store/rocksdb/final_vote.cpp b/nano/store/rocksdb/final_vote.cpp index 52afdfe34e..0875c11b7b 100644 --- a/nano/store/rocksdb/final_vote.cpp +++ b/nano/store/rocksdb/final_vote.cpp @@ -24,30 +24,22 @@ bool nano::store::rocksdb::final_vote::put (store::write_transaction const & tra return result; } -std::vector nano::store::rocksdb::final_vote::get (store::transaction const & transaction, nano::root const & root_a) +std::optional nano::store::rocksdb::final_vote::get (store::transaction const & transaction, nano::qualified_root const & qualified_root_a) { - std::vector result; - nano::qualified_root key_start{ root_a.raw, 0 }; - for (auto i = begin (transaction, key_start), n = end (transaction); i != n && nano::qualified_root{ i->first }.root () == root_a; ++i) + nano::store::rocksdb::db_val result; + auto status = store.get (transaction, tables::final_votes, qualified_root_a, result); + std::optional final_vote_hash; + if (store.success (status)) { - result.push_back (i->second); + final_vote_hash = static_cast (result); } - return result; + return final_vote_hash; } -void nano::store::rocksdb::final_vote::del (store::write_transaction const & transaction, nano::root const & root) +void nano::store::rocksdb::final_vote::del (store::write_transaction const & transaction, nano::qualified_root const & root) { - std::vector final_vote_qualified_roots; - for (auto i = begin (transaction, nano::qualified_root{ root.raw, 0 }), n = end (transaction); i != n && nano::qualified_root{ i->first }.root () == root; ++i) - { - final_vote_qualified_roots.push_back (i->first); - } - - for (auto & final_vote_qualified_root : final_vote_qualified_roots) - { - auto status = store.del (transaction, tables::final_votes, final_vote_qualified_root); - store.release_assert_success (status); - } + auto status = store.del (transaction, tables::final_votes, root); + store.release_assert_success (status); } size_t nano::store::rocksdb::final_vote::count (store::transaction const & transaction_a) const @@ -55,11 +47,6 @@ size_t nano::store::rocksdb::final_vote::count (store::transaction const & trans return store.count (transaction_a, tables::final_votes); } -void nano::store::rocksdb::final_vote::clear (store::write_transaction const & transaction_a, nano::root const & root_a) -{ - del (transaction_a, root_a); -} - void nano::store::rocksdb::final_vote::clear (store::write_transaction const & transaction_a) { store.drop (transaction_a, nano::tables::final_votes); diff --git a/nano/store/rocksdb/final_vote.hpp b/nano/store/rocksdb/final_vote.hpp index 67a1e0d52b..04b470fede 100644 --- a/nano/store/rocksdb/final_vote.hpp +++ b/nano/store/rocksdb/final_vote.hpp @@ -16,10 +16,9 @@ class final_vote : public nano::store::final_vote public: explicit final_vote (nano::store::rocksdb::component & store); bool put (store::write_transaction const & transaction_a, nano::qualified_root const & root_a, nano::block_hash const & hash_a) override; - std::vector get (store::transaction const & transaction_a, nano::root const & root_a) override; - void del (store::write_transaction const & transaction_a, nano::root const & root_a) override; + std::optional get (store::transaction const & transaction_a, nano::qualified_root const & qualified_root_a) override; + void del (store::write_transaction const & transaction_a, nano::qualified_root const & root_a) override; size_t count (store::transaction const & transaction_a) const override; - void clear (store::write_transaction const & transaction_a, nano::root const & root_a) override; void clear (store::write_transaction const & transaction_a) override; iterator begin (store::transaction const & transaction_a, nano::qualified_root const & root_a) const override; iterator begin (store::transaction const & transaction_a) const override; diff --git a/nano/store/rocksdb/rocksdb.cpp b/nano/store/rocksdb/rocksdb.cpp index fe6026fdfb..b356064f42 100644 --- a/nano/store/rocksdb/rocksdb.cpp +++ b/nano/store/rocksdb/rocksdb.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include diff --git a/nano/store/transaction.cpp b/nano/store/transaction.cpp index dcfc3d7e8d..6f475bf06c 100644 --- a/nano/store/transaction.cpp +++ b/nano/store/transaction.cpp @@ -9,6 +9,7 @@ nano::store::transaction_impl::transaction_impl (nano::id_dispenser::id_t const store_id_a) : store_id{ store_id_a } { + debug_assert (!nano::thread_role::is_network_io (), "database operations are not allowed to run on network IO threads"); } /* @@ -82,13 +83,15 @@ void nano::store::read_transaction::refresh () renew (); } -void nano::store::read_transaction::refresh_if_needed (std::chrono::milliseconds max_age) +bool nano::store::read_transaction::refresh_if_needed (std::chrono::milliseconds max_age) { auto now = std::chrono::steady_clock::now (); if (now - start > max_age) { refresh (); + return true; } + return false; } /* diff --git a/nano/store/transaction.hpp b/nano/store/transaction.hpp index 23459a9258..0697e1b1e8 100644 --- a/nano/store/transaction.hpp +++ b/nano/store/transaction.hpp @@ -66,7 +66,7 @@ class read_transaction final : public transaction void reset (); void renew (); void refresh (); - void refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }); + bool refresh_if_needed (std::chrono::milliseconds max_age = std::chrono::milliseconds{ 500 }); private: std::unique_ptr impl; diff --git a/nano/store/write_queue.cpp b/nano/store/write_queue.cpp index 41b687ce76..9791a06452 100644 --- a/nano/store/write_queue.cpp +++ b/nano/store/write_queue.cpp @@ -66,7 +66,9 @@ nano::store::write_guard nano::store::write_queue::wait (writer writer) bool nano::store::write_queue::contains (writer writer) const { nano::lock_guard guard{ mutex }; - return std::find (queue.cbegin (), queue.cend (), writer) != queue.cend (); + return std::any_of (queue.cbegin (), queue.cend (), [writer] (auto const & item) { + return item.first == writer; + }); } void nano::store::write_queue::pop () @@ -83,17 +85,19 @@ void nano::store::write_queue::acquire (writer writer) { nano::unique_lock lock{ mutex }; - // There should be no duplicates in the queue - debug_assert (std::none_of (queue.cbegin (), queue.cend (), [writer] (auto const & item) { return item == writer; })); + // There should be no duplicates in the queue (exception is testing) + debug_assert (std::none_of (queue.cbegin (), queue.cend (), [writer] (auto const & item) { + return item.first == writer; + }) + || writer == writer::testing); + + auto const id = next++; // Add writer to the end of the queue if it's not already waiting - auto exists = std::find (queue.cbegin (), queue.cend (), writer) != queue.cend (); - if (!exists) - { - queue.push_back (writer); - } + queue.push_back ({ writer, id }); - condition.wait (lock, [&] () { return queue.front () == writer; }); + // Wait until we are at the front of the queue + condition.wait (lock, [&] () { return queue.front ().second == id; }); } void nano::store::write_queue::release (writer writer) @@ -101,7 +105,7 @@ void nano::store::write_queue::release (writer writer) { nano::lock_guard guard{ mutex }; release_assert (!queue.empty ()); - release_assert (queue.front () == writer); + release_assert (queue.front ().first == writer); queue.pop_front (); } condition.notify_all (); diff --git a/nano/store/write_queue.hpp b/nano/store/write_queue.hpp index 6b4688618a..95ae5a030a 100644 --- a/nano/store/write_queue.hpp +++ b/nano/store/write_queue.hpp @@ -13,10 +13,12 @@ enum class writer { generic, node, - blockprocessor, + block_processor, confirmation_height, pruning, voting_final, + bounded_backlog, + online_weight, testing // Used in tests to emulate a write lock }; @@ -70,7 +72,9 @@ class write_queue final void release (writer writer); private: - std::deque queue; + uint64_t next{ 0 }; + using entry = std::pair; // uint64_t is a unique id for each write_guard + std::deque queue; mutable nano::mutex mutex; nano::condition_variable condition; diff --git a/nano/test_common/network.hpp b/nano/test_common/network.hpp index 3bfcd22c38..d201fee17d 100644 --- a/nano/test_common/network.hpp +++ b/nano/test_common/network.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include namespace nano diff --git a/nano/test_common/system.cpp b/nano/test_common/system.cpp index 758a5c72ee..2a61ca2652 100644 --- a/nano/test_common/system.cpp +++ b/nano/test_common/system.cpp @@ -1,8 +1,9 @@ #include #include #include +#include #include -#include +#include #include #include #include @@ -447,7 +448,7 @@ void nano::test::system::generate_rollback (nano::node & node_a, std::vector> rollback_list; + std::deque> rollback_list; auto error = node_a.ledger.rollback (transaction, hash, rollback_list); (void)error; debug_assert (!error); diff --git a/nano/test_common/system.hpp b/nano/test_common/system.hpp index 5644f0c2ce..0ad6b8170b 100644 --- a/nano/test_common/system.hpp +++ b/nano/test_common/system.hpp @@ -49,10 +49,10 @@ namespace test /** Generate work with difficulty between \p min_difficulty_a (inclusive) and \p max_difficulty_a (exclusive) */ uint64_t work_generate_limited (nano::block_hash const & root_a, uint64_t min_difficulty_a, uint64_t max_difficulty_a); /** - * Polls, sleep if there's no work to be done (default 50ms), then check the deadline + * Polls, sleep if there's no work to be done (default 10ms), then check the deadline * @returns 0 or nano::deadline_expired */ - std::error_code poll (std::chrono::nanoseconds const & sleep_time = std::chrono::milliseconds (50)); + std::error_code poll (std::chrono::nanoseconds const & sleep_time = std::chrono::milliseconds (10)); std::error_code poll_until_true (std::chrono::nanoseconds deadline, std::function); void delay_ms (std::chrono::milliseconds const & delay); void deadline_set (std::chrono::duration const & delta); diff --git a/nano/test_common/telemetry.cpp b/nano/test_common/telemetry.cpp index db2465e074..a2aa36a3ce 100644 --- a/nano/test_common/telemetry.cpp +++ b/nano/test_common/telemetry.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -61,4 +61,4 @@ bool nano::test::compare_telemetry (const nano::telemetry_data & data, const nan bool result = false; compare_telemetry_impl (data, node, result); return result; -} \ No newline at end of file +} diff --git a/nano/test_common/testutil.cpp b/nano/test_common/testutil.cpp index f2c3c77129..a3400f9b9f 100644 --- a/nano/test_common/testutil.cpp +++ b/nano/test_common/testutil.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,11 @@ bool nano::test::exists (nano::node & node, std::vector> const blocks) +{ + confirm (node.ledger, blocks); +} + void nano::test::confirm (nano::ledger & ledger, std::vector> const blocks) { for (auto const block : blocks) @@ -236,6 +242,13 @@ std::vector nano::test::blocks_to_hashes (std::vector> nano::test::clone (std::vector> blocks) +{ + std::vector> clones; + std::transform (blocks.begin (), blocks.end (), std::back_inserter (clones), [] (auto & block) { return block->clone (); }); + return clones; +} + std::shared_ptr nano::test::fake_channel (nano::node & node, nano::account node_id) { auto channel = std::make_shared (node); @@ -374,3 +387,8 @@ std::vector> nano::test::all_blocks (nano::node & n } return result; } + +nano::uint128_t nano::test::minimum_principal_weight () +{ + return nano::dev::genesis->balance ().number () / nano::dev::network_params.network.principal_weight_factor; +} diff --git a/nano/test_common/testutil.hpp b/nano/test_common/testutil.hpp index 716be359cf..1964af057b 100644 --- a/nano/test_common/testutil.hpp +++ b/nano/test_common/testutil.hpp @@ -2,9 +2,10 @@ #include #include -#include -#include +#include +#include #include +#include #include @@ -159,20 +160,6 @@ class stop_guard /* Convenience globals for gtest projects */ namespace nano { -class node; -using uint128_t = boost::multiprecision::uint128_t; -class keypair; -class public_key; -class block_hash; -class telemetry_data; -class network_params; -class vote; -class block; -class election; -class ledger; - -extern nano::uint128_t const & genesis_amount; - namespace test { class system; @@ -329,6 +316,7 @@ namespace test void confirm (nano::ledger & ledger, std::vector> const blocks); void confirm (nano::ledger & ledger, std::shared_ptr const block); void confirm (nano::ledger & ledger, nano::block_hash const & hash); + void confirm (nano::node & node, std::vector> const blocks); /* * Convenience function to check whether *all* of the hashes exists in node ledger or in the pruned table. * @return true if all blocks are fully processed and inserted in the ledger, false otherwise @@ -389,6 +377,10 @@ namespace test * Converts list of blocks to list of hashes */ std::vector blocks_to_hashes (std::vector> blocks); + /* + * Clones list of blocks + */ + std::vector> clone (std::vector> blocks); /* * Creates a new fake channel associated with `node` */ @@ -440,5 +432,7 @@ namespace test * Returns all blocks in the ledger */ std::vector> all_blocks (nano::node &); + + nano::uint128_t minimum_principal_weight (); } } diff --git a/ci/record_rep_weights.py b/record_rep_weights.py similarity index 67% rename from ci/record_rep_weights.py rename to record_rep_weights.py index cc7ea49ca8..b3cb6f8077 100644 --- a/ci/record_rep_weights.py +++ b/record_rep_weights.py @@ -7,7 +7,7 @@ parser = argparse.ArgumentParser( description='Generate bootstrap representative weight file.') -parser.add_argument("output", type=str, help="output weight file") +parser.add_argument("network", type=str, help="Network name. Eg Live or Beta") parser.add_argument("--rpc", help="node rpc host:port", default="http://[::1]:7076") parser.add_argument( @@ -43,9 +43,12 @@ supplymax /= int('1000000000000000000000000000000') supplymax = int(supplymax * args.limit) supplymax *= int('1000000000000000000000000000000') +outputfile = 'bootstrap_weights_' + args.network + '.hpp' -with open(args.output, 'wb') as of: - of.write(unhexlify("%032X" % block_height)) +with open(outputfile, 'w') as of: + of.write(f"#pragma once\n\n#include \n#include \n\nnamespace nano::weights\n{{\n") + of.write(f"// Bootstrap weights for {args.network} network\n") + of.write(f"std::vector> preconfigured_weights_{args.network} = {{\n") total = int(0) count = 0 @@ -54,17 +57,20 @@ break acc_val = int(hexlify(b32decode(rep["account"].encode( 'utf-8').replace(b"nano_", b"").translate(tbl) + b"====")), 16) - acc_bytes = unhexlify("%064X" % (((acc_val >> 36) & ((1 << 256) - 1)))) - weight_bytes = unhexlify("%032X" % rep["weight"]) - of.write(acc_bytes) - of.write(weight_bytes) + + of.write(f'\t{{ "{rep["account"]}", "{rep["weight"]}" }},\n') + total += rep["weight"] count += 1 - print(rep["account"] + ": " + str(rep["weight"])) + print(f'{rep["account"]} {rep["weight"]}') if total >= supplymax: break - - print("wrote %d rep weights" % count) - print("max supply %d" % supplymax) + of.write(f'}};\n') + of.write(f'uint64_t max_blocks_{args.network} = {block_height};\n') + of.write(f"}}\n") + + print(f"wrote {count} rep weights") + print(f"max supply {supplymax}") + print(f"Weight file generated: {outputfile}") of.close() diff --git a/rep_weights_live.bin b/rep_weights_live.bin deleted file mode 100644 index 0112f8aceb..0000000000 Binary files a/rep_weights_live.bin and /dev/null differ