Merge pull request #4519 from clemahieu/confirming_set
Reimplement and simplify confirmation_height_processor
This commit is contained in:
commit
277339ed7e
36 changed files with 1454 additions and 4128 deletions
|
|
@ -97,7 +97,6 @@ set_property(
|
|||
block_arrival
|
||||
block_processor
|
||||
block_uniquer
|
||||
confirmation_height_processor
|
||||
dropped_elections,
|
||||
election_winner_details
|
||||
gap_cache
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ add_executable(
|
|||
bootstrap_ascending.cpp
|
||||
bootstrap_server.cpp
|
||||
cli.cpp
|
||||
confirmation_height.cpp
|
||||
confirmation_solicitor.cpp
|
||||
confirming_set.cpp
|
||||
conflicts.cpp
|
||||
difficulty.cpp
|
||||
distributed_work.cpp
|
||||
|
|
@ -24,6 +24,7 @@ add_executable(
|
|||
frontiers_confirmation.cpp
|
||||
ipc.cpp
|
||||
ledger.cpp
|
||||
ledger_confirm.cpp
|
||||
locks.cpp
|
||||
logging.cpp
|
||||
message.cpp
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/jsonconfig.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/scheduler/component.hpp>
|
||||
#include <nano/node/scheduler/manual.hpp>
|
||||
|
|
@ -1243,7 +1244,7 @@ TEST (active_transactions, activate_inactive)
|
|||
ASSERT_NE (nullptr, election);
|
||||
election->force_confirm ();
|
||||
|
||||
ASSERT_TIMELY (5s, !node.confirmation_height_processor.is_processing_added_block (send2->hash ()));
|
||||
ASSERT_TIMELY (5s, !node.confirming_set.exists (send2->hash ()));
|
||||
ASSERT_TIMELY (5s, node.block_confirmed (send2->hash ()));
|
||||
ASSERT_TIMELY (5s, node.block_confirmed (send->hash ()));
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
280
nano/core_test/confirming_set.cpp
Normal file
280
nano/core_test/confirming_set.cpp
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/make_store.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
#include <nano/test_common/ledger.hpp>
|
||||
#include <nano/test_common/system.hpp>
|
||||
#include <nano/test_common/testutil.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <latch>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST (confirming_set, construction)
|
||||
{
|
||||
auto ctx = nano::test::context::ledger_empty ();
|
||||
nano::write_database_queue write_queue{ false };
|
||||
nano::confirming_set confirming_set (ctx.ledger (), write_queue);
|
||||
}
|
||||
|
||||
TEST (confirming_set, add_exists)
|
||||
{
|
||||
auto ctx = nano::test::context::ledger_send_receive ();
|
||||
nano::write_database_queue write_queue{ false };
|
||||
nano::confirming_set confirming_set (ctx.ledger (), write_queue);
|
||||
auto send = ctx.blocks ()[0];
|
||||
confirming_set.add (send->hash ());
|
||||
ASSERT_TRUE (confirming_set.exists (send->hash ()));
|
||||
}
|
||||
|
||||
TEST (confirming_set, process_one)
|
||||
{
|
||||
auto ctx = nano::test::context::ledger_send_receive ();
|
||||
nano::write_database_queue write_queue{ false };
|
||||
nano::confirming_set confirming_set (ctx.ledger (), write_queue);
|
||||
std::atomic<int> 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 ());
|
||||
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 ().cache.cemented_count);
|
||||
}
|
||||
|
||||
TEST (confirming_set, process_multiple)
|
||||
{
|
||||
auto ctx = nano::test::context::ledger_send_receive ();
|
||||
nano::write_database_queue write_queue{ false };
|
||||
nano::confirming_set confirming_set (ctx.ledger (), write_queue);
|
||||
std::atomic<int> 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 (ctx.blocks ()[1]->hash ());
|
||||
nano::test::start_stop_guard guard{ confirming_set };
|
||||
std::unique_lock lock{ mutex };
|
||||
ASSERT_TRUE (condition.wait_for (lock, 5s, [&] () { return count == 2; }));
|
||||
ASSERT_EQ (2, ctx.stats ().count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (3, ctx.ledger ().cache.cemented_count);
|
||||
}
|
||||
|
||||
TEST (confirmation_callback, observer_callbacks)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
|
||||
nano::keypair key1;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (send->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send->hash ()))
|
||||
.build ();
|
||||
|
||||
{
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
}
|
||||
|
||||
node->confirming_set.add (send1->hash ());
|
||||
|
||||
// Callback is performed for all blocks that are confirmed
|
||||
ASSERT_TIMELY_EQ (5s, 2, node->ledger.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out));
|
||||
|
||||
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (3, node->ledger.cache.cemented_count);
|
||||
ASSERT_EQ (0, node->active.election_winner_details_size ());
|
||||
}
|
||||
|
||||
// The callback and confirmation history should only be updated after confirmation height is set (and not just after voting)
|
||||
TEST (confirmation_callback, confirmed_history)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.force_use_write_database_queue = true;
|
||||
node_flags.disable_ascending_bootstrap = true;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
|
||||
nano::keypair key1;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
{
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
}
|
||||
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (send->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send->hash ()))
|
||||
.build ();
|
||||
|
||||
node->process_active (send1);
|
||||
std::shared_ptr<nano::election> election;
|
||||
ASSERT_TIMELY (5s, election = nano::test::start_election (system, *node, send1->hash ()));
|
||||
{
|
||||
// The write guard prevents the confirmation height processor doing any writes
|
||||
auto write_guard = node->write_database_queue.wait (nano::writer::testing);
|
||||
|
||||
// Confirm send1
|
||||
election->force_confirm ();
|
||||
ASSERT_TIMELY_EQ (10s, node->active.size (), 0);
|
||||
ASSERT_EQ (0, node->active.recently_cemented.list ().size ());
|
||||
ASSERT_TRUE (node->active.empty ());
|
||||
|
||||
auto transaction = node->store.tx_begin_read ();
|
||||
ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ()));
|
||||
|
||||
ASSERT_TIMELY (10s, node->write_database_queue.contains (nano::writer::confirmation_height));
|
||||
|
||||
// Confirm that no inactive callbacks have been called when the confirmation height processor has already iterated over it, waiting to write
|
||||
ASSERT_EQ (0, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
|
||||
}
|
||||
|
||||
ASSERT_TIMELY (10s, !node->write_database_queue.contains (nano::writer::confirmation_height));
|
||||
|
||||
auto transaction = node->store.tx_begin_read ();
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, send->hash ()));
|
||||
|
||||
ASSERT_TIMELY_EQ (10s, node->active.size (), 0);
|
||||
ASSERT_TIMELY_EQ (10s, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out), 1);
|
||||
|
||||
// Each block that's confirmed is in the recently_cemented history
|
||||
ASSERT_EQ (2, node->active.recently_cemented.list ().size ());
|
||||
ASSERT_TRUE (node->active.empty ());
|
||||
|
||||
// Confirm the callback is not called under this circumstance
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out));
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
|
||||
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (3, node->ledger.cache.cemented_count);
|
||||
ASSERT_EQ (0, node->active.election_winner_details_size ());
|
||||
}
|
||||
|
||||
TEST (confirmation_callback, dependent_election)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.force_use_write_database_queue = true;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
|
||||
nano::keypair key1;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (send->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send->hash ()))
|
||||
.build ();
|
||||
auto send2 = builder
|
||||
.send ()
|
||||
.previous (send1->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send1->hash ()))
|
||||
.build ();
|
||||
{
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send2));
|
||||
}
|
||||
|
||||
// This election should be confirmed as active_conf_height
|
||||
ASSERT_TRUE (nano::test::start_election (system, *node, send1->hash ()));
|
||||
// Start an election and confirm it
|
||||
auto election = nano::test::start_election (system, *node, send2->hash ());
|
||||
ASSERT_NE (nullptr, election);
|
||||
election->force_confirm ();
|
||||
|
||||
// Wait for blocks to be confirmed in ledger, callbacks will happen after
|
||||
ASSERT_TIMELY_EQ (5s, 3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
// Once the item added to the confirming set no longer exists, callbacks have completed
|
||||
ASSERT_TIMELY (5s, !node->confirming_set.exists (send2->hash ()));
|
||||
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out));
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out));
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
|
||||
ASSERT_EQ (4, node->ledger.cache.cemented_count);
|
||||
|
||||
ASSERT_EQ (0, node->active.election_winner_details_size ());
|
||||
}
|
||||
|
||||
TEST (confirmation_callback, election_winner_details_clearing_node_process_confirmed)
|
||||
{
|
||||
// Make sure election_winner_details is also cleared if the block never enters the confirmation height processor from node::process_confirmed
|
||||
nano::test::system system (1);
|
||||
auto node = system.nodes.front ();
|
||||
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (nano::dev::genesis->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
// Add to election_winner_details. Use an unrealistic iteration so that it should fall into the else case and do a cleanup
|
||||
node->active.add_election_winner_details (send->hash (), nullptr);
|
||||
nano::election_status election;
|
||||
election.winner = send;
|
||||
node->process_confirmed (election, 1000000);
|
||||
ASSERT_EQ (0, node->active.election_winner_details_size ());
|
||||
}
|
||||
886
nano/core_test/ledger_confirm.cpp
Normal file
886
nano/core_test/ledger_confirm.cpp
Normal file
|
|
@ -0,0 +1,886 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/make_store.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
#include <nano/test_common/system.hpp>
|
||||
#include <nano/test_common/testutil.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST (ledger_confirm, single)
|
||||
{
|
||||
auto amount (std::numeric_limits<nano::uint128_t>::max ());
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
auto node = system.add_node (node_flags);
|
||||
nano::keypair key1;
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
nano::block_hash latest1 (node->latest (nano::dev::genesis_key.pub));
|
||||
nano::block_builder builder;
|
||||
auto send1 = builder
|
||||
.state ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.previous (latest1)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (amount - 100)
|
||||
.link (key1.pub)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest1))
|
||||
.build ();
|
||||
|
||||
// Check confirmation heights before, should be uninitialized (1 for genesis).
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (1, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (nano::dev::genesis->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_FALSE (node->ledger.block_confirmed (transaction, send1->hash ()));
|
||||
node->ledger.confirm (transaction, send1->hash ());
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, send1->hash ()));
|
||||
ASSERT_EQ (2, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (send1->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
|
||||
// Rollbacks should fail as these blocks have been cemented
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, latest1));
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ()));
|
||||
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (2, node->ledger.cache.cemented_count);
|
||||
}
|
||||
|
||||
TEST (ledger_confirm, multiple_accounts)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
nano::keypair key1;
|
||||
nano::keypair key2;
|
||||
nano::keypair key3;
|
||||
nano::block_hash latest1 (system.nodes[0]->latest (nano::dev::genesis_key.pub));
|
||||
nano::block_builder builder;
|
||||
|
||||
// Send to all accounts
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (latest1)
|
||||
.destination (key1.pub)
|
||||
.balance (node->online_reps.delta () + 300)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest1))
|
||||
.build ();
|
||||
auto send2 = builder
|
||||
.send ()
|
||||
.previous (send1->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (node->online_reps.delta () + 200)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send1->hash ()))
|
||||
.build ();
|
||||
auto send3 = builder
|
||||
.send ()
|
||||
.previous (send2->hash ())
|
||||
.destination (key3.pub)
|
||||
.balance (node->online_reps.delta () + 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send2->hash ()))
|
||||
.build ();
|
||||
|
||||
// Open all accounts
|
||||
auto open1 = builder
|
||||
.open ()
|
||||
.source (send1->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.account (key1.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (key1.pub))
|
||||
.build ();
|
||||
auto open2 = builder
|
||||
.open ()
|
||||
.source (send2->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.account (key2.pub)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (key2.pub))
|
||||
.build ();
|
||||
auto open3 = builder
|
||||
.open ()
|
||||
.source (send3->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.account (key3.pub)
|
||||
.sign (key3.prv, key3.pub)
|
||||
.work (*system.work.generate (key3.pub))
|
||||
.build ();
|
||||
|
||||
// Send and receive various blocks to these accounts
|
||||
auto send4 = builder
|
||||
.send ()
|
||||
.previous (open1->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (50)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (open1->hash ()))
|
||||
.build ();
|
||||
auto send5 = builder
|
||||
.send ()
|
||||
.previous (send4->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (10)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (send4->hash ()))
|
||||
.build ();
|
||||
|
||||
auto receive1 = builder
|
||||
.receive ()
|
||||
.previous (open2->hash ())
|
||||
.source (send4->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (open2->hash ()))
|
||||
.build ();
|
||||
auto send6 = builder
|
||||
.send ()
|
||||
.previous (receive1->hash ())
|
||||
.destination (key3.pub)
|
||||
.balance (10)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (receive1->hash ()))
|
||||
.build ();
|
||||
auto receive2 = builder
|
||||
.receive ()
|
||||
.previous (send6->hash ())
|
||||
.source (send5->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (send6->hash ()))
|
||||
.build ();
|
||||
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send3));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, open1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, open2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, open3));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send4));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send5));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send6));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive2));
|
||||
|
||||
// Check confirmation heights of all the accounts (except genesis) are uninitialized (0),
|
||||
// as we have any just added them to the ledger and not processed any live transactions yet.
|
||||
ASSERT_EQ (1, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (nano::dev::genesis->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key1.pub));
|
||||
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key2.pub));
|
||||
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key3.pub));
|
||||
|
||||
// The nodes process a live receive which propagates across to all accounts
|
||||
auto receive3 = builder
|
||||
.receive ()
|
||||
.previous (open3->hash ())
|
||||
.source (send6->hash ())
|
||||
.sign (key3.prv, key3.pub)
|
||||
.work (*system.work.generate (open3->hash ()))
|
||||
.build ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive3));
|
||||
auto confirmed = node->ledger.confirm (transaction, receive3->hash ());
|
||||
ASSERT_EQ (10, confirmed.size ());
|
||||
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (11, node->ledger.cache.cemented_count);
|
||||
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ()));
|
||||
ASSERT_EQ (4, node->ledger.account_info (transaction, nano::dev::genesis_key.pub).value ().block_count);
|
||||
ASSERT_EQ (4, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (send3->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
ASSERT_EQ (3, node->ledger.account_info (transaction, key1.pub).value ().block_count);
|
||||
ASSERT_EQ (2, node->store.confirmation_height.get (transaction, key1.pub).value ().height);
|
||||
ASSERT_EQ (send4->hash (), node->store.confirmation_height.get (transaction, key1.pub).value ().frontier);
|
||||
ASSERT_EQ (4, node->ledger.account_info (transaction, key2.pub).value ().block_count);
|
||||
ASSERT_EQ (3, node->store.confirmation_height.get (transaction, key2.pub).value ().height);
|
||||
ASSERT_EQ (send6->hash (), node->store.confirmation_height.get (transaction, key2.pub).value ().frontier);
|
||||
ASSERT_EQ (2, node->ledger.account_info (transaction, key3.pub).value ().block_count);
|
||||
ASSERT_EQ (2, node->store.confirmation_height.get (transaction, key3.pub).value ().height);
|
||||
ASSERT_EQ (receive3->hash (), node->store.confirmation_height.get (transaction, key3.pub).value ().frontier);
|
||||
|
||||
// The accounts for key1 and key2 have 1 more block in the chain than is confirmed.
|
||||
// So this can be rolled back, but the one before that cannot. Check that this is the case
|
||||
ASSERT_FALSE (node->ledger.rollback (transaction, node->ledger.latest (transaction, key2.pub)));
|
||||
ASSERT_FALSE (node->ledger.rollback (transaction, node->ledger.latest (transaction, key1.pub)));
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, node->ledger.latest (transaction, key1.pub)));
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, node->ledger.latest (transaction, key2.pub)));
|
||||
|
||||
// Confirm the other latest can't be rolled back either
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, node->ledger.latest (transaction, key3.pub)));
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, node->ledger.latest (transaction, nano::dev::genesis_key.pub)));
|
||||
|
||||
// Attempt some others which have been cemented
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, open1->hash ()));
|
||||
ASSERT_TRUE (node->ledger.rollback (transaction, send2->hash ()));
|
||||
}
|
||||
|
||||
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.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
nano::keypair key1;
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
|
||||
nano::block_builder builder;
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (key1.pub)
|
||||
.balance (node->online_reps.delta () + 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
auto open1 = builder
|
||||
.open ()
|
||||
.source (send1->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.account (key1.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (key1.pub))
|
||||
.build ();
|
||||
auto send2 = builder
|
||||
.send ()
|
||||
.previous (open1->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (1000)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (open1->hash ()))
|
||||
.build ();
|
||||
auto send3 = builder
|
||||
.send ()
|
||||
.previous (send2->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (900)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (send2->hash ()))
|
||||
.build ();
|
||||
auto send4 = builder
|
||||
.send ()
|
||||
.previous (send3->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (500)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (send3->hash ()))
|
||||
.build ();
|
||||
auto receive1 = 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 ();
|
||||
auto receive2 = builder
|
||||
.receive ()
|
||||
.previous (receive1->hash ())
|
||||
.source (send3->hash ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive1->hash ()))
|
||||
.build ();
|
||||
auto receive3 = builder
|
||||
.receive ()
|
||||
.previous (receive2->hash ())
|
||||
.source (send4->hash ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive2->hash ()))
|
||||
.build ();
|
||||
auto send5 = builder
|
||||
.send ()
|
||||
.previous (receive3->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (node->online_reps.delta () + 1)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive3->hash ()))
|
||||
.build ();
|
||||
auto receive4 = builder
|
||||
.receive ()
|
||||
.previous (send4->hash ())
|
||||
.source (send5->hash ())
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (send4->hash ()))
|
||||
.build ();
|
||||
nano::keypair key2;
|
||||
auto send6 = builder
|
||||
.send ()
|
||||
.previous (send5->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (node->online_reps.delta () + 1)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send5->hash ()))
|
||||
.build ();
|
||||
// Unpocketed send
|
||||
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, open1));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive1));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send3));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send4));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive3));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send5));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send6));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive4));
|
||||
auto confirmed = node->ledger.confirm (transaction, receive4->hash ());
|
||||
ASSERT_EQ (10, confirmed.size ());
|
||||
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (11, node->ledger.cache.cemented_count);
|
||||
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive4->hash ()));
|
||||
ASSERT_EQ (7, node->ledger.account_info (transaction, nano::dev::genesis_key.pub).value ().block_count);
|
||||
ASSERT_EQ (6, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (send5->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
|
||||
ASSERT_EQ (5, node->ledger.account_info (transaction, key1.pub).value ().block_count);
|
||||
ASSERT_EQ (5, node->store.confirmation_height.get (transaction, key1.pub).value ().height);
|
||||
ASSERT_EQ (receive4->hash (), node->store.confirmation_height.get (transaction, key1.pub).value ().frontier);
|
||||
}
|
||||
|
||||
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.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
|
||||
nano::block_builder builder;
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
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 ();
|
||||
auto send2 = builder
|
||||
.send ()
|
||||
.previous (receive1->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive1->hash ()))
|
||||
.build ();
|
||||
auto send3 = builder
|
||||
.send ()
|
||||
.previous (send2->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 3)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send2->hash ()))
|
||||
.build ();
|
||||
auto receive2 = builder
|
||||
.receive ()
|
||||
.previous (send3->hash ())
|
||||
.source (send2->hash ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send3->hash ()))
|
||||
.build ();
|
||||
auto receive3 = builder
|
||||
.receive ()
|
||||
.previous (receive2->hash ())
|
||||
.source (send3->hash ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive2->hash ()))
|
||||
.build ();
|
||||
|
||||
// Send to another account to prevent automatic receiving on the genesis account
|
||||
nano::keypair key1;
|
||||
auto send4 = builder
|
||||
.send ()
|
||||
.previous (receive3->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (node->online_reps.delta ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (receive3->hash ()))
|
||||
.build ();
|
||||
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send3));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive3));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send4));
|
||||
|
||||
auto confirmed = node->ledger.confirm (transaction, receive3->hash ());
|
||||
ASSERT_EQ (6, confirmed.size ());
|
||||
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ()));
|
||||
ASSERT_EQ (8, node->ledger.account_info (transaction, nano::dev::genesis_key.pub).value ().block_count);
|
||||
ASSERT_EQ (7, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (receive3->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
ASSERT_EQ (7, node->ledger.cache.cemented_count);
|
||||
}
|
||||
|
||||
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.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
nano::keypair key1;
|
||||
nano::keypair key2;
|
||||
auto & store = node->store;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (latest)
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest))
|
||||
.build ();
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (send->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send->hash ()))
|
||||
.build ();
|
||||
|
||||
auto open = builder
|
||||
.open ()
|
||||
.source (send->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.account (key1.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (key1.pub))
|
||||
.build ();
|
||||
auto state_open = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (0)
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (send1->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (key2.pub))
|
||||
.build ();
|
||||
|
||||
auto send2 = builder
|
||||
.send ()
|
||||
.previous (open->hash ())
|
||||
.destination (key2.pub)
|
||||
.balance (0)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (open->hash ()))
|
||||
.build ();
|
||||
auto state_receive = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_open->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio * 2)
|
||||
.link (send2->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (state_open->hash ()))
|
||||
.build ();
|
||||
|
||||
auto state_send = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_receive->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (key1.pub)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (state_receive->hash ()))
|
||||
.build ();
|
||||
auto receive = builder
|
||||
.receive ()
|
||||
.previous (send2->hash ())
|
||||
.source (state_send->hash ())
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (send2->hash ()))
|
||||
.build ();
|
||||
|
||||
auto change = builder
|
||||
.change ()
|
||||
.previous (receive->hash ())
|
||||
.representative (key2.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (receive->hash ()))
|
||||
.build ();
|
||||
|
||||
auto state_change = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_send->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (0)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (state_send->hash ()))
|
||||
.build ();
|
||||
|
||||
auto epoch = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_change->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (node->ledger.epoch_link (nano::epoch::epoch_1))
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (state_change->hash ()))
|
||||
.build ();
|
||||
|
||||
auto epoch1 = builder
|
||||
.state ()
|
||||
.account (key1.pub)
|
||||
.previous (change->hash ())
|
||||
.representative (key2.pub)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (node->ledger.epoch_link (nano::epoch::epoch_1))
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (change->hash ()))
|
||||
.build ();
|
||||
auto state_send1 = builder
|
||||
.state ()
|
||||
.account (key1.pub)
|
||||
.previous (epoch1->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio - 1)
|
||||
.link (key2.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (epoch1->hash ()))
|
||||
.build ();
|
||||
auto state_receive2 = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (epoch->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio + 1)
|
||||
.link (state_send1->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (epoch->hash ()))
|
||||
.build ();
|
||||
|
||||
auto state_send2 = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_receive2->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio)
|
||||
.link (key1.pub)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (state_receive2->hash ()))
|
||||
.build ();
|
||||
auto state_send3 = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (state_send2->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio - 1)
|
||||
.link (key1.pub)
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*system.work.generate (state_send2->hash ()))
|
||||
.build ();
|
||||
|
||||
auto state_send4 = builder
|
||||
.state ()
|
||||
.account (key1.pub)
|
||||
.previous (state_send1->hash ())
|
||||
.representative (0)
|
||||
.balance (nano::Gxrb_ratio - 2)
|
||||
.link (nano::dev::genesis_key.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*system.work.generate (state_send1->hash ()))
|
||||
.build ();
|
||||
auto state_receive3 = builder
|
||||
.state ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.previous (send1->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2 + 1)
|
||||
.link (state_send4->hash ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send1->hash ()))
|
||||
.build ();
|
||||
|
||||
auto transaction (store.tx_begin_write ());
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, open));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_open));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_receive));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_send));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, receive));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, change));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_change));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, epoch));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, epoch1));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_send1));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_receive2));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_send2));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_send3));
|
||||
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_send4));
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, state_receive3));
|
||||
|
||||
auto confirmed = node->ledger.confirm (transaction, state_send2->hash ());
|
||||
ASSERT_EQ (15, confirmed.size ());
|
||||
ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (16, node->ledger.cache.cemented_count);
|
||||
|
||||
ASSERT_TRUE (node->ledger.block_confirmed (transaction, state_send2->hash ()));
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
ASSERT_LE (4, node->ledger.account_info (transaction, nano::dev::genesis_key.pub).value ().block_count);
|
||||
ASSERT_EQ (3, node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().height);
|
||||
ASSERT_EQ (send1->hash (), node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub).value ().frontier);
|
||||
|
||||
ASSERT_LE (7, node->ledger.account_info (transaction, key1.pub).value ().block_count);
|
||||
ASSERT_EQ (6, node->store.confirmation_height.get (transaction, key1.pub).value ().height);
|
||||
ASSERT_EQ (state_send1->hash (), node->store.confirmation_height.get (transaction, key1.pub).value ().frontier);
|
||||
|
||||
ASSERT_EQ (8, node->ledger.account_info (transaction, key2.pub).value ().block_count);
|
||||
ASSERT_EQ (7, node->store.confirmation_height.get (transaction, key2.pub).value ().height);
|
||||
ASSERT_EQ (state_send2->hash (), node->store.confirmation_height.get (transaction, key2.pub).value ().frontier);
|
||||
}
|
||||
|
||||
// This test ensures a block that's cemented cannot be rolled back by the node
|
||||
// A block is inserted and confirmed then later a different block is force inserted with a rollback attempt
|
||||
TEST (ledger_confirm, conflict_rollback_cemented)
|
||||
{
|
||||
nano::state_block_builder builder;
|
||||
auto const genesis_hash = nano::dev::genesis->hash ();
|
||||
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
auto node1 = system.add_node (node_flags);
|
||||
|
||||
nano::keypair key1;
|
||||
// create one side of a forked transaction on node1
|
||||
auto fork1a = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build ();
|
||||
{
|
||||
auto transaction = node1->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction, fork1a));
|
||||
node1->ledger.confirm (transaction, fork1a->hash ());
|
||||
}
|
||||
ASSERT_TRUE (nano::test::confirmed (*node1, { fork1a }));
|
||||
|
||||
// create the other side of the fork on node2
|
||||
nano::keypair key2;
|
||||
auto fork1b = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key2.pub) // Different destination same 'previous'
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build ();
|
||||
|
||||
node1->block_processor.force (fork1b);
|
||||
// node2 already has send2 forced confirmed whilst node1 should have confirmed send1 and therefore we have a cemented fork on node2
|
||||
// and node2 should print an error message on the log that it cannot rollback send2 because it is already cemented
|
||||
[[maybe_unused]] size_t count = 0;
|
||||
ASSERT_TIMELY_EQ (5s, 1, (count = node1->stats.count (nano::stat::type::ledger, nano::stat::detail::rollback_failed)));
|
||||
ASSERT_TRUE (nano::test::confirmed (*node1, { fork1a->hash () })); // fork1a should still remain after the rollback failed event
|
||||
}
|
||||
|
||||
TEST (ledger_confirm, observers)
|
||||
{
|
||||
auto amount (std::numeric_limits<nano::uint128_t>::max ());
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
auto node1 = system.add_node (node_flags);
|
||||
nano::keypair key1;
|
||||
nano::block_hash latest1 (node1->latest (nano::dev::genesis_key.pub));
|
||||
nano::block_builder builder;
|
||||
auto send1 = builder
|
||||
.send ()
|
||||
.previous (latest1)
|
||||
.destination (key1.pub)
|
||||
.balance (amount - node1->config.receive_minimum.number ())
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest1))
|
||||
.build ();
|
||||
|
||||
auto transaction = node1->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction, send1));
|
||||
node1->ledger.confirm (transaction, send1->hash ());
|
||||
ASSERT_TRUE (node1->ledger.block_confirmed (transaction, send1->hash ()));
|
||||
ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
|
||||
ASSERT_EQ (2, node1->ledger.cache.cemented_count);
|
||||
}
|
||||
|
||||
TEST (ledger_confirm, election_winner_details_clearing_node_process_confirmed)
|
||||
{
|
||||
// Make sure election_winner_details is also cleared if the block never enters the confirmation height processor from node::process_confirmed
|
||||
nano::test::system system (1);
|
||||
auto node = system.nodes.front ();
|
||||
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (nano::dev::genesis->hash ())
|
||||
.destination (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
// Add to election_winner_details. Use an unrealistic iteration so that it should fall into the else case and do a cleanup
|
||||
node->active.add_election_winner_details (send->hash (), nullptr);
|
||||
nano::election_status election;
|
||||
election.winner = send;
|
||||
node->process_confirmed (election, 1000000);
|
||||
ASSERT_EQ (0, node->active.election_winner_details_size ());
|
||||
}
|
||||
|
||||
TEST (ledger_confirm, pruned_source)
|
||||
{
|
||||
nano::logger logger;
|
||||
auto path (nano::unique_path ());
|
||||
auto store = nano::make_store (logger, path, nano::dev::constants);
|
||||
ASSERT_TRUE (!store->init_error ());
|
||||
nano::stats stats;
|
||||
nano::ledger ledger (*store, stats, nano::dev::constants);
|
||||
ledger.pruning = true;
|
||||
nano::write_database_queue write_database_queue (false);
|
||||
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
|
||||
nano::keypair key1, key2;
|
||||
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 - 100)
|
||||
.link (key1.pub)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*pool.generate (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
auto open1 = 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 ();
|
||||
auto send2 = builder
|
||||
.state ()
|
||||
.account (key1.pub)
|
||||
.previous (open1->hash ())
|
||||
.representative (key1.pub)
|
||||
.balance (50)
|
||||
.link (key2.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*pool.generate (open1->hash ()))
|
||||
.build ();
|
||||
auto send3 = builder
|
||||
.state ()
|
||||
.account (key1.pub)
|
||||
.previous (send2->hash ())
|
||||
.representative (key1.pub)
|
||||
.balance (25)
|
||||
.link (key2.pub)
|
||||
.sign (key1.prv, key1.pub)
|
||||
.work (*pool.generate (send2->hash ()))
|
||||
.build ();
|
||||
auto open2 = builder
|
||||
.state ()
|
||||
.account (key2.pub)
|
||||
.previous (0)
|
||||
.representative (key1.pub)
|
||||
.balance (50)
|
||||
.link (send2->hash ())
|
||||
.sign (key2.prv, key2.pub)
|
||||
.work (*pool.generate (key2.pub))
|
||||
.build ();
|
||||
auto transaction (store->tx_begin_write ());
|
||||
store->initialize (transaction, ledger.cache, nano::dev::constants);
|
||||
ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1));
|
||||
ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open1));
|
||||
ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2));
|
||||
ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send3));
|
||||
ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open2));
|
||||
ASSERT_EQ (2, ledger.pruning_action (transaction, send2->hash (), 2));
|
||||
ASSERT_FALSE (ledger.block_exists (transaction, send2->hash ()));
|
||||
ASSERT_FALSE (ledger.block_confirmed (transaction, open2->hash ()));
|
||||
auto confirmed = ledger.confirm (transaction, open2->hash ());
|
||||
ASSERT_TRUE (ledger.block_confirmed (transaction, open2->hash ()));
|
||||
}
|
||||
|
||||
// Test that if a block is marked to be confirmed that doesn't exist in the ledger the program aborts
|
||||
TEST (ledger_confirmDeathTest, rollback_added_block)
|
||||
{
|
||||
// For ASSERT_DEATH_IF_SUPPORTED
|
||||
testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||||
|
||||
// valgrind can be noisy with death tests
|
||||
if (!nano::running_within_valgrind ())
|
||||
{
|
||||
nano::logger logger;
|
||||
auto path (nano::unique_path ());
|
||||
auto store = nano::make_store (logger, path, nano::dev::constants);
|
||||
ASSERT_TRUE (!store->init_error ());
|
||||
nano::stats stats;
|
||||
nano::ledger ledger (*store, stats, nano::dev::constants);
|
||||
nano::write_database_queue write_database_queue (false);
|
||||
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
|
||||
nano::keypair key1;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (nano::dev::genesis->hash ())
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*pool.generate (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
auto transaction (store->tx_begin_write ());
|
||||
store->initialize (transaction, ledger.cache, ledger.constants);
|
||||
ASSERT_DEATH_IF_SUPPORTED (ledger.confirm (transaction, send->hash ()), "");
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include <nano/lib/config.hpp>
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/local_vote_history.hpp>
|
||||
#include <nano/node/make_store.hpp>
|
||||
|
|
@ -1983,7 +1984,7 @@ TEST (node, DISABLED_local_votes_cache_batch)
|
|||
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.store.tx_begin_write (), send1));
|
||||
node.confirmation_height_processor.add (send1);
|
||||
node.confirming_set.add (send1->hash ());
|
||||
ASSERT_TIMELY (5s, node.ledger.block_confirmed (node.store.tx_begin_read (), send1->hash ()));
|
||||
auto send2 = nano::state_block_builder ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/jsonconfig.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/local_vote_history.hpp>
|
||||
#include <nano/node/request_aggregator.hpp>
|
||||
|
|
@ -79,7 +80,7 @@ TEST (request_aggregator, one_update)
|
|||
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.store.tx_begin_write (), send1));
|
||||
node.confirmation_height_processor.add (send1);
|
||||
node.confirming_set.add (send1->hash ());
|
||||
ASSERT_TIMELY (5s, node.ledger.block_confirmed (node.store.tx_begin_read (), send1->hash ()));
|
||||
auto send2 = nano::state_block_builder ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
|
|
@ -145,7 +146,7 @@ TEST (request_aggregator, two)
|
|||
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
|
||||
.build ();
|
||||
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.store.tx_begin_write (), send1));
|
||||
node.confirmation_height_processor.add (send1);
|
||||
node.confirming_set.add (send1->hash ());
|
||||
ASSERT_TIMELY (5s, node.ledger.block_confirmed (node.store.tx_begin_read (), send1->hash ()));
|
||||
auto send2 = builder.make_block ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
|
|
@ -450,8 +451,6 @@ TEST (request_aggregator, cannot_vote)
|
|||
nano::node_flags flags;
|
||||
flags.disable_request_loop = true;
|
||||
auto & node (*system.add_node (flags));
|
||||
// This prevents activation of blocks which are cemented
|
||||
node.confirmation_height_processor.cemented_observers.clear ();
|
||||
nano::state_block_builder builder;
|
||||
auto send1 = builder.make_block ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ TEST (toml, daemon_config_deserialize_defaults)
|
|||
ASSERT_EQ (conf.node.bootstrap_serving_threads, defaults.node.bootstrap_serving_threads);
|
||||
ASSERT_EQ (conf.node.bootstrap_frontier_request_count, defaults.node.bootstrap_frontier_request_count);
|
||||
ASSERT_EQ (conf.node.bootstrap_fraction_numerator, defaults.node.bootstrap_fraction_numerator);
|
||||
ASSERT_EQ (conf.node.conf_height_processor_batch_min_time, defaults.node.conf_height_processor_batch_min_time);
|
||||
ASSERT_EQ (conf.node.confirming_set_batch_time, defaults.node.confirming_set_batch_time);
|
||||
ASSERT_EQ (conf.node.confirmation_history_size, defaults.node.confirmation_history_size);
|
||||
ASSERT_EQ (conf.node.enable_voting, defaults.node.enable_voting);
|
||||
ASSERT_EQ (conf.node.external_address, defaults.node.external_address);
|
||||
|
|
@ -396,7 +396,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
|
|||
bootstrap_serving_threads = 999
|
||||
bootstrap_frontier_request_count = 9999
|
||||
bootstrap_fraction_numerator = 999
|
||||
conf_height_processor_batch_min_time = 999
|
||||
confirming_set_batch_time = 999
|
||||
confirmation_history_size = 999
|
||||
enable_voting = false
|
||||
external_address = "0:0:0:0:0:ffff:7f01:101"
|
||||
|
|
@ -586,7 +586,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
|
|||
ASSERT_NE (conf.node.bootstrap_serving_threads, defaults.node.bootstrap_serving_threads);
|
||||
ASSERT_NE (conf.node.bootstrap_frontier_request_count, defaults.node.bootstrap_frontier_request_count);
|
||||
ASSERT_NE (conf.node.bootstrap_fraction_numerator, defaults.node.bootstrap_fraction_numerator);
|
||||
ASSERT_NE (conf.node.conf_height_processor_batch_min_time, defaults.node.conf_height_processor_batch_min_time);
|
||||
ASSERT_NE (conf.node.confirming_set_batch_time, defaults.node.confirming_set_batch_time);
|
||||
ASSERT_NE (conf.node.confirmation_history_size, defaults.node.confirmation_history_size);
|
||||
ASSERT_NE (conf.node.enable_voting, defaults.node.enable_voting);
|
||||
ASSERT_NE (conf.node.external_address, defaults.node.external_address);
|
||||
|
|
@ -1017,4 +1017,4 @@ TEST (toml, log_config_no_required)
|
|||
confg.deserialize_toml (toml);
|
||||
|
||||
ASSERT_FALSE (toml.get_error ()) << toml.get_error ().get_message ();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,8 +257,6 @@ char const * nano::mutex_identifier (mutexes mutex)
|
|||
return "block_uniquer";
|
||||
case mutexes::blockstore_cache:
|
||||
return "blockstore_cache";
|
||||
case mutexes::confirmation_height_processor:
|
||||
return "confirmation_height_processor";
|
||||
case mutexes::election_winner_details:
|
||||
return "election_winner_details";
|
||||
case mutexes::gap_cache:
|
||||
|
|
@ -286,4 +284,4 @@ char const * nano::mutex_identifier (mutexes mutex)
|
|||
}
|
||||
|
||||
throw std::runtime_error ("Invalid mutexes enum specified");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ enum class mutexes
|
|||
block_processor,
|
||||
block_uniquer,
|
||||
blockstore_cache,
|
||||
confirmation_height_processor,
|
||||
election_winner_details,
|
||||
gap_cache,
|
||||
network_filter,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <nano/nano_node/daemon.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/cli.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/daemonconfig.hpp>
|
||||
#include <nano/node/ipc/ipc_server.hpp>
|
||||
#include <nano/node/json_handler.hpp>
|
||||
|
|
@ -1212,7 +1213,7 @@ int main (int argc, char * const * argv)
|
|||
// Confirm blocks for node1
|
||||
for (auto & block : blocks)
|
||||
{
|
||||
node1->confirmation_height_processor.add (block);
|
||||
node1->confirming_set.add (block->hash ());
|
||||
}
|
||||
while (node1->ledger.cache.cemented_count != node1->ledger.cache.block_count)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,12 +59,8 @@ add_library(
|
|||
cli.cpp
|
||||
common.hpp
|
||||
common.cpp
|
||||
confirmation_height_bounded.hpp
|
||||
confirmation_height_bounded.cpp
|
||||
confirmation_height_processor.hpp
|
||||
confirmation_height_processor.cpp
|
||||
confirmation_height_unbounded.hpp
|
||||
confirmation_height_unbounded.cpp
|
||||
confirming_set.hpp
|
||||
confirming_set.cpp
|
||||
confirmation_solicitor.hpp
|
||||
confirmation_solicitor.cpp
|
||||
daemonconfig.hpp
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirmation_height_processor.hpp>
|
||||
#include <nano/node/confirmation_solicitor.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/node.hpp>
|
||||
#include <nano/node/repcrawler.hpp>
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
using namespace std::chrono;
|
||||
|
||||
nano::active_transactions::active_transactions (nano::node & node_a, nano::confirmation_height_processor & confirmation_height_processor_a, nano::block_processor & block_processor_a) :
|
||||
nano::active_transactions::active_transactions (nano::node & node_a, nano::confirming_set & confirming_set, nano::block_processor & block_processor_a) :
|
||||
node{ node_a },
|
||||
confirmation_height_processor{ confirmation_height_processor_a },
|
||||
confirming_set{ confirming_set },
|
||||
block_processor{ block_processor_a },
|
||||
recently_confirmed{ 65536 },
|
||||
recently_cemented{ node.config.confirmation_history_size },
|
||||
|
|
@ -26,12 +26,12 @@ nano::active_transactions::active_transactions (nano::node & node_a, nano::confi
|
|||
count_by_behavior.fill (0); // Zero initialize array
|
||||
|
||||
// Register a callback which will get called after a block is cemented
|
||||
confirmation_height_processor.add_cemented_observer ([this] (std::shared_ptr<nano::block> const & callback_block_a) {
|
||||
confirming_set.cemented_observers.add ([this] (std::shared_ptr<nano::block> const & callback_block_a) {
|
||||
this->block_cemented_callback (callback_block_a);
|
||||
});
|
||||
|
||||
// Register a callback which will get called if a block is already cemented
|
||||
confirmation_height_processor.add_block_already_cemented_observer ([this] (nano::block_hash const & hash_a) {
|
||||
confirming_set.block_already_cemented_observers.add ([this] (nano::block_hash const & hash_a) {
|
||||
this->block_already_cemented_callback (hash_a);
|
||||
});
|
||||
|
||||
|
|
@ -82,6 +82,7 @@ void nano::active_transactions::stop ()
|
|||
|
||||
void nano::active_transactions::block_cemented_callback (std::shared_ptr<nano::block> const & block)
|
||||
{
|
||||
debug_assert (node.block_confirmed (block->hash ()));
|
||||
if (auto election_l = election (block->qualified_root ()))
|
||||
{
|
||||
election_l->try_confirm (block->hash ());
|
||||
|
|
@ -95,7 +96,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptr<nano::b
|
|||
status = election->get_status ();
|
||||
votes = election->votes_with_weight ();
|
||||
}
|
||||
if (confirmation_height_processor.is_processing_added_block (block->hash ()))
|
||||
if (confirming_set.exists (block->hash ()))
|
||||
{
|
||||
status.type = nano::election_status_type::active_confirmed_quorum;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ class active_transactions;
|
|||
class block;
|
||||
class block_sideband;
|
||||
class block_processor;
|
||||
class confirming_set;
|
||||
class election;
|
||||
class vote;
|
||||
class confirmation_height_processor;
|
||||
class stats;
|
||||
}
|
||||
namespace nano::store
|
||||
|
|
@ -141,7 +141,7 @@ private: // Elections
|
|||
std::unordered_map<nano::block_hash, std::shared_ptr<nano::election>> blocks;
|
||||
|
||||
public:
|
||||
active_transactions (nano::node &, nano::confirmation_height_processor &, nano::block_processor &);
|
||||
active_transactions (nano::node &, nano::confirming_set &, nano::block_processor &);
|
||||
~active_transactions ();
|
||||
|
||||
void start ();
|
||||
|
|
@ -209,7 +209,7 @@ private:
|
|||
|
||||
private: // Dependencies
|
||||
nano::node & node;
|
||||
nano::confirmation_height_processor & confirmation_height_processor;
|
||||
nano::confirming_set & confirming_set;
|
||||
nano::block_processor & block_processor;
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1,580 +0,0 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/stats.hpp>
|
||||
#include <nano/node/confirmation_height_bounded.hpp>
|
||||
#include <nano/node/write_database_queue.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
#include <nano/store/block.hpp>
|
||||
#include <nano/store/confirmation_height.hpp>
|
||||
#include <nano/store/pruned.hpp>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
nano::confirmation_height_bounded::confirmation_height_bounded (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger & logger_a, std::atomic<bool> & stopped_a, uint64_t & batch_write_size_a, std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> const & notify_observers_callback_a, std::function<void (nano::block_hash const &)> const & notify_block_already_cemented_observers_callback_a, std::function<uint64_t ()> const & awaiting_processing_size_callback_a) :
|
||||
ledger (ledger_a),
|
||||
write_database_queue (write_database_queue_a),
|
||||
batch_separate_pending_min_time (batch_separate_pending_min_time_a),
|
||||
logger (logger_a),
|
||||
stopped (stopped_a),
|
||||
batch_write_size (batch_write_size_a),
|
||||
notify_observers_callback (notify_observers_callback_a),
|
||||
notify_block_already_cemented_observers_callback (notify_block_already_cemented_observers_callback_a),
|
||||
awaiting_processing_size_callback (awaiting_processing_size_callback_a)
|
||||
{
|
||||
}
|
||||
|
||||
// The next block hash to iterate over, the priority is as follows:
|
||||
// 1 - The next block in the account chain for the last processed receive (if there is any)
|
||||
// 2 - The next receive block which is closest to genesis
|
||||
// 3 - The last checkpoint hit.
|
||||
// 4 - The hash that was passed in originally. Either all checkpoints were exhausted (this can happen when there are many accounts to genesis)
|
||||
// or all other blocks have been processed.
|
||||
nano::confirmation_height_bounded::top_and_next_hash nano::confirmation_height_bounded::get_next_block (boost::optional<top_and_next_hash> const & next_in_receive_chain_a, boost::circular_buffer_space_optimized<nano::block_hash> const & checkpoints_a, boost::circular_buffer_space_optimized<receive_source_pair> const & receive_source_pairs, boost::optional<receive_chain_details> & receive_details_a, nano::block const & original_block)
|
||||
{
|
||||
top_and_next_hash next;
|
||||
if (next_in_receive_chain_a.is_initialized ())
|
||||
{
|
||||
next = *next_in_receive_chain_a;
|
||||
}
|
||||
else if (!receive_source_pairs.empty ())
|
||||
{
|
||||
auto next_receive_source_pair = receive_source_pairs.back ();
|
||||
receive_details_a = next_receive_source_pair.receive_details;
|
||||
next = { next_receive_source_pair.source_hash, receive_details_a->next, receive_details_a->height + 1 };
|
||||
}
|
||||
else if (!checkpoints_a.empty ())
|
||||
{
|
||||
next = { checkpoints_a.back (), boost::none, 0 };
|
||||
}
|
||||
else
|
||||
{
|
||||
next = { original_block.hash (), boost::none, 0 };
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void nano::confirmation_height_bounded::process (std::shared_ptr<nano::block> original_block)
|
||||
{
|
||||
if (pending_empty ())
|
||||
{
|
||||
clear_process_vars ();
|
||||
timer.restart ();
|
||||
}
|
||||
|
||||
boost::optional<top_and_next_hash> next_in_receive_chain;
|
||||
boost::circular_buffer_space_optimized<nano::block_hash> checkpoints{ max_items };
|
||||
boost::circular_buffer_space_optimized<receive_source_pair> receive_source_pairs{ max_items };
|
||||
nano::block_hash current;
|
||||
bool first_iter = true;
|
||||
auto transaction (ledger.store.tx_begin_read ());
|
||||
do
|
||||
{
|
||||
boost::optional<receive_chain_details> receive_details;
|
||||
auto hash_to_process = get_next_block (next_in_receive_chain, checkpoints, receive_source_pairs, receive_details, *original_block);
|
||||
current = hash_to_process.top;
|
||||
|
||||
auto top_level_hash = current;
|
||||
std::shared_ptr<nano::block> block;
|
||||
if (first_iter)
|
||||
{
|
||||
debug_assert (current == original_block->hash ());
|
||||
block = original_block;
|
||||
}
|
||||
else
|
||||
{
|
||||
block = ledger.block (transaction, current);
|
||||
}
|
||||
|
||||
if (!block)
|
||||
{
|
||||
if (ledger.pruning && ledger.store.pruned.exists (transaction, current))
|
||||
{
|
||||
if (!receive_source_pairs.empty ())
|
||||
{
|
||||
receive_source_pairs.pop_back ();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.critical (nano::log::type::conf_processor_bounded, "Ledger mismatch trying to set confirmation height for block {} (bounded processor)", current.to_string ());
|
||||
|
||||
release_assert (block);
|
||||
}
|
||||
}
|
||||
auto account = block->account ();
|
||||
|
||||
// Checks if we have encountered this account before but not commited changes yet, if so then update the cached confirmation height
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
auto account_it = accounts_confirmed_info.find (account);
|
||||
if (account_it != accounts_confirmed_info.cend ())
|
||||
{
|
||||
confirmation_height_info.height = account_it->second.confirmed_height;
|
||||
confirmation_height_info.frontier = account_it->second.iterated_frontier;
|
||||
}
|
||||
else
|
||||
{
|
||||
ledger.store.confirmation_height.get (transaction, account, confirmation_height_info);
|
||||
// This block was added to the confirmation height processor but is already confirmed
|
||||
if (first_iter && confirmation_height_info.height >= block->sideband ().height && current == original_block->hash ())
|
||||
{
|
||||
notify_block_already_cemented_observers_callback (original_block->hash ());
|
||||
}
|
||||
}
|
||||
|
||||
auto block_height = block->sideband ().height;
|
||||
bool already_cemented = confirmation_height_info.height >= block_height;
|
||||
|
||||
// If we are not already at the bottom of the account chain (1 above cemented frontier) then find it
|
||||
if (!already_cemented && block_height - confirmation_height_info.height > 1)
|
||||
{
|
||||
if (block_height - confirmation_height_info.height == 2)
|
||||
{
|
||||
// If there is 1 uncemented block in-between this block and the cemented frontier,
|
||||
// we can just use the previous block to get the least unconfirmed hash.
|
||||
current = block->previous ();
|
||||
--block_height;
|
||||
}
|
||||
else if (!next_in_receive_chain.is_initialized ())
|
||||
{
|
||||
current = get_least_unconfirmed_hash_from_top_level (transaction, current, account, confirmation_height_info, block_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the cached successor of the last receive which saves having to do more IO in get_least_unconfirmed_hash_from_top_level
|
||||
// as we already know what the next block we should process should be.
|
||||
current = *hash_to_process.next;
|
||||
block_height = hash_to_process.next_height;
|
||||
}
|
||||
}
|
||||
|
||||
auto top_most_non_receive_block_hash = current;
|
||||
|
||||
bool hit_receive = false;
|
||||
if (!already_cemented)
|
||||
{
|
||||
hit_receive = iterate (transaction, block_height, current, checkpoints, top_most_non_receive_block_hash, top_level_hash, receive_source_pairs, account);
|
||||
}
|
||||
|
||||
// Exit early when the processor has been stopped, otherwise this function may take a
|
||||
// while (and hence keep the process running) if updating a long chain.
|
||||
if (stopped)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// next_in_receive_chain can be modified when writing, so need to cache it here before resetting
|
||||
auto is_set = next_in_receive_chain.is_initialized ();
|
||||
next_in_receive_chain = boost::none;
|
||||
|
||||
// Need to also handle the case where we are hitting receives where the sends below should be confirmed
|
||||
if (!hit_receive || (receive_source_pairs.size () == 1 && top_most_non_receive_block_hash != current))
|
||||
{
|
||||
preparation_data preparation_data{ transaction, top_most_non_receive_block_hash, already_cemented, checkpoints, account_it, confirmation_height_info, account, block_height, current, receive_details, next_in_receive_chain };
|
||||
prepare_iterated_blocks_for_cementing (preparation_data);
|
||||
|
||||
// If used the top level, don't pop off the receive source pair because it wasn't used
|
||||
if (!is_set && !receive_source_pairs.empty ())
|
||||
{
|
||||
receive_source_pairs.pop_back ();
|
||||
}
|
||||
|
||||
auto total_pending_write_block_count = std::accumulate (pending_writes.cbegin (), pending_writes.cend (), uint64_t (0), [] (uint64_t total, auto const & write_details_a) {
|
||||
return total += write_details_a.top_height - write_details_a.bottom_height + 1;
|
||||
});
|
||||
|
||||
auto max_batch_write_size_reached = (total_pending_write_block_count >= batch_write_size);
|
||||
// When there are a lot of pending confirmation height blocks, it is more efficient to
|
||||
// bulk some of them up to enable better write performance which becomes the bottleneck.
|
||||
auto min_time_exceeded = (timer.since_start () >= batch_separate_pending_min_time);
|
||||
auto finished_iterating = current == original_block->hash ();
|
||||
auto non_awaiting_processing = awaiting_processing_size_callback () == 0;
|
||||
auto should_output = finished_iterating && (non_awaiting_processing || min_time_exceeded);
|
||||
auto force_write = pending_writes.size () >= pending_writes_max_size || accounts_confirmed_info.size () >= pending_writes_max_size;
|
||||
|
||||
if ((max_batch_write_size_reached || should_output || force_write) && !pending_writes.empty ())
|
||||
{
|
||||
// If nothing is currently using the database write lock then write the cemented pending blocks otherwise continue iterating
|
||||
if (write_database_queue.process (nano::writer::confirmation_height))
|
||||
{
|
||||
auto scoped_write_guard = write_database_queue.pop ();
|
||||
cement_blocks (scoped_write_guard);
|
||||
}
|
||||
else if (force_write)
|
||||
{
|
||||
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
|
||||
cement_blocks (scoped_write_guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
first_iter = false;
|
||||
transaction.refresh ();
|
||||
} while ((!receive_source_pairs.empty () || current != original_block->hash ()) && !stopped);
|
||||
|
||||
debug_assert (checkpoints.empty ());
|
||||
}
|
||||
|
||||
nano::block_hash nano::confirmation_height_bounded::get_least_unconfirmed_hash_from_top_level (store::transaction const & transaction_a, nano::block_hash const & hash_a, nano::account const & account_a, nano::confirmation_height_info const & confirmation_height_info_a, uint64_t & block_height_a)
|
||||
{
|
||||
nano::block_hash least_unconfirmed_hash = hash_a;
|
||||
if (confirmation_height_info_a.height != 0)
|
||||
{
|
||||
if (block_height_a > confirmation_height_info_a.height)
|
||||
{
|
||||
auto block = ledger.block (transaction_a, confirmation_height_info_a.frontier);
|
||||
release_assert (block != nullptr);
|
||||
least_unconfirmed_hash = block->sideband ().successor;
|
||||
block_height_a = block->sideband ().height + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No blocks have been confirmed, so the first block will be the open block
|
||||
auto info = ledger.account_info (transaction_a, account_a);
|
||||
release_assert (info);
|
||||
least_unconfirmed_hash = info->open_block;
|
||||
block_height_a = 1;
|
||||
}
|
||||
return least_unconfirmed_hash;
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_bounded::iterate (store::read_transaction const & transaction_a, uint64_t bottom_height_a, nano::block_hash const & bottom_hash_a, boost::circular_buffer_space_optimized<nano::block_hash> & checkpoints_a, nano::block_hash & top_most_non_receive_block_hash_a, nano::block_hash const & top_level_hash_a, boost::circular_buffer_space_optimized<receive_source_pair> & receive_source_pairs_a, nano::account const & account_a)
|
||||
{
|
||||
bool reached_target = false;
|
||||
bool hit_receive = false;
|
||||
auto hash = bottom_hash_a;
|
||||
uint64_t num_blocks = 0;
|
||||
while (!hash.is_zero () && !reached_target && !stopped)
|
||||
{
|
||||
// Keep iterating upwards until we either reach the desired block or the second receive.
|
||||
// Once a receive is cemented, we can cement all blocks above it until the next receive, so store those details for later.
|
||||
++num_blocks;
|
||||
auto block = ledger.block (transaction_a, hash);
|
||||
if (block->is_receive () && ledger.block_exists (transaction_a, block->source ()))
|
||||
{
|
||||
hit_receive = true;
|
||||
reached_target = true;
|
||||
auto const & sideband (block->sideband ());
|
||||
auto next = !sideband.successor.is_zero () && sideband.successor != top_level_hash_a ? boost::optional<nano::block_hash> (sideband.successor) : boost::none;
|
||||
receive_source_pairs_a.push_back ({ receive_chain_details{ account_a, sideband.height, hash, top_level_hash_a, next, bottom_height_a, bottom_hash_a }, block->source () });
|
||||
// Store a checkpoint every max_items so that we can always traverse a long number of accounts to genesis
|
||||
if (receive_source_pairs_a.size () % max_items == 0)
|
||||
{
|
||||
checkpoints_a.push_back (top_level_hash_a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found a send/change/epoch block which isn't the desired top level
|
||||
top_most_non_receive_block_hash_a = hash;
|
||||
if (hash == top_level_hash_a)
|
||||
{
|
||||
reached_target = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = block->sideband ().successor;
|
||||
}
|
||||
}
|
||||
|
||||
// We could be traversing a very large account so we don't want to open read transactions for too long.
|
||||
if ((num_blocks > 0) && num_blocks % batch_read_size == 0)
|
||||
{
|
||||
transaction_a.refresh ();
|
||||
}
|
||||
}
|
||||
|
||||
return hit_receive;
|
||||
}
|
||||
|
||||
// Once the path to genesis has been iterated to, we can begin to cement the lowest blocks in the accounts. This sets up
|
||||
// the non-receive blocks which have been iterated for an account, and the associated receive block.
|
||||
void nano::confirmation_height_bounded::prepare_iterated_blocks_for_cementing (preparation_data & preparation_data_a)
|
||||
{
|
||||
if (!preparation_data_a.already_cemented)
|
||||
{
|
||||
// Add the non-receive blocks iterated for this account
|
||||
auto block_height = (ledger.height (preparation_data_a.transaction, preparation_data_a.top_most_non_receive_block_hash));
|
||||
if (block_height > preparation_data_a.confirmation_height_info.height)
|
||||
{
|
||||
confirmed_info confirmed_info_l{ block_height, preparation_data_a.top_most_non_receive_block_hash };
|
||||
if (preparation_data_a.account_it != accounts_confirmed_info.cend ())
|
||||
{
|
||||
preparation_data_a.account_it->second = confirmed_info_l;
|
||||
}
|
||||
else
|
||||
{
|
||||
accounts_confirmed_info.emplace (preparation_data_a.account, confirmed_info_l);
|
||||
++accounts_confirmed_info_size;
|
||||
}
|
||||
|
||||
preparation_data_a.checkpoints.erase (std::remove (preparation_data_a.checkpoints.begin (), preparation_data_a.checkpoints.end (), preparation_data_a.top_most_non_receive_block_hash), preparation_data_a.checkpoints.end ());
|
||||
pending_writes.emplace_back (preparation_data_a.account, preparation_data_a.bottom_height, preparation_data_a.bottom_most, block_height, preparation_data_a.top_most_non_receive_block_hash);
|
||||
++pending_writes_size;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the receive block and all non-receive blocks above that one
|
||||
auto & receive_details = preparation_data_a.receive_details;
|
||||
if (receive_details)
|
||||
{
|
||||
auto receive_confirmed_info_it = accounts_confirmed_info.find (receive_details->account);
|
||||
if (receive_confirmed_info_it != accounts_confirmed_info.cend ())
|
||||
{
|
||||
auto & receive_confirmed_info = receive_confirmed_info_it->second;
|
||||
receive_confirmed_info.confirmed_height = receive_details->height;
|
||||
receive_confirmed_info.iterated_frontier = receive_details->hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
accounts_confirmed_info.emplace (std::piecewise_construct, std::forward_as_tuple (receive_details->account), std::forward_as_tuple (receive_details->height, receive_details->hash));
|
||||
++accounts_confirmed_info_size;
|
||||
}
|
||||
|
||||
if (receive_details->next.is_initialized ())
|
||||
{
|
||||
preparation_data_a.next_in_receive_chain = top_and_next_hash{ receive_details->top_level, receive_details->next, receive_details->height + 1 };
|
||||
}
|
||||
else
|
||||
{
|
||||
preparation_data_a.checkpoints.erase (std::remove (preparation_data_a.checkpoints.begin (), preparation_data_a.checkpoints.end (), receive_details->hash), preparation_data_a.checkpoints.end ());
|
||||
}
|
||||
|
||||
pending_writes.emplace_back (receive_details->account, receive_details->bottom_height, receive_details->bottom_most, receive_details->height, receive_details->hash);
|
||||
++pending_writes_size;
|
||||
}
|
||||
}
|
||||
|
||||
void nano::confirmation_height_bounded::cement_blocks (nano::write_guard & scoped_write_guard_a)
|
||||
{
|
||||
// Will contain all blocks that have been cemented (bounded by batch_write_size)
|
||||
// and will get run through the cemented observer callback
|
||||
std::vector<std::shared_ptr<nano::block>> cemented_blocks;
|
||||
auto const maximum_batch_write_time = 250; // milliseconds
|
||||
auto const maximum_batch_write_time_increase_cutoff = maximum_batch_write_time - (maximum_batch_write_time / 5);
|
||||
auto const amount_to_change = batch_write_size / 10; // 10%
|
||||
auto const minimum_batch_write_size = 16384u;
|
||||
nano::timer<> cemented_batch_timer;
|
||||
auto error = false;
|
||||
{
|
||||
// This only writes to the confirmation_height table and is the only place to do so in a single process
|
||||
auto transaction (ledger.store.tx_begin_write ({}, { nano::tables::confirmation_height }));
|
||||
cemented_batch_timer.start ();
|
||||
// Cement all pending entries, each entry is specific to an account and contains the least amount
|
||||
// of blocks to retain consistent cementing across all account chains to genesis.
|
||||
while (!error && !pending_writes.empty ())
|
||||
{
|
||||
auto const & pending = pending_writes.front ();
|
||||
auto const & account = pending.account;
|
||||
|
||||
auto write_confirmation_height = [&account, &ledger = ledger, &transaction] (uint64_t num_blocks_cemented, uint64_t confirmation_height, nano::block_hash const & confirmed_frontier) {
|
||||
#ifndef NDEBUG
|
||||
// Extra debug checks
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
ledger.store.confirmation_height.get (transaction, account, confirmation_height_info);
|
||||
auto block = ledger.block (transaction, confirmed_frontier);
|
||||
debug_assert (block != nullptr);
|
||||
debug_assert (block->sideband ().height == confirmation_height_info.height + num_blocks_cemented);
|
||||
#endif
|
||||
ledger.store.confirmation_height.put (transaction, account, nano::confirmation_height_info{ confirmation_height, confirmed_frontier });
|
||||
ledger.cache.cemented_count += num_blocks_cemented;
|
||||
ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in, num_blocks_cemented);
|
||||
ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in, num_blocks_cemented);
|
||||
};
|
||||
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
ledger.store.confirmation_height.get (transaction, pending.account, confirmation_height_info);
|
||||
|
||||
// Some blocks need to be cemented at least
|
||||
if (pending.top_height > confirmation_height_info.height)
|
||||
{
|
||||
// The highest hash which will be cemented
|
||||
nano::block_hash new_cemented_frontier;
|
||||
uint64_t num_blocks_confirmed = 0;
|
||||
uint64_t start_height = 0;
|
||||
if (pending.bottom_height > confirmation_height_info.height)
|
||||
{
|
||||
new_cemented_frontier = pending.bottom_hash;
|
||||
// If we are higher than the cemented frontier, we should be exactly 1 block above
|
||||
debug_assert (pending.bottom_height == confirmation_height_info.height + 1);
|
||||
num_blocks_confirmed = pending.top_height - pending.bottom_height + 1;
|
||||
start_height = pending.bottom_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto block = ledger.block (transaction, confirmation_height_info.frontier);
|
||||
new_cemented_frontier = block->sideband ().successor;
|
||||
num_blocks_confirmed = pending.top_height - confirmation_height_info.height;
|
||||
start_height = confirmation_height_info.height + 1;
|
||||
}
|
||||
|
||||
auto total_blocks_cemented = 0;
|
||||
auto block = ledger.block (transaction, new_cemented_frontier);
|
||||
|
||||
// Cementing starts from the bottom of the chain and works upwards. This is because chains can have effectively
|
||||
// an infinite number of send/change blocks in a row. We don't want to hold the write transaction open for too long.
|
||||
for (auto num_blocks_iterated = 0; num_blocks_confirmed - num_blocks_iterated != 0; ++num_blocks_iterated)
|
||||
{
|
||||
if (!block)
|
||||
{
|
||||
logger.critical (nano::log::type::conf_processor_bounded, "Failed to write confirmation height for block {} (bounded processor)", new_cemented_frontier.to_string ());
|
||||
|
||||
// Undo any blocks about to be cemented from this account for this pending write.
|
||||
cemented_blocks.erase (cemented_blocks.end () - num_blocks_iterated, cemented_blocks.end ());
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto last_iteration = (num_blocks_confirmed - num_blocks_iterated) == 1;
|
||||
|
||||
cemented_blocks.emplace_back (block);
|
||||
|
||||
// Flush these callbacks and continue as we write in batches (ideally maximum 250ms) to not hold write db transaction for too long.
|
||||
// Include a tolerance to save having to potentially wait on the block processor if the number of blocks to cement is only a bit higher than the max.
|
||||
if (cemented_blocks.size () > batch_write_size + (batch_write_size / 10))
|
||||
{
|
||||
auto time_spent_cementing = cemented_batch_timer.since_start ().count ();
|
||||
auto num_blocks_cemented = num_blocks_iterated - total_blocks_cemented + 1;
|
||||
total_blocks_cemented += num_blocks_cemented;
|
||||
write_confirmation_height (num_blocks_cemented, start_height + total_blocks_cemented - 1, new_cemented_frontier);
|
||||
transaction.commit ();
|
||||
|
||||
// Update the maximum amount of blocks to write next time based on the time it took to cement this batch.
|
||||
if (time_spent_cementing > maximum_batch_write_time)
|
||||
{
|
||||
// Reduce (unless we have hit a floor)
|
||||
batch_write_size = std::max<uint64_t> (minimum_batch_write_size, batch_write_size - amount_to_change);
|
||||
}
|
||||
else if (time_spent_cementing < maximum_batch_write_time_increase_cutoff)
|
||||
{
|
||||
// Increase amount of blocks written for next batch if the time for writing this one is sufficiently lower than the max time to warrant changing
|
||||
batch_write_size += amount_to_change;
|
||||
}
|
||||
|
||||
scoped_write_guard_a.release ();
|
||||
notify_observers_callback (cemented_blocks);
|
||||
cemented_blocks.clear ();
|
||||
|
||||
// Only aquire transaction if there are blocks left
|
||||
if (!(last_iteration && pending_writes.size () == 1))
|
||||
{
|
||||
scoped_write_guard_a = write_database_queue.wait (nano::writer::confirmation_height);
|
||||
transaction.renew ();
|
||||
}
|
||||
cemented_batch_timer.restart ();
|
||||
}
|
||||
|
||||
// Get the next block in the chain until we have reached the final desired one
|
||||
if (!last_iteration)
|
||||
{
|
||||
new_cemented_frontier = block->sideband ().successor;
|
||||
block = ledger.block (transaction, new_cemented_frontier);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Confirm it is indeed the last one
|
||||
debug_assert (new_cemented_frontier == pending.top_hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
// There was an error writing a block, do not process any more
|
||||
break;
|
||||
}
|
||||
|
||||
auto num_blocks_cemented = num_blocks_confirmed - total_blocks_cemented;
|
||||
if (num_blocks_cemented > 0)
|
||||
{
|
||||
write_confirmation_height (num_blocks_cemented, pending.top_height, new_cemented_frontier);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = accounts_confirmed_info.find (pending.account);
|
||||
if (it != accounts_confirmed_info.cend () && it->second.confirmed_height == pending.top_height)
|
||||
{
|
||||
accounts_confirmed_info.erase (pending.account);
|
||||
--accounts_confirmed_info_size;
|
||||
}
|
||||
pending_writes.pop_front ();
|
||||
--pending_writes_size;
|
||||
}
|
||||
}
|
||||
|
||||
auto time_spent_cementing = cemented_batch_timer.since_start ();
|
||||
|
||||
// Scope guard could have been released earlier (0 cemented_blocks would indicate that)
|
||||
if (scoped_write_guard_a.is_owned () && !cemented_blocks.empty ())
|
||||
{
|
||||
scoped_write_guard_a.release ();
|
||||
notify_observers_callback (cemented_blocks);
|
||||
}
|
||||
|
||||
// Bail if there was an error. This indicates that there was a fatal issue with the ledger
|
||||
// (the blocks probably got rolled back when they shouldn't have).
|
||||
release_assert (!error);
|
||||
|
||||
if (time_spent_cementing.count () > maximum_batch_write_time)
|
||||
{
|
||||
// Reduce (unless we have hit a floor)
|
||||
batch_write_size = std::max<uint64_t> (minimum_batch_write_size, batch_write_size - amount_to_change);
|
||||
}
|
||||
|
||||
debug_assert (pending_writes.empty ());
|
||||
debug_assert (pending_writes_size == 0);
|
||||
timer.restart ();
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_bounded::pending_empty () const
|
||||
{
|
||||
return pending_writes.empty ();
|
||||
}
|
||||
|
||||
void nano::confirmation_height_bounded::clear_process_vars ()
|
||||
{
|
||||
accounts_confirmed_info.clear ();
|
||||
accounts_confirmed_info_size = 0;
|
||||
}
|
||||
|
||||
nano::confirmation_height_bounded::receive_chain_details::receive_chain_details (nano::account const & account_a, uint64_t height_a, nano::block_hash const & hash_a, nano::block_hash const & top_level_a, boost::optional<nano::block_hash> next_a, uint64_t bottom_height_a, nano::block_hash const & bottom_most_a) :
|
||||
account (account_a),
|
||||
height (height_a),
|
||||
hash (hash_a),
|
||||
top_level (top_level_a),
|
||||
next (next_a),
|
||||
bottom_height (bottom_height_a),
|
||||
bottom_most (bottom_most_a)
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_bounded::write_details::write_details (nano::account const & account_a, uint64_t bottom_height_a, nano::block_hash const & bottom_hash_a, uint64_t top_height_a, nano::block_hash const & top_hash_a) :
|
||||
account (account_a),
|
||||
bottom_height (bottom_height_a),
|
||||
bottom_hash (bottom_hash_a),
|
||||
top_height (top_height_a),
|
||||
top_hash (top_hash_a)
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_bounded::receive_source_pair::receive_source_pair (confirmation_height_bounded::receive_chain_details const & receive_details_a, const block_hash & source_a) :
|
||||
receive_details (receive_details_a),
|
||||
source_hash (source_a)
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_bounded::confirmed_info::confirmed_info (uint64_t confirmed_height_a, nano::block_hash const & iterated_frontier_a) :
|
||||
confirmed_height (confirmed_height_a),
|
||||
iterated_frontier (iterated_frontier_a)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<nano::container_info_component> nano::collect_container_info (confirmation_height_bounded & confirmation_height_bounded, std::string const & name_a)
|
||||
{
|
||||
auto composite = std::make_unique<container_info_composite> (name_a);
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "pending_writes", confirmation_height_bounded.pending_writes_size, sizeof (decltype (confirmation_height_bounded.pending_writes)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "accounts_confirmed_info", confirmation_height_bounded.accounts_confirmed_info_size, sizeof (decltype (confirmation_height_bounded.accounts_confirmed_info)::value_type) }));
|
||||
return composite;
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/relaxed_atomic.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/timer.hpp>
|
||||
#include <nano/store/component.hpp>
|
||||
|
||||
#include <boost/circular_buffer.hpp>
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class ledger;
|
||||
class write_database_queue;
|
||||
class write_guard;
|
||||
|
||||
class confirmation_height_bounded final
|
||||
{
|
||||
public:
|
||||
confirmation_height_bounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds batch_separate_pending_min_time, nano::logger &, std::atomic<bool> & stopped, uint64_t & batch_write_size, std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> const & cemented_callback, std::function<void (nano::block_hash const &)> const & already_cemented_callback, std::function<uint64_t ()> const & awaiting_processing_size_query);
|
||||
|
||||
bool pending_empty () const;
|
||||
void clear_process_vars ();
|
||||
void process (std::shared_ptr<nano::block> original_block);
|
||||
void cement_blocks (nano::write_guard & scoped_write_guard_a);
|
||||
|
||||
private:
|
||||
class top_and_next_hash final
|
||||
{
|
||||
public:
|
||||
nano::block_hash top;
|
||||
boost::optional<nano::block_hash> next;
|
||||
uint64_t next_height;
|
||||
};
|
||||
|
||||
class confirmed_info
|
||||
{
|
||||
public:
|
||||
confirmed_info (uint64_t confirmed_height_a, nano::block_hash const & iterated_frontier);
|
||||
uint64_t confirmed_height;
|
||||
nano::block_hash iterated_frontier;
|
||||
};
|
||||
|
||||
class write_details final
|
||||
{
|
||||
public:
|
||||
write_details (nano::account const &, uint64_t, nano::block_hash const &, uint64_t, nano::block_hash const &);
|
||||
nano::account account;
|
||||
// This is the first block hash (bottom most) which is not cemented
|
||||
uint64_t bottom_height;
|
||||
nano::block_hash bottom_hash;
|
||||
// Desired cemented frontier
|
||||
uint64_t top_height;
|
||||
nano::block_hash top_hash;
|
||||
};
|
||||
|
||||
/** The maximum number of blocks to be read in while iterating over a long account chain */
|
||||
uint64_t const batch_read_size = 65536;
|
||||
|
||||
/** The maximum number of various containers to keep the memory bounded */
|
||||
uint32_t const max_items{ 131072 };
|
||||
|
||||
// All of the atomic variables here just track the size for use in collect_container_info.
|
||||
// This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed
|
||||
// for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted
|
||||
// upon in any way (does not synchronize with any other data).
|
||||
// This allows the load and stores to use relaxed atomic memory ordering.
|
||||
std::deque<write_details> pending_writes;
|
||||
nano::relaxed_atomic_integral<uint64_t> pending_writes_size{ 0 };
|
||||
uint32_t const pending_writes_max_size{ max_items };
|
||||
/* Holds confirmation height/cemented frontier in memory for accounts while iterating */
|
||||
std::unordered_map<account, confirmed_info> accounts_confirmed_info;
|
||||
nano::relaxed_atomic_integral<uint64_t> accounts_confirmed_info_size{ 0 };
|
||||
|
||||
class receive_chain_details final
|
||||
{
|
||||
public:
|
||||
receive_chain_details (nano::account const &, uint64_t, nano::block_hash const &, nano::block_hash const &, boost::optional<nano::block_hash>, uint64_t, nano::block_hash const &);
|
||||
nano::account account;
|
||||
uint64_t height;
|
||||
nano::block_hash hash;
|
||||
nano::block_hash top_level;
|
||||
boost::optional<nano::block_hash> next;
|
||||
uint64_t bottom_height;
|
||||
nano::block_hash bottom_most;
|
||||
};
|
||||
|
||||
class preparation_data final
|
||||
{
|
||||
public:
|
||||
store::transaction const & transaction;
|
||||
nano::block_hash const & top_most_non_receive_block_hash;
|
||||
bool already_cemented;
|
||||
boost::circular_buffer_space_optimized<nano::block_hash> & checkpoints;
|
||||
decltype (accounts_confirmed_info.begin ()) account_it;
|
||||
nano::confirmation_height_info const & confirmation_height_info;
|
||||
nano::account const & account;
|
||||
uint64_t bottom_height;
|
||||
nano::block_hash const & bottom_most;
|
||||
boost::optional<receive_chain_details> & receive_details;
|
||||
boost::optional<top_and_next_hash> & next_in_receive_chain;
|
||||
};
|
||||
|
||||
class receive_source_pair final
|
||||
{
|
||||
public:
|
||||
receive_source_pair (receive_chain_details const &, nano::block_hash const &);
|
||||
|
||||
receive_chain_details receive_details;
|
||||
nano::block_hash source_hash;
|
||||
};
|
||||
|
||||
nano::timer<std::chrono::milliseconds> timer;
|
||||
|
||||
top_and_next_hash get_next_block (boost::optional<top_and_next_hash> const &, boost::circular_buffer_space_optimized<nano::block_hash> const &, boost::circular_buffer_space_optimized<receive_source_pair> const & receive_source_pairs, boost::optional<receive_chain_details> &, nano::block const & original_block);
|
||||
nano::block_hash get_least_unconfirmed_hash_from_top_level (store::transaction const &, nano::block_hash const &, nano::account const &, nano::confirmation_height_info const &, uint64_t &);
|
||||
void prepare_iterated_blocks_for_cementing (preparation_data &);
|
||||
bool iterate (store::read_transaction const &, uint64_t, nano::block_hash const &, boost::circular_buffer_space_optimized<nano::block_hash> &, nano::block_hash &, nano::block_hash const &, boost::circular_buffer_space_optimized<receive_source_pair> &, nano::account const &);
|
||||
|
||||
nano::ledger & ledger;
|
||||
nano::write_database_queue & write_database_queue;
|
||||
std::chrono::milliseconds batch_separate_pending_min_time;
|
||||
nano::logger & logger;
|
||||
std::atomic<bool> & stopped;
|
||||
uint64_t & batch_write_size;
|
||||
std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> notify_observers_callback;
|
||||
std::function<void (nano::block_hash const &)> notify_block_already_cemented_observers_callback;
|
||||
std::function<uint64_t ()> awaiting_processing_size_callback;
|
||||
|
||||
friend std::unique_ptr<nano::container_info_component> collect_container_info (confirmation_height_bounded &, std::string const & name_a);
|
||||
};
|
||||
|
||||
std::unique_ptr<nano::container_info_component> collect_container_info (confirmation_height_bounded &, std::string const & name_a);
|
||||
}
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/thread_roles.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/node/confirmation_height_processor.hpp>
|
||||
#include <nano/node/write_database_queue.hpp>
|
||||
#include <nano/secure/common.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
|
||||
#include <boost/thread/latch.hpp>
|
||||
|
||||
nano::confirmation_height_processor::confirmation_height_processor (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger & logger_a, boost::latch & latch, confirmation_height_mode mode_a) :
|
||||
ledger (ledger_a),
|
||||
write_database_queue (write_database_queue_a),
|
||||
unbounded_processor (
|
||||
ledger_a, write_database_queue_a, batch_separate_pending_min_time_a, logger_a, stopped, batch_write_size,
|
||||
/* cemented_callback */ [this] (auto & cemented_blocks) { this->notify_cemented (cemented_blocks); },
|
||||
/* already cemented_callback */ [this] (auto const & block_hash_a) { this->notify_already_cemented (block_hash_a); },
|
||||
/* awaiting_processing_size_query */ [this] () { return this->awaiting_processing_size (); }),
|
||||
bounded_processor (
|
||||
ledger_a, write_database_queue_a, batch_separate_pending_min_time_a, logger_a, stopped, batch_write_size,
|
||||
/* cemented_callback */ [this] (auto & cemented_blocks) { this->notify_cemented (cemented_blocks); },
|
||||
/* already cemented_callback */ [this] (auto const & block_hash_a) { this->notify_already_cemented (block_hash_a); },
|
||||
/* awaiting_processing_size_query */ [this] () { return this->awaiting_processing_size (); }),
|
||||
thread ([this, &latch, mode_a] () {
|
||||
nano::thread_role::set (nano::thread_role::name::confirmation_height_processing);
|
||||
// Do not start running the processing thread until other threads have finished their operations
|
||||
latch.wait ();
|
||||
this->run (mode_a);
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_processor::~confirmation_height_processor ()
|
||||
{
|
||||
stop ();
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::stop ()
|
||||
{
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (mutex);
|
||||
stopped = true;
|
||||
}
|
||||
condition.notify_one ();
|
||||
if (thread.joinable ())
|
||||
{
|
||||
thread.join ();
|
||||
}
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::run (confirmation_height_mode mode_a)
|
||||
{
|
||||
nano::unique_lock<nano::mutex> lk (mutex);
|
||||
while (!stopped)
|
||||
{
|
||||
if (!paused && !awaiting_processing.empty ())
|
||||
{
|
||||
lk.unlock ();
|
||||
if (bounded_processor.pending_empty () && unbounded_processor.pending_empty ())
|
||||
{
|
||||
lk.lock ();
|
||||
original_hashes_pending.clear ();
|
||||
lk.unlock ();
|
||||
}
|
||||
|
||||
set_next_hash ();
|
||||
|
||||
auto const num_blocks_to_use_unbounded = confirmation_height::unbounded_cutoff;
|
||||
auto blocks_within_automatic_unbounded_selection = (ledger.cache.block_count < num_blocks_to_use_unbounded || ledger.cache.block_count - num_blocks_to_use_unbounded < ledger.cache.cemented_count);
|
||||
|
||||
// Don't want to mix up pending writes across different processors
|
||||
auto valid_unbounded = (mode_a == confirmation_height_mode::automatic && blocks_within_automatic_unbounded_selection && bounded_processor.pending_empty ());
|
||||
auto force_unbounded = (!unbounded_processor.pending_empty () || mode_a == confirmation_height_mode::unbounded);
|
||||
if (force_unbounded || valid_unbounded)
|
||||
{
|
||||
debug_assert (bounded_processor.pending_empty ());
|
||||
unbounded_processor.process (original_block);
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_assert (mode_a == confirmation_height_mode::bounded || mode_a == confirmation_height_mode::automatic);
|
||||
debug_assert (unbounded_processor.pending_empty ());
|
||||
bounded_processor.process (original_block);
|
||||
}
|
||||
|
||||
lk.lock ();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lock_and_cleanup = [&lk, this] () {
|
||||
lk.lock ();
|
||||
original_block = nullptr;
|
||||
original_hashes_pending.clear ();
|
||||
bounded_processor.clear_process_vars ();
|
||||
unbounded_processor.clear_process_vars ();
|
||||
};
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
lk.unlock ();
|
||||
|
||||
// If there are blocks pending cementing, then make sure we flush out the remaining writes
|
||||
if (!bounded_processor.pending_empty ())
|
||||
{
|
||||
debug_assert (unbounded_processor.pending_empty ());
|
||||
{
|
||||
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
|
||||
bounded_processor.cement_blocks (scoped_write_guard);
|
||||
}
|
||||
lock_and_cleanup ();
|
||||
}
|
||||
else if (!unbounded_processor.pending_empty ())
|
||||
{
|
||||
debug_assert (bounded_processor.pending_empty ());
|
||||
{
|
||||
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
|
||||
unbounded_processor.cement_blocks (scoped_write_guard);
|
||||
}
|
||||
lock_and_cleanup ();
|
||||
}
|
||||
else
|
||||
{
|
||||
lock_and_cleanup ();
|
||||
// A block could have been confirmed during the re-locking
|
||||
if (awaiting_processing.empty ())
|
||||
{
|
||||
condition.wait (lk);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pausing is only utilised in some tests to help prevent it processing added blocks until required.
|
||||
original_block = nullptr;
|
||||
condition.wait (lk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pausing only affects processing new blocks, not the current one being processed. Currently only used in tests
|
||||
void nano::confirmation_height_processor::pause ()
|
||||
{
|
||||
nano::lock_guard<nano::mutex> lk (mutex);
|
||||
paused = true;
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::unpause ()
|
||||
{
|
||||
{
|
||||
nano::lock_guard<nano::mutex> lk (mutex);
|
||||
paused = false;
|
||||
}
|
||||
condition.notify_one ();
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::add (std::shared_ptr<nano::block> const & block_a)
|
||||
{
|
||||
{
|
||||
nano::lock_guard<nano::mutex> lk (mutex);
|
||||
awaiting_processing.get<tag_sequence> ().emplace_back (block_a);
|
||||
}
|
||||
condition.notify_one ();
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::set_next_hash ()
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (mutex);
|
||||
debug_assert (!awaiting_processing.empty ());
|
||||
original_block = awaiting_processing.get<tag_sequence> ().front ().block;
|
||||
original_hashes_pending.insert (original_block->hash ());
|
||||
awaiting_processing.get<tag_sequence> ().pop_front ();
|
||||
}
|
||||
|
||||
// Not thread-safe, only call before this processor has begun cementing
|
||||
void nano::confirmation_height_processor::add_cemented_observer (std::function<void (std::shared_ptr<nano::block> const &)> const & callback_a)
|
||||
{
|
||||
cemented_observers.push_back (callback_a);
|
||||
}
|
||||
|
||||
// Not thread-safe, only call before this processor has begun cementing
|
||||
void nano::confirmation_height_processor::add_block_already_cemented_observer (std::function<void (nano::block_hash const &)> const & callback_a)
|
||||
{
|
||||
block_already_cemented_observers.push_back (callback_a);
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::notify_cemented (std::vector<std::shared_ptr<nano::block>> const & cemented_blocks)
|
||||
{
|
||||
for (auto const & block_callback_data : cemented_blocks)
|
||||
{
|
||||
for (auto const & observer : cemented_observers)
|
||||
{
|
||||
observer (block_callback_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nano::confirmation_height_processor::notify_already_cemented (nano::block_hash const & hash_already_cemented_a)
|
||||
{
|
||||
for (auto const & observer : block_already_cemented_observers)
|
||||
{
|
||||
observer (hash_already_cemented_a);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<nano::container_info_component> nano::collect_container_info (confirmation_height_processor & confirmation_height_processor_a, std::string const & name_a)
|
||||
{
|
||||
auto composite = std::make_unique<container_info_composite> (name_a);
|
||||
|
||||
std::size_t cemented_observers_count = confirmation_height_processor_a.cemented_observers.size ();
|
||||
std::size_t block_already_cemented_observers_count = confirmation_height_processor_a.block_already_cemented_observers.size ();
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "cemented_observers", cemented_observers_count, sizeof (decltype (confirmation_height_processor_a.cemented_observers)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "block_already_cemented_observers", block_already_cemented_observers_count, sizeof (decltype (confirmation_height_processor_a.block_already_cemented_observers)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "awaiting_processing", confirmation_height_processor_a.awaiting_processing_size (), sizeof (decltype (confirmation_height_processor_a.awaiting_processing)::value_type) }));
|
||||
composite->add_component (collect_container_info (confirmation_height_processor_a.bounded_processor, "bounded_processor"));
|
||||
composite->add_component (collect_container_info (confirmation_height_processor_a.unbounded_processor, "unbounded_processor"));
|
||||
return composite;
|
||||
}
|
||||
|
||||
std::size_t nano::confirmation_height_processor::awaiting_processing_size () const
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (mutex);
|
||||
return awaiting_processing.size ();
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_processor::is_processing_added_block (nano::block_hash const & hash_a) const
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (mutex);
|
||||
return original_hashes_pending.count (hash_a) > 0 || awaiting_processing.get<tag_hash> ().count (hash_a) > 0;
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_processor::is_processing_block (nano::block_hash const & hash_a) const
|
||||
{
|
||||
return is_processing_added_block (hash_a) || unbounded_processor.has_iterated_over_block (hash_a);
|
||||
}
|
||||
|
||||
nano::block_hash nano::confirmation_height_processor::current () const
|
||||
{
|
||||
nano::lock_guard<nano::mutex> lk (mutex);
|
||||
return original_block ? original_block->hash () : 0;
|
||||
}
|
||||
|
||||
std::reference_wrapper<nano::block_hash const> nano::confirmation_height_processor::block_wrapper::hash () const
|
||||
{
|
||||
return block->hash ();
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/timer.hpp>
|
||||
#include <nano/node/confirmation_height_bounded.hpp>
|
||||
#include <nano/node/confirmation_height_unbounded.hpp>
|
||||
#include <nano/secure/common.hpp>
|
||||
#include <nano/store/component.hpp>
|
||||
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/mem_fun.hpp>
|
||||
#include <boost/multi_index/sequenced_index.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mi = boost::multi_index;
|
||||
namespace boost
|
||||
{
|
||||
class latch;
|
||||
}
|
||||
namespace nano
|
||||
{
|
||||
class ledger;
|
||||
class write_database_queue;
|
||||
|
||||
class confirmation_height_processor final
|
||||
{
|
||||
public:
|
||||
confirmation_height_processor (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds, nano::logger &, boost::latch & initialized_latch, confirmation_height_mode = confirmation_height_mode::automatic);
|
||||
~confirmation_height_processor ();
|
||||
|
||||
void pause ();
|
||||
void unpause ();
|
||||
void stop ();
|
||||
void add (std::shared_ptr<nano::block> const &);
|
||||
void run (confirmation_height_mode);
|
||||
std::size_t awaiting_processing_size () const;
|
||||
bool is_processing_added_block (nano::block_hash const & hash_a) const;
|
||||
bool is_processing_block (nano::block_hash const &) const;
|
||||
nano::block_hash current () const;
|
||||
|
||||
/*
|
||||
* Called for each newly cemented block
|
||||
* Called from confirmation height processor thread
|
||||
*/
|
||||
void add_cemented_observer (std::function<void (std::shared_ptr<nano::block> const &)> const &);
|
||||
/*
|
||||
* Called when the block was added to the confirmation height processor but is already confirmed
|
||||
* Called from confirmation height processor thread
|
||||
*/
|
||||
void add_block_already_cemented_observer (std::function<void (nano::block_hash const &)> const &);
|
||||
|
||||
private:
|
||||
mutable nano::mutex mutex{ mutex_identifier (mutexes::confirmation_height_processor) };
|
||||
|
||||
// Hashes which have been added to the confirmation height processor, but not yet processed
|
||||
struct block_wrapper
|
||||
{
|
||||
explicit block_wrapper (std::shared_ptr<nano::block> const & block_a) :
|
||||
block (block_a)
|
||||
{
|
||||
}
|
||||
|
||||
std::reference_wrapper<nano::block_hash const> hash () const;
|
||||
|
||||
std::shared_ptr<nano::block> block;
|
||||
};
|
||||
// clang-format off
|
||||
class tag_sequence {};
|
||||
class tag_hash {};
|
||||
boost::multi_index_container<block_wrapper,
|
||||
mi::indexed_by<
|
||||
mi::sequenced<mi::tag<tag_sequence>>,
|
||||
mi::hashed_unique<mi::tag<tag_hash>,
|
||||
mi::const_mem_fun<block_wrapper, std::reference_wrapper<nano::block_hash const>, &block_wrapper::hash>>>> awaiting_processing;
|
||||
// clang-format on
|
||||
|
||||
// Hashes which have been added and processed, but have not been cemented
|
||||
std::unordered_set<nano::block_hash> original_hashes_pending;
|
||||
bool paused{ false };
|
||||
|
||||
/** This is the last block popped off the confirmation height pending collection */
|
||||
std::shared_ptr<nano::block> original_block;
|
||||
|
||||
nano::condition_variable condition;
|
||||
std::atomic<bool> stopped{ false };
|
||||
// No mutex needed for the observers as these should be set up during initialization of the node
|
||||
std::vector<std::function<void (std::shared_ptr<nano::block> const &)>> cemented_observers;
|
||||
std::vector<std::function<void (nano::block_hash const &)>> block_already_cemented_observers;
|
||||
|
||||
nano::ledger & ledger;
|
||||
nano::write_database_queue & write_database_queue;
|
||||
/** The maximum amount of blocks to write at once. This is dynamically modified by the bounded processor based on previous write performance **/
|
||||
uint64_t batch_write_size{ 16384 };
|
||||
|
||||
confirmation_height_unbounded unbounded_processor;
|
||||
confirmation_height_bounded bounded_processor;
|
||||
std::thread thread;
|
||||
|
||||
void set_next_hash ();
|
||||
void notify_cemented (std::vector<std::shared_ptr<nano::block>> const &);
|
||||
void notify_already_cemented (nano::block_hash const &);
|
||||
|
||||
friend std::unique_ptr<container_info_component> collect_container_info (confirmation_height_processor &, std::string const &);
|
||||
|
||||
private: // Tests
|
||||
friend class confirmation_height_pending_observer_callbacks_Test;
|
||||
friend class confirmation_height_dependent_election_Test;
|
||||
friend class confirmation_height_dependent_election_after_already_cemented_Test;
|
||||
friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test;
|
||||
friend class confirmation_height_many_accounts_many_confirmations_Test;
|
||||
friend class confirmation_height_long_chains_Test;
|
||||
friend class confirmation_height_many_accounts_single_confirmation_Test;
|
||||
friend class request_aggregator_cannot_vote_Test;
|
||||
friend class active_transactions_pessimistic_elections_Test;
|
||||
};
|
||||
|
||||
std::unique_ptr<container_info_component> collect_container_info (confirmation_height_processor &, std::string const &);
|
||||
}
|
||||
|
|
@ -1,498 +0,0 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/stats.hpp>
|
||||
#include <nano/node/confirmation_height_unbounded.hpp>
|
||||
#include <nano/node/write_database_queue.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
#include <nano/store/block.hpp>
|
||||
#include <nano/store/confirmation_height.hpp>
|
||||
#include <nano/store/pruned.hpp>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
nano::confirmation_height_unbounded::confirmation_height_unbounded (nano::ledger & ledger_a, nano::write_database_queue & write_database_queue_a, std::chrono::milliseconds batch_separate_pending_min_time_a, nano::logger & logger_a, std::atomic<bool> & stopped_a, uint64_t & batch_write_size_a, std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> const & notify_observers_callback_a, std::function<void (nano::block_hash const &)> const & notify_block_already_cemented_observers_callback_a, std::function<uint64_t ()> const & awaiting_processing_size_callback_a) :
|
||||
ledger (ledger_a),
|
||||
write_database_queue (write_database_queue_a),
|
||||
batch_separate_pending_min_time (batch_separate_pending_min_time_a),
|
||||
logger (logger_a),
|
||||
stopped (stopped_a),
|
||||
batch_write_size (batch_write_size_a),
|
||||
notify_observers_callback (notify_observers_callback_a),
|
||||
notify_block_already_cemented_observers_callback (notify_block_already_cemented_observers_callback_a),
|
||||
awaiting_processing_size_callback (awaiting_processing_size_callback_a)
|
||||
{
|
||||
}
|
||||
|
||||
void nano::confirmation_height_unbounded::process (std::shared_ptr<nano::block> original_block)
|
||||
{
|
||||
if (pending_empty ())
|
||||
{
|
||||
clear_process_vars ();
|
||||
timer.restart ();
|
||||
}
|
||||
std::shared_ptr<conf_height_details> receive_details;
|
||||
auto current = original_block->hash ();
|
||||
std::vector<nano::block_hash> orig_block_callback_data;
|
||||
|
||||
std::vector<receive_source_pair> receive_source_pairs;
|
||||
release_assert (receive_source_pairs.empty ());
|
||||
|
||||
bool first_iter = true;
|
||||
auto read_transaction (ledger.store.tx_begin_read ());
|
||||
|
||||
do
|
||||
{
|
||||
if (!receive_source_pairs.empty ())
|
||||
{
|
||||
receive_details = receive_source_pairs.back ().receive_details;
|
||||
current = receive_source_pairs.back ().source_hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If receive_details is set then this is the final iteration and we are back to the original chain.
|
||||
// We need to confirm any blocks below the original hash (incl self) and the first receive block
|
||||
// (if the original block is not already a receive)
|
||||
if (receive_details)
|
||||
{
|
||||
current = original_block->hash ();
|
||||
receive_details = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<nano::block> block;
|
||||
if (first_iter)
|
||||
{
|
||||
debug_assert (current == original_block->hash ());
|
||||
// This is the original block passed so can use it directly
|
||||
block = original_block;
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
block_cache[original_block->hash ()] = original_block;
|
||||
}
|
||||
else
|
||||
{
|
||||
block = get_block_and_sideband (current, read_transaction);
|
||||
}
|
||||
if (!block)
|
||||
{
|
||||
logger.critical (nano::log::type::conf_processor_unbounded, "Ledger mismatch trying to set confirmation height for block {} (unbounded processor)", current.to_string ());
|
||||
}
|
||||
release_assert (block);
|
||||
|
||||
auto account = block->account ();
|
||||
|
||||
auto block_height = block->sideband ().height;
|
||||
uint64_t confirmation_height = 0;
|
||||
auto account_it = confirmed_iterated_pairs.find (account);
|
||||
if (account_it != confirmed_iterated_pairs.cend ())
|
||||
{
|
||||
confirmation_height = account_it->second.confirmed_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
ledger.store.confirmation_height.get (read_transaction, account, confirmation_height_info);
|
||||
confirmation_height = confirmation_height_info.height;
|
||||
|
||||
// This block was added to the confirmation height processor but is already confirmed
|
||||
if (first_iter && confirmation_height >= block_height)
|
||||
{
|
||||
debug_assert (current == original_block->hash ());
|
||||
notify_block_already_cemented_observers_callback (original_block->hash ());
|
||||
}
|
||||
}
|
||||
auto iterated_height = confirmation_height;
|
||||
if (account_it != confirmed_iterated_pairs.cend () && account_it->second.iterated_height > iterated_height)
|
||||
{
|
||||
iterated_height = account_it->second.iterated_height;
|
||||
}
|
||||
|
||||
auto count_before_receive = receive_source_pairs.size ();
|
||||
std::vector<nano::block_hash> block_callback_datas_required;
|
||||
auto already_traversed = iterated_height >= block_height;
|
||||
if (!already_traversed)
|
||||
{
|
||||
collect_unconfirmed_receive_and_sources_for_account (block_height, iterated_height, block, current, account, read_transaction, receive_source_pairs, block_callback_datas_required, orig_block_callback_data, original_block);
|
||||
}
|
||||
|
||||
// Exit early when the processor has been stopped, otherwise this function may take a
|
||||
// while (and hence keep the process running) if updating a long chain.
|
||||
if (stopped)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// No longer need the read transaction
|
||||
read_transaction.reset ();
|
||||
|
||||
// If this adds no more open or receive blocks, then we can now confirm this account as well as the linked open/receive block
|
||||
// Collect as pending any writes to the database and do them in bulk after a certain time.
|
||||
auto confirmed_receives_pending = (count_before_receive != receive_source_pairs.size ());
|
||||
if (!confirmed_receives_pending)
|
||||
{
|
||||
preparation_data preparation_data{ block_height, confirmation_height, iterated_height, account_it, account, receive_details, already_traversed, current, block_callback_datas_required, orig_block_callback_data };
|
||||
prepare_iterated_blocks_for_cementing (preparation_data);
|
||||
|
||||
if (!receive_source_pairs.empty ())
|
||||
{
|
||||
// Pop from the end
|
||||
receive_source_pairs.erase (receive_source_pairs.end () - 1);
|
||||
}
|
||||
}
|
||||
else if (block_height > iterated_height)
|
||||
{
|
||||
if (account_it != confirmed_iterated_pairs.cend ())
|
||||
{
|
||||
account_it->second.iterated_height = block_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (account), std::forward_as_tuple (confirmation_height, block_height));
|
||||
++confirmed_iterated_pairs_size;
|
||||
}
|
||||
}
|
||||
|
||||
auto max_write_size_reached = (pending_writes.size () >= confirmation_height::unbounded_cutoff);
|
||||
// When there are a lot of pending confirmation height blocks, it is more efficient to
|
||||
// bulk some of them up to enable better write performance which becomes the bottleneck.
|
||||
auto min_time_exceeded = (timer.since_start () >= batch_separate_pending_min_time);
|
||||
auto finished_iterating = receive_source_pairs.empty ();
|
||||
auto no_pending = awaiting_processing_size_callback () == 0;
|
||||
auto should_output = finished_iterating && (no_pending || min_time_exceeded);
|
||||
|
||||
auto total_pending_write_block_count = std::accumulate (pending_writes.cbegin (), pending_writes.cend (), uint64_t (0), [] (uint64_t total, conf_height_details const & receive_details_a) {
|
||||
return total += receive_details_a.num_blocks_confirmed;
|
||||
});
|
||||
auto force_write = total_pending_write_block_count > batch_write_size;
|
||||
|
||||
if ((max_write_size_reached || should_output || force_write) && !pending_writes.empty ())
|
||||
{
|
||||
if (write_database_queue.process (nano::writer::confirmation_height))
|
||||
{
|
||||
auto scoped_write_guard = write_database_queue.pop ();
|
||||
cement_blocks (scoped_write_guard);
|
||||
}
|
||||
else if (force_write)
|
||||
{
|
||||
// Unbounded processor has grown too large, force a write
|
||||
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
|
||||
cement_blocks (scoped_write_guard);
|
||||
}
|
||||
}
|
||||
|
||||
first_iter = false;
|
||||
read_transaction.renew ();
|
||||
} while ((!receive_source_pairs.empty () || current != original_block->hash ()) && !stopped);
|
||||
}
|
||||
|
||||
void nano::confirmation_height_unbounded::collect_unconfirmed_receive_and_sources_for_account (uint64_t block_height_a, uint64_t confirmation_height_a, std::shared_ptr<nano::block> const & block_a, nano::block_hash const & hash_a, nano::account const & account_a, store::read_transaction const & transaction_a, std::vector<receive_source_pair> & receive_source_pairs_a, std::vector<nano::block_hash> & block_callback_data_a, std::vector<nano::block_hash> & orig_block_callback_data_a, std::shared_ptr<nano::block> original_block)
|
||||
{
|
||||
debug_assert (block_a->hash () == hash_a);
|
||||
auto hash (hash_a);
|
||||
auto num_to_confirm = block_height_a - confirmation_height_a;
|
||||
|
||||
// Handle any sends above a receive
|
||||
auto is_original_block = (hash == original_block->hash ());
|
||||
auto hit_receive = false;
|
||||
auto first_iter = true;
|
||||
while ((num_to_confirm > 0) && !hash.is_zero () && !stopped)
|
||||
{
|
||||
std::shared_ptr<nano::block> block;
|
||||
if (first_iter)
|
||||
{
|
||||
debug_assert (hash == hash_a);
|
||||
block = block_a;
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
block_cache[hash] = block_a;
|
||||
}
|
||||
else
|
||||
{
|
||||
block = get_block_and_sideband (hash, transaction_a);
|
||||
}
|
||||
|
||||
if (block)
|
||||
{
|
||||
if (block->is_receive () && ledger.block_exists (transaction_a, block->source ()))
|
||||
{
|
||||
if (!hit_receive && !block_callback_data_a.empty ())
|
||||
{
|
||||
// Add the callbacks to the associated receive to retrieve later
|
||||
debug_assert (!receive_source_pairs_a.empty ());
|
||||
auto & last_receive_details = receive_source_pairs_a.back ().receive_details;
|
||||
last_receive_details->source_block_callback_data.assign (block_callback_data_a.begin (), block_callback_data_a.end ());
|
||||
block_callback_data_a.clear ();
|
||||
}
|
||||
|
||||
is_original_block = false;
|
||||
hit_receive = true;
|
||||
|
||||
auto block_height = confirmation_height_a + num_to_confirm;
|
||||
receive_source_pairs_a.emplace_back (std::make_shared<conf_height_details> (account_a, hash, block_height, 1, std::vector<nano::block_hash>{ hash }), block->source ());
|
||||
}
|
||||
else if (is_original_block)
|
||||
{
|
||||
orig_block_callback_data_a.push_back (hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hit_receive)
|
||||
{
|
||||
// This block is cemented via a recieve, as opposed to below a receive being cemented
|
||||
block_callback_data_a.push_back (hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have hit a receive before, add the block to it
|
||||
auto & last_receive_details = receive_source_pairs_a.back ().receive_details;
|
||||
++last_receive_details->num_blocks_confirmed;
|
||||
last_receive_details->block_callback_data.push_back (hash);
|
||||
|
||||
implicit_receive_cemented_mapping[hash] = std::weak_ptr<conf_height_details> (last_receive_details);
|
||||
implicit_receive_cemented_mapping_size = implicit_receive_cemented_mapping.size ();
|
||||
}
|
||||
}
|
||||
|
||||
hash = block->previous ();
|
||||
}
|
||||
|
||||
--num_to_confirm;
|
||||
first_iter = false;
|
||||
}
|
||||
}
|
||||
|
||||
void nano::confirmation_height_unbounded::prepare_iterated_blocks_for_cementing (preparation_data & preparation_data_a)
|
||||
{
|
||||
auto receive_details = preparation_data_a.receive_details;
|
||||
auto block_height = preparation_data_a.block_height;
|
||||
if (block_height > preparation_data_a.confirmation_height)
|
||||
{
|
||||
// Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them
|
||||
if (preparation_data_a.account_it != confirmed_iterated_pairs.cend ())
|
||||
{
|
||||
preparation_data_a.account_it->second.confirmed_height = block_height;
|
||||
if (block_height > preparation_data_a.iterated_height)
|
||||
{
|
||||
preparation_data_a.account_it->second.iterated_height = block_height;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (preparation_data_a.account), std::forward_as_tuple (block_height, block_height));
|
||||
++confirmed_iterated_pairs_size;
|
||||
}
|
||||
|
||||
auto num_blocks_confirmed = block_height - preparation_data_a.confirmation_height;
|
||||
auto block_callback_data = preparation_data_a.block_callback_data;
|
||||
if (block_callback_data.empty ())
|
||||
{
|
||||
if (!receive_details)
|
||||
{
|
||||
block_callback_data = preparation_data_a.orig_block_callback_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_assert (receive_details);
|
||||
|
||||
if (preparation_data_a.already_traversed && receive_details->source_block_callback_data.empty ())
|
||||
{
|
||||
// We are confirming a block which has already been traversed and found no associated receive details for it.
|
||||
auto & above_receive_details_w = implicit_receive_cemented_mapping[preparation_data_a.current];
|
||||
debug_assert (!above_receive_details_w.expired ());
|
||||
auto above_receive_details = above_receive_details_w.lock ();
|
||||
|
||||
auto num_blocks_already_confirmed = above_receive_details->num_blocks_confirmed - (above_receive_details->height - preparation_data_a.confirmation_height);
|
||||
|
||||
auto end_it = above_receive_details->block_callback_data.begin () + above_receive_details->block_callback_data.size () - (num_blocks_already_confirmed);
|
||||
auto start_it = end_it - num_blocks_confirmed;
|
||||
|
||||
block_callback_data.assign (start_it, end_it);
|
||||
}
|
||||
else
|
||||
{
|
||||
block_callback_data = receive_details->source_block_callback_data;
|
||||
}
|
||||
|
||||
auto num_to_remove = block_callback_data.size () - num_blocks_confirmed;
|
||||
block_callback_data.erase (std::next (block_callback_data.rbegin (), num_to_remove).base (), block_callback_data.end ());
|
||||
receive_details->source_block_callback_data.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
pending_writes.emplace_back (preparation_data_a.account, preparation_data_a.current, block_height, num_blocks_confirmed, block_callback_data);
|
||||
++pending_writes_size;
|
||||
}
|
||||
|
||||
if (receive_details)
|
||||
{
|
||||
// Check whether the previous block has been seen. If so, the rest of sends below have already been seen so don't count them
|
||||
auto const & receive_account = receive_details->account;
|
||||
auto receive_account_it = confirmed_iterated_pairs.find (receive_account);
|
||||
if (receive_account_it != confirmed_iterated_pairs.cend ())
|
||||
{
|
||||
// Get current height
|
||||
auto current_height = receive_account_it->second.confirmed_height;
|
||||
receive_account_it->second.confirmed_height = receive_details->height;
|
||||
auto const orig_num_blocks_confirmed = receive_details->num_blocks_confirmed;
|
||||
receive_details->num_blocks_confirmed = receive_details->height - current_height;
|
||||
|
||||
// Get the difference and remove the callbacks
|
||||
auto block_callbacks_to_remove = orig_num_blocks_confirmed - receive_details->num_blocks_confirmed;
|
||||
receive_details->block_callback_data.erase (std::next (receive_details->block_callback_data.rbegin (), block_callbacks_to_remove).base (), receive_details->block_callback_data.end ());
|
||||
debug_assert (receive_details->block_callback_data.size () == receive_details->num_blocks_confirmed);
|
||||
}
|
||||
else
|
||||
{
|
||||
confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (receive_account), std::forward_as_tuple (receive_details->height, receive_details->height));
|
||||
++confirmed_iterated_pairs_size;
|
||||
}
|
||||
|
||||
pending_writes.push_back (*receive_details);
|
||||
++pending_writes_size;
|
||||
}
|
||||
}
|
||||
|
||||
void nano::confirmation_height_unbounded::cement_blocks (nano::write_guard & scoped_write_guard_a)
|
||||
{
|
||||
nano::timer<std::chrono::milliseconds> cemented_batch_timer;
|
||||
std::vector<std::shared_ptr<nano::block>> cemented_blocks;
|
||||
auto error = false;
|
||||
{
|
||||
auto transaction (ledger.store.tx_begin_write ({}, { nano::tables::confirmation_height }));
|
||||
cemented_batch_timer.start ();
|
||||
while (!pending_writes.empty ())
|
||||
{
|
||||
auto & pending = pending_writes.front ();
|
||||
nano::confirmation_height_info confirmation_height_info;
|
||||
ledger.store.confirmation_height.get (transaction, pending.account, confirmation_height_info);
|
||||
auto confirmation_height = confirmation_height_info.height;
|
||||
if (pending.height > confirmation_height)
|
||||
{
|
||||
auto block = ledger.block (transaction, pending.hash);
|
||||
debug_assert (ledger.pruning || block != nullptr);
|
||||
debug_assert (ledger.pruning || block->sideband ().height == pending.height);
|
||||
|
||||
if (!block)
|
||||
{
|
||||
if (ledger.pruning && ledger.store.pruned.exists (transaction, pending.hash))
|
||||
{
|
||||
pending_writes.erase (pending_writes.begin ());
|
||||
--pending_writes_size;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.critical (nano::log::type::conf_processor_unbounded, "Failed to write confirmation height for block {} (unbounded processor)", pending.hash.to_string ());
|
||||
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in, pending.height - confirmation_height);
|
||||
ledger.stats.add (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in, pending.height - confirmation_height);
|
||||
debug_assert (pending.num_blocks_confirmed == pending.height - confirmation_height);
|
||||
confirmation_height = pending.height;
|
||||
ledger.cache.cemented_count += pending.num_blocks_confirmed;
|
||||
ledger.store.confirmation_height.put (transaction, pending.account, { confirmation_height, pending.hash });
|
||||
|
||||
// Reverse it so that the callbacks start from the lowest newly cemented block and move upwards
|
||||
std::reverse (pending.block_callback_data.begin (), pending.block_callback_data.end ());
|
||||
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
std::transform (pending.block_callback_data.begin (), pending.block_callback_data.end (), std::back_inserter (cemented_blocks), [&block_cache = block_cache] (auto const & hash_a) {
|
||||
debug_assert (block_cache.count (hash_a) == 1);
|
||||
return block_cache.at (hash_a);
|
||||
});
|
||||
}
|
||||
pending_writes.erase (pending_writes.begin ());
|
||||
--pending_writes_size;
|
||||
}
|
||||
}
|
||||
|
||||
auto time_spent_cementing = cemented_batch_timer.since_start ().count ();
|
||||
|
||||
scoped_write_guard_a.release ();
|
||||
notify_observers_callback (cemented_blocks);
|
||||
release_assert (!error);
|
||||
|
||||
debug_assert (pending_writes.empty ());
|
||||
debug_assert (pending_writes_size == 0);
|
||||
timer.restart ();
|
||||
}
|
||||
|
||||
std::shared_ptr<nano::block> nano::confirmation_height_unbounded::get_block_and_sideband (nano::block_hash const & hash_a, store::transaction const & transaction_a)
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
auto block_cache_it = block_cache.find (hash_a);
|
||||
if (block_cache_it != block_cache.cend ())
|
||||
{
|
||||
return block_cache_it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto block = ledger.block (transaction_a, hash_a);
|
||||
block_cache.emplace (hash_a, block);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_unbounded::pending_empty () const
|
||||
{
|
||||
return pending_writes.empty ();
|
||||
}
|
||||
|
||||
void nano::confirmation_height_unbounded::clear_process_vars ()
|
||||
{
|
||||
// Separate blocks which are pending confirmation height can be batched by a minimum processing time (to improve lmdb disk write performance),
|
||||
// so make sure the slate is clean when a new batch is starting.
|
||||
confirmed_iterated_pairs.clear ();
|
||||
confirmed_iterated_pairs_size = 0;
|
||||
implicit_receive_cemented_mapping.clear ();
|
||||
implicit_receive_cemented_mapping_size = 0;
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
block_cache.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::confirmation_height_unbounded::has_iterated_over_block (nano::block_hash const & hash_a) const
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
return block_cache.count (hash_a) == 1;
|
||||
}
|
||||
|
||||
uint64_t nano::confirmation_height_unbounded::block_cache_size () const
|
||||
{
|
||||
nano::lock_guard<nano::mutex> guard (block_cache_mutex);
|
||||
return block_cache.size ();
|
||||
}
|
||||
|
||||
nano::confirmation_height_unbounded::conf_height_details::conf_height_details (nano::account const & account_a, nano::block_hash const & hash_a, uint64_t height_a, uint64_t num_blocks_confirmed_a, std::vector<nano::block_hash> const & block_callback_data_a) :
|
||||
account (account_a),
|
||||
hash (hash_a),
|
||||
height (height_a),
|
||||
num_blocks_confirmed (num_blocks_confirmed_a),
|
||||
block_callback_data (block_callback_data_a)
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_unbounded::receive_source_pair::receive_source_pair (std::shared_ptr<conf_height_details> const & receive_details_a, const block_hash & source_a) :
|
||||
receive_details (receive_details_a),
|
||||
source_hash (source_a)
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirmation_height_unbounded::confirmed_iterated_pair::confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a) :
|
||||
confirmed_height (confirmed_height_a),
|
||||
iterated_height (iterated_height_a)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<nano::container_info_component> nano::collect_container_info (confirmation_height_unbounded & confirmation_height_unbounded, std::string const & name_a)
|
||||
{
|
||||
auto composite = std::make_unique<container_info_composite> (name_a);
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "confirmed_iterated_pairs", confirmation_height_unbounded.confirmed_iterated_pairs_size, sizeof (decltype (confirmation_height_unbounded.confirmed_iterated_pairs)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "pending_writes", confirmation_height_unbounded.pending_writes_size, sizeof (decltype (confirmation_height_unbounded.pending_writes)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "implicit_receive_cemented_mapping", confirmation_height_unbounded.implicit_receive_cemented_mapping_size, sizeof (decltype (confirmation_height_unbounded.implicit_receive_cemented_mapping)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "block_cache", confirmation_height_unbounded.block_cache_size (), sizeof (decltype (confirmation_height_unbounded.block_cache)::value_type) }));
|
||||
return composite;
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/relaxed_atomic.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/timer.hpp>
|
||||
#include <nano/store/component.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class ledger;
|
||||
class write_database_queue;
|
||||
class write_guard;
|
||||
|
||||
class confirmation_height_unbounded final
|
||||
{
|
||||
public:
|
||||
confirmation_height_unbounded (nano::ledger &, nano::write_database_queue &, std::chrono::milliseconds batch_separate_pending_min_time, nano::logger &, std::atomic<bool> & stopped, uint64_t & batch_write_size, std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> const & cemented_callback, std::function<void (nano::block_hash const &)> const & already_cemented_callback, std::function<uint64_t ()> const & awaiting_processing_size_query);
|
||||
|
||||
bool pending_empty () const;
|
||||
void clear_process_vars ();
|
||||
void process (std::shared_ptr<nano::block> original_block);
|
||||
void cement_blocks (nano::write_guard &);
|
||||
bool has_iterated_over_block (nano::block_hash const &) const;
|
||||
|
||||
private:
|
||||
class confirmed_iterated_pair
|
||||
{
|
||||
public:
|
||||
confirmed_iterated_pair (uint64_t confirmed_height_a, uint64_t iterated_height_a);
|
||||
uint64_t confirmed_height;
|
||||
uint64_t iterated_height;
|
||||
};
|
||||
|
||||
class conf_height_details final
|
||||
{
|
||||
public:
|
||||
conf_height_details (nano::account const &, nano::block_hash const &, uint64_t, uint64_t, std::vector<nano::block_hash> const &);
|
||||
|
||||
nano::account account;
|
||||
nano::block_hash hash;
|
||||
uint64_t height;
|
||||
uint64_t num_blocks_confirmed;
|
||||
std::vector<nano::block_hash> block_callback_data;
|
||||
std::vector<nano::block_hash> source_block_callback_data;
|
||||
};
|
||||
|
||||
class receive_source_pair final
|
||||
{
|
||||
public:
|
||||
receive_source_pair (std::shared_ptr<conf_height_details> const &, nano::block_hash const &);
|
||||
|
||||
std::shared_ptr<conf_height_details> receive_details;
|
||||
nano::block_hash source_hash;
|
||||
};
|
||||
|
||||
// All of the atomic variables here just track the size for use in collect_container_info.
|
||||
// This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed
|
||||
// for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted
|
||||
// upon in any way (does not synchronize with any other data).
|
||||
// This allows the load and stores to use relaxed atomic memory ordering.
|
||||
std::unordered_map<account, confirmed_iterated_pair> confirmed_iterated_pairs;
|
||||
nano::relaxed_atomic_integral<uint64_t> confirmed_iterated_pairs_size{ 0 };
|
||||
std::shared_ptr<nano::block> get_block_and_sideband (nano::block_hash const &, store::transaction const &);
|
||||
std::deque<conf_height_details> pending_writes;
|
||||
nano::relaxed_atomic_integral<uint64_t> pending_writes_size{ 0 };
|
||||
std::unordered_map<nano::block_hash, std::weak_ptr<conf_height_details>> implicit_receive_cemented_mapping;
|
||||
nano::relaxed_atomic_integral<uint64_t> implicit_receive_cemented_mapping_size{ 0 };
|
||||
|
||||
mutable nano::mutex block_cache_mutex;
|
||||
std::unordered_map<nano::block_hash, std::shared_ptr<nano::block>> block_cache;
|
||||
uint64_t block_cache_size () const;
|
||||
|
||||
nano::timer<std::chrono::milliseconds> timer;
|
||||
|
||||
class preparation_data final
|
||||
{
|
||||
public:
|
||||
uint64_t block_height;
|
||||
uint64_t confirmation_height;
|
||||
uint64_t iterated_height;
|
||||
decltype (confirmed_iterated_pairs.begin ()) account_it;
|
||||
nano::account const & account;
|
||||
std::shared_ptr<conf_height_details> receive_details;
|
||||
bool already_traversed;
|
||||
nano::block_hash const & current;
|
||||
std::vector<nano::block_hash> const & block_callback_data;
|
||||
std::vector<nano::block_hash> const & orig_block_callback_data;
|
||||
};
|
||||
|
||||
void collect_unconfirmed_receive_and_sources_for_account (uint64_t, uint64_t, std::shared_ptr<nano::block> const &, nano::block_hash const &, nano::account const &, store::read_transaction const &, std::vector<receive_source_pair> &, std::vector<nano::block_hash> &, std::vector<nano::block_hash> &, std::shared_ptr<nano::block> original_block);
|
||||
void prepare_iterated_blocks_for_cementing (preparation_data &);
|
||||
|
||||
nano::ledger & ledger;
|
||||
nano::write_database_queue & write_database_queue;
|
||||
std::chrono::milliseconds batch_separate_pending_min_time;
|
||||
nano::logger & logger;
|
||||
std::atomic<bool> & stopped;
|
||||
uint64_t & batch_write_size;
|
||||
|
||||
std::function<void (std::vector<std::shared_ptr<nano::block>> const &)> notify_observers_callback;
|
||||
std::function<void (nano::block_hash const &)> notify_block_already_cemented_observers_callback;
|
||||
std::function<uint64_t ()> awaiting_processing_size_callback;
|
||||
|
||||
friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test;
|
||||
friend std::unique_ptr<nano::container_info_component> collect_container_info (confirmation_height_unbounded &, std::string const & name_a);
|
||||
};
|
||||
|
||||
std::unique_ptr<nano::container_info_component> collect_container_info (confirmation_height_unbounded &, std::string const & name_a);
|
||||
}
|
||||
120
nano/node/confirming_set.cpp
Normal file
120
nano/node/confirming_set.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#include <nano/lib/thread_roles.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/write_database_queue.hpp>
|
||||
#include <nano/secure/ledger.hpp>
|
||||
#include <nano/store/component.hpp>
|
||||
|
||||
nano::confirming_set::confirming_set (nano::ledger & ledger, nano::write_database_queue & write_queue, std::chrono::milliseconds batch_time) :
|
||||
ledger{ ledger },
|
||||
write_queue{ write_queue },
|
||||
batch_time{ batch_time }
|
||||
{
|
||||
}
|
||||
|
||||
nano::confirming_set::~confirming_set ()
|
||||
{
|
||||
debug_assert (!thread.joinable ());
|
||||
}
|
||||
|
||||
void nano::confirming_set::add (nano::block_hash const & hash)
|
||||
{
|
||||
std::lock_guard lock{ mutex };
|
||||
set.insert (hash);
|
||||
condition.notify_all ();
|
||||
}
|
||||
|
||||
void nano::confirming_set::start ()
|
||||
{
|
||||
thread = std::thread{ [this] () { run (); } };
|
||||
}
|
||||
|
||||
void nano::confirming_set::stop ()
|
||||
{
|
||||
{
|
||||
std::lock_guard lock{ mutex };
|
||||
stopped = true;
|
||||
condition.notify_all ();
|
||||
}
|
||||
if (thread.joinable ())
|
||||
{
|
||||
thread.join ();
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::confirming_set::exists (nano::block_hash const & hash) const
|
||||
{
|
||||
std::lock_guard lock{ mutex };
|
||||
return set.count (hash) != 0 || processing.count (hash) != 0;
|
||||
}
|
||||
|
||||
std::size_t nano::confirming_set::size () const
|
||||
{
|
||||
std::lock_guard lock{ mutex };
|
||||
return set.size () + processing.size ();
|
||||
}
|
||||
|
||||
void nano::confirming_set::run ()
|
||||
{
|
||||
nano::thread_role::set (nano::thread_role::name::confirmation_height_processing);
|
||||
std::unique_lock lock{ mutex };
|
||||
// Run the confirmation loop until stopped
|
||||
while (!stopped)
|
||||
{
|
||||
condition.wait (lock, [&] () { return !set.empty () || stopped; });
|
||||
// Loop if there are items to process
|
||||
if (!stopped && !set.empty ())
|
||||
{
|
||||
std::deque<std::shared_ptr<nano::block>> cemented;
|
||||
std::deque<nano::block_hash> already;
|
||||
// Move items in to back buffer and release lock so more items can be added to the front buffer
|
||||
processing = std::move (this->set);
|
||||
// Process all items in the back buffer
|
||||
for (auto i = processing.begin (), n = processing.end (); !stopped && i != n;)
|
||||
{
|
||||
lock.unlock (); // Waiting for db write is potentially slow
|
||||
auto guard = write_queue.wait (nano::writer::confirmation_height);
|
||||
auto tx = ledger.store.tx_begin_write ({ nano::tables::confirmation_height });
|
||||
lock.lock ();
|
||||
// Process items in the back buffer within a single transaction for a limited amount of time
|
||||
for (auto timeout = std::chrono::steady_clock::now () + batch_time; !stopped && std::chrono::steady_clock::now () < timeout && i != n; ++i)
|
||||
{
|
||||
auto item = *i;
|
||||
lock.unlock ();
|
||||
auto added = ledger.confirm (tx, item);
|
||||
if (!added.empty ())
|
||||
{
|
||||
// Confirming this block may implicitly confirm more
|
||||
cemented.insert (cemented.end (), added.begin (), added.end ());
|
||||
}
|
||||
else
|
||||
{
|
||||
already.push_back (item);
|
||||
}
|
||||
lock.lock ();
|
||||
}
|
||||
}
|
||||
lock.unlock ();
|
||||
for (auto const & i : cemented)
|
||||
{
|
||||
cemented_observers.notify (i);
|
||||
}
|
||||
for (auto const & i : already)
|
||||
{
|
||||
block_already_cemented_observers.notify (i);
|
||||
}
|
||||
lock.lock ();
|
||||
// Clear and free back buffer by re-initializing
|
||||
processing = decltype (processing){};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<nano::container_info_component> nano::confirming_set::collect_container_info (std::string const & name) const
|
||||
{
|
||||
std::lock_guard guard{ mutex };
|
||||
|
||||
auto composite = std::make_unique<container_info_composite> (name);
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "set", set.size (), sizeof (typename decltype (set)::value_type) }));
|
||||
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "processing", processing.size (), sizeof (typename decltype (processing)::value_type) }));
|
||||
return composite;
|
||||
}
|
||||
57
nano/node/confirming_set.hpp
Normal file
57
nano/node/confirming_set.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/observer_set.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class block;
|
||||
class ledger;
|
||||
class write_database_queue;
|
||||
}
|
||||
|
||||
namespace nano
|
||||
{
|
||||
/**
|
||||
* Set of blocks to be durably confirmed
|
||||
*/
|
||||
class confirming_set final
|
||||
{
|
||||
friend class confirmation_heightDeathTest_missing_block_Test;
|
||||
friend class confirmation_height_pruned_source_Test;
|
||||
|
||||
public:
|
||||
confirming_set (nano::ledger & ledger, nano::write_database_queue & write_queue, std::chrono::milliseconds batch_time = std::chrono::milliseconds{ 500 });
|
||||
~confirming_set ();
|
||||
// Adds a block to the set of blocks to be confirmed
|
||||
void add (nano::block_hash const & hash);
|
||||
void start ();
|
||||
void stop ();
|
||||
// Added blocks will remain in this set until after ledger has them marked as confirmed.
|
||||
bool exists (nano::block_hash const & hash) const;
|
||||
std::size_t size () const;
|
||||
std::unique_ptr<container_info_component> collect_container_info (std::string const & name) const;
|
||||
|
||||
// Observers will be called once ledger has blocks marked as confirmed
|
||||
nano::observer_set<std::shared_ptr<nano::block>> cemented_observers;
|
||||
nano::observer_set<nano::block_hash const &> block_already_cemented_observers;
|
||||
|
||||
private:
|
||||
void run ();
|
||||
nano::ledger & ledger;
|
||||
nano::write_database_queue & write_queue;
|
||||
std::chrono::milliseconds batch_time;
|
||||
std::unordered_set<nano::block_hash> set;
|
||||
std::unordered_set<nano::block_hash> processing;
|
||||
bool stopped{ false };
|
||||
mutable std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
std::thread thread;
|
||||
};
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include <nano/node/bootstrap/bootstrap_lazy.hpp>
|
||||
#include <nano/node/bootstrap_ascending/service.hpp>
|
||||
#include <nano/node/common.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/json_handler.hpp>
|
||||
#include <nano/node/node.hpp>
|
||||
|
|
@ -1205,7 +1206,7 @@ void nano::json_handler::block_confirm ()
|
|||
if (!node.ledger.block_confirmed (transaction, hash))
|
||||
{
|
||||
// Start new confirmation for unconfirmed (or not being confirmed) block
|
||||
if (!node.confirmation_height_processor.is_processing_block (hash))
|
||||
if (!node.confirming_set.exists (hash))
|
||||
{
|
||||
node.start_election (std::move (block_l));
|
||||
}
|
||||
|
|
@ -2001,20 +2002,6 @@ void nano::json_handler::confirmation_active ()
|
|||
response_errors ();
|
||||
}
|
||||
|
||||
void nano::json_handler::confirmation_height_currently_processing ()
|
||||
{
|
||||
auto hash = node.confirmation_height_processor.current ();
|
||||
if (!hash.is_zero ())
|
||||
{
|
||||
response_l.put ("hash", hash.to_string ());
|
||||
}
|
||||
else
|
||||
{
|
||||
ec = nano::error_rpc::confirmation_height_not_processing;
|
||||
}
|
||||
response_errors ();
|
||||
}
|
||||
|
||||
void nano::json_handler::confirmation_history ()
|
||||
{
|
||||
boost::property_tree::ptree elections;
|
||||
|
|
@ -5333,7 +5320,6 @@ ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map ()
|
|||
no_arg_funcs.emplace ("bootstrap_lazy", &nano::json_handler::bootstrap_lazy);
|
||||
no_arg_funcs.emplace ("bootstrap_status", &nano::json_handler::bootstrap_status);
|
||||
no_arg_funcs.emplace ("confirmation_active", &nano::json_handler::confirmation_active);
|
||||
no_arg_funcs.emplace ("confirmation_height_currently_processing", &nano::json_handler::confirmation_height_currently_processing);
|
||||
no_arg_funcs.emplace ("confirmation_history", &nano::json_handler::confirmation_history);
|
||||
no_arg_funcs.emplace ("confirmation_info", &nano::json_handler::confirmation_info);
|
||||
no_arg_funcs.emplace ("confirmation_quorum", &nano::json_handler::confirmation_quorum);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ public:
|
|||
void confirmation_history ();
|
||||
void confirmation_info ();
|
||||
void confirmation_quorum ();
|
||||
void confirmation_height_currently_processing ();
|
||||
void debug_bootstrap_priority_info ();
|
||||
void database_txn_tracker ();
|
||||
void delegators ();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/common.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/daemonconfig.hpp>
|
||||
#include <nano/node/election_status.hpp>
|
||||
#include <nano/node/local_vote_history.hpp>
|
||||
|
|
@ -171,8 +172,9 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
|
|||
application_path (application_path_a),
|
||||
port_mapping (*this),
|
||||
block_processor (*this, write_database_queue),
|
||||
confirmation_height_processor (ledger, write_database_queue, config.conf_height_processor_batch_min_time, logger, node_initialized_latch, flags.confirmation_height_processor_mode),
|
||||
active_impl{ std::make_unique<nano::active_transactions> (*this, confirmation_height_processor, block_processor) },
|
||||
confirming_set_impl{ std::make_unique<nano::confirming_set> (ledger, write_database_queue, config.confirming_set_batch_time) },
|
||||
confirming_set{ *confirming_set_impl },
|
||||
active_impl{ std::make_unique<nano::active_transactions> (*this, confirming_set, block_processor) },
|
||||
active{ *active_impl },
|
||||
rep_crawler (config.rep_crawler, *this),
|
||||
rep_tiers{ ledger, network_params, online_reps, stats, logger },
|
||||
|
|
@ -464,7 +466,7 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
|
|||
std::exit (1);
|
||||
}
|
||||
}
|
||||
confirmation_height_processor.add_cemented_observer ([this] (auto const & block) {
|
||||
confirming_set.cemented_observers.add ([this] (auto const & block) {
|
||||
if (block->is_send ())
|
||||
{
|
||||
auto transaction = store.tx_begin_read ();
|
||||
|
|
@ -568,7 +570,7 @@ std::unique_ptr<nano::container_info_component> nano::collect_container_info (no
|
|||
composite->add_component (node.history.collect_container_info ("history"));
|
||||
composite->add_component (node.block_uniquer.collect_container_info ("block_uniquer"));
|
||||
composite->add_component (node.vote_uniquer.collect_container_info ("vote_uniquer"));
|
||||
composite->add_component (collect_container_info (node.confirmation_height_processor, "confirmation_height_processor"));
|
||||
composite->add_component (node.confirming_set.collect_container_info ("confirming_set"));
|
||||
composite->add_component (collect_container_info (node.distributed_work, "distributed_work"));
|
||||
composite->add_component (collect_container_info (node.aggregator, "request_aggregator"));
|
||||
composite->add_component (node.scheduler.collect_container_info ("election_scheduler"));
|
||||
|
|
@ -679,6 +681,7 @@ void nano::node::start ()
|
|||
active.start ();
|
||||
generator.start ();
|
||||
final_generator.start ();
|
||||
confirming_set.start ();
|
||||
scheduler.start ();
|
||||
backlog.start ();
|
||||
bootstrap_server.start ();
|
||||
|
|
@ -719,7 +722,7 @@ void nano::node::stop ()
|
|||
active.stop ();
|
||||
generator.stop ();
|
||||
final_generator.stop ();
|
||||
confirmation_height_processor.stop ();
|
||||
confirming_set.stop ();
|
||||
telemetry.stop ();
|
||||
websocket.stop ();
|
||||
bootstrap_server.stop ();
|
||||
|
|
@ -1185,7 +1188,7 @@ bool nano::node::block_confirmed (nano::block_hash const & hash_a)
|
|||
|
||||
bool nano::node::block_confirmed_or_being_confirmed (nano::store::transaction const & transaction, nano::block_hash const & hash_a)
|
||||
{
|
||||
return confirmation_height_processor.is_processing_block (hash_a) || ledger.block_confirmed (transaction, hash_a);
|
||||
return confirming_set.exists (hash_a) || ledger.block_confirmed (transaction, hash_a);
|
||||
}
|
||||
|
||||
bool nano::node::block_confirmed_or_being_confirmed (nano::block_hash const & hash_a)
|
||||
|
|
@ -1257,7 +1260,7 @@ void nano::node::process_confirmed (nano::election_status const & status_a, uint
|
|||
{
|
||||
logger.trace (nano::log::type::node, nano::log::detail::process_confirmed, nano::log::arg{ "block", block_l });
|
||||
|
||||
confirmation_height_processor.add (block_l);
|
||||
confirming_set.add (block_l->hash ());
|
||||
}
|
||||
else if (iteration_a < num_iters)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
#include <nano/node/bootstrap/bootstrap_attempt.hpp>
|
||||
#include <nano/node/bootstrap/bootstrap_server.hpp>
|
||||
#include <nano/node/bootstrap_ascending/service.hpp>
|
||||
#include <nano/node/confirmation_height_processor.hpp>
|
||||
#include <nano/node/distributed_work_factory.hpp>
|
||||
#include <nano/node/epoch_upgrader.hpp>
|
||||
#include <nano/node/local_block_broadcaster.hpp>
|
||||
|
|
@ -47,6 +46,7 @@
|
|||
namespace nano
|
||||
{
|
||||
class active_transactions;
|
||||
class confirming_set;
|
||||
class node;
|
||||
class work_pool;
|
||||
|
||||
|
|
@ -168,7 +168,8 @@ public:
|
|||
nano::node_observers observers;
|
||||
nano::port_mapping port_mapping;
|
||||
nano::block_processor block_processor;
|
||||
nano::confirmation_height_processor confirmation_height_processor;
|
||||
std::unique_ptr<nano::confirming_set> confirming_set_impl;
|
||||
nano::confirming_set & confirming_set;
|
||||
std::unique_ptr<nano::active_transactions> active_impl;
|
||||
nano::active_transactions & active;
|
||||
nano::online_reps online_reps;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const
|
|||
toml.put ("bootstrap_bandwidth_limit", bootstrap_bandwidth_limit, "Outbound bootstrap 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");
|
||||
toml.put ("bootstrap_bandwidth_burst_ratio", bootstrap_bandwidth_burst_ratio, "Burst ratio for outbound bootstrap traffic.\ntype:double");
|
||||
|
||||
toml.put ("conf_height_processor_batch_min_time", conf_height_processor_batch_min_time.count (), "Minimum write batching time when there are blocks pending confirmation height.\ntype:milliseconds");
|
||||
toml.put ("confirming_set_batch_time", confirming_set_batch_time.count (), "Maximum time the confirming set will hold the database write transaction.\ntype:milliseconds");
|
||||
toml.put ("backup_before_upgrade", backup_before_upgrade, "Backup the ledger database before performing upgrades.\nWarning: uses more disk storage and increases startup time when upgrading.\ntype:bool");
|
||||
toml.put ("max_work_generate_multiplier", max_work_generate_multiplier, "Maximum allowed difficulty multiplier for work generation.\ntype:double,[1..]");
|
||||
toml.put ("frontiers_confirmation", serialize_frontiers_confirmation (frontiers_confirmation), "Mode controlling frontier confirmation rate.\ntype:string,{auto,always,disabled}");
|
||||
|
|
@ -432,9 +432,9 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml)
|
|||
|
||||
toml.get<bool> ("backup_before_upgrade", backup_before_upgrade);
|
||||
|
||||
auto conf_height_processor_batch_min_time_l (conf_height_processor_batch_min_time.count ());
|
||||
toml.get ("conf_height_processor_batch_min_time", conf_height_processor_batch_min_time_l);
|
||||
conf_height_processor_batch_min_time = std::chrono::milliseconds (conf_height_processor_batch_min_time_l);
|
||||
auto confirming_set_batch_time_l (confirming_set_batch_time.count ());
|
||||
toml.get ("confirming_set_batch_time", confirming_set_batch_time_l);
|
||||
confirming_set_batch_time = std::chrono::milliseconds (confirming_set_batch_time_l);
|
||||
|
||||
toml.get<double> ("max_work_generate_multiplier", max_work_generate_multiplier);
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public:
|
|||
/** Bootstrap traffic does not need bursts */
|
||||
double bootstrap_bandwidth_burst_ratio{ 1. };
|
||||
nano::bootstrap_ascending_config bootstrap_ascending;
|
||||
std::chrono::milliseconds conf_height_processor_batch_min_time{ 50 };
|
||||
std::chrono::milliseconds confirming_set_batch_time{ 250 };
|
||||
bool backup_before_upgrade{ false };
|
||||
double max_work_generate_multiplier{ 64. };
|
||||
uint32_t max_queued_requests{ 512 };
|
||||
|
|
@ -175,7 +175,6 @@ public:
|
|||
bool fast_bootstrap{ false };
|
||||
bool read_only{ false };
|
||||
bool disable_connection_cleanup{ false };
|
||||
nano::confirmation_height_mode confirmation_height_processor_mode{ nano::confirmation_height_mode::automatic };
|
||||
nano::generate_cache_flags generate_cache;
|
||||
bool inactive_node{ false };
|
||||
std::size_t block_processor_batch_size{ 0 };
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/node.hpp>
|
||||
#include <nano/node/wallet.hpp>
|
||||
|
|
@ -1197,7 +1198,7 @@ bool nano::wallet::search_receivable (store::transaction const & wallet_transact
|
|||
// Receive confirmed block
|
||||
receive_async (hash, representative, amount, account, [] (std::shared_ptr<nano::block> const &) {});
|
||||
}
|
||||
else if (!wallets.node.confirmation_height_processor.is_processing_block (hash))
|
||||
else if (!wallets.node.confirming_set.exists (hash))
|
||||
{
|
||||
auto block = wallets.node.ledger.block (block_transaction, hash);
|
||||
if (block)
|
||||
|
|
|
|||
|
|
@ -152,7 +152,6 @@ std::unordered_set<std::string> create_rpc_control_impls ()
|
|||
set.emplace ("backoff_info");
|
||||
set.emplace ("block_create");
|
||||
set.emplace ("bootstrap_lazy");
|
||||
set.emplace ("confirmation_height_currently_processing");
|
||||
set.emplace ("database_txn_tracker");
|
||||
set.emplace ("epoch_upgrade");
|
||||
set.emplace ("keepalive");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <nano/lib/thread_runner.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/ipc/ipc_server.hpp>
|
||||
#include <nano/node/json_handler.hpp>
|
||||
|
|
@ -5201,72 +5202,6 @@ TEST (rpc, online_reps)
|
|||
node2->stop ();
|
||||
}
|
||||
|
||||
TEST (rpc, confirmation_height_currently_processing)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.force_use_write_database_queue = true;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
|
||||
auto node = add_ipc_enabled_node (system, node_config, node_flags);
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
auto previous_genesis_chain_hash = node->latest (nano::dev::genesis_key.pub);
|
||||
{
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
nano::keypair key1;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.send ()
|
||||
.previous (previous_genesis_chain_hash)
|
||||
.destination (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio - 1)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (previous_genesis_chain_hash))
|
||||
.build ();
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
previous_genesis_chain_hash = send->hash ();
|
||||
}
|
||||
|
||||
std::shared_ptr<nano::block> frontier;
|
||||
{
|
||||
auto transaction = node->store.tx_begin_read ();
|
||||
frontier = node->ledger.block (transaction, previous_genesis_chain_hash);
|
||||
}
|
||||
|
||||
boost::property_tree::ptree request;
|
||||
request.put ("action", "confirmation_height_currently_processing");
|
||||
|
||||
auto const rpc_ctx = add_rpc (system, node);
|
||||
|
||||
// Begin process for confirming the block (and setting confirmation height)
|
||||
{
|
||||
// Write guard prevents the confirmation height processor writing the blocks, so that we can inspect contents during the response
|
||||
auto write_guard = node->write_database_queue.wait (nano::writer::testing);
|
||||
nano::test::start_election (system, *node, frontier->hash ());
|
||||
|
||||
ASSERT_TIMELY_EQ (5s, node->confirmation_height_processor.current (), frontier->hash ());
|
||||
|
||||
// Make the request
|
||||
{
|
||||
auto response (wait_response (system, rpc_ctx, request, 10s));
|
||||
auto hash (response.get<std::string> ("hash"));
|
||||
ASSERT_EQ (frontier->hash ().to_string (), hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until confirmation has been set and not processing anything
|
||||
ASSERT_TIMELY (10s, node->confirmation_height_processor.current ().is_zero () && node->confirmation_height_processor.awaiting_processing_size () == 0);
|
||||
|
||||
// Make the same request, it should now return an error
|
||||
{
|
||||
auto response (wait_response (system, rpc_ctx, request, 10s));
|
||||
std::error_code ec (nano::error_rpc::confirmation_height_not_processing);
|
||||
ASSERT_EQ (response.get<std::string> ("error"), ec.message ());
|
||||
}
|
||||
}
|
||||
|
||||
TEST (rpc, confirmation_history)
|
||||
{
|
||||
nano::test::system system;
|
||||
|
|
@ -5885,7 +5820,7 @@ TEST (rpc, block_confirmed)
|
|||
ASSERT_TRUE (nano::test::start_elections (system, *node, { send }, true));
|
||||
|
||||
// Wait until the confirmation height has been set
|
||||
ASSERT_TIMELY (5s, node->ledger.block_confirmed (node->store.tx_begin_read (), send->hash ()) && !node->confirmation_height_processor.is_processing_block (send->hash ()));
|
||||
ASSERT_TIMELY (5s, node->ledger.block_confirmed (node->store.tx_begin_read (), send->hash ()) && !node->confirming_set.exists (send->hash ()));
|
||||
|
||||
// Requesting confirmation for this should now succeed
|
||||
request.put ("hash", send->hash ().to_string ());
|
||||
|
|
|
|||
|
|
@ -341,12 +341,5 @@ public:
|
|||
nano::bootstrap_constants bootstrap;
|
||||
};
|
||||
|
||||
enum class confirmation_height_mode
|
||||
{
|
||||
automatic,
|
||||
unbounded,
|
||||
bounded
|
||||
};
|
||||
|
||||
nano::wallet_id random_wallet_id ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
#include <nano/store/rep_weight.hpp>
|
||||
#include <nano/store/version.hpp>
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include <cryptopp/words.h>
|
||||
|
||||
namespace
|
||||
|
|
@ -875,6 +877,50 @@ std::optional<nano::pending_info> nano::ledger::pending_info (store::transaction
|
|||
return store.pending.get (transaction, key);
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<nano::block>> nano::ledger::confirm (nano::store::write_transaction const & transaction, nano::block_hash const & hash)
|
||||
{
|
||||
std::deque<std::shared_ptr<nano::block>> result;
|
||||
std::stack<nano::block_hash> stack;
|
||||
stack.push (hash);
|
||||
while (!stack.empty ())
|
||||
{
|
||||
auto hash = stack.top ();
|
||||
auto block = this->block (transaction, hash);
|
||||
release_assert (block);
|
||||
auto dependents = dependent_blocks (transaction, *block);
|
||||
for (auto const & dependent : dependents)
|
||||
{
|
||||
if (!dependent.is_zero () && !block_confirmed (transaction, dependent))
|
||||
{
|
||||
stack.push (dependent);
|
||||
}
|
||||
}
|
||||
if (stack.top () == hash)
|
||||
{
|
||||
stack.pop ();
|
||||
if (!block_confirmed (transaction, hash))
|
||||
{
|
||||
result.push_back (block);
|
||||
confirm (transaction, *block);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// unconfirmed dependencies were added
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void nano::ledger::confirm (nano::store::write_transaction const & transaction, nano::block const & block)
|
||||
{
|
||||
debug_assert ((!store.confirmation_height.get (transaction, block.account ()) && block.sideband ().height == 1) || store.confirmation_height.get (transaction, block.account ()).value ().height + 1 == block.sideband ().height);
|
||||
confirmation_height_info info{ block.sideband ().height, block.hash () };
|
||||
store.confirmation_height.put (transaction, block.account (), info);
|
||||
++cache.cemented_count;
|
||||
stats.inc (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed);
|
||||
}
|
||||
|
||||
nano::block_status nano::ledger::process (store::write_transaction const & transaction_a, std::shared_ptr<nano::block> block_a)
|
||||
{
|
||||
debug_assert (!constants.work.validate_entry (*block_a) || constants.genesis == nano::dev::genesis);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <nano/secure/ledger_cache.hpp>
|
||||
#include <nano/secure/pending_info.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
namespace nano::store
|
||||
|
|
@ -76,6 +77,7 @@ public:
|
|||
std::string block_text (nano::block_hash const &);
|
||||
std::pair<nano::block_hash, nano::block_hash> hash_root_random (store::transaction const &) const;
|
||||
std::optional<nano::pending_info> pending_info (store::transaction const & transaction, nano::pending_key const & key) const;
|
||||
std::deque<std::shared_ptr<nano::block>> confirm (nano::store::write_transaction const & transaction, nano::block_hash const & hash);
|
||||
nano::block_status process (store::write_transaction const & transaction, std::shared_ptr<nano::block> block);
|
||||
bool rollback (store::write_transaction const &, nano::block_hash const &, std::vector<std::shared_ptr<nano::block>> &);
|
||||
bool rollback (store::write_transaction const &, nano::block_hash const &);
|
||||
|
|
@ -115,6 +117,7 @@ private:
|
|||
// Returns the next receivable entry equal or greater than 'key'
|
||||
std::optional<std::pair<nano::pending_key, nano::pending_info>> receivable_lower_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const;
|
||||
void initialize (nano::generate_cache_flags const &);
|
||||
void confirm (nano::store::write_transaction const & transaction, nano::block const & block);
|
||||
};
|
||||
|
||||
std::unique_ptr<container_info_component> collect_container_info (ledger & ledger, std::string const & name);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <nano/lib/logging.hpp>
|
||||
#include <nano/lib/thread_runner.hpp>
|
||||
#include <nano/node/active_transactions.hpp>
|
||||
#include <nano/node/confirming_set.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/make_store.hpp>
|
||||
#include <nano/node/scheduler/component.hpp>
|
||||
|
|
@ -643,7 +644,6 @@ TEST (confirmation_height, many_accounts_single_confirmation)
|
|||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
// The number of frontiers should be more than the nano::confirmation_height::unbounded_cutoff to test the amount of blocks confirmed is correct.
|
||||
node->confirmation_height_processor.batch_write_size = 500;
|
||||
auto const num_accounts = nano::confirmation_height::unbounded_cutoff * 2 + 50;
|
||||
nano::keypair last_keypair = nano::dev::genesis_key;
|
||||
nano::block_builder builder;
|
||||
|
|
@ -727,7 +727,6 @@ TEST (confirmation_height, many_accounts_many_confirmations)
|
|||
auto node = system.add_node (node_config);
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
node->confirmation_height_processor.batch_write_size = 500;
|
||||
auto const num_accounts = nano::confirmation_height::unbounded_cutoff * 2 + 50;
|
||||
auto latest_genesis = node->latest (nano::dev::genesis_key.pub);
|
||||
nano::block_builder builder;
|
||||
|
|
@ -806,7 +805,6 @@ TEST (confirmation_height, long_chains)
|
|||
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
|
||||
system.wallet (0)->insert_adhoc (key1.prv);
|
||||
|
||||
node->confirmation_height_processor.batch_write_size = 500;
|
||||
auto const num_blocks = nano::confirmation_height::unbounded_cutoff * 2 + 50;
|
||||
|
||||
nano::block_builder builder;
|
||||
|
|
@ -977,10 +975,10 @@ TEST (confirmation_height, dynamic_algorithm)
|
|||
}
|
||||
}
|
||||
|
||||
node->confirmation_height_processor.add (state_blocks.front ());
|
||||
node->confirming_set.add (state_blocks.front ()->hash ());
|
||||
ASSERT_TIMELY_EQ (20s, node->ledger.cache.cemented_count, 2);
|
||||
|
||||
node->confirmation_height_processor.add (latest_genesis);
|
||||
node->confirming_set.add (latest_genesis->hash ());
|
||||
|
||||
ASSERT_TIMELY_EQ (20s, node->ledger.cache.cemented_count, num_blocks + 1);
|
||||
|
||||
|
|
@ -990,98 +988,6 @@ TEST (confirmation_height, dynamic_algorithm)
|
|||
ASSERT_TIMELY_EQ (10s, node->active.election_winner_details_size (), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This tests an issue of incorrect cemented block counts during the transition of conf height algorithms
|
||||
* The scenario was as follows:
|
||||
* - There is at least 1 pending write entry in the unbounded conf height processor
|
||||
* - 0 blocks currently awaiting processing in the main conf height processor class
|
||||
* - A block was confirmed when hit the chain in the pending write above but was not a block higher than it.
|
||||
* - It must be in `confirmation_height_processor::pause ()` function so that `pause` is set (and the difference between the number
|
||||
* of blocks uncemented is > unbounded_cutoff so that it hits the bounded processor), the main `run` loop on the conf height processor is iterated.
|
||||
*
|
||||
* This cause unbounded pending entries not to be written, and then the bounded processor would write them, causing some inconsistencies.
|
||||
*/
|
||||
TEST (confirmation_height, dynamic_algorithm_no_transition_while_pending)
|
||||
{
|
||||
// Repeat in case of intermittent issues not replicating the issue talked about above.
|
||||
for (auto _ = 0; _ < 3; ++_)
|
||||
{
|
||||
nano::test::system system;
|
||||
nano::node_config node_config = system.default_config ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.force_use_write_database_queue = true;
|
||||
auto node = system.add_node (node_config, node_flags);
|
||||
nano::keypair key;
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
auto latest_genesis = node->latest (nano::dev::genesis_key.pub);
|
||||
std::vector<std::shared_ptr<nano::state_block>> state_blocks;
|
||||
auto const num_blocks = nano::confirmation_height::unbounded_cutoff - 2;
|
||||
|
||||
auto add_block_to_genesis_chain = [&] (store::write_transaction & transaction) {
|
||||
static int num = 0;
|
||||
nano::block_builder builder;
|
||||
auto send = builder
|
||||
.state ()
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.previous (latest_genesis)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - num - 1)
|
||||
.link (key.pub)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (latest_genesis))
|
||||
.build ();
|
||||
latest_genesis = send->hash ();
|
||||
state_blocks.push_back (send);
|
||||
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, send));
|
||||
++num;
|
||||
};
|
||||
|
||||
for (auto i = 0; i < num_blocks; ++i)
|
||||
{
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
add_block_to_genesis_chain (transaction);
|
||||
}
|
||||
|
||||
{
|
||||
auto write_guard = node->write_database_queue.wait (nano::writer::testing);
|
||||
// To limit any data races we are not calling node.block_confirm
|
||||
node->confirmation_height_processor.add (state_blocks.back ());
|
||||
|
||||
nano::timer<> timer;
|
||||
timer.start ();
|
||||
while (node->confirmation_height_processor.current ().is_zero ())
|
||||
{
|
||||
ASSERT_LT (timer.since_start (), 2s);
|
||||
}
|
||||
|
||||
// Pausing prevents any writes in the outer while loop in the confirmation height processor (implementation detail)
|
||||
node->confirmation_height_processor.pause ();
|
||||
|
||||
timer.restart ();
|
||||
ASSERT_TIMELY (10s, node->confirmation_height_processor.unbounded_processor.pending_writes_size != 0);
|
||||
|
||||
{
|
||||
// Make it so that the number of blocks exceed the unbounded cutoff would go into the bounded processor (but shouldn't due to unbounded pending writes)
|
||||
auto transaction = node->store.tx_begin_write ();
|
||||
add_block_to_genesis_chain (transaction);
|
||||
add_block_to_genesis_chain (transaction);
|
||||
}
|
||||
// Make sure this is at a height lower than the block in the add () call above
|
||||
node->confirmation_height_processor.add (state_blocks.front ());
|
||||
node->confirmation_height_processor.unpause ();
|
||||
}
|
||||
|
||||
ASSERT_TIMELY_EQ (10s, node->ledger.cache.cemented_count, num_blocks + 1);
|
||||
|
||||
ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_blocks);
|
||||
ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), 0);
|
||||
ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), num_blocks);
|
||||
ASSERT_TIMELY_EQ (10s, node->active.election_winner_details_size (), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST (confirmation_height, many_accounts_send_receive_self)
|
||||
{
|
||||
nano::test::system system;
|
||||
|
|
@ -1090,7 +996,6 @@ TEST (confirmation_height, many_accounts_send_receive_self)
|
|||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
node_config.active_elections_size = 400000;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.confirmation_height_processor_mode = nano::confirmation_height_mode::unbounded;
|
||||
auto node = system.add_node (node_config);
|
||||
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
|
|
@ -1238,7 +1143,8 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections)
|
|||
boost::latch initialized_latch{ 0 };
|
||||
|
||||
nano::block_hash block_hash_being_processed{ 0 };
|
||||
nano::confirmation_height_processor confirmation_height_processor{ ledger, write_database_queue, 10ms, logger, initialized_latch, confirmation_height_mode::automatic };
|
||||
nano::write_database_queue write_queue{ false };
|
||||
nano::confirming_set confirming_set{ ledger, write_queue };
|
||||
|
||||
auto const num_accounts = 100000;
|
||||
|
||||
|
|
@ -1283,7 +1189,7 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections)
|
|||
|
||||
for (auto & open_block : open_blocks)
|
||||
{
|
||||
confirmation_height_processor.add (open_block);
|
||||
confirming_set.add (open_block->hash ());
|
||||
}
|
||||
|
||||
system.deadline_set (1000s);
|
||||
|
|
@ -1334,8 +1240,8 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections)
|
|||
// Now send and receive to self
|
||||
for (int i = 0; i < open_blocks.size (); ++i)
|
||||
{
|
||||
confirmation_height_processor.add (send_blocks[i]);
|
||||
confirmation_height_processor.add (receive_blocks[i]);
|
||||
confirming_set.add (send_blocks[i]->hash ());
|
||||
confirming_set.add (receive_blocks[i]->hash ());
|
||||
}
|
||||
|
||||
system.deadline_set (1000s);
|
||||
|
|
@ -1345,7 +1251,7 @@ TEST (confirmation_height, many_accounts_send_receive_self_no_elections)
|
|||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
||||
while (!confirmation_height_processor.current ().is_zero ())
|
||||
while (confirming_set.size () > 0)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
|
@ -2172,7 +2078,7 @@ TEST (node, wallet_create_block_confirm_conflicts)
|
|||
election->force_confirm ();
|
||||
}
|
||||
|
||||
ASSERT_TIMELY (120s, node->ledger.block_confirmed (node->store.tx_begin_read (), latest) && node->confirmation_height_processor.current () == 0);
|
||||
ASSERT_TIMELY (120s, node->ledger.block_confirmed (node->store.tx_begin_read (), latest) && node->confirming_set.size () == 0);
|
||||
done = true;
|
||||
t.join ();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue