From ceebac427779fa0182659fe3337f1a632fb24d14 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Tue, 11 Feb 2020 17:53:21 +0000 Subject: [PATCH 1/4] Aggressive flooding for local blocks Floods locally produced blocks (going through the work watcher, or process_local - wallet, RPC process) such that they are sent to all PRs and random subset of other peers. This allows more protection against Sybil attacks, and more streamlined elections as there are less occasions of votes arriving before blocks. As a consequence, the average amount of echoes will increase by 1 for PRs. However, with this change in place and used by most of the network, a future enhancement would be to reduce the republishing fanout. This change is based on a proposal by @Srayman for more information see the original proposal at https://medium.com/nanocurrency/proposal-for-nano-node-network-optimizations-21003e79cdba --- nano/node/blockprocessor.cpp | 15 +++++++++++---- nano/node/blockprocessor.hpp | 4 ++-- nano/node/network.cpp | 19 +++++++++++++++++++ nano/node/network.hpp | 10 ++++------ nano/node/node.cpp | 2 +- nano/node/wallet.cpp | 1 + 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 79078028bf..3fca6212c8 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -370,7 +370,7 @@ void nano::block_processor::process_batch (nano::unique_lock & lock_ } } -void nano::block_processor::process_live (nano::block_hash const & hash_a, std::shared_ptr block_a, const bool watch_work_a) +void nano::block_processor::process_live (nano::block_hash const & hash_a, std::shared_ptr block_a, const bool watch_work_a, const bool initial_publish_a) { // Add to work watcher to prevent dropping the election if (watch_work_a) @@ -382,7 +382,14 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: node.active.insert (block_a, false); // Announce block contents to the network - node.network.flood_block (block_a, false); + if (initial_publish_a) + { + node.network.flood_block_initial (block_a); + } + else + { + node.network.flood_block (block_a, false); + } if (node.config.enable_voting && node.wallets.rep_counts ().voting > 0) { // Announce our weighted vote to the network @@ -390,7 +397,7 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: } } -nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, nano::unchecked_info info_a, const bool watch_work_a) +nano::process_return nano::block_processor::process_one (nano::write_transaction const & transaction_a, nano::unchecked_info info_a, const bool watch_work_a, const bool first_publish_a) { nano::process_return result; auto hash (info_a.block->hash ()); @@ -408,7 +415,7 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } if (info_a.modified > nano::seconds_since_epoch () - 300 && node.block_arrival.recent (hash)) { - process_live (hash, info_a.block, watch_work_a); + process_live (hash, info_a.block, watch_work_a, first_publish_a); } queue_unchecked (transaction_a, hash); break; diff --git a/nano/node/blockprocessor.hpp b/nano/node/blockprocessor.hpp index 15614d42dd..9563a21310 100644 --- a/nano/node/blockprocessor.hpp +++ b/nano/node/blockprocessor.hpp @@ -42,7 +42,7 @@ class block_processor final bool should_log (bool); bool have_blocks (); void process_blocks (); - nano::process_return process_one (nano::write_transaction const &, nano::unchecked_info, const bool = false); + nano::process_return process_one (nano::write_transaction const &, nano::unchecked_info, const bool = false, const bool = false); nano::process_return process_one (nano::write_transaction const &, std::shared_ptr, const bool = false); nano::vote_generator generator; // Delay required for average network propagartion before requesting confirmation @@ -52,7 +52,7 @@ class block_processor final void queue_unchecked (nano::write_transaction const &, nano::block_hash const &); void verify_state_blocks (nano::unique_lock &, size_t = std::numeric_limits::max ()); void process_batch (nano::unique_lock &); - void process_live (nano::block_hash const &, std::shared_ptr, const bool = false); + void process_live (nano::block_hash const &, std::shared_ptr, const bool = false, const bool = false); void requeue_invalid (nano::block_hash const &, nano::unchecked_info const &); bool stopped; bool active; diff --git a/nano/node/network.cpp b/nano/node/network.cpp index 3289132622..700d8e92c6 100644 --- a/nano/node/network.cpp +++ b/nano/node/network.cpp @@ -154,6 +154,25 @@ void nano::network::flood_message (nano::message const & message_a, bool const i } } +void nano::network::flood_block (std::shared_ptr const & block_a, bool const is_droppable_a) +{ + nano::publish message (block_a); + flood_message (message, is_droppable_a); +} + +void nano::network::flood_block_initial (std::shared_ptr const & block_a) +{ + nano::publish message (block_a); + for (auto const & i : node.rep_crawler.principal_representatives ()) + { + i.channel->send (message, nullptr, false); + } + for (auto & i : list_non_pr (fanout (1.0))) + { + i->send (message, nullptr, false); + } +} + void nano::network::flood_vote (std::shared_ptr const & vote_a, float scale) { nano::confirm_ack message (vote_a); diff --git a/nano/node/network.hpp b/nano/node/network.hpp index e55790e154..e07075050c 100644 --- a/nano/node/network.hpp +++ b/nano/node/network.hpp @@ -107,12 +107,10 @@ class network final } void flood_vote (std::shared_ptr const &, float scale); void flood_vote_pr (std::shared_ptr const &); - void flood_block (std::shared_ptr block_a, bool const is_droppable_a = true) - { - nano::publish publish (block_a); - flood_message (publish, is_droppable_a); - } - + // Publish block to a random selection of peers + void flood_block (std::shared_ptr const &, bool const is_droppable_a = true); + // Publish block to all PRs and a random selection of non-PRs + void flood_block_initial (std::shared_ptr const &); 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 &); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index a131596e76..def4d704ab 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -627,7 +627,7 @@ nano::process_return nano::node::process_local (std::shared_ptr blo block_processor.wait_write (); // Process block auto transaction (store.tx_begin_write ()); - return block_processor.process_one (transaction, info, work_watcher_a); + return block_processor.process_one (transaction, info, work_watcher_a, true); } void nano::node::start () diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 386c48b1aa..fbdf2d32c6 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1454,6 +1454,7 @@ void nano::work_watcher::watching (nano::qualified_root const & root_a, std::sha if (!ec) { + watcher_l->node.network.flood_block_initial (block); watcher_l->node.active.update_difficulty (block); watcher_l->update (root_a, block); updated_l = true; From c767a2c57cf8bdc3150b72f2b1ee9e8dadd033df Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Tue, 18 Feb 2020 18:13:56 +0000 Subject: [PATCH 2/4] Add flag to disable republishing in block processor and a test for aggressive flooding --- nano/core_test/node.cpp | 67 ++++++++++++++++++++++++++++++++++++ nano/node/blockprocessor.cpp | 2 +- nano/node/nodeconfig.hpp | 1 + 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 1865c1d599..d6ed6a5aee 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -3613,6 +3613,73 @@ TEST (node, bandwidth_limiter) node.stop (); } +// Tests that local blocks are flooded to all principal representatives +TEST (node, aggressive_flooding) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + node_flags.disable_block_processor_republishing = true; + auto & node1 (*system.add_node (node_flags)); + auto & wallet1 (*system.wallet (0)); + wallet1.insert_adhoc (nano::test_genesis_key.prv); + std::array, std::shared_ptr>, 5> nodes_wallets{}; + std::generate (nodes_wallets.begin (), nodes_wallets.end (), [&system, node_flags]() { + nano::node_config node_config; + node_config.peering_port = nano::get_available_port (); + auto node (system.add_node (node_config, node_flags)); + return std::make_pair (node, system.wallet (system.nodes.size () - 1)); + }); + auto large_amount = (nano::genesis_amount / 2) / nodes_wallets.size (); + for (auto & node_wallet : nodes_wallets) + { + nano::keypair keypair; + node_wallet.second->store.representative_set (node_wallet.first->wallets.tx_begin_write (), keypair.pub); + node_wallet.second->insert_adhoc (keypair.prv); + wallet1.send_action (nano::test_genesis_key.pub, keypair.pub, large_amount); + } + // Wait until all nodes have a representative + system.deadline_set (10s); + while (node1.rep_crawler.principal_representatives ().size () != nodes_wallets.size ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + // Generate a few blocks and ensure they reach all representatives, even though they do not republish blocks + nano::block_builder builder; + std::shared_ptr block{}; + { + auto transaction (node1.store.tx_begin_read ()); + block = builder.state () + .account (nano::test_genesis_key.pub) + .representative (nano::test_genesis_key.pub) + .previous (node1.ledger.latest (transaction, nano::test_genesis_key.pub)) + .balance (node1.ledger.account_balance (transaction, nano::test_genesis_key.pub) - 1) + .link (nano::test_genesis_key.pub) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (*node1.work_generate_blocking (node1.ledger.latest (transaction, nano::test_genesis_key.pub))) + .build (); + // Processing locally goes through the aggressive block flooding path + node1.process_local (block, false); + } + system.deadline_set (3s); + while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [hash = block->hash ()](auto & node_wallet) { + return node_wallet.first->block (hash) == nullptr; + })) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Do the same for a wallet block + auto wallet_block = wallet1.send_sync (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 1); + system.deadline_set (3s); + while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [wallet_block](auto & node_wallet) { + return node_wallet.first->block (wallet_block) == nullptr; + })) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + TEST (active_difficulty, recalculate_work) { nano::system system; diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 3fca6212c8..41591bdd9c 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -386,7 +386,7 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: { node.network.flood_block_initial (block_a); } - else + else if (!node.flags.disable_block_processor_republishing) { node.network.flood_block (block_a, false); } diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index 7ea91e5da6..1227cbeac4 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -126,6 +126,7 @@ class node_flags final bool disable_unchecked_drop{ true }; bool disable_providing_telemetry_metrics{ false }; bool disable_block_processor_unchecked_deletion{ false }; + bool disable_block_processor_republishing{ false }; bool fast_bootstrap{ false }; bool read_only{ false }; nano::generate_cache generate_cache; From bb644c46dc870b36a41746f094249d631c19743f Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Tue, 18 Feb 2020 18:49:33 +0000 Subject: [PATCH 3/4] Adjust timings for sanitizer builds --- nano/core_test/node.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index d6ed6a5aee..59d4d07ca8 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -3639,7 +3639,7 @@ TEST (node, aggressive_flooding) wallet1.send_action (nano::test_genesis_key.pub, keypair.pub, large_amount); } // Wait until all nodes have a representative - system.deadline_set (10s); + system.deadline_set (!is_sanitizer_build ? 5s : 15s); while (node1.rep_crawler.principal_representatives ().size () != nodes_wallets.size ()) { ASSERT_NO_ERROR (system.poll ()); @@ -3661,7 +3661,7 @@ TEST (node, aggressive_flooding) // Processing locally goes through the aggressive block flooding path node1.process_local (block, false); } - system.deadline_set (3s); + system.deadline_set (!is_sanitizer_build ? 3s : 10s); while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [hash = block->hash ()](auto & node_wallet) { return node_wallet.first->block (hash) == nullptr; })) @@ -3671,7 +3671,7 @@ TEST (node, aggressive_flooding) // Do the same for a wallet block auto wallet_block = wallet1.send_sync (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 1); - system.deadline_set (3s); + system.deadline_set (!is_sanitizer_build ? 3s : 10s); while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [wallet_block](auto & node_wallet) { return node_wallet.first->block (wallet_block) == nullptr; })) From 40efd28c40b5b8172974b0e6f155a4f4a7907448 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Wed, 19 Feb 2020 09:33:43 +0000 Subject: [PATCH 4/4] Wrap in a lambda to simplify test, and adjust it --- nano/core_test/node.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index fd0ed1a721..60537aef61 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -3644,7 +3644,7 @@ TEST (node, aggressive_flooding) { ASSERT_NO_ERROR (system.poll ()); } - // Generate a few blocks and ensure they reach all representatives, even though they do not republish blocks + // Generate blocks and ensure they are sent to all representatives nano::block_builder builder; std::shared_ptr block{}; { @@ -3658,23 +3658,34 @@ TEST (node, aggressive_flooding) .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) .work (*node1.work_generate_blocking (node1.ledger.latest (transaction, nano::test_genesis_key.pub))) .build (); - // Processing locally goes through the aggressive block flooding path - node1.process_local (block, false); } + // Processing locally goes through the aggressive block flooding path + node1.process_local (block, false); + + auto all_have_block = [&nodes_wallets](nano::block_hash const & hash_a) { + return std::all_of (nodes_wallets.begin (), nodes_wallets.end (), [hash = hash_a](auto const & node_wallet) { + return node_wallet.first->block (hash) != nullptr; + }); + }; + system.deadline_set (!is_sanitizer_build ? 3s : 10s); - while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [hash = block->hash ()](auto & node_wallet) { - return node_wallet.first->block (hash) == nullptr; - })) + while (!all_have_block (block->hash ())) { ASSERT_NO_ERROR (system.poll ()); } // Do the same for a wallet block - auto wallet_block = wallet1.send_sync (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 1); + auto wallet_block = wallet1.send_sync (nano::test_genesis_key.pub, nano::test_genesis_key.pub, 10); system.deadline_set (!is_sanitizer_build ? 3s : 10s); - while (std::any_of (nodes_wallets.begin (), nodes_wallets.end (), [wallet_block](auto & node_wallet) { - return node_wallet.first->block (wallet_block) == nullptr; - })) + while (!all_have_block (wallet_block)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Wait until the main node has all blocks: genesis + (send+open) for each representative + 2 local blocks + // The main node only sees all blocks if other nodes are flooding their PR's open block to all other PRs + system.deadline_set (5s); + while (node1.ledger.cache.block_count < 1 + 2 * nodes_wallets.size () + 2) { ASSERT_NO_ERROR (system.poll ()); }