Remove gap cache (#4422)

* Move gap tracker logic into `gap_cache`

* Remove gap cache
This commit is contained in:
Piotr Wójcik 2024-02-08 15:00:09 +01:00 committed by GitHub
commit 9332812c69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1 additions and 452 deletions

View file

@ -22,7 +22,6 @@ add_executable(
enums.cpp
epochs.cpp
frontiers_confirmation.cpp
gap_cache.cpp
ipc.cpp
ledger.cpp
locks.cpp

View file

@ -1,168 +0,0 @@
#include <nano/store/block.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
using namespace std::chrono_literals;
TEST (gap_cache, add_new)
{
nano::test::system system (1);
nano::gap_cache cache (*system.nodes[0]);
nano::block_builder builder;
auto block1 = builder
.send ()
.previous (0)
.destination (1)
.balance (2)
.sign (nano::keypair ().prv, 4)
.work (5)
.build_shared ();
cache.add (block1->hash ());
}
TEST (gap_cache, add_existing)
{
nano::test::system system (1);
nano::gap_cache cache (*system.nodes[0]);
nano::block_builder builder;
auto block1 = builder
.send ()
.previous (0)
.destination (1)
.balance (2)
.sign (nano::keypair ().prv, 4)
.work (5)
.build_shared ();
cache.add (block1->hash ());
nano::unique_lock<nano::mutex> lock{ cache.mutex };
auto existing1 (cache.blocks.get<1> ().find (block1->hash ()));
ASSERT_NE (cache.blocks.get<1> ().end (), existing1);
auto arrival (existing1->arrival);
lock.unlock ();
ASSERT_TIMELY (20s, arrival != std::chrono::steady_clock::now ());
cache.add (block1->hash ());
ASSERT_EQ (1, cache.size ());
lock.lock ();
auto existing2 (cache.blocks.get<1> ().find (block1->hash ()));
ASSERT_NE (cache.blocks.get<1> ().end (), existing2);
ASSERT_GT (existing2->arrival, arrival);
}
TEST (gap_cache, comparison)
{
nano::test::system system (1);
nano::gap_cache cache (*system.nodes[0]);
nano::block_builder builder;
auto block1 = builder
.send ()
.previous (1)
.destination (0)
.balance (2)
.sign (nano::keypair ().prv, 4)
.work (5)
.build_shared ();
cache.add (block1->hash ());
nano::unique_lock<nano::mutex> lock{ cache.mutex };
auto existing1 (cache.blocks.get<1> ().find (block1->hash ()));
ASSERT_NE (cache.blocks.get<1> ().end (), existing1);
auto arrival (existing1->arrival);
lock.unlock ();
ASSERT_TIMELY (20s, std::chrono::steady_clock::now () != arrival);
auto block3 = builder
.send ()
.previous (0)
.destination (42)
.balance (1)
.sign (nano::keypair ().prv, 3)
.work (4)
.build_shared ();
cache.add (block3->hash ());
ASSERT_EQ (2, cache.size ());
lock.lock ();
auto existing2 (cache.blocks.get<1> ().find (block3->hash ()));
ASSERT_NE (cache.blocks.get<1> ().end (), existing2);
ASSERT_GT (existing2->arrival, arrival);
ASSERT_EQ (arrival, cache.blocks.get<1> ().begin ()->arrival);
}
// Upon receiving enough votes for a gapped block, a lazy bootstrap should be initiated
TEST (gap_cache, gap_bootstrap)
{
nano::node_flags node_flags;
node_flags.disable_legacy_bootstrap = true;
node_flags.disable_request_loop = true; // to avoid fallback behavior of broadcasting blocks
nano::test::system system (2, nano::transport::transport_type::tcp, node_flags);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
nano::block_hash latest (node1.latest (nano::dev::genesis_key.pub));
nano::keypair key;
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build_shared ();
ASSERT_EQ (nano::process_result::progress, node1.process (*send).code);
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, node1.balance (nano::dev::genesis->account ()));
ASSERT_EQ (nano::dev::constants.genesis_amount, node2.balance (nano::dev::genesis->account ()));
// Confirm send block, allowing voting on the upcoming block
auto election = nano::test::start_election (system, node1, send->hash ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (5s, node1.block_confirmed (send->hash ()));
node1.active.erase (*send);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto latest_block (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key.pub, 100));
ASSERT_NE (nullptr, latest_block);
ASSERT_TIMELY_EQ (5s, nano::dev::constants.genesis_amount - 200, node1.balance (nano::dev::genesis->account ()));
ASSERT_TIMELY_EQ (5s, nano::dev::constants.genesis_amount, node2.balance (nano::dev::genesis->account ()));
ASSERT_TIMELY_EQ (5s, node2.balance (nano::dev::genesis->account ()), nano::dev::constants.genesis_amount - 200);
}
TEST (gap_cache, two_dependencies)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
nano::keypair key;
nano::block_builder builder;
auto send1 = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (key.pub)
.balance (1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build_shared ();
auto send2 = builder
.send ()
.previous (send1->hash ())
.destination (key.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build_shared ();
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_shared ();
ASSERT_EQ (0, node1.gap_cache.size ());
node1.block_processor.add (send2);
ASSERT_TIMELY_EQ (5s, 1, node1.gap_cache.size ());
node1.block_processor.add (open);
ASSERT_TIMELY_EQ (5s, 2, node1.gap_cache.size ());
node1.block_processor.add (send1);
ASSERT_TIMELY_EQ (5s, node1.gap_cache.size (), 0);
ASSERT_TIMELY (5s, node1.store.block.exists (node1.store.tx_begin_read (), send1->hash ()));
ASSERT_TIMELY (5s, node1.store.block.exists (node1.store.tx_begin_read (), send2->hash ()));
ASSERT_TIMELY (5s, node1.store.block.exists (node1.store.tx_begin_read (), open->hash ()));
}

View file

@ -338,26 +338,6 @@ TEST (node, auto_bootstrap_age)
node1->stop ();
}
// Test ensures the block processor adds the published block to the gap cache.
TEST (node, receive_gap)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
ASSERT_EQ (0, node1.gap_cache.size ());
auto block = nano::send_block_builder ()
.previous (5)
.destination (1)
.balance (2)
.sign (nano::keypair ().prv, 4)
.work (0)
.build_shared ();
node1.work_generate_blocking (*block);
nano::publish message{ nano::dev::network_params.network, block };
auto channel1 = std::make_shared<nano::transport::fake::channel> (node1);
node1.network.inbound (message, channel1);
ASSERT_TIMELY_EQ (5s, 1, node1.gap_cache.size ());
}
TEST (node, merge_peers)
{
nano::test::system system (1);

View file

@ -26,8 +26,6 @@ add_library(
block_broadcast.hpp
block_publisher.cpp
block_publisher.hpp
gap_tracker.cpp
gap_tracker.hpp
blocking_observer.cpp
blocking_observer.hpp
blockprocessor.hpp
@ -88,8 +86,6 @@ add_library(
election_insertion_result.hpp
epoch_upgrader.hpp
epoch_upgrader.cpp
gap_cache.hpp
gap_cache.cpp
inactive_cache_information.hpp
inactive_cache_information.cpp
inactive_cache_status.hpp

View file

@ -343,7 +343,6 @@ nano::process_return nano::block_processor::process_one (store::write_transactio
void nano::block_processor::queue_unchecked (store::write_transaction const & transaction_a, nano::hash_or_account const & hash_or_account_a)
{
node.unchecked.trigger (hash_or_account_a);
node.gap_cache.erase (hash_or_account_a.hash);
}
std::unique_ptr<nano::container_info_component> nano::collect_container_info (block_processor & block_processor, std::string const & name)

View file

@ -1,136 +0,0 @@
#include <nano/node/gap_cache.hpp>
#include <nano/node/node.hpp>
#include <boost/format.hpp>
nano::gap_cache::gap_cache (nano::node & node_a) :
node (node_a)
{
}
void nano::gap_cache::add (nano::block_hash const & hash_a, std::chrono::steady_clock::time_point time_point_a)
{
nano::lock_guard<nano::mutex> lock{ mutex };
auto existing (blocks.get<tag_hash> ().find (hash_a));
if (existing != blocks.get<tag_hash> ().end ())
{
blocks.get<tag_hash> ().modify (existing, [time_point_a] (nano::gap_information & info) {
info.arrival = time_point_a;
});
}
else
{
blocks.get<tag_arrival> ().emplace (nano::gap_information{ time_point_a, hash_a, std::vector<nano::account> () });
if (blocks.get<tag_arrival> ().size () > max)
{
blocks.get<tag_arrival> ().erase (blocks.get<tag_arrival> ().begin ());
}
}
}
void nano::gap_cache::erase (nano::block_hash const & hash_a)
{
nano::lock_guard<nano::mutex> lock{ mutex };
blocks.get<tag_hash> ().erase (hash_a);
}
void nano::gap_cache::vote (std::shared_ptr<nano::vote> const & vote_a)
{
nano::lock_guard<nano::mutex> lock{ mutex };
for (auto const & hash : vote_a->hashes)
{
auto & gap_blocks_by_hash (blocks.get<tag_hash> ());
auto existing (gap_blocks_by_hash.find (hash));
if (existing != gap_blocks_by_hash.end () && !existing->bootstrap_started)
{
auto is_new (false);
gap_blocks_by_hash.modify (existing, [&is_new, &vote_a] (nano::gap_information & info) {
auto it = std::find (info.voters.begin (), info.voters.end (), vote_a->account);
is_new = (it == info.voters.end ());
if (is_new)
{
info.voters.push_back (vote_a->account);
}
});
if (is_new)
{
if (bootstrap_check (existing->voters, hash))
{
gap_blocks_by_hash.modify (existing, [] (nano::gap_information & info) {
info.bootstrap_started = true;
});
}
}
}
}
}
bool nano::gap_cache::bootstrap_check (std::vector<nano::account> const & voters_a, nano::block_hash const & hash_a)
{
nano::uint128_t tally;
for (auto const & voter : voters_a)
{
tally += node.ledger.weight (voter);
}
bool start_bootstrap (false);
if (!node.flags.disable_lazy_bootstrap)
{
if (tally >= node.online_reps.delta ())
{
start_bootstrap = true;
}
}
else if (!node.flags.disable_legacy_bootstrap && tally > bootstrap_threshold ())
{
start_bootstrap = true;
}
if (start_bootstrap && !node.ledger.block_or_pruned_exists (hash_a))
{
bootstrap_start (hash_a);
}
return start_bootstrap;
}
void nano::gap_cache::bootstrap_start (nano::block_hash const & hash_a)
{
auto node_l (node.shared ());
node.workers.add_timed_task (std::chrono::steady_clock::now () + node.network_params.bootstrap.gap_cache_bootstrap_start_interval, [node_l, hash_a] () {
if (!node_l->ledger.block_or_pruned_exists (hash_a))
{
if (!node_l->bootstrap_initiator.in_progress ())
{
node_l->logger.debug (nano::log::type::gap_cache, "Block {} has enough votes to warrant lazy bootstrapping it", hash_a.to_string ());
}
if (!node_l->flags.disable_lazy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap_lazy (hash_a);
}
else if (!node_l->flags.disable_legacy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap ();
}
}
});
}
nano::uint128_t nano::gap_cache::bootstrap_threshold ()
{
auto result ((node.online_reps.trended () / 256) * node.config.bootstrap_fraction_numerator);
return result;
}
std::size_t nano::gap_cache::size ()
{
nano::lock_guard<nano::mutex> lock{ mutex };
return blocks.size ();
}
std::unique_ptr<nano::container_info_component> nano::collect_container_info (gap_cache & gap_cache, std::string const & name)
{
auto count = gap_cache.size ();
auto sizeof_element = sizeof (decltype (gap_cache.blocks)::value_type);
auto composite = std::make_unique<container_info_composite> (name);
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "blocks", count, sizeof_element }));
return composite;
}

View file

@ -1,59 +0,0 @@
#pragma once
#include <nano/lib/numbers.hpp>
#include <nano/lib/utility.hpp>
#include <nano/secure/common.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index_container.hpp>
#include <chrono>
#include <memory>
#include <vector>
namespace nano
{
class node;
/** For each gap in account chains, track arrival time and voters */
class gap_information final
{
public:
std::chrono::steady_clock::time_point arrival;
nano::block_hash hash;
std::vector<nano::account> voters;
bool bootstrap_started{ false };
};
/** Maintains voting and arrival information for gaps (missing source or previous blocks in account chains) */
class gap_cache final
{
public:
explicit gap_cache (nano::node &);
void add (nano::block_hash const &, std::chrono::steady_clock::time_point = std::chrono::steady_clock::now ());
void erase (nano::block_hash const & hash_a);
void vote (std::shared_ptr<nano::vote> const &);
bool bootstrap_check (std::vector<nano::account> const &, nano::block_hash const &);
void bootstrap_start (nano::block_hash const & hash_a);
nano::uint128_t bootstrap_threshold ();
std::size_t size ();
// clang-format off
class tag_arrival {};
class tag_hash {};
using ordered_gaps = boost::multi_index_container<nano::gap_information,
boost::multi_index::indexed_by<
boost::multi_index::ordered_non_unique<boost::multi_index::tag<tag_arrival>,
boost::multi_index::member<gap_information, std::chrono::steady_clock::time_point, &gap_information::arrival>>,
boost::multi_index::hashed_unique<boost::multi_index::tag<tag_hash>,
boost::multi_index::member<gap_information, nano::block_hash, &gap_information::hash>>>>;
ordered_gaps blocks;
// clang-format on
std::size_t const max = 256;
nano::mutex mutex{ mutex_identifier (mutexes::gap_cache) };
nano::node & node;
};
std::unique_ptr<container_info_component> collect_container_info (gap_cache & gap_cache, std::string const & name);
}

View file

@ -1,29 +0,0 @@
#include <nano/lib/blocks.hpp>
#include <nano/node/blockprocessor.hpp>
#include <nano/node/gap_cache.hpp>
#include <nano/node/gap_tracker.hpp>
nano::gap_tracker::gap_tracker (nano::gap_cache & gap_cache) :
gap_cache{ gap_cache }
{
}
void nano::gap_tracker::connect (nano::block_processor & block_processor)
{
block_processor.processed.add ([this] (auto const & result, auto const & block) {
switch (result.code)
{
case nano::process_result::gap_previous:
case nano::process_result::gap_source:
observe (block);
break;
default:
break;
}
});
}
void nano::gap_tracker::observe (std::shared_ptr<nano::block> block)
{
gap_cache.add (block->hash ());
}

View file

@ -1,24 +0,0 @@
#pragma once
#include <memory>
namespace nano
{
class gap_cache;
class block_processor;
class block;
// Observes the processed blocks and tracks them (gap_cache) if they are gap blocks.
class gap_tracker
{
public:
gap_tracker (nano::gap_cache & gap_cache);
void connect (nano::block_processor & block_processor);
private:
// Block_processor observer
void observe (std::shared_ptr<nano::block> block);
nano::gap_cache & gap_cache;
};
}

View file

@ -156,7 +156,6 @@ nano::node::node (boost::asio::io_context & io_ctx_a, std::filesystem::path cons
unchecked{ config.max_unchecked_blocks, stats, flags.disable_block_processor_unchecked_deletion },
wallets_store_impl (std::make_unique<nano::mdb_wallets_store> (application_path_a / "wallets.ldb", config_a.lmdb_config)),
wallets_store (*wallets_store_impl),
gap_cache (*this),
ledger (store, stats, network_params.ledger, flags_a.generate_cache),
outbound_limiter{ outbound_bandwidth_limiter_config (config) },
// empty `config.peering_port` means the user made no port choice at all;
@ -200,14 +199,12 @@ nano::node::node (boost::asio::io_context & io_ctx_a, std::filesystem::path cons
node_seq (seq),
block_broadcast{ network, block_arrival, !flags.disable_block_processor_republishing },
block_publisher{ active },
gap_tracker{ gap_cache },
process_live_dispatcher{ ledger, scheduler.priority, vote_cache, websocket }
{
logger.debug (nano::log::type::node, "Constructing node...");
block_broadcast.connect (block_processor);
block_publisher.connect (block_processor);
gap_tracker.connect (block_processor);
process_live_dispatcher.connect (block_processor);
unchecked.satisfied.add ([this] (nano::unchecked_info const & info) {
this->block_processor.add (info.block);
@ -331,7 +328,6 @@ nano::node::node (boost::asio::io_context & io_ctx_a, std::filesystem::path cons
// Representative is defined as online if replying to live votes or rep_crawler queries
this->online_reps.observe (vote_a->account);
}
this->gap_cache.vote (vote_a);
});
// Cancelling local work generation
@ -531,7 +527,6 @@ std::unique_ptr<nano::container_info_component> nano::collect_container_info (no
{
auto composite = std::make_unique<container_info_composite> (name);
composite->add_component (collect_container_info (node.work, "work"));
composite->add_component (collect_container_info (node.gap_cache, "gap_cache"));
composite->add_component (collect_container_info (node.ledger, "ledger"));
composite->add_component (collect_container_info (node.active, "active"));
composite->add_component (collect_container_info (node.bootstrap_initiator, "bootstrap_initiator"));
@ -1371,7 +1366,7 @@ void nano::node::bootstrap_block (const nano::block_hash & hash)
if (!ledger.pruning || !store.pruned.exists (store.tx_begin_read (), hash))
{
// We don't have the block, try to bootstrap it
gap_cache.bootstrap_start (hash);
// TODO: Use ascending bootstraper to bootstrap block hash
}
}

View file

@ -20,8 +20,6 @@
#include <nano/node/distributed_work_factory.hpp>
#include <nano/node/election.hpp>
#include <nano/node/epoch_upgrader.hpp>
#include <nano/node/gap_cache.hpp>
#include <nano/node/gap_tracker.hpp>
#include <nano/node/network.hpp>
#include <nano/node/node_observers.hpp>
#include <nano/node/nodeconfig.hpp>
@ -156,7 +154,6 @@ public:
nano::unchecked_map unchecked;
std::unique_ptr<nano::wallets_store> wallets_store_impl;
nano::wallets_store & wallets_store;
nano::gap_cache gap_cache;
nano::ledger ledger;
nano::outbound_bandwidth_limiter outbound_limiter;
nano::network network;
@ -195,7 +192,6 @@ public:
nano::epoch_upgrader epoch_upgrader;
nano::block_broadcast block_broadcast;
nano::block_publisher block_publisher;
nano::gap_tracker gap_tracker;
nano::process_live_dispatcher process_live_dispatcher;
std::chrono::steady_clock::time_point const startup_time;