diff --git a/.gitignore b/.gitignore index 7b1936ffd0..02f16e1168 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ tests/chain_bench tests/chain_test tests/intense_test tests/performance_test +tests/cli_test +tests/es_test doxygen diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index b2eb5185cb..96d9582381 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -67,7 +67,6 @@ #include #include #include -#include #include #include @@ -119,66 +118,14 @@ #define testnetlog(...) do {} while (0) #endif -namespace graphene { namespace net { +#include "node_impl.hxx" - namespace detail - { - namespace bmi = boost::multi_index; - class blockchain_tied_message_cache - { - private: - static const uint32_t cache_duration_in_blocks = GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS; - - struct message_hash_index{}; - struct message_contents_hash_index{}; - struct block_clock_index{}; - struct message_info - { - message_hash_type message_hash; - message message_body; - uint32_t block_clock_when_received; - - // for network performance stats - message_propagation_data propagation_data; - fc::uint160_t message_contents_hash; // hash of whatever the message contains (if it's a transaction, this is the transaction id, if it's a block, it's the block_id) - - message_info( const message_hash_type& message_hash, - const message& message_body, - uint32_t block_clock_when_received, - const message_propagation_data& propagation_data, - fc::uint160_t message_contents_hash ) : - message_hash( message_hash ), - message_body( message_body ), - block_clock_when_received( block_clock_when_received ), - propagation_data( propagation_data ), - message_contents_hash( message_contents_hash ) - {} - }; - typedef boost::multi_index_container - < message_info, - bmi::indexed_by< bmi::ordered_unique< bmi::tag, - bmi::member >, - bmi::ordered_non_unique< bmi::tag, - bmi::member >, - bmi::ordered_non_unique< bmi::tag, - bmi::member > > - > message_cache_container; - - message_cache_container _message_cache; - - uint32_t block_clock; - - public: - blockchain_tied_message_cache() : - block_clock( 0 ) - {} - void block_accepted(); - void cache_message( const message& message_to_cache, const message_hash_type& hash_of_message_to_cache, - const message_propagation_data& propagation_data, const fc::uint160_t& message_content_hash ); - message get_message( const message_hash_type& hash_of_message_to_lookup ); - message_propagation_data get_message_propagation_data( const fc::uint160_t& hash_of_message_contents_to_lookup ) const; - size_t size() const { return _message_cache.size(); } - }; +FC_REFLECT(graphene::net::detail::node_configuration, (listen_endpoint) + (accept_incoming_connections) + (wait_if_endpoint_is_busy) + (private_key)); + +namespace graphene { namespace net { namespace detail { void blockchain_tied_message_cache::block_accepted() { @@ -221,37 +168,6 @@ namespace graphene { namespace net { FC_THROW_EXCEPTION( fc::key_not_found_exception, "Requested message not in cache" ); } -///////////////////////////////////////////////////////////////////////////////////////////////////////// - - // This specifies configuration info for the local node. It's stored as JSON - // in the configuration directory (application data directory) - struct node_configuration - { - node_configuration() : accept_incoming_connections(true), wait_if_endpoint_is_busy(true) {} - - fc::ip::endpoint listen_endpoint; - bool accept_incoming_connections; - bool wait_if_endpoint_is_busy; - /** - * Originally, our p2p code just had a 'node-id' that was a random number identifying this node - * on the network. This is now a private key/public key pair, where the public key is used - * in place of the old random node-id. The private part is unused, but might be used in - * the future to support some notion of trusted peers. - */ - fc::ecc::private_key private_key; - }; - - -} } } // end namespace graphene::net::detail -FC_REFLECT(graphene::net::detail::node_configuration, (listen_endpoint) - (accept_incoming_connections) - (wait_if_endpoint_is_busy) - (private_key)); - -#include "node_impl.hxx" - -namespace graphene { namespace net { namespace detail { - void node_impl_deleter::operator()(node_impl* impl_to_delete) { #ifdef P2P_IN_DEDICATED_THREAD diff --git a/libraries/net/node_impl.hxx b/libraries/net/node_impl.hxx index 6cebda8f8f..01b8d97e3f 100644 --- a/libraries/net/node_impl.hxx +++ b/libraries/net/node_impl.hxx @@ -3,12 +3,22 @@ #include #include #include +#include + #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include + namespace graphene { namespace net { namespace detail { // when requesting items from peers, we want to prioritize any blocks before @@ -163,6 +173,84 @@ private: uint8_t get_current_block_interval_in_seconds() const override; }; + namespace bmi = boost::multi_index; + class blockchain_tied_message_cache + { + private: + static const uint32_t cache_duration_in_blocks = GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS; + + struct message_hash_index{}; + struct message_contents_hash_index{}; + struct block_clock_index{}; + struct message_info + { + message_hash_type message_hash; + message message_body; + uint32_t block_clock_when_received; + + // for network performance stats + message_propagation_data propagation_data; + fc::uint160_t message_contents_hash; // hash of whatever the message contains (if it's a transaction, this is the transaction id, if it's a block, it's the block_id) + + message_info( const message_hash_type& message_hash, + const message& message_body, + uint32_t block_clock_when_received, + const message_propagation_data& propagation_data, + fc::uint160_t message_contents_hash ) : + message_hash( message_hash ), + message_body( message_body ), + block_clock_when_received( block_clock_when_received ), + propagation_data( propagation_data ), + message_contents_hash( message_contents_hash ) + {} + }; + typedef boost::multi_index_container + < message_info, + bmi::indexed_by< bmi::ordered_unique< bmi::tag, + bmi::member >, + bmi::ordered_non_unique< bmi::tag, + bmi::member >, + bmi::ordered_non_unique< bmi::tag, + bmi::member > > + > message_cache_container; + + message_cache_container _message_cache; + + uint32_t block_clock; + + public: + blockchain_tied_message_cache() : + block_clock( 0 ) + {} + void block_accepted(); + void cache_message( const message& message_to_cache, const message_hash_type& hash_of_message_to_cache, + const message_propagation_data& propagation_data, const fc::uint160_t& message_content_hash ); + message get_message( const message_hash_type& hash_of_message_to_lookup ); + message_propagation_data get_message_propagation_data( const fc::uint160_t& hash_of_message_contents_to_lookup ) const; + size_t size() const { return _message_cache.size(); } + }; + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +// This specifies configuration info for the local node. It's stored as JSON +// in the configuration directory (application data directory) +struct node_configuration +{ + node_configuration() : accept_incoming_connections(true), wait_if_endpoint_is_busy(true) {} + + fc::ip::endpoint listen_endpoint; + bool accept_incoming_connections; + bool wait_if_endpoint_is_busy; + /** + * Originally, our p2p code just had a 'node-id' that was a random number identifying this node + * on the network. This is now a private key/public key pair, where the public key is used + * in place of the old random node-id. The private part is unused, but might be used in + * the future to support some notion of trusted peers. + */ + fc::ecc::private_key private_key; +}; + + class node_impl : public peer_connection_delegate { public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bdde264f4f..55e0d217d8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,10 +21,14 @@ file(GLOB BENCH_MARKS "benchmarks/*.cpp") add_executable( chain_bench ${BENCH_MARKS} ${COMMON_SOURCES} ) target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none 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 SYS_SOURCES "app/system_test.cpp") +add_executable( system_test ${SYS_SOURCES} ) +target_link_libraries( system_test graphene_app graphene_wallet 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} ) target_link_libraries( cli_test graphene_app graphene_wallet graphene_witness graphene_account_history graphene_net graphene_chain graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) diff --git a/tests/app/main.cpp b/tests/app/main.cpp index 848d60d664..6774b85174 100644 --- a/tests/app/main.cpp +++ b/tests/app/main.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -325,6 +327,8 @@ BOOST_AUTO_TEST_CASE( two_node_network ) BOOST_CHECK_EQUAL(app1.chain_database()->head_block_num(), 1); BOOST_TEST_MESSAGE( "Checking GRAPHENE_NULL_ACCOUNT has balance" ); + BOOST_CHECK_EQUAL( db1->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 ); + BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 ); } catch( fc::exception& e ) { edump((e.to_detail_string())); throw; @@ -349,3 +353,123 @@ BOOST_AUTO_TEST_CASE(application_impl_breakout) { graphene::net::item_id id; BOOST_CHECK(impl.has_item(id)); } + +#include "../../libraries/net/node_impl.hxx" + +/****** + * @brief create a malformed message and make sure the application can handle it + */ +BOOST_AUTO_TEST_CASE( bad_message ) +{ + // TODO: Implement a message_oriented_connection to override read_loop() + /* + class my_peer_connection : public graphene::net::peer_connection + { + my_peer_connection(graphene::net::peer_connection_delegate* delegate) : + graphene::net::peer_connection(delegate) + { + } + }; + */ + + // override application_impl to override get_item + class my_app_impl : public graphene::app::detail::application_impl { + private: + graphene::chain::chain_id_type _chain_id; + public: + my_app_impl() : application_impl(nullptr) {} + bool has_item(const net::item_id& id) override { + return true; + } + graphene::chain::chain_id_type get_chain_id() const { return _chain_id; } + graphene::net::message get_item(const graphene::net::item_id& id) { + graphene::net::message result; + try { + FC_ASSERT( false ); + } FC_CAPTURE_AND_RETHROW( (id)) + return result; + } + }; + + // override node_impl (actually no longer necessary) + class my_node_impl : public graphene::net::detail::node_impl { + public: + my_node_impl() : node_impl("user agent") { } + }; + + my_node_impl _node_impl; + my_app_impl _app_impl; + _node_impl.set_node_delegate(&_app_impl, &fc::thread::current()); + graphene::net::peer_connection* peer = nullptr; + + graphene::net::fetch_items_message msg; + graphene::chain::block_id_type block_id; + msg.items_to_fetch.push_back(block_id); + + try + { + _node_impl.on_message(peer, msg); + std::cout << "on_message was successful.\n"; + } + catch( fc::out_of_range_exception& e ) + { + std::cerr << "Out of range exception thrown.\n"; + } + catch( std::exception& e) + { + std::cerr << "Uh oh... Exception thrown. " << e.what() << '\n'; + } + catch (...) + { + std::cerr << "Unknown exception thrown.\n"; + } +} + +bool testit(size_t size) +{ + const int BUFFER_SIZE = 16; + const int LEFTOVER = BUFFER_SIZE - sizeof(graphene::net::message_header); + + char buffer[BUFFER_SIZE]; + { + // a cheat to get bytes into buffer + graphene::net::message_header h; + h.size = size; + h.msg_type = graphene::net::block_message_type; + memcpy((char*)&buffer[0], (char*)&h, sizeof(graphene::net::message_header)); + } + graphene::net::message m; + memcpy((char*)&m, buffer, sizeof(graphene::net::message_header)); + size_t _bytes_received = BUFFER_SIZE; + + // max size is 2,097,152 + FC_ASSERT( m.size <= MAX_MESSAGE_SIZE, "", ("m.size",m.size)("MAX_MESSAGE_SIZE",MAX_MESSAGE_SIZE) ); + + size_t remaining_bytes_with_padding = 16 * ((m.size - LEFTOVER + 15) / 16); + m.data.resize(LEFTOVER + remaining_bytes_with_padding); + std::copy(buffer + sizeof(graphene::net::message_header), buffer + sizeof(buffer), m.data.begin()); + if (remaining_bytes_with_padding) + { + char bytes[remaining_bytes_with_padding]; + memset(&bytes[0], 0, remaining_bytes_with_padding); + // make some bytes + for(size_t i = 0; i < remaining_bytes_with_padding; ++i) + { + bytes[i] = (char)(i % 10); + } + memcpy(&m.data[LEFTOVER], &bytes[0], remaining_bytes_with_padding); + _bytes_received += remaining_bytes_with_padding; + } + m.data.resize(m.size); // truncate off the padding bytes + // now see what we've got + return true; +} + +BOOST_AUTO_TEST_CASE( malformed_message ) +{ + for(size_t i = MAX_MESSAGE_SIZE - 10; i <= MAX_MESSAGE_SIZE; ++i) + { + std::cout << "Tesing " << std::to_string(i) << '\n'; + testit(i); + } +} diff --git a/tests/app/system_test.cpp b/tests/app/system_test.cpp new file mode 100644 index 0000000000..367ea73694 --- /dev/null +++ b/tests/app/system_test.cpp @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2018 John Jones (jmjatlanta), and contributors. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../common/genesis_file_util.hpp" + +template +class BlockingQueue +{ +private: + std::mutex d_mutex; + std::condition_variable d_condition; + std::deque d_queue; +public: + void push(T const& value) { + if (d_queue.size() > 1000) + { + // silently skip this one and give the queue a rest + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + else + { + std::unique_lock lock(d_mutex); + d_queue.push_front(value); + } + d_condition.notify_one(); + } + T pop() { + std::unique_lock lock(d_mutex); + d_condition.wait(lock, [=] { return !d_queue.empty(); }); + T rc(std::move(d_queue.back())); + d_queue.pop_back(); + return rc; + } +}; + +/****** + * @brief attempt to find an available port on localhost + * @returns an available port number, or -1 on error + */ +int 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; + close(socket_fd); + return ntohs(sin.sin_port); +} + + +/***** + * Makes a client connecting to a node easer + */ +class client_connection +{ +public: + client_connection(std::shared_ptr app, std::string data_dir, const int server_port_number, std::string wallet_file_name ) + { + 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); + this->wallet_filename = data_dir + "/" + wallet_file_name; + wallet_api_ptr->set_wallet_filename(this->wallet_filename); + + 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 << "Client: Server has disconnected us.\n"; + wallet_cli->stop(); + })); + (void)(closed_connection); + } + void disconnect() + { + websocket_client.disconnect(); + } +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; +}; + + +/*** + * An autonomous client that does typical client things + */ +class client{ +public: + client(std::shared_ptr application, std::string app_dir, unsigned int id, std::string private_key, unsigned int port) + : application(application), app_dir(app_dir), my_id(id), private_key(private_key), port(port) + { + // connect to api node + name = "client" + std::to_string(my_id); + } + ~client() + { + shutdown(); + } + bool connect() + { + if (!shutting_down && !is_connected) + { + connection = std::make_shared(application, app_dir, port, name + ".json"); + // save wallet file + connection->wallet_api_ptr->set_password("supersecret"); + connection->wallet_api_ptr->unlock("supersecret"); + std::vector keys{private_key}; + std::vector import_txs = connection->wallet_api_ptr->import_balance(name, keys, true); + connection->wallet_api_ptr->import_key(name, private_key); + std::string wallet_filename = app_dir + "/" + name + ".json"; + connection->wallet_api_ptr->set_wallet_filename(wallet_filename); + is_connected = true; + } + return is_connected; + } + void start_message_loop() + { + //start message loop + message_loop_thread = std::thread(message_loop, this); + pthread_setname_np(message_loop_thread.native_handle(), (name + "ml").c_str()); + } + void do_random_action() + { + // put something in the queue + queue.push( rand() % 3); + } + void do_random_actions_every(std::chrono::steady_clock::duration duration) + { + random_action_duration = duration; + if (!random_loop_running) + { + random_actions_thread = std::thread(random_action_loop, this); + pthread_setname_np(random_actions_thread.native_handle(), (name + "rl").c_str()); + } + } + + void shutdown() + { + // shut down message loop first + shutting_down = true; + if (message_loop_running) + { + // throw one more item in the queue, just to be sure + do_random_action(); + if (message_loop_thread.joinable()) + message_loop_thread.join(); + } + // now shut down random_actions thread + if (random_loop_running) + { + if (random_actions_thread.joinable()) + random_actions_thread.join(); + } + connection->disconnect(); + is_connected = false; + } + void stop_random_actions() { random_loop_running = false; } + void setDirectory(std::shared_ptr>> c) { clients = c; } + unsigned int my_id; + std::string name; + std::shared_ptr connection; +protected: + std::thread message_loop_thread; + std::thread random_actions_thread; + volatile bool is_connected = false; + volatile bool shutting_down = false; + volatile bool random_loop_running = false; + volatile bool message_loop_running = false; + std::chrono::steady_clock::duration random_action_duration; + BlockingQueue queue; + std::shared_ptr>> clients; + std::string private_key; + std::shared_ptr application; + std::string app_dir; + unsigned int port; + +private: + static void message_loop(client* c) + { + c->message_loop_running = true; + while( !c->shutting_down) + { + if (c->is_connected) + { + int task = c->queue.pop(); + switch(task) + { + case (0): + c->request_block(); + break; + case (1): + c->transfer(); + break; + case (2): + c->disconnect_reconnect(); + break; + } + } + } + c->message_loop_running = false; + } + static void random_action_loop(client* c) + { + c->random_loop_running = true; + while ( !c->shutting_down ) + { + c->do_random_action(); + std::this_thread::sleep_for(c->random_action_duration); + } + c->random_loop_running = false; + } + void request_block() + { + std::cout << "Client: RequestBlock\n"; + } + void disconnect_reconnect() + { + connection->disconnect(); + is_connected = false; + connect(); + std::cout << "Client: Disconnected/reconnected\n"; + } + void transfer() + { + try + { + // we must have at least 2 + if (clients->size() < 2) + return; + // pick a random person that is not me + unsigned int my_friend_no = my_id; + while (my_friend_no == my_id) + my_friend_no = rand() % clients->size(); + std::shared_ptr my_friend = clients->at(my_friend_no); + // give them some CORE + connection->wallet_api_ptr->transfer(name, my_friend->name, "1000", "BTS", "", true); + std::cout << "client: Transfer complete\n"; + } catch (fc::exception &ex) + { + std::cerr << "Client: Caught exception attempting to transfer. Error was: " + ex.to_detail_string(fc::log_level(fc::log_level::all)) + "\n"; + } catch (std::exception &ex) + { + std::cerr << "Client: Caught std exception attempting to tranfer: " << ex.what() << "\n"; + } catch (...) + { + std::cerr << "Client:Caught unknown exception attempting to transfer.\n"; + } + } +}; + +class application_server +{ + public: + std::shared_ptr app_dir; + unsigned int p2p_port; + unsigned int rpc_port; + std::shared_ptr app; + std::string seed_nodes; + volatile bool disconnect_loop_shutdown = true; + private: + void restart() + { + app->shutdown(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + start_application(); + } + void shutdown() + { + disconnect_loop_shutdown = true; + app->shutdown(); + } + public: + void start_disconnect_loop(std::chrono::milliseconds disconnect, std::chrono::microseconds delta) + { + // calculate a somewhat random time + const int64_t rnd = rand() % delta.count(); + int plus_minus = rand() % 1; + std::chrono::nanoseconds disconnect_time = disconnect; + if (plus_minus) + disconnect_time += std::chrono::nanoseconds(rnd); + else + disconnect_time -= std::chrono::nanoseconds(rnd); + disconnect_loop_shutdown = false; + // find a way to tell the application to disconnect from all(?) nodes + // maybe just kill it and restart it? + std::thread( [disconnect_time, this]() { + while( !disconnect_loop_shutdown) + { + std::this_thread::sleep_for(disconnect_time); + std::cout << "Server: Attempting to disconnect from all other nodes and reconnect\n"; + restart(); + } + }).detach(); + } + std::shared_ptr start_application() + { + app = std::make_shared(); + app->register_plugin< graphene::account_history::account_history_plugin>(); + app->register_plugin< graphene::market_history::market_history_plugin >(); + app->register_plugin< graphene::witness_plugin::witness_plugin >(); + app->register_plugin< graphene::grouped_orders::grouped_orders_plugin>(); + app->startup_plugins(); + boost::program_options::variables_map cfg; + cfg.emplace("rpc-endpoint", boost::program_options::variable_value("127.0.0.1:" + std::to_string(rpc_port), false)); + cfg.emplace("p2p-endpoint", boost::program_options::variable_value("127.0.0.1:" + std::to_string(p2p_port), false)); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(*app_dir), false)); + cfg.emplace("seed-nodes", boost::program_options::variable_value(seed_nodes, false)); + app->initialize((*app_dir).path(), cfg); + app->startup(); + fc::usleep(fc::milliseconds(500)); + return app; + } + + /////////// + /// Send a block to the db + /// @param app the application + /// @returns true on success + /////////// + bool generate_block() { + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + auto db = app->chain_database(); + auto block_1 = db->generate_block( db->get_slot_time(1), + db->get_scheduled_witness(1), + committee_key, + database::skip_nothing ); + return true; + } catch (exception &e) { + return false; + } + } +}; + +std::string calculate_seed_nodes(const std::vector>& servers) +{ + std::string retVal = "["; + for(const auto& server : servers ) + { + if (retVal.size() > 1) + retVal += ", "; + retVal += "\"127.0.0.1:" + std::to_string(server->p2p_port) + "\""; + } + retVal += "]"; + return retVal; +} + +/**** + * Create some nodes and clients that do random things + */ +int main(int argc, char** argv) +{ + unsigned int num_clients = 2; + if (argc > 1) + num_clients = atoi(argv[1]); + unsigned int num_servers = 2; + if (argc > 2) + num_clients = atoi(argv[2]); + std::chrono::seconds client_message_loop_speed(3); + std::chrono::seconds server_disconnect_speed(20); + std::chrono::milliseconds server_disconnect_plus_minus(1); + + std::vector> servers; + + try + { + // set up servers + std::string seed_nodes = "[]"; + for(unsigned int i = 0; i < num_servers; ++i) + { + std::shared_ptr current_server = std::make_shared(); + current_server->p2p_port = get_available_port(); + current_server->rpc_port = get_available_port(); + current_server->app_dir = std::make_shared( graphene::utilities::temp_directory_path() ); + current_server->seed_nodes = seed_nodes; + current_server->start_application(); + servers.push_back( current_server ); + seed_nodes = calculate_seed_nodes(servers); + if (i != 0) + current_server->start_disconnect_loop(server_disconnect_speed, server_disconnect_plus_minus); + } + + std::shared_ptr main_server = servers.at(0); + client_connection con(main_server->app, main_server->app_dir->path().generic_string(), main_server->rpc_port, "nathan.json"); + // have the nathan account give new users some CORE + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + con.wallet_api_ptr->import_key("nathan", nathan_keys[0]); + std::vector import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + account_object nathan_acct = con.wallet_api_ptr->get_account("nathan"); + + std::shared_ptr>> clients = std::make_shared > >(); + + // create a number of clients, each with their own account, and that know each other + for(unsigned int i = 0; i < num_clients; ++i) + { + // have nathan create an account + std::string new_account_name = "client" + std::to_string(i); + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, + new_account_name, "nathan", "nathan", true); + // transfer CORE from nathan to this new client + con.wallet_api_ptr->transfer("nathan", new_account_name, "1000000", "BTS", "", true); + unsigned int server_number = i % num_servers; + const auto& current_server = servers.at(server_number); + std::shared_ptr current_client = std::make_shared(current_server->app, current_server->app_dir->path().generic_string(), + i, bki.wif_priv_key, current_server->rpc_port); + clients->push_back( current_client ); + } + // make sure all nodes have the new accounts + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // have the clients become autonomous + for( size_t i = 0; i < clients->size(); ++i ) + { + std::shared_ptr c = clients->at(i); + c->connect(); + c->setDirectory(clients); + c->start_message_loop(); + c->do_random_actions_every( client_message_loop_speed ); + } + // now wait until the user wants to stop + fc::promise::ptr exit_promise = new fc::promise("UNIX Signal Handler"); + + fc::set_signal_handler([&exit_promise](int signal) { + exit_promise->set_value(signal); + }, SIGINT); + + fc::set_signal_handler([&exit_promise](int signal) { + exit_promise->set_value(signal); + }, SIGTERM); + + int signal = exit_promise->wait(); + for (const auto& server : servers) + { + server->app->shutdown(); + } + } + catch (fc::exception& ex) + { + std::cerr << "Main Thread: FC Exception thrown: " << ex.to_detail_string(fc::log_level(fc::log_level::all)) << "\n"; + } + catch (...) + { + std::cerr << "Main Thread: Uncaught exception thrown.\n"; + } + return 0; +} \ No newline at end of file diff --git a/tests/common/genesis_file_util.hpp b/tests/common/genesis_file_util.hpp index a87d9585af..468b262595 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,7 +15,7 @@ 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 create_genesis_file(const fc::temp_directory& directory) { boost::filesystem::path genesis_path = boost::filesystem::path{directory.path().generic_string()} / "genesis.json"; fc::path genesis_out = genesis_path; graphene::chain::genesis_state_type genesis_state = graphene::app::detail::create_example_genesis();