diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 66e8c9e9cc..78a137d91b 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1003,8 +1003,12 @@ void nano::json_handler::block_info () { nano::account account (block->account ().is_zero () ? block->sideband ().account : block->account ()); response_l.put ("block_account", account.to_account ()); - auto amount (node.ledger.amount (transaction, hash)); - response_l.put ("amount", amount.convert_to ()); + bool error_or_pruned (false); + auto amount (node.ledger.amount_safe (transaction, hash, error_or_pruned)); + if (!error_or_pruned) + { + response_l.put ("amount", amount.convert_to ()); + } auto balance (node.ledger.balance (transaction, hash)); response_l.put ("balance", balance.convert_to ()); response_l.put ("height", std::to_string (block->sideband ().height)); @@ -1067,11 +1071,15 @@ void nano::json_handler::block_confirm () // Trigger callback for confirmed block node.block_arrival.add (hash); auto account (node.ledger.account (transaction, hash)); - auto amount (node.ledger.amount (transaction, hash)); + bool error_or_pruned (false); + auto amount (node.ledger.amount_safe (transaction, hash, error_or_pruned)); bool is_state_send (false); - if (auto state = dynamic_cast (block_l.get ())) + if (!error_or_pruned) { - is_state_send = node.ledger.is_send (transaction, *state); + if (auto state = dynamic_cast (block_l.get ())) + { + is_state_send = node.ledger.is_send (transaction, *state); + } } node.observers.blocks.notify (status, account, amount, is_state_send); } @@ -1153,8 +1161,12 @@ void nano::json_handler::blocks_info () boost::property_tree::ptree entry; nano::account account (block->account ().is_zero () ? block->sideband ().account : block->account ()); entry.put ("block_account", account.to_account ()); - auto amount (node.ledger.amount (transaction, hash)); - entry.put ("amount", amount.convert_to ()); + bool error_or_pruned (false); + auto amount (node.ledger.amount_safe (transaction, hash, error_or_pruned)); + if (!error_or_pruned) + { + entry.put ("amount", amount.convert_to ()); + } auto balance (node.ledger.balance (transaction, hash)); entry.put ("balance", balance.convert_to ()); entry.put ("height", std::to_string (block->sideband ().height)); @@ -1257,6 +1269,11 @@ void nano::json_handler::block_count () response_l.put ("count", std::to_string (node.ledger.cache.block_count)); response_l.put ("unchecked", std::to_string (node.store.unchecked_count (node.store.tx_begin_read ()))); response_l.put ("cemented", std::to_string (node.ledger.cache.cemented_count)); + if (node.flags.enable_pruning) + { + response_l.put ("full", std::to_string (node.ledger.cache.block_count - node.ledger.cache.pruned_count)); + response_l.put ("pruned", std::to_string (node.ledger.cache.pruned_count)); + } response_errors (); } @@ -2180,8 +2197,12 @@ class history_visitor : public nano::block_visitor tree.put ("type", "send"); auto account (block_a.hashables.destination.to_account ()); tree.put ("account", account); - auto amount (handler.node.ledger.amount (transaction, hash).convert_to ()); - tree.put ("amount", amount); + bool error_or_pruned (false); + auto amount (handler.node.ledger.amount_safe (transaction, hash, error_or_pruned).convert_to ()); + if (!error_or_pruned) + { + tree.put ("amount", amount); + } if (raw) { tree.put ("destination", account); @@ -2192,10 +2213,17 @@ class history_visitor : public nano::block_visitor void receive_block (nano::receive_block const & block_a) { tree.put ("type", "receive"); - auto account (handler.node.ledger.account (transaction, block_a.hashables.source).to_account ()); - tree.put ("account", account); - auto amount (handler.node.ledger.amount (transaction, hash).convert_to ()); - tree.put ("amount", amount); + bool error_or_pruned (false); + auto amount (handler.node.ledger.amount_safe (transaction, hash, error_or_pruned).convert_to ()); + if (!error_or_pruned) + { + auto source_account (handler.node.ledger.account_safe (transaction, block_a.hashables.source, error_or_pruned)); + if (!error_or_pruned) + { + tree.put ("account", source_account.to_account ()); + } + tree.put ("amount", amount); + } if (raw) { tree.put ("source", block_a.hashables.source.to_string ()); @@ -2218,8 +2246,17 @@ class history_visitor : public nano::block_visitor } if (block_a.hashables.source != network_params.ledger.genesis_account) { - tree.put ("account", handler.node.ledger.account (transaction, block_a.hashables.source).to_account ()); - tree.put ("amount", handler.node.ledger.amount (transaction, hash).convert_to ()); + bool error_or_pruned (false); + auto amount (handler.node.ledger.amount_safe (transaction, hash, error_or_pruned).convert_to ()); + if (!error_or_pruned) + { + auto source_account (handler.node.ledger.account_safe (transaction, block_a.hashables.source, error_or_pruned)); + if (!error_or_pruned) + { + tree.put ("account", source_account.to_account ()); + } + tree.put ("amount", amount); + } } else { @@ -2247,8 +2284,20 @@ class history_visitor : public nano::block_visitor tree.put ("previous", block_a.hashables.previous.to_string ()); } auto balance (block_a.hashables.balance.number ()); - auto previous_balance (handler.node.ledger.balance (transaction, block_a.hashables.previous)); - if (balance < previous_balance) + bool error_or_pruned (false); + auto previous_balance (handler.node.ledger.balance_safe (transaction, block_a.hashables.previous, error_or_pruned)); + if (error_or_pruned) + { + if (raw) + { + tree.put ("subtype", "unknown"); + } + else + { + tree.put ("type", "unknown"); + } + } + else if (balance < previous_balance) { if (should_ignore_account (block_a.hashables.link.as_account ())) { @@ -2285,8 +2334,8 @@ class history_visitor : public nano::block_visitor } else { - auto account (handler.node.ledger.account (transaction, block_a.hashables.link.as_block_hash ())); - if (should_ignore_account (account)) + auto source_account (handler.node.ledger.account_safe (transaction, block_a.hashables.link.as_block_hash (), error_or_pruned)); + if (!error_or_pruned && should_ignore_account (source_account)) { tree.clear (); return; @@ -2299,7 +2348,10 @@ class history_visitor : public nano::block_visitor { tree.put ("type", "receive"); } - tree.put ("account", account.to_account ()); + if (!error_or_pruned) + { + tree.put ("account", source_account.to_account ()); + } tree.put ("amount", (balance - previous_balance).convert_to ()); } } @@ -3176,14 +3228,15 @@ void nano::json_handler::receive () auto block (node.store.block_get (block_transaction, hash)); if (block != nullptr) { - if (node.store.pending_exists (block_transaction, nano::pending_key (account, hash))) + nano::pending_info pending_info; + if (!node.store.pending_get (block_transaction, nano::pending_key (account, hash), pending_info)) { auto work (work_optional_impl ()); if (!ec && work) { nano::account_info info; nano::root head; - nano::epoch epoch = block->sideband ().details.epoch; + nano::epoch epoch = pending_info.epoch; if (!node.store.account_get (block_transaction, account, info)) { head = info.head; @@ -4681,7 +4734,14 @@ void nano::json_handler::wallet_republish () { hashes.push_back (latest); block = node.store.block_get (block_transaction, latest); - latest = block->previous (); + if (block != nullptr) + { + latest = block->previous (); + } + else + { + latest.clear (); + } } std::reverse (hashes.begin (), hashes.end ()); for (auto & hash : hashes) diff --git a/nano/qt/qt.cpp b/nano/qt/qt.cpp index 2b36a58f8a..3d52d76018 100644 --- a/nano/qt/qt.cpp +++ b/nano/qt/qt.cpp @@ -522,13 +522,23 @@ class short_text_visitor : public nano::block_visitor { type = "Send"; account = block_a.hashables.destination; - amount = ledger.amount (transaction, block_a.hash ()); + bool error_or_pruned (false); + amount = ledger.amount_safe (transaction, block_a.hash (), error_or_pruned); + if (error_or_pruned) + { + type = "Send (pruned)"; + } } void receive_block (nano::receive_block const & block_a) { type = "Receive"; - account = ledger.account (transaction, block_a.source ()); - amount = ledger.amount (transaction, block_a.source ()); + bool error_or_pruned (false); + account = ledger.account_safe (transaction, block_a.hashables.source, error_or_pruned); + amount = ledger.amount_safe (transaction, block_a.hash (), error_or_pruned); + if (error_or_pruned) + { + type = "Receive (pruned)"; + } } void open_block (nano::open_block const & block_a) { @@ -536,8 +546,13 @@ class short_text_visitor : public nano::block_visitor type = "Receive"; if (block_a.hashables.source != params.ledger.genesis_account) { - account = ledger.account (transaction, block_a.hashables.source); - amount = ledger.amount (transaction, block_a.hash ()); + bool error_or_pruned (false); + account = ledger.account_safe (transaction, block_a.hashables.source, error_or_pruned); + amount = ledger.amount_safe (transaction, block_a.hash (), error_or_pruned); + if (error_or_pruned) + { + type = "Receive (pruned)"; + } } else { @@ -554,8 +569,15 @@ class short_text_visitor : public nano::block_visitor void state_block (nano::state_block const & block_a) { auto balance (block_a.hashables.balance.number ()); - auto previous_balance (ledger.balance (transaction, block_a.hashables.previous)); - if (balance < previous_balance) + bool error_or_pruned (false); + auto previous_balance (ledger.balance_safe (transaction, block_a.hashables.previous, error_or_pruned)); + if (error_or_pruned) + { + type = "Unknown (pruned)"; + amount = 0; + account = block_a.hashables.account; + } + else if (balance < previous_balance) { type = "Send"; amount = previous_balance - balance; @@ -576,7 +598,11 @@ class short_text_visitor : public nano::block_visitor else { type = "Receive"; - account = ledger.account (transaction, block_a.hashables.link.as_block_hash ()); + account = ledger.account_safe (transaction, block_a.hashables.link.as_block_hash (), error_or_pruned); + if (error_or_pruned) + { + type = "Receive (pruned)"; + } } amount = balance - previous_balance; } @@ -599,16 +625,18 @@ void nano_qt::history::refresh () { QList items; auto block (ledger.store.block_get (transaction, hash)); - debug_assert (block != nullptr); - block->visit (visitor); - items.push_back (new QStandardItem (QString (visitor.type.c_str ()))); - items.push_back (new QStandardItem (QString (visitor.account.to_account ().c_str ()))); - auto balanceItem = new QStandardItem (QString (wallet.format_balance (visitor.amount).c_str ())); - balanceItem->setData (Qt::AlignRight, Qt::TextAlignmentRole); - items.push_back (balanceItem); - items.push_back (new QStandardItem (QString (hash.to_string ().c_str ()))); - hash = block->previous (); - model->appendRow (items); + if (block != nullptr) + { + block->visit (visitor); + items.push_back (new QStandardItem (QString (visitor.type.c_str ()))); + items.push_back (new QStandardItem (QString (visitor.account.to_account ().c_str ()))); + auto balanceItem = new QStandardItem (QString (wallet.format_balance (visitor.amount).c_str ())); + balanceItem->setData (Qt::AlignRight, Qt::TextAlignmentRole); + items.push_back (balanceItem); + items.push_back (new QStandardItem (QString (hash.to_string ().c_str ()))); + hash = block->previous (); + model->appendRow (items); + } } } diff --git a/nano/qt_test/qt.cpp b/nano/qt_test/qt.cpp index 6ad095d97f..1cdf9eb56c 100644 --- a/nano/qt_test/qt.cpp +++ b/nano/qt_test/qt.cpp @@ -501,6 +501,78 @@ TEST (history, short_text) ASSERT_EQ (4, history.model->rowCount ()); } +TEST (history, pruned_source) +{ + if (nano::using_rocksdb_in_tests ()) + { + // Don't test this in rocksdb mode + return; + } + nano_qt::eventloop_processor processor; + nano::keypair key; + nano::system system (1); + system.wallet (0)->insert_adhoc (key.prv); + nano::account account; + { + auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + account = system.account (transaction, 0); + } + auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), account)); + auto store = nano::make_store (system.nodes[0]->logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::genesis genesis; + nano::ledger ledger (*store, system.nodes[0]->stats); + ledger.pruning = true; + nano::block_hash next_pruning; + // Basic pruning for legacy blocks. Previous block is pruned, source is pruned + { + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + auto latest (ledger.latest (transaction, nano::dev_genesis_key.pub)); + nano::send_block send1 (latest, nano::dev_genesis_key.pub, nano::genesis_amount - 100, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *system.work.generate (latest)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); + nano::send_block send2 (send1.hash (), key.pub, nano::genesis_amount - 200, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *system.work.generate (send1.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send2).code); + nano::receive_block receive (send2.hash (), send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *system.work.generate (send2.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive).code); + nano::open_block open (send2.hash (), key.pub, key.pub, key.prv, key.pub, *system.work.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open).code); + ASSERT_EQ (1, ledger.pruning_action (transaction, send1.hash (), 2)); + next_pruning = send2.hash (); + } + nano_qt::history history1 (ledger, nano::dev_genesis_key.pub, *wallet); + history1.refresh (); + ASSERT_EQ (2, history1.model->rowCount ()); + nano_qt::history history2 (ledger, key.pub, *wallet); + history2.refresh (); + ASSERT_EQ (1, history2.model->rowCount ()); + // Additional legacy test + { + auto transaction (store->tx_begin_write ()); + ASSERT_EQ (1, ledger.pruning_action (transaction, next_pruning, 2)); + } + history1.refresh (); + ASSERT_EQ (1, history1.model->rowCount ()); + history2.refresh (); + ASSERT_EQ (1, history2.model->rowCount ()); + // Pruning for state blocks. Previous block is pruned, source is pruned + { + auto transaction (store->tx_begin_write ()); + auto latest (ledger.latest (transaction, nano::dev_genesis_key.pub)); + nano::state_block send (nano::dev_genesis_key.pub, latest, nano::dev_genesis_key.pub, nano::genesis_amount - 200, key.pub, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *system.work.generate (latest)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send).code); + auto latest_key (ledger.latest (transaction, key.pub)); + nano::state_block receive (key.pub, latest_key, key.pub, 200, send.hash (), key.prv, key.pub, *system.work.generate (latest_key)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive).code); + ASSERT_EQ (1, ledger.pruning_action (transaction, latest, 2)); + ASSERT_EQ (1, ledger.pruning_action (transaction, latest_key, 2)); + } + history1.refresh (); + ASSERT_EQ (1, history1.model->rowCount ()); + history2.refresh (); + ASSERT_EQ (1, history2.model->rowCount ()); +} + TEST (wallet, startup_work) { nano_qt::eventloop_processor processor; diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 18c0f25d7d..3426d46112 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -1562,6 +1562,155 @@ TEST (rpc, history_count) ASSERT_EQ (1, history_node.size ()); } +TEST (rpc, history_pruning) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; // Remove after allowing pruned voting + nano::node_flags node_flags; + node_flags.enable_pruning = true; + auto node0 = add_ipc_enabled_node (system, node_config, node_flags); + nano::genesis genesis; + auto change (std::make_shared (genesis.hash (), nano::dev_genesis_key.pub, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work.generate (genesis.hash ()))); + node0->process_active (change); + auto send (std::make_shared (change->hash (), nano::dev_genesis_key.pub, nano::genesis_amount - node0->config.receive_minimum.number (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work.generate (change->hash ()))); + node0->process_active (send); + auto receive (std::make_shared (send->hash (), send->hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work.generate (send->hash ()))); + node0->process_active (receive); + auto usend (std::make_shared (nano::genesis_account, receive->hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work_generate_blocking (receive->hash ()))); + auto ureceive (std::make_shared (nano::genesis_account, usend->hash (), nano::genesis_account, nano::genesis_amount, usend->hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work_generate_blocking (usend->hash ()))); + auto uchange (std::make_shared (nano::genesis_account, ureceive->hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node0->work_generate_blocking (ureceive->hash ()))); + node0->process_active (usend); + node0->process_active (ureceive); + node0->process_active (uchange); + node0->block_processor.flush (); + system.wallet (0)->insert_adhoc (nano::dev_genesis_key.prv); + // Confirm last block to prune previous + { + auto election = node0->active.election (change->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->block_confirmed (change->hash ()) && node0->active.active (send->qualified_root ())); + { + auto election = node0->active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->block_confirmed (send->hash ()) && node0->active.active (receive->qualified_root ())); + { + auto election = node0->active.election (receive->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->block_confirmed (receive->hash ()) && node0->active.active (usend->qualified_root ())); + { + auto election = node0->active.election (usend->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->block_confirmed (usend->hash ()) && node0->active.active (ureceive->qualified_root ())); + { + auto election = node0->active.election (ureceive->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->block_confirmed (ureceive->hash ()) && node0->active.active (uchange->qualified_root ())); + { + auto election = node0->active.election (uchange->qualified_root ()); + ASSERT_NE (nullptr, election); + election->force_confirm (); + } + ASSERT_TIMELY (2s, node0->active.empty () && node0->block_confirmed (uchange->hash ())); + ASSERT_TIMELY (2s, node0->ledger.cache.cemented_count == 7 && node0->confirmation_height_processor.current ().is_zero () && node0->confirmation_height_processor.awaiting_processing_size () == 0); + // Pruning action + { + auto transaction (node0->store.tx_begin_write ()); + ASSERT_EQ (1, node0->ledger.pruning_action (transaction, change->hash (), 1)); + } + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (*node0, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node0->config.ipc_config.transport_tcp.port; + 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", "history"); + request.put ("hash", send->hash ().to_string ()); + request.put ("count", 100); + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + std::vector> history_l; + auto & history_node (response.json.get_child ("history")); + for (auto i (history_node.begin ()), n (history_node.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account", "-1"), i->second.get ("amount", "-1"), i->second.get ("hash"))); + boost::optional amount (i->second.get_optional ("amount")); + ASSERT_FALSE (amount.is_initialized ()); // Cannot calculate amount + } + ASSERT_EQ (1, history_l.size ()); + ASSERT_EQ ("send", std::get<0> (history_l[0])); + ASSERT_EQ (nano::dev_genesis_key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ ("-1", std::get<2> (history_l[0])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[0])); + // Pruning action + { + auto transaction (node0->store.tx_begin_write ()); + ASSERT_EQ (1, node0->ledger.pruning_action (transaction, send->hash (), 1)); + } + boost::property_tree::ptree request2; + request2.put ("action", "history"); + request2.put ("hash", receive->hash ().to_string ()); + request2.put ("count", 100); + test_response response2 (request2, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response2.status != 0); + ASSERT_EQ (200, response2.status); + history_l.clear (); + auto & history_node2 (response2.json.get_child ("history")); + for (auto i (history_node2.begin ()), n (history_node2.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account", "-1"), i->second.get ("amount", "-1"), i->second.get ("hash"))); + boost::optional amount (i->second.get_optional ("amount")); + ASSERT_FALSE (amount.is_initialized ()); // Cannot calculate amount + boost::optional account (i->second.get_optional ("account")); + ASSERT_FALSE (account.is_initialized ()); // Cannot find source account + } + ASSERT_EQ (1, history_l.size ()); + ASSERT_EQ ("receive", std::get<0> (history_l[0])); + ASSERT_EQ ("-1", std::get<1> (history_l[0])); + ASSERT_EQ ("-1", std::get<2> (history_l[0])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[0])); + // Pruning action + { + auto transaction (node0->store.tx_begin_write ()); + ASSERT_EQ (1, node0->ledger.pruning_action (transaction, receive->hash (), 1)); + } + boost::property_tree::ptree request3; + request3.put ("action", "history"); + request3.put ("hash", uchange->hash ().to_string ()); + request3.put ("count", 100); + test_response response3 (request3, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response3.status != 0); + ASSERT_EQ (200, response3.status); + history_l.clear (); + auto & history_node3 (response3.json.get_child ("history")); + for (auto i (history_node3.begin ()), n (history_node3.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account", "-1"), i->second.get ("amount", "-1"), i->second.get ("hash"))); + } + ASSERT_EQ (2, history_l.size ()); + ASSERT_EQ ("receive", std::get<0> (history_l[0])); + ASSERT_EQ (ureceive->hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (nano::dev_genesis_key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[0])); + ASSERT_EQ ("unknown", std::get<0> (history_l[1])); + ASSERT_EQ ("-1", std::get<1> (history_l[1])); + ASSERT_EQ ("-1", std::get<2> (history_l[1])); + ASSERT_EQ (usend->hash ().to_string (), std::get<3> (history_l[1])); +} + TEST (rpc, process_block) { nano::system system; @@ -3180,6 +3329,50 @@ TEST (rpc, block_count) } } +TEST (rpc, block_count_pruning) +{ + nano::system system; + auto & node0 = *system.add_node (); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; // Remove after allowing pruned voting + nano::node_flags node_flags; + node_flags.enable_pruning = true; + auto & node1 = *add_ipc_enabled_node (system, node_config, node_flags); + auto latest (node1.latest (nano::dev_genesis_key.pub)); + auto send1 (std::make_shared (latest, nano::dev_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (latest))); + node1.process_active (send1); + auto receive1 (std::make_shared (send1->hash (), send1->hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (send1->hash ()))); + node1.process_active (receive1); + node1.block_processor.flush (); + system.wallet (0)->insert_adhoc (nano::dev_genesis_key.prv); + ASSERT_TIMELY (5s, node1.ledger.cache.cemented_count == 3 && node1.confirmation_height_processor.current ().is_zero () && node1.confirmation_height_processor.awaiting_processing_size () == 0); + // Pruning action + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (1, node1.ledger.pruning_action (transaction, send1->hash (), 1)); + } + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + 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 request1; + request1.put ("action", "block_count"); + { + test_response response1 (request1, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (10s, response1.status != 0); + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("3", response1.json.get ("count")); + ASSERT_EQ ("0", response1.json.get ("unchecked")); + ASSERT_EQ ("3", response1.json.get ("cemented")); + ASSERT_EQ ("2", response1.json.get ("full")); + ASSERT_EQ ("1", response1.json.get ("pruned")); + } +} + TEST (rpc, frontier_count) { nano::system system; @@ -5059,6 +5252,65 @@ TEST (rpc, blocks_info_subtype) ASSERT_EQ (change_subtype, "change"); } +TEST (rpc, block_info_pruning) +{ + nano::system system; + auto & node0 = *system.add_node (); + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.enable_voting = false; // Remove after allowing pruned voting + nano::node_flags node_flags; + node_flags.enable_pruning = true; + auto & node1 = *add_ipc_enabled_node (system, node_config, node_flags); + auto latest (node1.latest (nano::dev_genesis_key.pub)); + auto send1 (std::make_shared (latest, nano::dev_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (latest))); + node1.process_active (send1); + auto receive1 (std::make_shared (send1->hash (), send1->hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *node1.work_generate_blocking (send1->hash ()))); + node1.process_active (receive1); + node1.block_processor.flush (); + system.wallet (0)->insert_adhoc (nano::dev_genesis_key.prv); + ASSERT_TIMELY (5s, node1.ledger.cache.cemented_count == 3 && node1.confirmation_height_processor.current ().is_zero () && node1.confirmation_height_processor.awaiting_processing_size () == 0); + // Pruning action + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (1, node1.ledger.pruning_action (transaction, send1->hash (), 1)); + } + scoped_io_thread_name_change scoped_thread_name_io; + nano::node_rpc_config node_rpc_config; + nano::ipc::ipc_server ipc_server (node1, node_rpc_config); + nano::rpc_config rpc_config (nano::get_available_port (), true); + rpc_config.rpc_process.ipc_port = node1.config.ipc_config.transport_tcp.port; + 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 (); + // Pruned block + boost::property_tree::ptree request; + request.put ("action", "block_info"); + request.put ("hash", send1->hash ().to_string ()); + test_response response (request, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response.status != 0); + ASSERT_EQ (200, response.status); + ASSERT_EQ (std::error_code (nano::error_blocks::not_found).message (), response.json.get ("error")); + // Existing block with previous pruned + boost::property_tree::ptree request2; + request2.put ("action", "block_info"); + request2.put ("json_block", "true"); + request2.put ("hash", receive1->hash ().to_string ()); + test_response response2 (request2, rpc.config.port, system.io_ctx); + ASSERT_TIMELY (5s, response2.status != 0); + ASSERT_EQ (200, response2.status); + std::string account_text (response2.json.get ("block_account")); + ASSERT_EQ (nano::dev_genesis_key.pub.to_account (), account_text); + boost::optional amount (response2.json.get_optional ("amount")); + ASSERT_FALSE (amount.is_initialized ()); // Cannot calculate amount + bool json_error{ false }; + nano::receive_block receive_from_json (json_error, response2.json.get_child ("contents")); + ASSERT_FALSE (json_error); + ASSERT_EQ (receive1->full_hash (), receive_from_json.full_hash ()); + std::string balance_text (response2.json.get ("balance")); + ASSERT_EQ (nano::genesis_amount.convert_to (), balance_text); + ASSERT_TRUE (response2.json.get ("confirmed")); +} + TEST (rpc, work_peers_all) { nano::system system;