diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index f6607a5e2f..28b30a721b 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -128,7 +128,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) for( const string& endpoint_string : seeds ) { try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); + std::vector endpoints = application::resolve_string_to_ip_endpoints(endpoint_string); for (const fc::ip::endpoint& endpoint : endpoints) { ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); @@ -149,7 +149,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) for( const string& endpoint_string : seeds ) { try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); + std::vector endpoints = application::resolve_string_to_ip_endpoints(endpoint_string); for (const fc::ip::endpoint& endpoint : endpoints) { ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); @@ -188,7 +188,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) for( const string& endpoint_string : seeds ) { try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); + std::vector endpoints = application::resolve_string_to_ip_endpoints(endpoint_string); for (const fc::ip::endpoint& endpoint : endpoints) { ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); @@ -214,36 +214,6 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) std::vector()); } FC_CAPTURE_AND_RETHROW() } -std::vector application_impl::resolve_string_to_ip_endpoints(const std::string& endpoint_string) -{ - try - { - string::size_type colon_pos = endpoint_string.find(':'); - if (colon_pos == std::string::npos) - FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", - ("endpoint_string", endpoint_string)); - std::string port_string = endpoint_string.substr(colon_pos + 1); - try - { - uint16_t port = boost::lexical_cast(port_string); - - std::string hostname = endpoint_string.substr(0, colon_pos); - std::vector endpoints = fc::resolve(hostname, port); - if (endpoints.empty()) - FC_THROW_EXCEPTION( fc::unknown_host_exception, - "The host name can not be resolved: ${hostname}", - ("hostname", hostname) ); - return endpoints; - } - catch (const boost::bad_lexical_cast&) - { - FC_THROW("Bad port: ${port}", ("port", port_string)); - } - } - FC_CAPTURE_AND_RETHROW((endpoint_string)) -} - - void application_impl::new_connection( const fc::http::websocket_connection_ptr& c ) { auto wsc = std::make_shared(*c, GRAPHENE_NET_MAX_NESTED_OBJECTS); @@ -1100,5 +1070,34 @@ const application_options& application::get_options() return my->_app_options; } +std::vector application::resolve_string_to_ip_endpoints(const std::string& endpoint_string) +{ + try + { + string::size_type colon_pos = endpoint_string.find(':'); + if (colon_pos == std::string::npos) + FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", + ("endpoint_string", endpoint_string)); + std::string port_string = endpoint_string.substr(colon_pos + 1); + try + { + uint16_t port = boost::lexical_cast(port_string); + + std::string hostname = endpoint_string.substr(0, colon_pos); + std::vector endpoints = fc::resolve(hostname, port); + if (endpoints.empty()) + FC_THROW_EXCEPTION( fc::unknown_host_exception, + "The host name can not be resolved: ${hostname}", + ("hostname", hostname) ); + return endpoints; + } + catch (const boost::bad_lexical_cast&) + { + FC_THROW("Bad port: ${port}", ("port", port_string)); + } + } + FC_CAPTURE_AND_RETHROW((endpoint_string)) +} + // namespace detail } } diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 2d5d48080d..831b92bc08 100644 --- a/libraries/app/application_impl.hxx +++ b/libraries/app/application_impl.hxx @@ -22,8 +22,6 @@ class application_impl : public net::node_delegate void reset_p2p_node(const fc::path& data_dir); - std::vector resolve_string_to_ip_endpoints(const std::string& endpoint_string); - void new_connection( const fc::http::websocket_connection_ptr& c ); void reset_websocket_server(); diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index 66a73f3999..6b1627249d 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -56,6 +56,7 @@ namespace graphene { namespace app { void shutdown(); void startup_plugins(); void shutdown_plugins(); + static std::vector resolve_string_to_ip_endpoints(const std::string& endpoint_string); template std::shared_ptr register_plugin(bool auto_load = false) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b863c6347e..c8cc3934a1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,11 +16,11 @@ endif(MSVC) file(GLOB PERFORMANCE_TESTS "performance/*.cpp") add_executable( performance_test ${COMMON_SOURCES} ${PERFORMANCE_TESTS} ) -target_link_libraries( performance_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( performance_test graphene_chain graphene_app graphene_witness graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) file(GLOB BENCH_MARKS "benchmarks/*.cpp") add_executable( chain_bench ${COMMON_SOURCES} ${BENCH_MARKS} ) -target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( chain_bench graphene_chain graphene_witness graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) file(GLOB APP_SOURCES "app/*.cpp") add_executable( app_test ${APP_SOURCES} ) @@ -38,6 +38,6 @@ endif(MSVC) file(GLOB ES_SOURCES "elasticsearch/*.cpp") add_executable( es_test ${COMMON_SOURCES} ${ES_SOURCES} ) -target_link_libraries( es_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( es_test graphene_chain graphene_app graphene_witness graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) add_subdirectory( generate_empty_blocks ) diff --git a/tests/common/application_helper.cpp b/tests/common/application_helper.cpp new file mode 100644 index 0000000000..6273108713 --- /dev/null +++ b/tests/common/application_helper.cpp @@ -0,0 +1,138 @@ +#include "application_helper.hpp" +#include "genesis_file_util.hpp" +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #include + #include +int sockInit(void) +{ + WSADATA wsa_data; + return WSAStartup(MAKEWORD(1,1), &wsa_data); +} +int sockQuit(void) +{ + return WSACleanup(); +} +#else + #include + #include + #include +#endif + +#include + +namespace graphene { namespace test { + +application_runner::application_runner() +{ + _app = std::make_shared(); + + _app->register_plugin< graphene::account_history::account_history_plugin >(true); + _app->register_plugin< graphene::market_history::market_history_plugin >(true); + _app->register_plugin< graphene::witness_plugin::witness_plugin >(true); + _app->register_plugin< graphene::grouped_orders::grouped_orders_plugin >(true); + _app->startup_plugins(); +#ifdef _WIN32 + sockInit(); +#endif + rpc_port_number = get_available_port(); + p2p_port_number = get_available_port(); +} + +void application_runner::start() +{ + boost::program_options::variables_map cfg; + cfg.emplace( + "rpc-endpoint", + boost::program_options::variable_value(std::string("127.0.0.1:" + std::to_string(rpc_port_number)), false) + ); + cfg.emplace( "p2p-endpoint", + boost::program_options::variable_value(std::string("127.0.0.1:" + std::to_string(p2p_port_number)), false)); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(_dir), false)); + std::string seed_node_string = "["; + bool needs_comma = false; + for(auto url : seed_nodes) + { + if (needs_comma) + seed_node_string += ", "; + seed_node_string += "\"" + url + "\""; + needs_comma = true; + } + seed_node_string += "]"; + cfg.emplace("seed-nodes", boost::program_options::variable_value(seed_node_string, false)); + _app->initialize(_dir.path(), cfg); + + _app->initialize_plugins(cfg); + _app->startup_plugins(); + + _app->startup(); + fc::usleep(fc::milliseconds(500)); +} + +std::shared_ptr application_runner::get_app() +{ + return _app; +} + +void application_runner::add_seed_node(std::string addr) +{ + /* + std::vector endpoints = graphene::app::application::resolve_string_to_ip_endpoints(addr); + for(const auto& ep : endpoints) + _app->p2p_node()->add_node(ep); + */ + seed_nodes.push_back(addr); +} + +uint32_t application_runner::get_connection_count() +{ + return _app->p2p_node()->get_connection_count(); +} + +bool application_runner::is_connected( std::string addr ) +{ + auto peer_statuses = _app->p2p_node()->get_connected_peers(); + for (auto status : peer_statuses ) + { + std::string host = status.host; + if ( host == addr ) + return true; + } + return false; +} + +////// +/// @brief attempt to find an available port on localhost +/// @returns an available port number, or -1 on error +///// +int application_runner::get_available_port() +{ + struct sockaddr_in sin; + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) + return -1; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (::bind(socket_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1) + return -1; + socklen_t len = sizeof(sin); + if (getsockname(socket_fd, (struct sockaddr *)&sin, &len) == -1) + return -1; +#ifdef _WIN32 + closesocket(socket_fd); +#else + close(socket_fd); +#endif + return ntohs(sin.sin_port); +} + +}} diff --git a/tests/common/application_helper.hpp b/tests/common/application_helper.hpp new file mode 100644 index 0000000000..89e5684fca --- /dev/null +++ b/tests/common/application_helper.hpp @@ -0,0 +1,99 @@ +#pragma once +#include +#include +#include + +#include +#include +#include +#include + +namespace graphene { namespace test { + +/** + * Handles creating a running node + */ +class application_runner +{ + public: + application_runner(); + void start(); + int rpc_port_number; + int p2p_port_number; + std::shared_ptr get_app(); + // networking + void add_seed_node(std::string addr); + bool is_connected( std::string addr ); + uint32_t get_connection_count(); + private: + std::shared_ptr _app; + fc::temp_directory _dir; + static int get_available_port(); + std::vector seed_nodes; +}; + +/////////// +/// @brief a class to make connecting to the application server easier +/////////// +class client_connection +{ +public: + ///////// + // constructor + ///////// + client_connection( + std::shared_ptr app, + const int server_port_number, + const fc::temp_directory& data_dir = fc::temp_directory() + ) + { + wallet_data.chain_id = app->chain_database()->get_chain_id(); + wallet_data.ws_server = "ws://127.0.0.1:" + std::to_string(server_port_number); + wallet_data.ws_user = ""; + wallet_data.ws_password = ""; + websocket_connection = websocket_client.connect( wallet_data.ws_server ); + + api_connection = std::make_shared(*websocket_connection, GRAPHENE_MAX_NESTED_OBJECTS); + + remote_login_api = api_connection->get_remote_api< graphene::app::login_api >(1); + remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ); + + + wallet_api_ptr = std::make_shared(wallet_data, remote_login_api); + wallet_filename = data_dir.path().generic_string() + "/wallet.json"; + wallet_api_ptr->set_wallet_filename(wallet_filename); + wallet_api_ptr->save_wallet_file(); + + wallet_api = fc::api(wallet_api_ptr); + + wallet_cli = std::make_shared(GRAPHENE_MAX_NESTED_OBJECTS); + for( auto& name_formatter : wallet_api_ptr->get_result_formatters() ) + wallet_cli->format_result( name_formatter.first, name_formatter.second ); + + boost::signals2::scoped_connection closed_connection(websocket_connection->closed.connect([=]{ + std::cerr << "Server has disconnected us.\n"; + wallet_cli->stop(); + })); + (void)(closed_connection); + } + bool import_nathan_account() + { + std::string wif_key = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"; + std::vector keys( { wif_key } ); + bool ret_val = wallet_api_ptr->import_key("nathan", wif_key); + wallet_api_ptr->import_balance("nathan", keys, true); + return ret_val; + } +public: + fc::http::websocket_client websocket_client; + graphene::wallet::wallet_data wallet_data; + fc::http::websocket_connection_ptr websocket_connection; + std::shared_ptr api_connection; + fc::api remote_login_api; + std::shared_ptr wallet_api_ptr; + fc::api wallet_api; + std::shared_ptr wallet_cli; + std::string wallet_filename; +}; + +}} diff --git a/tests/common/genesis_file_util.hpp b/tests/common/genesis_file_util.hpp index a87d9585af..24e6cbea5c 100644 --- a/tests/common/genesis_file_util.hpp +++ b/tests/common/genesis_file_util.hpp @@ -1,5 +1,7 @@ #pragma once +#include + ///////// /// @brief forward declaration, using as a hack to generate a genesis.json file /// for testing diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 2c7eec7d05..6c063beaa0 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -39,8 +39,10 @@ #include #include +#include #include "../common/database_fixture.hpp" +#include "../common/application_helper.hpp" using namespace graphene::chain; using namespace graphene::chain::test; @@ -778,6 +780,154 @@ BOOST_AUTO_TEST_CASE( tapos ) } } +/***** + * Important: The next two tests run for a very long time. They SHOULD NOT be run + * as part of the normal test suite + * +BOOST_AUTO_TEST_CASE( big_block_p2p ) +{ + try + { + graphene::test::application_runner app1; + app1.start(); + std::string app1_p2p_address = "127.0.0.1:" + std::to_string( app1.p2p_port_number ); + + graphene::test::application_runner app2; + std::string app2_p2p_address = "127.0.0.1:" + std::to_string( app2.p2p_port_number ); + app2.add_seed_node( app1_p2p_address ); + app2.start(); + + BOOST_CHECK_EQUAL( app1.get_connection_count(), 1u ); + BOOST_CHECK( app2.is_connected( app1_p2p_address ) ); + BOOST_CHECK( app1.is_connected( app2_p2p_address ) ); + + // connect to a node, send a very big tx, and make sure the nodes stay connected to each other + fc::temp_directory wallet_dir; + graphene::test::client_connection conn1(app1.get_app(), app1.rpc_port_number, wallet_dir); + conn1.wallet_api_ptr->set_password("supersecret"); + conn1.wallet_api_ptr->unlock("supersecret"); + conn1.import_nathan_account(); + + graphene::chain::account_object nathan = conn1.wallet_api_ptr->get_account( "nathan" ); + + transfer_operation xfer_op; + xfer_op.from = nathan.id; + xfer_op.to = GRAPHENE_NULL_ACCOUNT; + xfer_op.amount = asset( 400000 ); + + graphene::wallet::transaction_handle_type trx_handle = conn1.wallet_api_ptr->begin_builder_transaction(); + for(int i = 0; i < 50000; ++i) + { + conn1.wallet_api_ptr->add_operation_to_builder_transaction( trx_handle, xfer_op ); + xfer_op.amount.amount++; + } + conn1.wallet_api_ptr->set_fees_on_builder_transaction( trx_handle, GRAPHENE_SYMBOL ); + conn1.wallet_api_ptr->sign_builder_transaction( trx_handle, true ); + + BOOST_CHECK( app2.is_connected( app1_p2p_address ) ); + BOOST_CHECK( app1.is_connected( app2_p2p_address ) ); + } + catch(fc::exception& e) + { + edump((e.to_detail_string())); + } + +} + +BOOST_AUTO_TEST_CASE( big_transaction_p2p ) +{ + try + { + graphene::test::application_runner app1; + app1.start(); + std::string app1_p2p_address = "127.0.0.1:" + std::to_string( app1.p2p_port_number ); + + graphene::test::application_runner app2; + std::string app2_p2p_address = "127.0.0.1:" + std::to_string( app2.p2p_port_number ); + app2.add_seed_node( app1_p2p_address ); + app2.start(); + + BOOST_CHECK_EQUAL( app1.get_connection_count(), 1u ); + BOOST_CHECK( app2.is_connected( app1_p2p_address ) ); + BOOST_CHECK( app1.is_connected( app2_p2p_address ) ); + + // connect to a node + fc::temp_directory wallet_dir; + graphene::test::client_connection conn1(app1.get_app(), app1.rpc_port_number, wallet_dir); + conn1.wallet_api_ptr->set_password("supersecret"); + conn1.wallet_api_ptr->unlock("supersecret"); + conn1.import_nathan_account(); + + graphene::chain::account_object nathan = conn1.wallet_api_ptr->get_account( "nathan" ); + + // create a proposal + proposal_create_operation op; + op.expiration_time = time_point::now() + fc::minutes(30000); + op.review_period_seconds = 100; + + fc::optional asset_obj = conn1.wallet_api_ptr->get_asset(GRAPHENE_SYMBOL); + FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", GRAPHENE_SYMBOL)); + + account_object from_account = nathan; + account_object to_account = nathan; + account_id_type from_id = from_account.id; + account_id_type to_id = to_account.id; + + op.fee_paying_account = nathan.id; + + // create a transfer operation + transfer_operation xfer_op; + xfer_op.from = from_id; + xfer_op.to = to_id; + xfer_op.amount = asset_obj->amount_from_string("1000"); + xfer_op.fee = asset(2000000, asset_id_type(0)); + + // build a transaction + graphene::wallet::transaction_handle_type trx_handle = conn1.wallet_api_ptr->begin_builder_transaction(); + + // add the transfer operation to the transaction (nt sure why) + conn1.wallet_api_ptr->add_operation_to_builder_transaction(trx_handle, xfer_op); + conn1.wallet_api_ptr->set_fees_on_builder_transaction( trx_handle, GRAPHENE_SYMBOL ); + + // add 16 transfer operations and 16 proposal_create operations to the transaction + for (uint64_t j=0; j < 16; j++) { + op.proposed_ops.emplace_back( xfer_op ); + op.proposed_ops.emplace_back( op ); + } + + // remove the proposal_create operation added to the transaction earlier + // replace it with the large proposal_create operation + conn1.wallet_api_ptr->replace_operation_in_builder_transaction(trx_handle, 0, op); + + // calculate fees for the transaction + conn1.wallet_api_ptr->set_fees_on_builder_transaction( trx_handle, GRAPHENE_SYMBOL ); + + // send the transaction 17 times to the same server + auto func = [&trx_handle, &conn1](){ conn1.wallet_api_ptr->sign_builder_transaction(trx_handle, true); }; + std::vector< fc::future > futures; + for (uint64_t i=0; i < 17; i++) { + futures.push_back( fc::async(func, "htd_attack") ); + } + + // wait for everything to complete + for(auto f : futures) + { + f.wait(); + } + + // verify that the two nodes are still talking to each other + BOOST_CHECK( app2.is_connected( app1_p2p_address ) ); + BOOST_CHECK( app1.is_connected( app2_p2p_address ) ); + + } + catch(fc::exception& e) + { + edump((e.to_detail_string())); + } + +} +*/ + BOOST_FIXTURE_TEST_CASE( optional_tapos, database_fixture ) { try