Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add receive_hash option for the blocks_info RPC #3702

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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