From 16e0b5353a7531a13c70ee27d93dbcafe157842f Mon Sep 17 00:00:00 2001 From: Blockchain Projects BV Date: Tue, 27 Mar 2018 19:30:36 +0300 Subject: [PATCH] Added Pending-transactions --- libraries/app/api.cpp | 50 +++++ libraries/app/include/graphene/app/api.hpp | 23 +++ tests/tests/network_node_api_tests.cpp | 202 +++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 tests/tests/network_node_api_tests.cpp diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 6c6359c2..12f2cb9d 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -44,6 +44,18 @@ namespace graphene { namespace app { +namespace { + + std::vector::const_iterator find_transaction( const std::vector& transactions, const transaction& transaction_to_find ) + { + auto transaction_it = std::find_if(transactions.begin(), transactions.end(), + [&]( const signed_transaction& transaction ){ + return transaction.id() == transaction_to_find.id(); + }); + return transaction_it; + } +} + login_api::login_api(application& a) :_app(a) { @@ -193,6 +205,29 @@ namespace graphene { namespace app { network_node_api::network_node_api( application& a ) : _app( a ) { + _pending_trx_connection = _app.chain_database()->on_pending_transaction.connect([this]( const signed_transaction& transaction ){ + auto transaction_it = find_transaction(_pending_transactions, transaction); + if (_pending_transactions.end() == transaction_it) + { + _pending_transactions.push_back(transaction); + } + + if (_on_pending_transaction) + { + _on_pending_transaction(fc::variant(transaction)); + } + }); + + _applied_block_connection = _app.chain_database()->applied_block.connect([this]( const signed_block& block ){ + for (const auto& transaction: block.transactions) + { + auto transaction_it = find_transaction(_pending_transactions, transaction); + if (_pending_transactions.end() != transaction_it) + { + _pending_transactions.erase(transaction_it); + } + } + }); } fc::variant_object network_node_api::get_info() const @@ -227,6 +262,21 @@ namespace graphene { namespace app { return _app.p2p_node()->set_advanced_node_parameters(params); } + std::vector network_node_api::list_pending_transactions() const + { + return _pending_transactions; + } + + void network_node_api::subscribe_to_pending_transactions( std::function callback ) + { + _on_pending_transaction = callback; + } + + void network_node_api::unsubscribe_from_pending_transactions() + { + _on_pending_transaction = std::function(); + } + fc::api login_api::network_broadcast()const { FC_ASSERT(_network_broadcast_api); diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index d4532b42..aa28fd6f 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -264,8 +264,28 @@ namespace graphene { namespace app { */ std::vector get_potential_peers() const; + /** + * @brief Return list of pending transactions. + */ + std::vector list_pending_transactions() const; + + /** + * @brief Subscribes caller for notifications about pending transactions. + * @param callback a functional object which will be called when new transaction is created. + */ + void subscribe_to_pending_transactions(std::function callback); + + /** + * @brief Unsubscribes caller from notifications about pending transactions. + */ + void unsubscribe_from_pending_transactions(); + private: application& _app; + std::vector< signed_transaction > _pending_transactions; + boost::signals2::scoped_connection _pending_trx_connection; + boost::signals2::scoped_connection _applied_block_connection; + std::function _on_pending_transaction; }; class crypto_api @@ -418,6 +438,9 @@ FC_API(graphene::app::network_node_api, (get_potential_peers) (get_advanced_node_parameters) (set_advanced_node_parameters) + (list_pending_transactions) + (subscribe_to_pending_transactions) + (unsubscribe_from_pending_transactions) ) FC_API(graphene::app::crypto_api, (blind_sign) diff --git a/tests/tests/network_node_api_tests.cpp b/tests/tests/network_node_api_tests.cpp new file mode 100644 index 00000000..93dcf443 --- /dev/null +++ b/tests/tests/network_node_api_tests.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2017 Blockchain BV + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +struct network_node_api_tests_fixture : public database_fixture +{ + processed_transaction push_transaction_for_account_creation(const std::string& account_name) + { + auto account_key = generate_private_key(account_name); + signed_transaction trx; + set_expiration(db, trx); + trx.operations.push_back(make_account(account_name, account_key.get_public_key())); + trx.validate(); + return db.push_transaction(trx, ~0); + } + + void trigger_transactions_applying() + { + db.generate_block(db.get_slot_time(1), + db.get_scheduled_witness(1), + generate_private_key("null_key"), + ~0 | database::skip_undo_history_check); + } + + void check_transactions_equal(const transaction& left, const transaction& right) + { + BOOST_CHECK(left.id() == right.id()); + BOOST_CHECK(left.digest() == right.digest()); + } +}; + +BOOST_FIXTURE_TEST_SUITE(network_node_api_tests, network_node_api_tests_fixture) + +BOOST_AUTO_TEST_CASE(list_pending_proposals_empty) { + try { + graphene::app::network_node_api network_node_api(app); + + auto pending_transactions = network_node_api.list_pending_transactions(); + BOOST_CHECK(pending_transactions.empty()); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(list_pending_proposals_one) { + try { + graphene::app::network_node_api network_node_api(app); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + + auto pending_transactions = network_node_api.list_pending_transactions(); + + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 1); + check_transactions_equal(pending_transactions[0], sam_transaction); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(list_pending_proposals_several) { + try { + graphene::app::network_node_api network_node_api(app); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + auto dan_transaction = push_transaction_for_account_creation("dan"); + + auto pending_transactions = network_node_api.list_pending_transactions(); + + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 2); + check_transactions_equal(pending_transactions[0], sam_transaction); + check_transactions_equal(pending_transactions[1], dan_transaction); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(list_pending_proposals_one_after_applying) { + try { + graphene::app::network_node_api network_node_api(app); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + + auto pending_transactions = network_node_api.list_pending_transactions(); + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 1); + check_transactions_equal(pending_transactions[0], sam_transaction); + + trigger_transactions_applying(); + + pending_transactions = network_node_api.list_pending_transactions(); + BOOST_CHECK(pending_transactions.empty()); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(list_pending_proposals_several_after_applying) { + try { + graphene::app::network_node_api network_node_api(app); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + auto dan_transaction = push_transaction_for_account_creation("dan"); + + auto pending_transactions = network_node_api.list_pending_transactions(); + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 2); + check_transactions_equal(pending_transactions[0], sam_transaction); + check_transactions_equal(pending_transactions[1], dan_transaction); + + trigger_transactions_applying(); + + pending_transactions = network_node_api.list_pending_transactions(); + BOOST_CHECK(pending_transactions.empty()); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(list_pending_proposals_postponed) { + try { + graphene::app::network_node_api network_node_api(app); + + db.modify(db.get_global_properties(), [](global_property_object& properties) { + //Size in bytes. Empiricaly found to limit block size for two test transactions + properties.parameters.maximum_block_size = 650; + }); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + auto dan_transaction = push_transaction_for_account_creation("dan"); + auto jon_transaction = push_transaction_for_account_creation("jon"); + + auto pending_transactions = network_node_api.list_pending_transactions(); + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 3); + check_transactions_equal(pending_transactions[0], sam_transaction); + check_transactions_equal(pending_transactions[1], dan_transaction); + check_transactions_equal(pending_transactions[2], jon_transaction); + + trigger_transactions_applying(); + + pending_transactions = network_node_api.list_pending_transactions(); + BOOST_REQUIRE_EQUAL(pending_transactions.size(), 1); + check_transactions_equal(pending_transactions[0], jon_transaction); + + trigger_transactions_applying(); + + pending_transactions = network_node_api.list_pending_transactions(); + BOOST_CHECK(pending_transactions.empty()); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(subscribe_to_pending_transactions) { + try { + graphene::app::network_node_api network_node_api(app); + + signed_transaction transaction_in_notification; + network_node_api.subscribe_to_pending_transactions([&]( const variant& signed_transaction_object ){ + transaction_in_notification = signed_transaction_object.as(); + }); + + auto sam_transaction = push_transaction_for_account_creation("sam"); + check_transactions_equal(sam_transaction, transaction_in_notification); + + auto dan_transaction = push_transaction_for_account_creation("dan"); + check_transactions_equal(dan_transaction, transaction_in_notification); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(unsubscribe_from_pending_transactions) { + try { + graphene::app::network_node_api network_node_api(app); + + network_node_api.subscribe_to_pending_transactions([&]( const variant& signed_transaction_object ){ + BOOST_FAIL("This callback should not be called, because subscription was canceled."); + }); + + network_node_api.unsubscribe_from_pending_transactions(); + + push_transaction_for_account_creation("sam"); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END()