diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 44a3d6f836..7422032c93 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1239,6 +1239,7 @@ void nano::json_handler::blocks_info () { bool const pending = request.get ("pending", false); bool const receivable = request.get ("receivable", pending); + bool const receive_hash = request.get ("receive_hash", false); bool const source = request.get ("source", false); bool const json_block_l = request.get ("json_block", false); bool const include_not_found = request.get ("include_not_found", false); @@ -1291,16 +1292,47 @@ void nano::json_handler::blocks_info () auto subtype (nano::state_subtype (block->sideband ().details)); entry.put ("subtype", subtype); } - if (receivable) + if (receivable || receive_hash) { - bool exists (false); auto destination (node.ledger.block_destination (transaction, *block)); - if (!destination.is_zero ()) + if (destination.is_zero ()) + { + if (receivable) + { + entry.put ("pending", "0"); + entry.put ("receivable", "0"); + } + if (receive_hash) + { + entry.put ("receive_hash", nano::block_hash (0).to_string ()); + } + } + else if (node.store.pending.exists (transaction, nano::pending_key (destination, hash))) + { + if (receivable) + { + entry.put ("pending", "1"); + entry.put ("receivable", "1"); + } + if (receive_hash) + { + entry.put ("receive_hash", nano::block_hash (0).to_string ()); + } + } + else { - exists = node.store.pending.exists (transaction, nano::pending_key (destination, hash)); + if (receivable) + { + entry.put ("pending", "0"); + entry.put ("receivable", "0"); + } + if (receive_hash) + { + std::shared_ptr receive_block = node.ledger.find_receive_block_by_send_hash (transaction, destination, hash); + std::string receive_hash = receive_block ? receive_block->hash ().to_string () : nano::block_hash (0).to_string (); + entry.put ("receive_hash", receive_hash); + } } - entry.put ("pending", exists ? "1" : "0"); - entry.put ("receivable", exists ? "1" : "0"); } if (source) { diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index a07c7ead9f..52023d676c 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -16,7 +16,9 @@ #include #include +#include #include +#include using namespace std::chrono_literals; @@ -4062,6 +4064,8 @@ TEST (rpc, blocks_info) ASSERT_FALSE (blocks_text.empty ()); boost::optional receivable (blocks.second.get_optional ("receivable")); ASSERT_FALSE (receivable.is_initialized ()); + boost::optional receive_hash (blocks.second.get_optional ("receive_hash")); + ASSERT_FALSE (receive_hash.is_initialized ()); boost::optional source (blocks.second.get_optional ("source_account")); ASSERT_FALSE (source.is_initialized ()); std::string balance_text (blocks.second.get ("balance")); @@ -4101,16 +4105,97 @@ TEST (rpc, blocks_info) } request.put ("source", "true"); request.put ("receivable", "1"); + request.put ("receive_hash", "1"); { auto response (wait_response (system, rpc_ctx, request)); for (auto & blocks : response.get_child ("blocks")) { ASSERT_EQ ("0", blocks.second.get ("source_account")); ASSERT_EQ ("0", blocks.second.get ("receivable")); + std::string receive_hash (blocks.second.get ("receive_hash")); + ASSERT_EQ (nano::block_hash (0).to_string (), receive_hash); } } } +/** + * Test to check the receive_hash option of blocks_info rpc command. + * The test does 4 sends from genesis to key1. + * Then it does 4 receives, one for each send. + * Then it issues the blocks_info RPC command and checks that the receive block of each send block is correctly found. + */ +TEST (rpc, blocks_info_receive_hash) +{ + nano::system system; + auto node = add_ipc_enabled_node (system); + nano::keypair key1; + system.wallet (0)->insert_adhoc (key1.prv); + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + + // do 4 sends + auto send1 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 1); + auto send2 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 2); + auto send3 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 3); + auto send4 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 4); + + // do 4 receives, mix up the ordering a little + auto recv1 (system.wallet (0)->receive_action (send1->hash (), key1.pub, node->config.receive_minimum.number (), send1->link ().as_account ())); + auto recv4 (system.wallet (0)->receive_action (send4->hash (), key1.pub, node->config.receive_minimum.number (), send4->link ().as_account ())); + auto recv3 (system.wallet (0)->receive_action (send3->hash (), key1.pub, node->config.receive_minimum.number (), send3->link ().as_account ())); + auto recv2 (system.wallet (0)->receive_action (send2->hash (), key1.pub, node->config.receive_minimum.number (), send2->link ().as_account ())); + + // function to check that all 4 receive blocks are cemented + auto all_blocks_cemented = [node, &key1] () -> bool { + nano::confirmation_height_info info; + if (node->store.confirmation_height.get (node->store.tx_begin_read (), key1.pub, info)) + { + return false; + } + return info.height == 4; + }; + + ASSERT_TIMELY (5s, all_blocks_cemented ()); + ASSERT_EQ (node->ledger.account_balance (node->store.tx_begin_read (), key1.pub, true), 10); + + // create the RPC request + boost::property_tree::ptree request; + boost::property_tree::ptree hashes; + boost::property_tree::ptree child; + child.put ("", send1->hash ().to_string ()); + hashes.push_back (std::make_pair ("", child)); + child.put ("", send2->hash ().to_string ()); + hashes.push_back (std::make_pair ("", child)); + child.put ("", send3->hash ().to_string ()); + hashes.push_back (std::make_pair ("", child)); + child.put ("", send4->hash ().to_string ()); + hashes.push_back (std::make_pair ("", child)); + request.put ("action", "blocks_info"); + request.add_child ("hashes", hashes); + request.put ("receive_hash", "true"); + request.put ("json_block", "true"); + + // send the request + auto const rpc_ctx = add_rpc (system, node); + auto response = wait_response (system, rpc_ctx, request); + + // create a map of the expected receives hashes for each send hash + std::map send_recv_map{ + { send1->hash ().to_string (), recv1->hash ().to_string () }, + { send2->hash ().to_string (), recv2->hash ().to_string () }, + { send3->hash ().to_string (), recv3->hash ().to_string () }, + { send4->hash ().to_string (), recv4->hash ().to_string () }, + }; + + for (auto & blocks : response.get_child ("blocks")) + { + auto hash = blocks.first; + std::string receive_hash = blocks.second.get ("receive_hash"); + ASSERT_EQ (receive_hash, send_recv_map[hash]); + send_recv_map.erase (hash); + } + ASSERT_EQ (send_recv_map.size (), 0); +} + TEST (rpc, blocks_info_subtype) { nano::system system; diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index c97fabf376..2e049904df 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -229,9 +229,14 @@ class confirmation_height_info final public: confirmation_height_info () = default; confirmation_height_info (uint64_t, nano::block_hash const &); + void serialize (nano::stream &) const; bool deserialize (nano::stream &); + + /** height of the cemented frontier */ uint64_t height{}; + + /** hash of the highest cemented block, the cemented/confirmed frontier */ nano::block_hash frontier{}; }; diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 1590ec4e19..b4ab456eed 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -1237,6 +1237,54 @@ std::array nano::ledger::dependent_blocks (nano::transactio return visitor.result; } +/** Given the block hash of a send block, find the associated receive block that receives that send. + * The send block hash is not checked in any way, it is assumed to be correct. + * @return Return the receive block on success and null on failure + */ +std::shared_ptr nano::ledger::find_receive_block_by_send_hash (nano::transaction const & transaction, nano::account const & destination, nano::block_hash const & send_block_hash) +{ + std::shared_ptr result; + debug_assert (send_block_hash != 0); + + // get the cemented frontier + nano::confirmation_height_info info; + if (store.confirmation_height.get (transaction, destination, info)) + { + return nullptr; + } + auto possible_receive_block = store.block.get (transaction, info.frontier); + + // walk down the chain until the source field of a receive block matches the send block hash + while (possible_receive_block != nullptr) + { + // if source is non-zero then it is a legacy receive or open block + nano::block_hash source = possible_receive_block->source (); + + // if source is zero then it could be a state block, which needs a different kind of access + auto state_block = dynamic_cast (possible_receive_block.get ()); + if (state_block != nullptr) + { + // we read the block from the database, so we expect it to have sideband + debug_assert (state_block->has_sideband ()); + if (state_block->sideband ().details.is_receive) + { + source = state_block->hashables.link.as_block_hash (); + } + } + + if (send_block_hash == source) + { + // we have a match + result = possible_receive_block; + break; + } + + possible_receive_block = store.block.get (transaction, possible_receive_block->previous ()); + } + + return result; +} + nano::account const & nano::ledger::epoch_signer (nano::link const & link_a) const { return constants.epochs.signer (constants.epochs.epoch (link_a)); diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 963304e190..e994908522 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -63,6 +63,7 @@ class ledger final bool dependents_confirmed (nano::transaction const &, nano::block const &) const; bool is_epoch_link (nano::link const &) const; std::array dependent_blocks (nano::transaction const &, nano::block const &) const; + std::shared_ptr find_receive_block_by_send_hash (nano::transaction const & transaction, nano::account const & destination, nano::block_hash const & send_block_hash); nano::account const & epoch_signer (nano::link const &) const; nano::link const & epoch_link (nano::epoch) const; std::multimap> unconfirmed_frontiers () const; diff --git a/nano/secure/store.hpp b/nano/secure/store.hpp index edea5acb16..be8ce844a2 100644 --- a/nano/secure/store.hpp +++ b/nano/secure/store.hpp @@ -725,7 +725,14 @@ class confirmation_height_store { public: virtual void put (nano::write_transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info const & confirmation_height_info_a) = 0; + + /** Retrieves confirmation height info relating to an account. + * The parameter confirmation_height_info_a is always written. + * On error, the confirmation height and frontier hash are set to 0. + * Ruturns true on error, false on success. + */ virtual bool get (nano::transaction const & transaction_a, nano::account const & account_a, nano::confirmation_height_info & confirmation_height_info_a) = 0; + virtual bool exists (nano::transaction const & transaction_a, nano::account const & account_a) const = 0; virtual void del (nano::write_transaction const & transaction_a, nano::account const & account_a) = 0; virtual uint64_t count (nano::transaction const & transaction_a) = 0;