From ed10a6fb6b45184ce9c9253079b29cc45bcfc6d5 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Thu, 26 Mar 2020 19:42:06 +0000 Subject: [PATCH] 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) --- nano/core_test/ledger.cpp | 29 ++++++++ nano/core_test/wallet.cpp | 4 +- nano/core_test/websocket.cpp | 6 +- nano/node/distributed_work.cpp | 6 +- nano/node/json_handler.cpp | 4 +- nano/node/node.cpp | 9 ++- nano/node/node.hpp | 2 + nano/node/wallet.cpp | 3 +- nano/rpc_test/rpc.cpp | 132 +++++++++++++++++++++++++++++++-- nano/secure/common.hpp | 1 + nano/secure/ledger.cpp | 7 ++ 11 files changed, 182 insertions(+), 21 deletions(-) diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 6e234696..85c7aa87 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -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 ()); +} diff --git a/nano/core_test/wallet.cpp b/nano/core_test/wallet.cpp index fe14874d..85e19943 100644 --- a/nano/core_test/wallet.cpp +++ b/nano/core_test/wallet.cpp @@ -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 (); } } diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index f6c123f5..603ef531 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -680,15 +680,15 @@ TEST (websocket, work) auto & request = contents.get_child ("request"); ASSERT_EQ (request.get ("version"), nano::to_string (nano::work_version::work_1)); ASSERT_EQ (request.get ("hash"), hash.to_string ()); - ASSERT_EQ (request.get ("difficulty"), nano::to_string_hex (node1->network_params.network.publish_thresholds.base)); + ASSERT_EQ (request.get ("difficulty"), nano::to_string_hex (node1->default_difficulty ())); ASSERT_EQ (request.get ("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 ("difficulty"), result_difficulty); - ASSERT_GE (result_difficulty, node1->network_params.network.publish_thresholds.base); - ASSERT_NEAR (result.get ("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 ("multiplier"), nano::difficulty::to_multiplier (result_difficulty, node1->default_difficulty ()), 1e-6); ASSERT_EQ (result.get ("work"), nano::to_string_hex (work.get ())); ASSERT_EQ (1, contents.count ("bad_peers")); diff --git a/nano/node/distributed_work.cpp b/nano/node/distributed_work.cpp index a21771cd..42b975fb 100644 --- a/nano/node/distributed_work.cpp +++ b/nano/node/distributed_work.cpp @@ -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); diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 50651816..493c558a 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -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 difficulty_text (request.get_optional ("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 (); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 116ffec0..5535b6d5 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -1004,6 +1004,11 @@ int nano::node::price (nano::uint128_t const & balance_a, int amount_a) return static_cast (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 nano::node::work_generate_blocking (nano::work_version boost::optional 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 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 nano::node::work_generate_blocking (nano::root const & root_a, uint64_t difficulty_a) diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 81510eec..ed406d09 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -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> const &) const; diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 25061062..559a020a 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -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 ()); diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 11a7bc1e..40b9d2aa 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -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 ("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 ("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 ("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 ("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 ("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 ("valid")); + std::string difficulty_text (response.json.get ("difficulty")); + uint64_t difficulty{ 0 }; + ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); + double multiplier (response.json.get ("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 ("valid")); + std::string difficulty_text (response.json.get ("difficulty")); + uint64_t difficulty{ 0 }; + ASSERT_FALSE (nano::from_string_hex (difficulty_text, difficulty)); + double multiplier (response.json.get ("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 ()); } } diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 2f2403b7..9ca46715 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -508,6 +508,7 @@ public: std::atomic block_count{ 0 }; std::atomic unchecked_count{ 0 }; std::atomic account_count{ 0 }; + std::atomic epoch_2_started{ 0 }; }; /* Defines the possible states for an election to stop in */ diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index ef60eaac..d6eeb7da 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -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)