Skip to content

Commit

Permalink
Add receive_hash option for the blocks_info RPC
Browse files Browse the repository at this point in the history
Added the function nano::node::find_receive_block_by_send_hash to
find the receive block linked with a send block for better code reuse.

Co-authored-by: Dimitrios Siganos <[email protected]>
Co-authored-by: Thiago Silva <[email protected]>
  • Loading branch information
3 people committed Mar 28, 2022
1 parent a37a148 commit 77c9fda
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 6 deletions.
44 changes: 38 additions & 6 deletions nano/node/json_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,7 @@ void nano::json_handler::blocks_info ()
{
bool const pending = request.get<bool> ("pending", false);
bool const receivable = request.get<bool> ("receivable", pending);
bool const receive_hash = request.get<bool> ("receive_hash", false);
bool const source = request.get<bool> ("source", false);
bool const json_block_l = request.get<bool> ("json_block", false);
bool const include_not_found = request.get<bool> ("include_not_found", false);
Expand Down Expand Up @@ -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<nano::block> 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)
{
Expand Down
85 changes: 85 additions & 0 deletions nano/rpc_test/rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
#include <boost/property_tree/json_parser.hpp>

#include <algorithm>
#include <map>
#include <tuple>
#include <utility>

using namespace std::chrono_literals;

Expand Down Expand Up @@ -4062,6 +4064,8 @@ TEST (rpc, blocks_info)
ASSERT_FALSE (blocks_text.empty ());
boost::optional<std::string> receivable (blocks.second.get_optional<std::string> ("receivable"));
ASSERT_FALSE (receivable.is_initialized ());
boost::optional<std::string> receive_hash (blocks.second.get_optional<std::string> ("receive_hash"));
ASSERT_FALSE (receive_hash.is_initialized ());
boost::optional<std::string> source (blocks.second.get_optional<std::string> ("source_account"));
ASSERT_FALSE (source.is_initialized ());
std::string balance_text (blocks.second.get<std::string> ("balance"));
Expand Down Expand Up @@ -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<std::string> ("source_account"));
ASSERT_EQ ("0", blocks.second.get<std::string> ("receivable"));
std::string receive_hash (blocks.second.get<std::string> ("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<std::string, std::string> 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<std::string> ("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;
Expand Down
5 changes: 5 additions & 0 deletions nano/secure/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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{};
};

Expand Down
48 changes: 48 additions & 0 deletions nano/secure/ledger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,54 @@ std::array<nano::block_hash, 2> 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::block> 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<nano::block> 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<nano::state_block const *> (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));
Expand Down
1 change: 1 addition & 0 deletions nano/secure/ledger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<nano::block_hash, 2> dependent_blocks (nano::transaction const &, nano::block const &) const;
std::shared_ptr<nano::block> 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<uint64_t, uncemented_info, std::greater<>> unconfirmed_frontiers () const;
Expand Down
7 changes: 7 additions & 0 deletions nano/secure/store.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 77c9fda

Please sign in to comment.