From 04135d0aff9a420c5a7ad54568b0e7b214d89379 Mon Sep 17 00:00:00 2001 From: Sergey Kroshnin Date: Fri, 15 May 2020 13:24:50 +0300 Subject: [PATCH] Optional "block" given to RPC "work_generate" to infer difficulty (#2754) * Simplify block_impl () json/text retrieval * Optional block for work_generate () * Test for "work_generate" with block * Apply Guilherme review * Difficulty from previous block function for "block_create" & "work_generate" --- nano/lib/errors.cpp | 6 ++ nano/lib/errors.hpp | 3 + nano/node/json_handler.cpp | 176 ++++++++++++++++++------------------ nano/node/json_handler.hpp | 2 +- nano/node/testing.cpp | 1 + nano/rpc_test/rpc.cpp | 180 ++++++++++++++++++++++++++++++++++++- 6 files changed, 275 insertions(+), 93 deletions(-) diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index ca29b7012..29aac08de 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -163,6 +163,12 @@ std::string nano::error_rpc_messages::message (int ev) const return "Representative account and previous hash required"; case nano::error_rpc::block_create_requirements_send: return "Destination account, previous hash, current balance and amount required"; + case nano::error_rpc::block_root_mismatch: + return "Root mismatch for block"; + case nano::error_rpc::block_work_enough: + return "Provided work is already enough for given difficulty"; + case nano::error_rpc::block_work_version_mismatch: + return "Work version mismatch for block"; case nano::error_rpc::confirmation_height_not_processing: return "There are no blocks currently being processed for adding confirmation height"; case nano::error_rpc::confirmation_not_found: diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 570a3cabf..6a78034d1 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -94,6 +94,9 @@ enum class error_rpc block_create_requirements_receive, block_create_requirements_change, block_create_requirements_send, + block_root_mismatch, + block_work_enough, + block_work_version_mismatch, confirmation_height_not_processing, confirmation_not_found, difficulty_limit, diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 3120afda1..6b23e9daf 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -268,19 +268,27 @@ nano::amount nano::json_handler::amount_impl () std::shared_ptr nano::json_handler::block_impl (bool signature_work_required) { + const bool json_block_l = request.get ("json_block", false); std::shared_ptr result{ nullptr }; if (!ec) { - std::string block_text (request.get ("block")); boost::property_tree::ptree block_l; - std::stringstream block_stream (block_text); - try + if (json_block_l) { - boost::property_tree::read_json (block_stream, block_l); + block_l = request.get_child ("block"); } - catch (...) + else { - ec = nano::error_blocks::invalid_block; + std::string block_text (request.get ("block")); + std::stringstream block_stream (block_text); + try + { + boost::property_tree::read_json (block_stream, block_l); + } + catch (...) + { + ec = nano::error_blocks::invalid_block; + } } if (!ec) { @@ -299,26 +307,6 @@ std::shared_ptr nano::json_handler::block_impl (bool signature_work return result; } -std::shared_ptr nano::json_handler::block_json_impl (bool signature_work_required) -{ - std::shared_ptr result; - if (!ec) - { - auto block_l (request.get_child ("block")); - if (!signature_work_required) - { - block_l.put ("signature", "0"); - block_l.put ("work", "0"); - } - result = nano::deserialize_block_json (block_l); - if (result == nullptr) - { - ec = nano::error_blocks::invalid_block; - } - } - return result; -} - nano::block_hash nano::json_handler::hash_impl (std::string search_text) { nano::block_hash result (0); @@ -375,6 +363,43 @@ uint64_t nano::json_handler::difficulty_optional_impl (nano::work_version const return difficulty; } +uint64_t nano::json_handler::difficulty_ledger (nano::block const & block_a) +{ + nano::block_details details (nano::epoch::epoch_0, false, false, false); + bool details_found (false); + auto transaction (node.store.tx_begin_read ()); + // Previous block find + std::shared_ptr block_previous (nullptr); + auto previous (block_a.previous ()); + if (!previous.is_zero ()) + { + block_previous = node.store.block_get (transaction, previous); + } + // Send check + if (block_previous != nullptr) + { + details.is_send = node.store.block_balance (transaction, previous) > block_a.balance ().number (); + details_found = true; + } + // Epoch check + if (block_previous != nullptr) + { + details.epoch = block_previous->sideband ().details.epoch; + } + auto link (block_a.link ()); + if (!link.is_zero () && !details.is_send) + { + auto block_link (node.store.block_get (transaction, link)); + if (block_link != nullptr && node.store.pending_exists (transaction, nano::pending_key (block_a.account (), link))) + { + details.epoch = std::max (details.epoch, block_link->sideband ().details.epoch); + details.is_receive = true; + details_found = true; + } + } + return details_found ? nano::work_threshold (block_a.work_version (), details) : node.default_difficulty (block_a.work_version ()); +} + double nano::json_handler::multiplier_optional_impl (nano::work_version const version_a, uint64_t & difficulty) { double multiplier (1.); @@ -1555,37 +1580,7 @@ void nano::json_handler::block_create () // Difficulty calculation if (request.count ("difficulty") == 0) { - nano::block_details details (nano::epoch::epoch_0, false, false, false); - bool details_found (false); - auto transaction (node.store.tx_begin_read ()); - // Previous block find - std::shared_ptr block_previous (nullptr); - if (!previous.is_zero ()) - { - block_previous = node.store.block_get (transaction, previous); - } - // Send check - if (block_previous != nullptr) - { - details.is_send = node.store.block_balance (transaction, previous) > balance.number (); - details_found = true; - } - // Epoch check - if (block_previous != nullptr) - { - details.epoch = block_previous->sideband ().details.epoch; - } - if (!link.is_zero () && !details.is_send) - { - auto block_link (node.store.block_get (transaction, link)); - if (block_link != nullptr && node.store.pending_exists (transaction, nano::pending_key (pub, link))) - { - details.epoch = std::max (details.epoch, block_link->sideband ().details.epoch); - details.is_receive = true; - details_found = true; - } - } - difficulty_l = details_found ? nano::work_threshold (work_version, details) : node.default_difficulty (work_version); + difficulty_l = difficulty_ledger (*block_l); } node.work_generate (work_version, root_l, difficulty_l, get_callback_l (block_l), nano::account (pub)); } @@ -1610,16 +1605,7 @@ void nano::json_handler::block_create () void nano::json_handler::block_hash () { - const bool json_block_l = request.get ("json_block", false); - std::shared_ptr block; - if (json_block_l) - { - block = block_json_impl (true); - } - else - { - block = block_impl (true); - } + auto block (block_impl (true)); if (!ec) { @@ -3014,17 +3000,8 @@ void nano::json_handler::payment_wait () void nano::json_handler::process () { node.worker.push_task (create_worker_task ([](std::shared_ptr const & rpc_l) { - const bool json_block_l = rpc_l->request.get ("json_block", false); const bool watch_work_l = rpc_l->request.get ("watch_work", true); - std::shared_ptr block; - if (json_block_l) - { - block = rpc_l->block_json_impl (true); - } - else - { - block = rpc_l->block_impl (true); - } + auto block (rpc_l->block_impl (true)); // State blocks subtype check if (!rpc_l->ec && block->type () == nano::block_type::state) @@ -3617,17 +3594,9 @@ void nano::json_handler::sign () } // Retrieving block std::shared_ptr block; - boost::optional block_text (request.get_optional ("block")); - if (!ec && block_text.is_initialized ()) + if (!ec && request.count ("block")) { - if (json_block_l) - { - block = block_json_impl (true); - } - else - { - block = block_impl (true); - } + block = block_impl (true); if (block != nullptr) { hash = block->hash (); @@ -4785,7 +4754,38 @@ void nano::json_handler::work_generate () { ec = nano::error_rpc::difficulty_limit; } - if (!ec) + // Retrieving optional block + std::shared_ptr block; + if (!ec && request.count ("block")) + { + block = block_impl (true); + if (block != nullptr) + { + if (hash != block->root ()) + { + ec = nano::error_rpc::block_root_mismatch; + } + if (request.count ("version") == 0) + { + work_version = block->work_version (); + } + else if (!ec && work_version != block->work_version ()) + { + ec = nano::error_rpc::block_work_version_mismatch; + } + // Difficulty calculation + if (!ec && request.count ("difficulty") == 0 && request.count ("multiplier") == 0) + { + difficulty = difficulty_ledger (*block); + } + // If optional block difficulty is higher than requested difficulty, send error + if (!ec && block->difficulty () >= difficulty) + { + ec = nano::error_rpc::block_work_enough; + } + } + } + if (!ec && response_l.empty ()) { auto use_peers (request.get ("use_peers", false)); auto rpc_l (shared_from_this ()); diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp index 2cd970d79..f739ab6ec 100644 --- a/nano/node/json_handler.hpp +++ b/nano/node/json_handler.hpp @@ -154,7 +154,6 @@ public: nano::account_info account_info_impl (nano::transaction const &, nano::account const &); nano::amount amount_impl (); std::shared_ptr block_impl (bool = true); - std::shared_ptr block_json_impl (bool = true); nano::block_hash hash_impl (std::string = "hash"); nano::amount threshold_optional_impl (); uint64_t work_optional_impl (); @@ -162,6 +161,7 @@ public: uint64_t count_optional_impl (uint64_t = std::numeric_limits::max ()); uint64_t offset_optional_impl (uint64_t = 0); uint64_t difficulty_optional_impl (nano::work_version const); + uint64_t difficulty_ledger (nano::block const &); double multiplier_optional_impl (nano::work_version const, uint64_t &); nano::work_version work_version_optional_impl (nano::work_version const default_a); bool enable_sign_hash{ false }; diff --git a/nano/node/testing.cpp b/nano/node/testing.cpp index 3b25ed1a3..0bb20d45d 100644 --- a/nano/node/testing.cpp +++ b/nano/node/testing.cpp @@ -164,6 +164,7 @@ nano::account nano::system::account (nano::transaction const & transaction_a, si uint64_t nano::system::work_generate_limited (nano::block_hash const & root_a, uint64_t min_a, uint64_t max_a) { + debug_assert (min_a > 0); uint64_t result = 0; do { diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 13e63b293..e623fee4e 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -3122,10 +3122,6 @@ TEST (rpc, work_generate_multiplier) } ASSERT_EQ (200, response.status); auto work_text (response.json.get_optional ("work")); - if (!work_text) - { - std::cout << response.json.get ("error") << std::endl; - } ASSERT_TRUE (work_text.is_initialized ()); uint64_t work; ASSERT_FALSE (nano::from_string_hex (*work_text, work)); @@ -3225,6 +3221,182 @@ TEST (rpc, work_generate_epoch_2) } } +TEST (rpc, work_generate_block_high) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + 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 (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, *node->work_generate_blocking (key.pub)); + nano::block_hash hash (block.root ()); + auto block_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, block.block_work ())); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "true"); + boost::property_tree::ptree json; + block.serialize_json (json); + request.add_child ("block", json); + { + 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_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::block_work_enough).message (), response.json.get ("error")); + } +} + +TEST (rpc, work_generate_block_low) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + 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 (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, 0); + auto threshold (node->default_difficulty (block.work_version ())); + block.block_work_set (system.work_generate_limited (block.root (), threshold, nano::difficulty::from_multiplier (node->config.max_work_generate_multiplier / 10, threshold))); + nano::block_hash hash (block.root ()); + auto block_difficulty (block.difficulty ()); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("difficulty", nano::to_string_hex (block_difficulty + 1)); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto work_text (response.json.get_optional ("work")); + ASSERT_TRUE (work_text.is_initialized ()); + uint64_t work; + ASSERT_FALSE (nano::from_string_hex (*work_text, work)); + ASSERT_NE (block.block_work (), work); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); + auto response_difficulty_text (response.json.get ("difficulty")); + uint64_t response_difficulty; + ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); + ASSERT_EQ (result_difficulty, response_difficulty); + ASSERT_LT (block_difficulty, result_difficulty); + } +} + +TEST (rpc, work_generate_block_root_mismatch) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + 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 (); + nano::keypair key; + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, 123, key.prv, key.pub, *node->work_generate_blocking (key.pub)); + nano::block_hash hash (1); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + { + 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_EQ (1, response.json.count ("error")); + ASSERT_EQ (std::error_code (nano::error_rpc::block_root_mismatch).message (), response.json.get ("error")); + } +} + +TEST (rpc, work_generate_block_ledger_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); + auto epoch2 = system.upgrade_genesis_epoch (*node, nano::epoch::epoch_2); + ASSERT_NE (nullptr, epoch2); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + 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 (); + nano::state_block block (key.pub, 0, nano::test_genesis_key.pub, nano::Gxrb_ratio, send_block->hash (), key.prv, key.pub, 0); + auto threshold (nano::work_threshold (block.work_version (), nano::block_details (nano::epoch::epoch_2, false, true, false))); + block.block_work_set (system.work_generate_limited (block.root (), 1, threshold - 1)); + nano::block_hash hash (block.root ()); + boost::property_tree::ptree request; + request.put ("action", "work_generate"); + request.put ("hash", hash.to_string ()); + request.put ("json_block", "false"); + std::string json; + block.serialize_json (json); + request.put ("block", json); + bool finished (false); + auto iteration (0); + while (!finished) + { + test_response response (request, rpc.config.port, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto work_text (response.json.get_optional ("work")); + ASSERT_TRUE (work_text.is_initialized ()); + uint64_t work; + ASSERT_FALSE (nano::from_string_hex (*work_text, work)); + auto result_difficulty (nano::work_difficulty (nano::work_version::work_1, hash, work)); + auto response_difficulty_text (response.json.get ("difficulty")); + uint64_t response_difficulty; + ASSERT_FALSE (nano::from_string_hex (response_difficulty_text, response_difficulty)); + ASSERT_EQ (result_difficulty, response_difficulty); + ASSERT_GE (result_difficulty, node->network_params.network.publish_thresholds.epoch_2_receive); + finished = result_difficulty < node->network_params.network.publish_thresholds.epoch_1; + ASSERT_LT (++iteration, 200); + } +} + TEST (rpc, work_cancel) { nano::system system;