diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 92a839ddd6..1c98f72e8e 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -514,7 +514,8 @@ void nano::json_handler::account_balance () auto account (account_impl ()); if (!ec) { - auto balance (node.balance_pending (account)); + const bool include_only_confirmed = request.get ("include_only_confirmed", false); + auto balance (node.balance_pending (account, include_only_confirmed)); response_l.put ("balance", balance.first.convert_to ()); response_l.put ("pending", balance.second.convert_to ()); } @@ -601,10 +602,11 @@ void nano::json_handler::account_info () const bool representative = request.get ("representative", false); const bool weight = request.get ("weight", false); const bool pending = request.get ("pending", false); + const bool include_confirmed = request.get ("include_confirmed", false); auto transaction (node.store.tx_begin_read ()); auto info (account_info_impl (transaction, account)); nano::confirmation_height_info confirmation_height_info; - if (node.store.confirmation_height_get (transaction, account, confirmation_height_info)) + if (node.store.confirmation_height_get (transaction, account, confirmation_height_info) && include_confirmed) { ec = nano::error_common::account_not_found; } @@ -613,17 +615,67 @@ void nano::json_handler::account_info () response_l.put ("frontier", info.head.to_string ()); response_l.put ("open_block", info.open_block.to_string ()); response_l.put ("representative_block", node.ledger.representative (transaction, info.head).to_string ()); + nano::amount balance_l (info.balance); std::string balance; - nano::uint128_union (info.balance).encode_dec (balance); + balance_l.encode_dec (balance); + response_l.put ("balance", balance); + + nano::amount confirmed_balance_l; + if (include_confirmed) + { + if (info.block_count != confirmation_height_info.height) + { + confirmed_balance_l = node.ledger.balance (transaction, confirmation_height_info.frontier); + } + else + { + // block_height and confirmed height are the same, so can just reuse balance + confirmed_balance_l = balance_l; + } + std::string confirmed_balance; + confirmed_balance_l.encode_dec (confirmed_balance); + response_l.put ("confirmed_balance", confirmed_balance); + } + response_l.put ("modified_timestamp", std::to_string (info.modified)); response_l.put ("block_count", std::to_string (info.block_count)); response_l.put ("account_version", epoch_as_string (info.epoch ())); response_l.put ("confirmation_height", std::to_string (confirmation_height_info.height)); - response_l.put ("confirmation_height_frontier", confirmation_height_info.frontier.to_string ()); + auto confirmed_frontier = confirmation_height_info.frontier.to_string (); + if (include_confirmed) + { + response_l.put ("confirmed_frontier", confirmed_frontier); + } + else + { + // For backwards compatibility purposes + response_l.put ("confirmation_height_frontier", confirmed_frontier); + } + + std::shared_ptr confirmed_frontier_block; + if (include_confirmed && confirmation_height_info.height > 0) + { + confirmed_frontier_block = node.store.block_get (transaction, confirmed_frontier); + } + if (representative) { response_l.put ("representative", info.representative.to_account ()); + if (include_confirmed) + { + nano::account confirmed_representative{ 0 }; + if (confirmed_frontier_block) + { + confirmed_representative = confirmed_frontier_block->representative (); + if (confirmed_representative.is_zero ()) + { + confirmed_representative = node.store.block_get (transaction, node.ledger.representative (transaction, confirmed_frontier))->representative (); + } + } + + response_l.put ("confirmed_representative", confirmed_representative.to_account ()); + } } if (weight) { @@ -634,6 +686,12 @@ void nano::json_handler::account_info () { auto account_pending (node.ledger.account_pending (transaction, account)); response_l.put ("pending", account_pending.convert_to ()); + + if (include_confirmed) + { + auto account_pending (node.ledger.account_pending (transaction, account, true)); + response_l.put ("confirmed_pending", account_pending.convert_to ()); + } } } } @@ -828,7 +886,7 @@ void nano::json_handler::accounts_balances () if (!ec) { boost::property_tree::ptree entry; - auto balance (node.balance_pending (account)); + auto balance (node.balance_pending (account, false)); entry.put ("balance", balance.first.convert_to ()); entry.put ("pending", balance.second.convert_to ()); balances.push_back (std::make_pair (account.to_account (), entry)); @@ -985,7 +1043,7 @@ void nano::json_handler::available_supply () auto genesis_balance (node.balance (node.network_params.ledger.genesis_account)); // Cold storage genesis auto landing_balance (node.balance (nano::account ("059F68AAB29DE0D3A27443625C7EA9CDDB6517A8B76FE37727EF6A4D76832AD5"))); // Active unavailable account auto faucet_balance (node.balance (nano::account ("8E319CE6F3025E5B2DF66DA7AB1467FE48F1679C13DD43BFDB29FA2E9FC40D3B"))); // Faucet account - auto burned_balance ((node.balance_pending (nano::account (0))).second); // Burning 0 account + auto burned_balance ((node.balance_pending (nano::account (0), false)).second); // Burning 0 account auto available (node.network_params.ledger.genesis_amount - genesis_balance - landing_balance - faucet_balance - burned_balance); response_l.put ("available", available.convert_to ()); response_errors (); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 731e0d1e94..09f470f4bb 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -751,12 +751,12 @@ std::shared_ptr nano::node::block (nano::block_hash const & hash_a) return store.block_get (transaction, hash_a); } -std::pair nano::node::balance_pending (nano::account const & account_a) +std::pair nano::node::balance_pending (nano::account const & account_a, bool only_confirmed_a) { std::pair result; auto transaction (store.tx_begin_read ()); - result.first = ledger.account_balance (transaction, account_a); - result.second = ledger.account_pending (transaction, account_a); + result.first = ledger.account_balance (transaction, account_a, only_confirmed_a); + result.second = ledger.account_pending (transaction, account_a, only_confirmed_a); return result; } diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 238bb3cc6f..ce6ae0a7b8 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -119,7 +119,7 @@ class node final : public std::enable_shared_from_this nano::block_hash latest (nano::account const &); nano::uint128_t balance (nano::account const &); std::shared_ptr block (nano::block_hash const &); - std::pair balance_pending (nano::account const &); + std::pair balance_pending (nano::account const &, bool only_confirmed); nano::uint128_t weight (nano::account const &); nano::block_hash rep_block (nano::account const &); nano::uint128_t minimum_principal_weight (); diff --git a/nano/qt/qt.cpp b/nano/qt/qt.cpp index 3d52d76018..97d1972b1c 100644 --- a/nano/qt/qt.cpp +++ b/nano/qt/qt.cpp @@ -774,7 +774,7 @@ wallet (wallet_a) { show_line_ok (*account_line); this->history.refresh (); - auto balance (this->wallet.node.balance_pending (account)); + auto balance (this->wallet.node.balance_pending (account, false)); auto final_text (std::string ("Balance (NANO): ") + wallet.format_balance (balance.first)); if (!balance.second.is_zero ()) { @@ -1105,7 +1105,7 @@ void nano_qt::wallet::ongoing_refresh () if (needs_balance_refresh) { needs_balance_refresh = false; - auto balance_l (node.balance_pending (account)); + auto balance_l (node.balance_pending (account, false)); application.postEvent (&processor, new eventloop_event ([wallet_w, balance_l]() { if (auto this_l = wallet_w.lock ()) { @@ -1451,7 +1451,7 @@ void nano_qt::wallet::change_rendering_ratio (nano::uint128_t const & rendering_ { application.postEvent (&processor, new eventloop_event ([this, rendering_ratio_a]() { this->rendering_ratio = rendering_ratio_a; - auto balance_l (this->node.balance_pending (account)); + auto balance_l (this->node.balance_pending (account, false)); this->self.set_balance_text (balance_l); this->refresh (); })); diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 287ffe2485..0efc4f8d94 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -168,6 +168,22 @@ TEST (rpc, account_balance) { nano::system system; auto node = add_ipc_enabled_node (system); + + // Add a send block (which will add a pending entry too) for the genesis account + nano::state_block_builder builder; + + auto send1 = builder.make_block () + .account (nano::dev_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::dev_genesis_key.pub) + .balance (nano::genesis_amount - 1) + .link (nano::dev_genesis_key.pub) + .sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build (); + + ASSERT_EQ (nano::process_result::progress, node->process (*send1).code); + scoped_io_thread_name_change scoped_thread_name_io; nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (*node, node_rpc_config); @@ -176,16 +192,31 @@ TEST (rpc, account_balance) nano::ipc_rpc_processor ipc_rpc_processor (system.io_ctx, rpc_config); nano::rpc rpc (system.io_ctx, rpc_config, ipc_rpc_processor); rpc.start (); + boost::property_tree::ptree request; request.put ("action", "account_balance"); request.put ("account", nano::dev_genesis_key.pub.to_account ()); - test_response response (request, rpc.config.port, system.io_ctx); - ASSERT_TIMELY (5s, response.status != 0); - ASSERT_EQ (200, response.status); - std::string balance_text (response.json.get ("balance")); - ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); - std::string pending_text (response.json.get ("pending")); - ASSERT_EQ ("0", pending_text); + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + std::string balance_text (response.json.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211454", balance_text); + std::string pending_text (response.json.get ("pending")); + ASSERT_EQ ("1", pending_text); + } + + // The send and pending should be unconfirmed + request.put ("include_only_confirmed", true); + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + std::string balance_text (response.json.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); + std::string pending_text (response.json.get ("pending")); + ASSERT_EQ ("0", pending_text); + } } TEST (rpc, account_block_count) @@ -4797,6 +4828,76 @@ TEST (rpc, account_info) std::string representative2 (response.json.get ("representative")); ASSERT_EQ (nano::dev_genesis_key.pub.to_account (), representative2); } + + // Test for confirmed only blocks + scoped_thread_name_io.reset (); + nano::keypair key1; + { + latest = node1.latest (nano::dev_genesis_key.pub); + nano::send_block send1 (latest, key1.pub, 50, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (latest)); + node1.process (send1); + nano::send_block send2 (send1.hash (), key1.pub, 25, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (send1.hash ())); + node1.process (send2); + + nano::state_block state_change (nano::dev_genesis_key.pub, send2.hash (), key1.pub, 25, 0, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (send2.hash ())); + node1.process (state_change); + + nano::open_block open (send1.hash (), nano::dev_genesis_key.pub, key1.pub, key1.prv, key1.pub, *node1.work_generate_blocking (key1.pub)); + node1.process (open); + } + + scoped_thread_name_io.renew (); + + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + std::string balance (response.json.get ("balance")); + ASSERT_EQ ("25", balance); + } + + request.put ("include_confirmed", true); + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + std::string balance (response.json.get ("balance")); + ASSERT_EQ ("25", balance); + std::string confirmed_balance (response.json.get ("confirmed_balance")); + ASSERT_EQ ("340282366920938463463374607431768211455", confirmed_balance); + + auto representative (response.json.get ("representative")); + ASSERT_EQ (representative, key1.pub.to_account ()); + + auto confirmed_representative (response.json.get ("confirmed_representative")); + ASSERT_EQ (confirmed_representative, nano::dev_genesis_key.pub.to_account ()); + } + + request.put ("account", key1.pub.to_account ()); + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + std::string pending (response.json.get ("pending")); + ASSERT_EQ ("25", pending); + std::string confirmed_pending (response.json.get ("confirmed_pending")); + ASSERT_EQ ("0", confirmed_pending); + } + + request.put ("include_confirmed", false); + { + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + std::string pending (response.json.get ("pending")); + ASSERT_EQ ("25", pending); + + // These fields shouldn't exist + auto confirmed_balance (response.json.get_optional ("confirmed_balance")); + ASSERT_FALSE (confirmed_balance.is_initialized ()); + + auto confirmed_pending (response.json.get_optional ("confirmed_pending")); + ASSERT_FALSE (confirmed_pending.is_initialized ()); + + auto confirmed_representative (response.json.get_optional ("confirmed_representative")); + ASSERT_FALSE (confirmed_representative.is_initialized ()); + } } /** Make sure we can use json block literals instead of string as input */ diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 7eb1a3591a..89b4fc6f02 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -827,26 +827,47 @@ nano::uint128_t nano::ledger::balance_safe (nano::transaction const & transactio } // Balance for an account by account number -nano::uint128_t nano::ledger::account_balance (nano::transaction const & transaction_a, nano::account const & account_a) +nano::uint128_t nano::ledger::account_balance (nano::transaction const & transaction_a, nano::account const & account_a, bool only_confirmed_a) { nano::uint128_t result (0); - nano::account_info info; - auto none (store.account_get (transaction_a, account_a, info)); - if (!none) + if (only_confirmed_a) + { + nano::confirmation_height_info info; + if (!store.confirmation_height_get (transaction_a, account_a, info)) + { + result = balance (transaction_a, info.frontier); + } + } + else { - result = info.balance.number (); + nano::account_info info; + auto none (store.account_get (transaction_a, account_a, info)); + if (!none) + { + result = info.balance.number (); + } } return result; } -nano::uint128_t nano::ledger::account_pending (nano::transaction const & transaction_a, nano::account const & account_a) +nano::uint128_t nano::ledger::account_pending (nano::transaction const & transaction_a, nano::account const & account_a, bool only_confirmed_a) { nano::uint128_t result (0); nano::account end (account_a.number () + 1); for (auto i (store.pending_begin (transaction_a, nano::pending_key (account_a, 0))), n (store.pending_begin (transaction_a, nano::pending_key (end, 0))); i != n; ++i) { nano::pending_info const & info (i->second); - result += info.amount.number (); + if (only_confirmed_a) + { + if (block_confirmed (transaction_a, i->first.hash) || (pruning && store.pruned_exists (transaction_a, i->first.hash))) + { + result += info.amount.number (); + } + } + else + { + result += info.amount.number (); + } } return result; } diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 392c75b4d3..0d6499f066 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -34,8 +34,8 @@ class ledger final nano::uint128_t amount_safe (nano::transaction const &, nano::block_hash const & hash_a, bool &) const; nano::uint128_t balance (nano::transaction const &, nano::block_hash const &) const; nano::uint128_t balance_safe (nano::transaction const &, nano::block_hash const &, bool &) const; - nano::uint128_t account_balance (nano::transaction const &, nano::account const &); - nano::uint128_t account_pending (nano::transaction const &, nano::account const &); + nano::uint128_t account_balance (nano::transaction const &, nano::account const &, bool = false); + nano::uint128_t account_pending (nano::transaction const &, nano::account const &, bool = false); nano::uint128_t weight (nano::account const &); std::shared_ptr successor (nano::transaction const &, nano::qualified_root const &); std::shared_ptr forked_block (nano::transaction const &, nano::block const &);