From a35a89037e9b18411e5be01edb5da2088b942b12 Mon Sep 17 00:00:00 2001 From: MajorChump <18538429+MajorChump@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:02:53 +0100 Subject: [PATCH] Update confirmation_info RPC call to include representatives final votes for monitoring purposes (#3734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update confirmation_info RPC call to include representatives final votes for monitoring purposes * Make changes backwards compatitable and fix tests * Keep representative list sorted * Tests --------- Co-authored-by: Piotr Wójcik <3044353+pwojcikdev@users.noreply.github.com> --- nano/node/json_handler.cpp | 21 +++++++-- nano/rpc_test/rpc.cpp | 89 +++++++++++++++++++++++++++++++++++ nano/test_common/testutil.cpp | 2 +- nano/test_common/testutil.hpp | 2 +- 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index e1076cf52..560b2f453 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -2131,20 +2131,33 @@ void nano::json_handler::confirmation_info () if (representatives) { std::multimap> representatives; + std::multimap> representatives_final; for (auto const & [representative, vote] : info.votes) { if (block->hash () == vote.hash) { auto amount (node.ledger.cache.rep_weights.representation_get (representative)); - representatives.emplace (std::move (amount), representative); + representatives.emplace (amount, representative); + if (vote.timestamp == std::numeric_limits::max ()) + { + representatives_final.emplace (amount, representative); + } } } - boost::property_tree::ptree representatives_list; + + boost::property_tree::ptree representatives_ptree; + boost::property_tree::ptree representatives_ptree_final; for (auto const & [amount, representative] : representatives) { - representatives_list.put (representative.to_account (), amount.convert_to ()); + representatives_ptree.put (representative.to_account (), amount.convert_to ()); } - entry.add_child ("representatives", representatives_list); + for (auto const & [amount, representative] : representatives_final) + { + representatives_ptree_final.put (representative.to_account (), amount.convert_to ()); + } + + entry.add_child ("representatives", representatives_ptree); + entry.add_child ("representatives_final", representatives_ptree_final); } blocks.add_child ((block->hash ()).to_string (), entry); } diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index f70b7c13c..35b7c3aeb 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -6842,6 +6842,91 @@ TEST (rpc, confirmation_active) } TEST (rpc, confirmation_info) +{ + nano::test::system system; + nano::node_config node_config; + node_config.backlog_scan.enable = false; // Disable backlog scan to avoid unwanted elections + auto node = add_ipc_enabled_node (system, node_config); + auto const rpc_ctx = add_rpc (system, node); + + auto rep1 = nano::test::setup_rep (system, *node, 100 * nano::Knano_ratio); + auto rep2 = nano::test::setup_rep (system, *node, 1000 * nano::Knano_ratio); + auto rep3 = nano::test::setup_rep (system, *node, 2000 * nano::Knano_ratio); + auto rep4 = nano::keypair{}; // Representative with zero weight + auto rep5 = nano::test::setup_rep (system, *node, 1500 * nano::Knano_ratio); // Additional final representative + + nano::block_builder builder; + auto send = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (node->latest (nano::dev::genesis_key.pub)) + .link (nano::public_key{}) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 10000 * nano::Knano_ratio) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (node->latest (nano::dev::genesis_key.pub))) + .build (); + node->process_active (send); + + ASSERT_TIMELY (5s, !node->active.empty ()); + + auto election = node->active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + + auto vote1 = nano::test::make_vote (rep1, { send }); + auto vote2 = nano::test::make_vote (rep2, { send }); + auto vote3 = nano::test::make_final_vote (rep3, { send }); + auto vote4 = nano::test::make_vote (rep4, { send }); // Add vote from zero-weight representative + auto vote5 = nano::test::make_final_vote (rep5, { send }); // Add another final vote + + node->vote_processor.vote_blocking (vote1, nano::test::fake_channel (*node)); + node->vote_processor.vote_blocking (vote2, nano::test::fake_channel (*node)); + node->vote_processor.vote_blocking (vote3, nano::test::fake_channel (*node)); + node->vote_processor.vote_blocking (vote4, nano::test::fake_channel (*node)); + node->vote_processor.vote_blocking (vote5, nano::test::fake_channel (*node)); + + boost::property_tree::ptree request; + request.put ("action", "confirmation_info"); + request.put ("root", send->qualified_root ().to_string ()); + request.put ("representatives", "true"); + request.put ("json_block", "true"); + { + auto response (wait_response (system, rpc_ctx, request)); + + ASSERT_EQ (0, response.get ("announcements")); + // FIXME: The voters and representatives list always contains a "ghost" representative with zero weight, investigate why + ASSERT_EQ (6, response.get ("voters")); + ASSERT_EQ (send->hash ().to_string (), response.get ("last_winner")); + ASSERT_EQ ("4600000000000000000000000000000000000", response.get ("total_tally")); + ASSERT_EQ ("3500000000000000000000000000000000000", response.get ("final_tally")); + + auto & blocks (response.get_child ("blocks")); + ASSERT_EQ (1, blocks.size ()); + + auto block_info = blocks.find (send->hash ().to_string ()); + ASSERT_NE (block_info, blocks.not_found ()); + + ASSERT_EQ ("4600000000000000000000000000000000000", block_info->second.get ("tally")); + + auto & representatives = block_info->second.get_child ("representatives"); + ASSERT_EQ (6, representatives.size ()); + ASSERT_EQ ("100000000000000000000000000000000000", representatives.get (rep1.pub.to_account ())); + ASSERT_EQ ("1000000000000000000000000000000000000", representatives.get (rep2.pub.to_account ())); + ASSERT_EQ ("2000000000000000000000000000000000000", representatives.get (rep3.pub.to_account ())); + ASSERT_EQ ("0", representatives.get (rep4.pub.to_account ())); + ASSERT_EQ ("1500000000000000000000000000000000000", representatives.get (rep5.pub.to_account ())); + + auto & representatives_final = block_info->second.get_child ("representatives_final"); + ASSERT_EQ (2, representatives_final.size ()); + ASSERT_EQ ("2000000000000000000000000000000000000", representatives_final.get (rep3.pub.to_account ())); + ASSERT_EQ ("1500000000000000000000000000000000000", representatives_final.get (rep5.pub.to_account ())); + + // Verify the contents field exists + ASSERT_TRUE (block_info->second.get_child_optional ("contents").is_initialized ()); + } +} + +TEST (rpc, confirmation_info_empty) { nano::test::system system; auto node1 = add_ipc_enabled_node (system); @@ -6867,13 +6952,17 @@ TEST (rpc, confirmation_info) { auto response (wait_response (system, rpc_ctx, request)); ASSERT_EQ (1, response.count ("announcements")); + // FIXME: The voters and representatives list always contains a "ghost" representative with zero weight, investigate why ASSERT_EQ (1, response.get ("voters")); ASSERT_EQ (send->hash ().to_string (), response.get ("last_winner")); auto & blocks (response.get_child ("blocks")); ASSERT_EQ (1, blocks.size ()); auto & representatives (blocks.front ().second.get_child ("representatives")); ASSERT_EQ (1, representatives.size ()); + auto & representatives_final (blocks.front ().second.get_child ("representatives_final")); + ASSERT_EQ (0, representatives_final.size ()); ASSERT_EQ (0, response.get ("total_tally")); + ASSERT_EQ (0, response.get ("final_tally")); } } diff --git a/nano/test_common/testutil.cpp b/nano/test_common/testutil.cpp index 0224b0e58..9dbe4210d 100644 --- a/nano/test_common/testutil.cpp +++ b/nano/test_common/testutil.cpp @@ -246,7 +246,7 @@ std::vector> nano::test::clone (std::vector nano::test::fake_channel (nano::node & node, nano::account node_id) +std::shared_ptr nano::test::fake_channel (nano::node & node, nano::account node_id) { auto channel = std::make_shared (node); if (!node_id.is_zero ()) diff --git a/nano/test_common/testutil.hpp b/nano/test_common/testutil.hpp index 3f5e0ffdb..b0e162f59 100644 --- a/nano/test_common/testutil.hpp +++ b/nano/test_common/testutil.hpp @@ -385,7 +385,7 @@ namespace test /* * Creates a new fake channel associated with `node` */ - std::shared_ptr fake_channel (nano::node & node, nano::account node_id = { 0 }); + std::shared_ptr fake_channel (nano::node & node, nano::account node_id = { 0 }); /* * Creates a new test channel associated with `node` */