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"
This commit is contained in:
Sergey Kroshnin 2020-05-15 13:24:50 +03:00 committed by GitHub
commit 04135d0aff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 275 additions and 93 deletions

View file

@ -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:

View file

@ -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,

View file

@ -268,19 +268,27 @@ nano::amount nano::json_handler::amount_impl ()
std::shared_ptr<nano::block> nano::json_handler::block_impl (bool signature_work_required)
{
const bool json_block_l = request.get<bool> ("json_block", false);
std::shared_ptr<nano::block> result{ nullptr };
if (!ec)
{
std::string block_text (request.get<std::string> ("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<std::string> ("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::block> nano::json_handler::block_impl (bool signature_work
return result;
}
std::shared_ptr<nano::block> nano::json_handler::block_json_impl (bool signature_work_required)
{
std::shared_ptr<nano::block> 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<nano::block> 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<nano::block> 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<bool> ("json_block", false);
std::shared_ptr<nano::block> 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<nano::json_handler> const & rpc_l) {
const bool json_block_l = rpc_l->request.get<bool> ("json_block", false);
const bool watch_work_l = rpc_l->request.get<bool> ("watch_work", true);
std::shared_ptr<nano::block> 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<nano::block> block;
boost::optional<std::string> block_text (request.get_optional<std::string> ("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<nano::block> 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<bool> ("use_peers", false));
auto rpc_l (shared_from_this ());

View file

@ -154,7 +154,6 @@ public:
nano::account_info account_info_impl (nano::transaction const &, nano::account const &);
nano::amount amount_impl ();
std::shared_ptr<nano::block> block_impl (bool = true);
std::shared_ptr<nano::block> 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<uint64_t>::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 };

View file

@ -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
{

View file

@ -3122,10 +3122,6 @@ TEST (rpc, work_generate_multiplier)
}
ASSERT_EQ (200, response.status);
auto work_text (response.json.get_optional<std::string> ("work"));
if (!work_text)
{
std::cout << response.json.get<std::string> ("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<std::string> ("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<std::string> ("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<std::string> ("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<std::string> ("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<std::string> ("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<std::string> ("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;