From 900e89e18ffc51f623e65fc6d7dbf68762ed5211 Mon Sep 17 00:00:00 2001 From: Sergey Kroshnin Date: Wed, 31 Mar 2021 12:13:18 +0300 Subject: [PATCH] Add gap pending epoch open blocks to unchecked (#3108) * Add gap pending epoch open blocks to unchecked to address found by @Srayman full bootstrap slowdown * Expanding test to show that these epochs are picked up when a pending entry comes in. * Using block_processor.flush () instead of ASSERT_TIMELY. * More checks in ledger.epoch_open_pending test * Do not check last epoch blocks * Explain settings Co-authored-by: clemahieu --- nano/core_test/ledger.cpp | 37 +++++++++++++++++++++++++++--- nano/lib/errors.cpp | 2 ++ nano/lib/errors.hpp | 1 + nano/node/blockprocessor.cpp | 35 ++++++++++++++++++++++++---- nano/node/blockprocessor.hpp | 2 +- nano/node/json_handler.cpp | 5 ++++ nano/secure/blockstore_partial.hpp | 6 ++--- nano/secure/common.cpp | 10 ++++++-- nano/secure/common.hpp | 4 +++- nano/secure/ledger.cpp | 2 +- 10 files changed, 88 insertions(+), 16 deletions(-) diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 629cc928c..3c2641cbd 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -2631,13 +2631,44 @@ TEST (ledger, successor_epoch) TEST (ledger, epoch_open_pending) { + nano::block_builder builder; nano::system system (1); auto & node1 (*system.nodes[0]); nano::work_pool pool (std::numeric_limits::max ()); nano::keypair key1; - nano::state_block epoch_open (key1.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (key1.pub)); - auto transaction (node1.store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::block_position, node1.ledger.process (transaction, epoch_open).code); + auto epoch_open = builder.state () + .account (key1.pub) + .previous (0) + .representative (0) + .balance (0) + .link (node1.ledger.epoch_link (nano::epoch::epoch_1)) + .sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub) + .work (*pool.generate (key1.pub)) + .build_shared (); + auto process_result (node1.ledger.process (node1.store.tx_begin_write (), *epoch_open)); + ASSERT_EQ (nano::process_result::gap_epoch_open_pending, process_result.code); + ASSERT_EQ (nano::signature_verification::valid_epoch, process_result.verified); + node1.block_processor.add (epoch_open); + node1.block_processor.flush (); + ASSERT_FALSE (node1.ledger.block_exists (epoch_open->hash ())); + // Open block should be inserted into unchecked + auto blocks (node1.store.unchecked_get (node1.store.tx_begin_read (), nano::hash_or_account (epoch_open->account ()).hash)); + ASSERT_EQ (blocks.size (), 1); + ASSERT_EQ (blocks[0].block->full_hash (), epoch_open->full_hash ()); + ASSERT_EQ (blocks[0].verified, nano::signature_verification::valid_epoch); + // New block to process epoch open + auto send1 = builder.state () + .account (nano::genesis_account) + .previous (nano::genesis_hash) + .representative (nano::genesis_account) + .balance (nano::genesis_amount - 100) + .link (key1.pub) + .sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub) + .work (*pool.generate (nano::genesis_hash)) + .build_shared (); + node1.block_processor.add (send1); + node1.block_processor.flush (); + ASSERT_TRUE (node1.ledger.block_exists (epoch_open->hash ())); } TEST (ledger, block_hash_account_conflict) diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 0e28bf1d7..41e2b2003 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -248,6 +248,8 @@ std::string nano::error_process_messages::message (int ev) const return "Gap previous block"; case nano::error_process::gap_source: return "Gap source block"; + case nano::error_process::gap_epoch_open_pending: + return "Gap pending for open epoch block"; case nano::error_process::opened_burn_account: return "Burning account"; case nano::error_process::balance_mismatch: diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 3e416a0e6..a47171ea8 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -137,6 +137,7 @@ enum class error_process unreceivable, // Source block doesn't exist or has already been received gap_previous, // Block marked as previous is unknown gap_source, // Block marked as source is unknown + gap_epoch_open_pending, // Block marked as pending blocks required for epoch open block are unknown opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. balance_mismatch, // Balance and amount delta don't match block_position, // This block cannot follow the previous block diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 26d27c0b7..ab8a5f9fe 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -389,6 +389,15 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction events_a.events.emplace_back ([this, hash, block = info_a.block, result, watch_work_a, origin_a](nano::transaction const & post_event_transaction_a) { process_live (post_event_transaction_a, hash, block, result, watch_work_a, origin_a); }); } 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. */ + if (block->type () == nano::block_type::send || (block->type () == nano::block_type::state && block->sideband ().details.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 ().is_zero () ? block->link () : block->destination ()); + } break; } case nano::process_result::gap_previous: @@ -431,6 +440,24 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); break; } + case nano::process_result::gap_epoch_open_pending: + { + if (node.config.logging.ledger_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Gap pending entries for epoch open: %1%") % hash.to_string ())); + } + info_a.verified = result.verified; + if (info_a.modified == 0) + { + info_a.modified = nano::seconds_since_epoch (); + } + + nano::unchecked_key unchecked_key (block->account (), hash); // Specific unchecked key starting with epoch open block account public key + node.store.unchecked_put (transaction_a, unchecked_key, info_a); + + node.stats.inc (nano::stat::type::ledger, nano::stat::detail::gap_source); + break; + } case nano::process_result::old: { if (node.config.logging.ledger_duplicate_logging ()) @@ -537,18 +564,18 @@ void nano::block_processor::process_old (nano::transaction const & transaction_a } } -void nano::block_processor::queue_unchecked (nano::write_transaction const & transaction_a, nano::block_hash const & hash_a) +void nano::block_processor::queue_unchecked (nano::write_transaction const & transaction_a, nano::hash_or_account const & hash_or_account_a) { - auto unchecked_blocks (node.store.unchecked_get (transaction_a, hash_a)); + auto unchecked_blocks (node.store.unchecked_get (transaction_a, hash_or_account_a.hash)); for (auto & info : unchecked_blocks) { if (!node.flags.disable_block_processor_unchecked_deletion) { - node.store.unchecked_del (transaction_a, nano::unchecked_key (hash_a, info.block->hash ())); + node.store.unchecked_del (transaction_a, nano::unchecked_key (hash_or_account_a, info.block->hash ())); } add (info, true); } - node.gap_cache.erase (hash_a); + node.gap_cache.erase (hash_or_account_a.hash); } void nano::block_processor::requeue_invalid (nano::block_hash const & hash_a, nano::unchecked_info const & info_a) diff --git a/nano/node/blockprocessor.hpp b/nano/node/blockprocessor.hpp index 44f3612c0..ad1f4f74a 100644 --- a/nano/node/blockprocessor.hpp +++ b/nano/node/blockprocessor.hpp @@ -70,7 +70,7 @@ public: static std::chrono::milliseconds constexpr confirmation_request_delay{ 1500 }; private: - void queue_unchecked (nano::write_transaction const &, nano::block_hash const &); + void queue_unchecked (nano::write_transaction const &, nano::hash_or_account const &); void process_batch (nano::unique_lock &); void process_live (nano::transaction const &, nano::block_hash const &, std::shared_ptr const &, nano::process_return const &, const bool = false, nano::block_origin const = nano::block_origin::remote); void process_old (nano::transaction const &, std::shared_ptr const &, nano::block_origin const); diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 222104031..9f68eedee 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -3107,6 +3107,11 @@ void nano::json_handler::process () rpc_l->ec = nano::error_process::block_position; break; } + case nano::process_result::gap_epoch_open_pending: + { + rpc_l->ec = nano::error_process::gap_epoch_open_pending; + break; + } case nano::process_result::fork: { const bool force = rpc_l->request.get ("force", false); diff --git a/nano/secure/blockstore_partial.hpp b/nano/secure/blockstore_partial.hpp index dcbb3f518..f3970e3fe 100644 --- a/nano/secure/blockstore_partial.hpp +++ b/nano/secure/blockstore_partial.hpp @@ -806,10 +806,8 @@ public: { parallel_traversal ( [&action_a, this](nano::uint512_t const & start, nano::uint512_t const & end, bool const is_last) { - nano::uint512_union union_start (start); - nano::uint512_union union_end (end); - nano::unchecked_key key_start (union_start.uint256s[0].number (), union_start.uint256s[1].number ()); - nano::unchecked_key key_end (union_end.uint256s[0].number (), union_end.uint256s[1].number ()); + nano::unchecked_key key_start (start); + nano::unchecked_key key_end (end); auto transaction (this->tx_begin_read ()); action_a (transaction, this->unchecked_begin (transaction, key_start), !is_last ? this->unchecked_begin (transaction, key_end) : this->unchecked_end ()); }); diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 3b6a596ec..24279d071 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -822,12 +822,18 @@ nano::wallet_id nano::random_wallet_id () return wallet_id; } -nano::unchecked_key::unchecked_key (nano::block_hash const & previous_a, nano::block_hash const & hash_a) : -previous (previous_a), +nano::unchecked_key::unchecked_key (nano::hash_or_account const & previous_a, nano::block_hash const & hash_a) : +previous (previous_a.hash), hash (hash_a) { } +nano::unchecked_key::unchecked_key (nano::uint512_union const & union_a) : +previous (union_a.uint256s[0].number ()), +hash (union_a.uint256s[1].number ()) +{ +} + bool nano::unchecked_key::deserialize (nano::stream & stream_a) { auto error (false); diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 0e8c9f0dc..31c5a8a9c 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -171,7 +171,8 @@ class unchecked_key final { public: unchecked_key () = default; - unchecked_key (nano::block_hash const &, nano::block_hash const &); + unchecked_key (nano::hash_or_account const &, nano::block_hash const &); + unchecked_key (nano::uint512_union const &); bool deserialize (nano::stream &); bool operator== (nano::unchecked_key const &) const; nano::block_hash const & key () const; @@ -312,6 +313,7 @@ enum class process_result unreceivable, // Source block doesn't exist, has already been received, or requires an account upgrade (epoch blocks) gap_previous, // Block marked as previous is unknown gap_source, // Block marked as source is unknown + gap_epoch_open_pending, // Block marked as pending blocks required for epoch open block are unknown opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. balance_mismatch, // Balance and amount delta don't match representative_mismatch, // Representative is changed when it is not allowed diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index d198b1679..cd1834227 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -431,7 +431,7 @@ void ledger_processor::epoch_block_impl (nano::state_block & block_a) if (result.code == nano::process_result::progress) { bool pending_exists = ledger.store.pending_any (transaction, block_a.hashables.account); - result.code = pending_exists ? nano::process_result::progress : nano::process_result::block_position; + result.code = pending_exists ? nano::process_result::progress : nano::process_result::gap_epoch_open_pending; } } if (result.code == nano::process_result::progress)