diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 5421fbc6bd..c9b4c04ec8 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)); @@ -170,7 +170,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)); @@ -196,36 +196,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); @@ -988,6 +958,35 @@ application::~application() } } +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)) +} + void application::set_program_options(boost::program_options::options_description& command_line_options, boost::program_options::options_description& configuration_file_options) const { diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 175648e10f..accc8fe4f1 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 a9b6655cc3..78c9404c26 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -128,6 +128,8 @@ namespace graphene { namespace app { bool is_plugin_enabled(const string& name) const; + static std::vector resolve_string_to_ip_endpoints(const std::string& endpoint_string); + std::shared_ptr elasticsearch_thread; private: diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index 9a9b98386b..5026ca6786 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -1356,7 +1356,7 @@ namespace graphene { namespace net { namespace detail { item_hash_t head_block_id = _delegate->get_head_block_id(); user_data["last_known_block_hash"] = fc::variant( head_block_id, 1 ); user_data["last_known_block_number"] = _delegate->get_block_number(head_block_id); - user_data["last_known_block_time"] = _delegate->get_block_time(head_block_id); + user_data["last_known_block_time"] = _delegate->get_block_time(head_block_id); if (!_hard_fork_block_numbers.empty()) user_data["last_known_fork_block_number"] = _hard_fork_block_numbers.back(); @@ -4184,6 +4184,7 @@ namespace graphene { namespace net { namespace detail { void node_impl::add_node(const fc::ip::endpoint& ep) { + dlog("Attempting to add node ${ip}", ("ip", ep)); VERIFY_CORRECT_THREAD(); // if we're connecting to them, we believe they're not firewalled potential_peer_record updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_endpoint(ep); @@ -5076,12 +5077,14 @@ namespace graphene { namespace net { namespace detail { fc::time_point_sec statistics_gathering_node_delegate_wrapper::get_block_time(const item_hash_t& block_id) { - INVOKE_AND_COLLECT_STATISTICS(get_block_time, block_id); + //INVOKE_AND_COLLECT_STATISTICS(get_block_time, block_id); + return _node_delegate->get_block_time(block_id); } item_hash_t statistics_gathering_node_delegate_wrapper::get_head_block_id() const { - INVOKE_AND_COLLECT_STATISTICS(get_head_block_id); + //INVOKE_AND_COLLECT_STATISTICS(get_head_block_id); + _node_delegate->get_head_block_id(); } uint32_t statistics_gathering_node_delegate_wrapper::estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d05bf13183..8bfcf1433d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB COMMON_SOURCES "common/*.cpp") +file(GLOB COMMON_SOURCES "common/database_fixture.cpp") find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) @@ -31,10 +31,15 @@ target_link_libraries( chain_bench graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes fc ${PLATFORM_SPECIFIC_LIBS} ) -file(GLOB APP_SOURCES "app/*.cpp") +file(GLOB APP_SOURCES "app/main.cpp") add_executable( app_test ${APP_SOURCES} ) target_link_libraries( app_test graphene_app graphene_account_history graphene_net graphene_witness graphene_chain graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB GOOD_NODE_SOURCES "common/application_helper.cpp" "app/good_node.cpp") +add_executable( good_node ${GOOD_NODE_SOURCES} ) +target_include_directories( good_node PRIVATE "../libraries/plugins/witness/include" ) +target_link_libraries( good_node graphene_app graphene_account_history graphene_net graphene_witness graphene_chain graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) + file(GLOB CLI_SOURCES "cli/*.cpp") add_executable( cli_test ${CLI_SOURCES} ) if(WIN32) diff --git a/tests/app/good_node.cpp b/tests/app/good_node.cpp new file mode 100644 index 0000000000..b528477161 --- /dev/null +++ b/tests/app/good_node.cpp @@ -0,0 +1,69 @@ +#include "../common/application_helper.hpp" +#include +#include +#include +#include +#include + +namespace fc { + extern std::unordered_map& get_logger_map(); + extern std::unordered_map& get_appender_map(); +} + +int main(int argc, char** argv) +{ + fc::temp_directory td; + std::shared_ptr temp_dir = nullptr; + std::string remote_node_ip; + std::string my_port = "0"; + int timeout = -1; + for (int i = 1; i < argc; i++) + { + if (std::string(argv[i]) == "-d") + temp_dir = std::make_shared( fc::path(argv[i+1])); + if (std::string(argv[i]) == "-s") + remote_node_ip = argv[i+1]; + if (std::string(argv[i]) == "-p") + my_port = argv[i+1]; + if (std::string(argv[i]) == "-t") + timeout = std::stoi(argv[i+1]); + } + // we were not passed a temp directory, create one + if (!temp_dir) + temp_dir = std::make_shared( td.path() ); + // start node + graphene::test::application_runner app(temp_dir, std::stoi(my_port)); + app.start(); + std::string p2p_address = "127.0.0.1:" + std::to_string( app.p2p_port_number ); + + // adjust logging + auto log_map = fc::get_logger_map(); + auto appenders = fc::get_appender_map(); + auto p2p_logger = log_map["p2p"]; + p2p_logger.add_appender( appenders["stdout"] ); + p2p_logger.set_log_level(fc::log_level::debug); + auto default_logger = log_map["default"]; + default_logger.set_log_level(fc::log_level::debug); + std::cout << "Running on " << p2p_address << std::endl; + + // add node if passed in + if (!remote_node_ip.empty()) + { + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "attempting to add node " << remote_node_ip << "\n"; + app.add_node(remote_node_ip); + } + + if (timeout == -1) + { + std::cout << "Press e [enter] to exit" << std::endl; + char c = 0; + while( c != 'e' ) + std::cin >> c; + } + else + { + std::this_thread::sleep_for(std::chrono::seconds(timeout)); + ::raise(SIGINT); + } +} diff --git a/tests/app/main.cpp b/tests/app/main.cpp index b85b847ae5..1097167935 100644 --- a/tests/app/main.cpp +++ b/tests/app/main.cpp @@ -227,7 +227,7 @@ BOOST_AUTO_TEST_CASE( two_node_network ) app1.startup_plugins(); boost::program_options::variables_map cfg; cfg.emplace("p2p-endpoint", boost::program_options::variable_value(string("127.0.0.1:3939"), false)); - cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir.path()), false)); cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); app1.initialize(app_dir.path(), cfg); BOOST_TEST_MESSAGE( "Starting app1 and waiting 500 ms" ); @@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE( two_node_network ) auto cfg2 = cfg; cfg2.erase("p2p-endpoint"); cfg2.emplace("p2p-endpoint", boost::program_options::variable_value(string("127.0.0.1:4040"), false)); - cfg2.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); + cfg2.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir.path()), false)); cfg2.emplace("seed-node", boost::program_options::variable_value(vector{"127.0.0.1:3939"}, false)); cfg2.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); app2.initialize(app2_dir.path(), cfg2); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index c2c0278a43..afb302cfe4 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -136,7 +136,7 @@ std::shared_ptr start_application(fc::temp_directory "rpc-endpoint", boost::program_options::variable_value(string("127.0.0.1:" + std::to_string(server_port_number)), false) ); - cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir.path()), false)); cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); app1->initialize(app_dir.path(), cfg); diff --git a/tests/common/application_helper.cpp b/tests/common/application_helper.cpp new file mode 100644 index 0000000000..2efa13e743 --- /dev/null +++ b/tests/common/application_helper.cpp @@ -0,0 +1,167 @@ +#include "application_helper.hpp" +#include "genesis_file_util.hpp" +#include +#include +#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(std::shared_ptr dir, int port) : application_runner() +{ + _dir = dir; + if (port != 0) + p2p_port_number = port; +} +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(); + _dir = std::make_shared(); +} + +bool file_exists(std::string path) +{ + std::ifstream s(path); + if (s) + return true; + return false; +} + +void application_runner::start() +{ + std::string genesis_path = _dir->generic_string() + "/genesis.json"; + boost::filesystem::path genesis = boost::filesystem::path(genesis_path); + if (!file_exists(genesis_path)) + genesis = create_genesis_file(*_dir); + start(*_dir, genesis); +} +void application_runner::start(const fc::path& data_path, const boost::filesystem::path& genesis ) +{ + 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( genesis, 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(data_path, cfg); + + _app->initialize_plugins(cfg); + _app->startup_plugins(); + _app->startup(); + fc::usleep(fc::milliseconds(250)); +} + +std::shared_ptr application_runner::get_app() +{ + return _app; +} + +void application_runner::add_seed_node(std::string addr) +{ + seed_nodes.push_back(addr); +} + +void application_runner::add_node(std::string addr) +{ + auto endpoints = application::resolve_string_to_ip_endpoints(addr); + if (endpoints.empty()) + { + std::cerr << "Invalid node address passed\n"; + return; + } + _app->p2p_node()->add_node( endpoints[0] ); +} + +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); +} + +}} \ No newline at end of file diff --git a/tests/common/application_helper.hpp b/tests/common/application_helper.hpp new file mode 100644 index 0000000000..41d055ec9d --- /dev/null +++ b/tests/common/application_helper.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include + +#include +#include +#include +#include + +namespace graphene { namespace test { + +/** + * Handles creating a running node + */ +class application_runner +{ + public: + application_runner(); + application_runner(std::shared_ptr dir, int port); + void start(); + void start(const fc::path& data_path, const boost::filesystem::path& genesis_path); + int rpc_port_number; + int p2p_port_number; + std::shared_ptr get_app(); + // networking + void add_seed_node(std::string addr); + void add_node(std::string addr); + bool is_connected( std::string addr ); + uint32_t get_connection_count(); + + private: + std::shared_ptr _app; + std::shared_ptr _dir; + static int get_available_port(); + std::vector seed_nodes; + boost::program_options::variables_map cfg; +}; + +/////////// +/// @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; +}; + +}} \ No newline at end of file diff --git a/tests/common/genesis_file_util.hpp b/tests/common/genesis_file_util.hpp index a87d9585af..81dcf993c5 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 @@ -13,8 +15,8 @@ namespace graphene { namespace app { namespace detail { /// @param directory the directory to place the file "genesis.json" /// @returns the full path to the file //////// -boost::filesystem::path create_genesis_file(fc::temp_directory& directory) { - boost::filesystem::path genesis_path = boost::filesystem::path{directory.path().generic_string()} / "genesis.json"; +boost::filesystem::path create_genesis_file(const fc::path& directory) { + boost::filesystem::path genesis_path = boost::filesystem::path{directory.generic_string()} / "genesis.json"; fc::path genesis_out = genesis_path; graphene::chain::genesis_state_type genesis_state = graphene::app::detail::create_example_genesis();