Epoch 2 started flag in ledger cache (#2684)

* Epoch 2 started flag in ledger cache

This flag is flipped when the first epoch 2 block is successfully processed.

Differences in behavior after the flag is flipped:
- RPC `work_generate` uses the new epoch 2 threshold as default
- RPC `work_validate` validates for the new threshold as default (breaking behavior in previous PR)
- RPC `block_create` uses the new threshold as default
- Node wallet pre-caches work at the new threshold

* Adjust tests for node default difficulty

* Also affect work websocket multiplier, and change default test work generate difficulty

* Fix rpc.work_validate test

* Ensure state is kept on ledger initialization (node restart)
This commit is contained in:
Guilherme Lawless 2020-03-26 19:42:06 +00:00 committed by GitHub
commit ed10a6fb6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 182 additions and 21 deletions

View file

@ -3056,3 +3056,32 @@ TEST (ledger, work_validation)
process_block (open, {});
process_block (epoch, nano::block_details (nano::epoch::epoch_1, false, false, true));
}
TEST (ledger, epoch_2_started_flag)
{
nano::system system (2);
auto & node1 = *system.nodes[0];
ASSERT_FALSE (node1.ledger.cache.epoch_2_started.load ());
ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node1, nano::epoch::epoch_1));
ASSERT_FALSE (node1.ledger.cache.epoch_2_started.load ());
ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node1, nano::epoch::epoch_2));
ASSERT_TRUE (node1.ledger.cache.epoch_2_started.load ());
auto & node2 = *system.nodes[1];
nano::keypair key;
auto epoch1 = system.upgrade_genesis_epoch (node2, nano::epoch::epoch_1);
ASSERT_NE (nullptr, epoch1);
ASSERT_FALSE (node2.ledger.cache.epoch_2_started.load ());
nano::state_block send (nano::test_genesis_key.pub, epoch1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (epoch1->hash ()));
ASSERT_EQ (nano::process_result::progress, node2.process (send).code);
ASSERT_FALSE (node2.ledger.cache.epoch_2_started.load ());
nano::state_block epoch2 (key.pub, 0, 0, 0, node2.ledger.epoch_link (nano::epoch::epoch_2), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key.pub));
ASSERT_EQ (nano::process_result::progress, node2.process (epoch2).code);
ASSERT_TRUE (node2.ledger.cache.epoch_2_started.load ());
// Ensure state is kept on ledger initialization
nano::stat stats;
nano::ledger ledger (node1.store, stats);
ASSERT_TRUE (ledger.cache.epoch_2_started.load ());
}

View file

@ -649,7 +649,7 @@ TEST (wallet, work)
uint64_t work (0);
if (!wallet->store.work_get (transaction, nano::test_genesis_key.pub, work))
{
done = nano::work_difficulty (genesis.open->work_version (), genesis.hash (), work) >= nano::work_threshold_base (genesis.open->work_version ());
done = nano::work_difficulty (genesis.open->work_version (), genesis.hash (), work) >= system.nodes[0]->default_difficulty ();
}
ASSERT_NO_ERROR (system.poll ());
}
@ -683,7 +683,7 @@ TEST (wallet, work_generate)
ASSERT_NO_ERROR (system.poll ());
auto block_transaction (node1.store.tx_begin_read ());
auto transaction (system.wallet (0)->wallets.tx_begin_read ());
again = wallet->store.work_get (transaction, account1, work1) || nano::work_difficulty (block->work_version (), node1.ledger.latest_root (block_transaction, account1), work1) < nano::work_threshold_base (block->work_version ());
again = wallet->store.work_get (transaction, account1, work1) || nano::work_difficulty (block->work_version (), node1.ledger.latest_root (block_transaction, account1), work1) < node1.default_difficulty ();
}
}

View file

@ -680,15 +680,15 @@ TEST (websocket, work)
auto & request = contents.get_child ("request");
ASSERT_EQ (request.get<std::string> ("version"), nano::to_string (nano::work_version::work_1));
ASSERT_EQ (request.get<std::string> ("hash"), hash.to_string ());
ASSERT_EQ (request.get<std::string> ("difficulty"), nano::to_string_hex (node1->network_params.network.publish_thresholds.base));
ASSERT_EQ (request.get<std::string> ("difficulty"), nano::to_string_hex (node1->default_difficulty ()));
ASSERT_EQ (request.get<double> ("multiplier"), 1.0);
ASSERT_EQ (1, contents.count ("result"));
auto & result = contents.get_child ("result");
uint64_t result_difficulty;
nano::from_string_hex (result.get<std::string> ("difficulty"), result_difficulty);
ASSERT_GE (result_difficulty, node1->network_params.network.publish_thresholds.base);
ASSERT_NEAR (result.get<double> ("multiplier"), nano::difficulty::to_multiplier (result_difficulty, node1->network_params.network.publish_thresholds.base), 1e-6);
ASSERT_GE (result_difficulty, node1->default_difficulty ());
ASSERT_NEAR (result.get<double> ("multiplier"), nano::difficulty::to_multiplier (result_difficulty, node1->default_difficulty ()), 1e-6);
ASSERT_EQ (result.get<std::string> ("work"), nano::to_string_hex (work.get ()));
ASSERT_EQ (1, contents.count ("bad_peers"));

View file

@ -43,15 +43,15 @@ nano::distributed_work::~distributed_work ()
nano::websocket::message_builder builder;
if (status == work_generation_status::success)
{
node_l->websocket_server->broadcast (builder.work_generation (request.version, request.root, work_result, request.difficulty, node_l->network_params.network.publish_thresholds.base, elapsed.value (), winner, bad_peers));
node_l->websocket_server->broadcast (builder.work_generation (request.version, request.root, work_result, request.difficulty, node_l->default_difficulty (), elapsed.value (), winner, bad_peers));
}
else if (status == work_generation_status::cancelled)
{
node_l->websocket_server->broadcast (builder.work_cancelled (request.version, request.root, request.difficulty, node_l->network_params.network.publish_thresholds.base, elapsed.value (), bad_peers));
node_l->websocket_server->broadcast (builder.work_cancelled (request.version, request.root, request.difficulty, node_l->default_difficulty (), elapsed.value (), bad_peers));
}
else if (status == work_generation_status::failure_local || status == work_generation_status::failure_peers)
{
node_l->websocket_server->broadcast (builder.work_failed (request.version, request.root, request.difficulty, node_l->network_params.network.publish_thresholds.base, elapsed.value (), bad_peers));
node_l->websocket_server->broadcast (builder.work_failed (request.version, request.root, request.difficulty, node_l->default_difficulty (), elapsed.value (), bad_peers));
}
}
stop_once (true);

View file

@ -345,7 +345,7 @@ uint64_t nano::json_handler::work_optional_impl ()
uint64_t nano::json_handler::difficulty_optional_impl ()
{
uint64_t difficulty (node.network_params.network.publish_thresholds.base);
auto difficulty (node.default_difficulty ());
boost::optional<std::string> difficulty_text (request.get_optional<std::string> ("difficulty"));
if (!ec && difficulty_text.is_initialized ())
{
@ -5085,7 +5085,7 @@ void nano::json_handler::work_validate ()
auto result_difficulty (nano::work_difficulty (work_version, hash, work));
response_l.put ("valid", (result_difficulty >= difficulty) ? "1" : "0");
response_l.put ("difficulty", nano::to_string_hex (result_difficulty));
auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, node.network_params.network.publish_thresholds.base);
auto result_multiplier = nano::difficulty::to_multiplier (result_difficulty, node.default_difficulty ());
response_l.put ("multiplier", nano::to_string (result_multiplier));
}
response_errors ();

View file

@ -1004,6 +1004,11 @@ int nano::node::price (nano::uint128_t const & balance_a, int amount_a)
return static_cast<int> (result * 100.0);
}
uint64_t nano::node::default_difficulty () const
{
return ledger.cache.epoch_2_started ? network_params.network.publish_thresholds.base : network_params.network.publish_thresholds.epoch_1;
}
bool nano::node::local_work_generation_enabled () const
{
return config.work_threads > 0 || work.opencl;
@ -1053,13 +1058,13 @@ boost::optional<uint64_t> nano::node::work_generate_blocking (nano::work_version
boost::optional<uint64_t> nano::node::work_generate_blocking (nano::block & block_a)
{
debug_assert (network_params.network.is_test_network ());
return work_generate_blocking (block_a, network_params.network.publish_thresholds.base);
return work_generate_blocking (block_a, default_difficulty ());
}
boost::optional<uint64_t> nano::node::work_generate_blocking (nano::root const & root_a)
{
debug_assert (network_params.network.is_test_network ());
return work_generate_blocking (root_a, network_params.network.publish_thresholds.base);
return work_generate_blocking (root_a, default_difficulty ());
}
boost::optional<uint64_t> nano::node::work_generate_blocking (nano::root const & root_a, uint64_t difficulty_a)

View file

@ -126,6 +126,8 @@ public:
void bootstrap_wallet ();
void unchecked_cleanup ();
int price (nano::uint128_t const &, int);
// The default difficulty updates to base only when the first epoch_2 block is processed
uint64_t default_difficulty () const;
bool local_work_generation_enabled () const;
bool work_generation_enabled () const;
bool work_generation_enabled (std::vector<std::pair<std::string, uint16_t>> const &) const;

View file

@ -1383,7 +1383,8 @@ void nano::wallet::work_cache_blocking (nano::account const & account_a, nano::r
{
if (wallets.node.work_generation_enabled ())
{
auto opt_work_l (wallets.node.work_generate_blocking (nano::work_version::work_1, root_a, wallets.node.network_params.network.publish_thresholds.base, account_a));
auto difficulty (wallets.node.default_difficulty ());
auto opt_work_l (wallets.node.work_generate_blocking (nano::work_version::work_1, root_a, difficulty, account_a));
if (opt_work_l.is_initialized ())
{
auto transaction_l (wallets.tx_begin_write ());

View file

@ -2310,7 +2310,7 @@ TEST (rpc, payment_begin_end)
ASSERT_LT (work, 50);
}
system.deadline_set (10s);
while (nano::work_difficulty (nano::work_version::work_1, root1, work) < nano::work_threshold_base (nano::work_version::work_1))
while (nano::work_difficulty (nano::work_version::work_1, root1, work) < node1->default_difficulty ())
{
auto ec = system.poll ();
auto transaction (wallet->wallets.tx_begin_read ());
@ -3073,6 +3073,67 @@ TEST (rpc, work_generate_multiplier)
}
}
TEST (rpc, work_generate_epoch_2)
{
nano::system system;
auto node = add_ipc_enabled_node (system);
auto epoch1 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1);
ASSERT_NE (nullptr, epoch1);
scoped_io_thread_name_change scoped_thread_name_io;
nano::node_rpc_config node_rpc_config;
nano::ipc::ipc_server ipc_server (*node, node_rpc_config);
nano::rpc_config rpc_config (nano::get_available_port (), true);
rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port;
nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config);
nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor);
rpc.start ();
boost::property_tree::ptree request;
auto verify_response = [node, &rpc, &system](auto & request, nano::block_hash const & hash, uint64_t & out_difficulty) {
request.put ("hash", hash.to_string ());
test_response response (request, rpc.config.port, system.io_ctx);
system.deadline_set (5s);
while (response.status == 0)
{
ASSERT_NO_ERROR (system.poll ());
}
ASSERT_EQ (200, response.status);
auto work_text (response.json.get<std::string> ("work"));
uint64_t work{ 0 };
ASSERT_FALSE (nano::from_string_hex (work_text, work));
out_difficulty = nano::work_difficulty (nano::work_version::work_1, hash, work);
};
request.put ("action", "work_generate");
// Before upgrading to epoch 2 should use epoch_1 difficulty as default
{
unsigned const max_tries = 30;
uint64_t difficulty{ 0 };
unsigned tries = 0;
while (++tries < max_tries)
{
verify_response (request, epoch1->hash (), difficulty);
if (difficulty < node->network_params.network.publish_thresholds.base)
{
break;
}
}
ASSERT_LT (tries, max_tries);
}
// After upgrading, should always use the higher difficulty by default
ASSERT_EQ (node->network_params.network.publish_thresholds.epoch_2, node->network_params.network.publish_thresholds.base);
scoped_thread_name_io.reset ();
auto epoch2 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2);
ASSERT_NE (nullptr, epoch2);
scoped_thread_name_io.renew ();
{
for (auto i = 0; i < 5; ++i)
{
uint64_t difficulty{ 0 };
verify_response (request, epoch1->hash (), difficulty);
ASSERT_GE (difficulty, node->network_params.network.publish_thresholds.base);
}
}
}
TEST (rpc, work_cancel)
{
nano::system system;
@ -3917,7 +3978,6 @@ TEST (rpc, wallet_frontiers)
TEST (rpc, work_validate)
{
nano::network_params params;
nano::system system;
auto & node1 = *add_ipc_enabled_node (system);
nano::keypair key;
@ -3950,9 +4010,9 @@ TEST (rpc, work_validate)
std::string difficulty_text (response.json.get<std::string> ("difficulty"));
uint64_t difficulty;
ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty));
ASSERT_GE (difficulty, params.network.publish_thresholds.base);
ASSERT_GE (difficulty, node1.default_difficulty ());
double multiplier (response.json.get<double> ("multiplier"));
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, params.network.publish_thresholds.base), 1e-6);
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node1.default_difficulty ()), 1e-6);
}
uint64_t work2 (0);
request.put ("work", nano::to_string_hex (work2));
@ -3969,12 +4029,12 @@ TEST (rpc, work_validate)
std::string difficulty_text (response.json.get<std::string> ("difficulty"));
uint64_t difficulty;
ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty));
ASSERT_GE (params.network.publish_thresholds.base, difficulty);
ASSERT_GE (node1.default_difficulty (), difficulty);
double multiplier (response.json.get<double> ("multiplier"));
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, params.network.publish_thresholds.base), 1e-6);
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node1.default_difficulty ()), 1e-6);
}
auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work1));
ASSERT_GE (result_difficulty, params.network.publish_thresholds.base);
ASSERT_GE (result_difficulty, node1.default_difficulty ());
request.put ("work", nano::to_string_hex (work1));
request.put ("difficulty", nano::to_string_hex (result_difficulty));
{
@ -4017,6 +4077,62 @@ TEST (rpc, work_validate)
}
}
TEST (rpc, work_validate_epoch_2)
{
nano::system system;
auto node = add_ipc_enabled_node (system);
auto epoch1 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_1);
ASSERT_NE (nullptr, epoch1);
ASSERT_EQ (node->network_params.network.publish_thresholds.epoch_2, node->network_params.network.publish_thresholds.base);
auto work = system.work_generate_limited (epoch1->hash (), node->network_params.network.publish_thresholds.epoch_1, node->network_params.network.publish_thresholds.base);
scoped_io_thread_name_change scoped_thread_name_io;
nano::node_rpc_config node_rpc_config;
nano::ipc::ipc_server ipc_server (*node, node_rpc_config);
nano::rpc_config rpc_config (nano::get_available_port (), true);
rpc_config.rpc_process.ipc_port = node->config.ipc_config.transport_tcp.port;
nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config);
nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor);
rpc.start ();
boost::property_tree::ptree request;
request.put ("action", "work_validate");
request.put ("hash", epoch1->hash ().to_string ());
request.put ("work", nano::to_string_hex (work));
{
test_response response (request, rpc.config.port, system.io_ctx);
system.deadline_set (5s);
while (response.status == 0)
{
ASSERT_NO_ERROR (system.poll ());
}
ASSERT_EQ (200, response.status);
ASSERT_TRUE (response.json.get<bool> ("valid"));
std::string difficulty_text (response.json.get<std::string> ("difficulty"));
uint64_t difficulty{ 0 };
ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty));
double multiplier (response.json.get<double> ("multiplier"));
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node->network_params.network.publish_thresholds.epoch_1), 1e-6);
};
// After upgrading, the higher difficulty is used to validate and calculate the multiplier
scoped_thread_name_io.reset ();
ASSERT_NE (nullptr, system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2));
scoped_thread_name_io.renew ();
{
test_response response (request, rpc.config.port, system.io_ctx);
system.deadline_set (5s);
while (response.status == 0)
{
ASSERT_NO_ERROR (system.poll ());
}
ASSERT_EQ (200, response.status);
ASSERT_FALSE (response.json.get<bool> ("valid"));
std::string difficulty_text (response.json.get<std::string> ("difficulty"));
uint64_t difficulty{ 0 };
ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty));
double multiplier (response.json.get<double> ("multiplier"));
ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (difficulty, node->network_params.network.publish_thresholds.base), 1e-6);
};
}
TEST (rpc, successors)
{
nano::system system;
@ -5911,7 +6027,7 @@ TEST (rpc, block_create_state_request_work)
boost::property_tree::read_json (block_stream, block_l);
auto block (nano::deserialize_block_json (block_l));
ASSERT_NE (nullptr, block);
ASSERT_GE (block->difficulty (), nano::work_threshold_base (nano::work_version::work_1));
ASSERT_GE (block->difficulty (), node->default_difficulty ());
}
}

View file

@ -508,6 +508,7 @@ public:
std::atomic<uint64_t> block_count{ 0 };
std::atomic<uint64_t> unchecked_count{ 0 };
std::atomic<uint64_t> account_count{ 0 };
std::atomic<bool> epoch_2_started{ 0 };
};
/* Defines the possible states for an election to stop in */

View file

@ -439,6 +439,10 @@ void ledger_processor::epoch_block_impl (nano::state_block & block_a)
{
ledger.store.frontier_del (transaction, info.head);
}
if (epoch == nano::epoch::epoch_2)
{
ledger.cache.epoch_2_started.store (true);
}
}
}
}
@ -725,12 +729,15 @@ check_bootstrap_weights (true)
auto transaction = store.tx_begin_read ();
if (generate_cache_a.reps || generate_cache_a.account_count)
{
bool epoch_2_started_l{ false };
for (auto i (store.latest_begin (transaction)), n (store.latest_end ()); i != n; ++i)
{
nano::account_info const & info (i->second);
cache.rep_weights.representation_add (info.representative, info.balance.number ());
++cache.account_count;
epoch_2_started_l = epoch_2_started_l || info.epoch () == nano::epoch::epoch_2;
}
cache.epoch_2_started.store (epoch_2_started_l);
}
if (generate_cache_a.cemented_count)