Add receive_hash option for the blocks_info RPC (#3702)
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 <dimitris@siganos.org> Co-authored-by: Thiago Silva <thiago@nano.org>
This commit is contained in:
parent
a37a1480d8
commit
4752c66c80
6 changed files with 184 additions and 6 deletions
|
@ -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);
|
||||
|
@ -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 ())
|
||||
{
|
||||
exists = node.store.pending.exists (transaction, nano::pending_key (destination, hash));
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
|
@ -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"));
|
||||
|
@ -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;
|
||||
|
|
|
@ -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{};
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -63,6 +63,7 @@ public:
|
|||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue