diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index 8c28e500..7c913cc6 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -1236,6 +1236,159 @@ TEST (rpc, process_republish) } } +TEST (rpc, process_subtype_send) +{ + nano::system system (24000, 2); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "receive"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "change"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "send"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (send.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (nano::test_genesis_key.pub) != send.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_subtype_open) +{ + nano::system system (24000, 2); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); + } + node1.active.start (std::make_shared (send)); + nano::state_block open (key.pub, 0, key.pub, nano::Gxrb_ratio, send.hash (), key.prv, key.pub, node1.work_generate_blocking (key.pub)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + open.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "send"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "epoch"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "open"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (open.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (key.pub) != open.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_subtype_receive) +{ + nano::system system (24000, 2); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); + } + node1.active.start (std::make_shared (send)); + nano::state_block receive (nano::test_genesis_key.pub, send.hash (), nano::test_genesis_key.pub, nano::genesis_amount, send.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (send.hash ())); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + receive.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "send"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "open"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ec = nano::error_rpc::invalid_subtype_previous; + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "receive"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (receive.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (nano::test_genesis_key.pub) != receive.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + TEST (rpc, keepalive) { nano::system system (24000, 1); diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 920a7c73..369abbab 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -158,6 +158,14 @@ std::string nano::error_rpc_messages::message (int ev) const return "Invalid root hash"; case nano::error_rpc::invalid_sources: return "Invalid sources number"; + case nano::error_rpc::invalid_subtype: + return "Invalid block subtype"; + case nano::error_rpc::invalid_subtype_balance: + return "Invalid block balance for given subtype"; + case nano::error_rpc::invalid_subtype_epoch_link: + return "Invalid epoch link"; + case nano::error_rpc::invalid_subtype_previous: + return "Invalid previous block for given subtype"; case nano::error_rpc::invalid_timestamp: return "Invalid timestamp"; case nano::error_rpc::payment_account_balance: diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index b45fc679..86ac654a 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -95,6 +95,10 @@ enum class error_rpc invalid_missing_type, invalid_root, invalid_sources, + invalid_subtype, + invalid_subtype_balance, + invalid_subtype_epoch_link, + invalid_subtype_previous, invalid_timestamp, payment_account_balance, payment_unable_create_account, diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index 32d9dc2b..98448b50 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -2484,6 +2484,73 @@ void nano::rpc_handler::payment_wait () void nano::rpc_handler::process () { auto block (block_impl (true)); + // State blocks subtype check + if (!ec && block->type () == nano::block_type::state) + { + std::string subtype_text (request.get ("subtype", "")); + if (!subtype_text.empty ()) + { + std::shared_ptr block_state (std::static_pointer_cast (block)); + auto transaction (node.store.tx_begin_read ()); + if (!block_state->hashables.previous.is_zero () && !node.store.block_exists (transaction, block_state->hashables.previous)) + { + ec = nano::error_process::gap_previous; + } + else + { + auto balance (node.ledger.account_balance (transaction, block_state->hashables.account)); + if (subtype_text == "send") + { + if (balance <= block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + // Send with previous == 0 fails balance check. No previous != 0 check required + } + else if (subtype_text == "receive") + { + if (balance > block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + // Receive can be point to open block. No previous != 0 check required + } + else if (subtype_text == "open") + { + if (!block_state->hashables.previous.is_zero ()) + { + ec = nano::error_rpc::invalid_subtype_previous; + } + } + else if (subtype_text == "change") + { + if (balance != block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + else if (block_state->hashables.previous.is_zero ()) + { + ec = nano::error_rpc::invalid_subtype_previous; + } + } + else if (subtype_text == "epoch") + { + if (balance != block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + else if (!node.ledger.is_epoch_link (block_state->hashables.link)) + { + ec = ec = nano::error_rpc::invalid_subtype_epoch_link; + } + } + else + { + ec = nano::error_rpc::invalid_subtype; + } + } + } + } if (!ec) { if (!nano::work_validate (*block))