Add include_only_confirmed to account_balance & account_info (#3022)

* Add include_only_confirmed to account_balance & account_info

* Don't error if confirmation_height doesn't exist for this account when calling account_info

* Add pruning check in account_pending (cryptocode review)

* Actually still error but only if using include_only_confirmed

* Change to include_confirmed for account_info and add more dedicated entries for it
This commit is contained in:
Wesley Shillingford 2020-10-30 12:43:26 +00:00 committed by GitHub
commit cd7891db1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 209 additions and 29 deletions

View file

@ -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<bool> ("include_only_confirmed", false);
auto balance (node.balance_pending (account, include_only_confirmed));
response_l.put ("balance", balance.first.convert_to<std::string> ());
response_l.put ("pending", balance.second.convert_to<std::string> ());
}
@ -601,10 +602,11 @@ void nano::json_handler::account_info ()
const bool representative = request.get<bool> ("representative", false);
const bool weight = request.get<bool> ("weight", false);
const bool pending = request.get<bool> ("pending", false);
const bool include_confirmed = request.get<bool> ("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<nano::block> 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<std::string> ());
if (include_confirmed)
{
auto account_pending (node.ledger.account_pending (transaction, account, true));
response_l.put ("confirmed_pending", account_pending.convert_to<std::string> ());
}
}
}
}
@ -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<std::string> ());
entry.put ("pending", balance.second.convert_to<std::string> ());
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<std::string> ());
response_errors ();

View file

@ -751,12 +751,12 @@ std::shared_ptr<nano::block> nano::node::block (nano::block_hash const & hash_a)
return store.block_get (transaction, hash_a);
}
std::pair<nano::uint128_t, nano::uint128_t> nano::node::balance_pending (nano::account const & account_a)
std::pair<nano::uint128_t, nano::uint128_t> nano::node::balance_pending (nano::account const & account_a, bool only_confirmed_a)
{
std::pair<nano::uint128_t, nano::uint128_t> 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;
}

View file

@ -119,7 +119,7 @@ public:
nano::block_hash latest (nano::account const &);
nano::uint128_t balance (nano::account const &);
std::shared_ptr<nano::block> block (nano::block_hash const &);
std::pair<nano::uint128_t, nano::uint128_t> balance_pending (nano::account const &);
std::pair<nano::uint128_t, nano::uint128_t> 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 ();

View file

@ -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 ();
}));

View file

@ -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<std::string> ("balance"));
ASSERT_EQ ("340282366920938463463374607431768211455", balance_text);
std::string pending_text (response.json.get<std::string> ("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<std::string> ("balance"));
ASSERT_EQ ("340282366920938463463374607431768211454", balance_text);
std::string pending_text (response.json.get<std::string> ("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<std::string> ("balance"));
ASSERT_EQ ("340282366920938463463374607431768211455", balance_text);
std::string pending_text (response.json.get<std::string> ("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<std::string> ("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<std::string> ("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<std::string> ("balance"));
ASSERT_EQ ("25", balance);
std::string confirmed_balance (response.json.get<std::string> ("confirmed_balance"));
ASSERT_EQ ("340282366920938463463374607431768211455", confirmed_balance);
auto representative (response.json.get<std::string> ("representative"));
ASSERT_EQ (representative, key1.pub.to_account ());
auto confirmed_representative (response.json.get<std::string> ("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<std::string> ("pending"));
ASSERT_EQ ("25", pending);
std::string confirmed_pending (response.json.get<std::string> ("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<std::string> ("pending"));
ASSERT_EQ ("25", pending);
// These fields shouldn't exist
auto confirmed_balance (response.json.get_optional<std::string> ("confirmed_balance"));
ASSERT_FALSE (confirmed_balance.is_initialized ());
auto confirmed_pending (response.json.get_optional<std::string> ("confirmed_pending"));
ASSERT_FALSE (confirmed_pending.is_initialized ());
auto confirmed_representative (response.json.get_optional<std::string> ("confirmed_representative"));
ASSERT_FALSE (confirmed_representative.is_initialized ());
}
}
/** Make sure we can use json block literals instead of string as input */

View file

@ -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)
{
result = info.balance.number ();
nano::confirmation_height_info info;
if (!store.confirmation_height_get (transaction_a, account_a, info))
{
result = balance (transaction_a, info.frontier);
}
}
else
{
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;
}

View file

@ -34,8 +34,8 @@ public:
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<nano::block> successor (nano::transaction const &, nano::qualified_root const &);
std::shared_ptr<nano::block> forked_block (nano::transaction const &, nano::block const &);