/** * @file * @copyright defined in eos/LICENSE.txt */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace eosio { namespace detail { struct txn_test_gen_empty {}; }} FC_REFLECT(eosio::detail::txn_test_gen_empty, ); namespace eosio { static appbase::abstract_plugin& _txn_test_gen_plugin = app().register_plugin(); using namespace eosio::chain; #define CALL(api_name, api_handle, call_name, INVOKE, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ [this](string, string body, url_response_callback cb) mutable { \ try { \ if (body.empty()) body = "{}"; \ INVOKE \ cb(http_response_code, fc::json::to_string(result)); \ } catch (...) { \ http_plugin::handle_exception(#api_name, #call_name, body, cb); \ } \ }} #define INVOKE_V_R_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \ const auto& vs = fc::json::json::from_string(body).as(); \ api_handle->call_name(vs.at(0).as(), vs.at(1).as(), vs.at(2).as()); \ eosio::detail::txn_test_gen_empty result; #define INVOKE_V_R_R(api_handle, call_name, in_param0, in_param1) \ const auto& vs = fc::json::json::from_string(body).as(); \ api_handle->call_name(vs.at(0).as(), vs.at(1).as()); \ eosio::detail::txn_test_gen_empty result; #define INVOKE_V_V(api_handle, call_name) \ api_handle->call_name(); \ eosio::detail::txn_test_gen_empty result; struct txn_test_gen_plugin_impl { void push_transaction( signed_transaction& trx ) { try { chain_plugin& cp = app().get_plugin(); cp.accept_transaction( packed_transaction(trx) ); } FC_CAPTURE_AND_RETHROW( (transaction_header(trx)) ) } void create_test_accounts(const std::string& init_name, const std::string& init_priv_key) { name newaccountA("txn.test.a"); name newaccountB("txn.test.b"); name newaccountC("txn.test.t"); name creator(init_name); abi_def currency_abi_def = fc::json::from_string(eosio_token_abi).as(); controller& cc = app().get_plugin().chain(); chain::chain_id_type chainid; app().get_plugin().get_chain_id(chainid); fc::crypto::private_key txn_test_receiver_A_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'a'))); fc::crypto::private_key txn_test_receiver_B_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'b'))); fc::crypto::private_key txn_test_receiver_C_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'c'))); fc::crypto::public_key txn_text_receiver_A_pub_key = txn_test_receiver_A_priv_key.get_public_key(); fc::crypto::public_key txn_text_receiver_B_pub_key = txn_test_receiver_B_priv_key.get_public_key(); fc::crypto::public_key txn_text_receiver_C_pub_key = txn_test_receiver_C_priv_key.get_public_key(); fc::crypto::private_key creator_priv_key = fc::crypto::private_key(init_priv_key); //create some test accounts { signed_transaction trx; //create "A" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_A_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountA, owner_auth, active_auth}); } //create "B" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_B_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountB, owner_auth, active_auth}); } //create "txn.test.t" account { auto owner_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; auto active_auth = eosio::chain::authority{1, {{txn_text_receiver_C_pub_key, 1}}, {}}; trx.actions.emplace_back(vector{{creator,"active"}}, newaccount{creator, newaccountC, owner_auth, active_auth}); } trx.expiration = cc.head_block_time() + fc::seconds(30); trx.set_reference_block(cc.head_block_id()); trx.sign(creator_priv_key, chainid); push_transaction(trx); } //set txn.test.t contract to eosio.token & initialize it { signed_transaction trx; vector wasm = wast_to_wasm(std::string(eosio_token_wast)); setcode handler; handler.account = newaccountC; handler.code.assign(wasm.begin(), wasm.end()); trx.actions.emplace_back( vector{{newaccountC,"active"}}, handler); { setabi handler; handler.account = newaccountC; handler.abi = fc::raw::pack(json::from_string(eosio_token_abi).as()); trx.actions.emplace_back( vector{{newaccountC,"active"}}, handler); } { action act; act.account = N(txn.test.t); act.name = N(create); act.authorization = vector{{newaccountC,config::active_name}}; act.data = eosio_token_serializer.variant_to_binary("create", fc::json::from_string("{\"issuer\":\"txn.test.t\",\"maximum_supply\":\"1000000000.0000 CUR\"}}")); trx.actions.push_back(act); } { action act; act.account = N(txn.test.t); act.name = N(issue); act.authorization = vector{{newaccountC,config::active_name}}; act.data = eosio_token_serializer.variant_to_binary("issue", fc::json::from_string("{\"to\":\"txn.test.t\",\"quantity\":\"600.0000 CUR\",\"memo\":\"\"}")); trx.actions.push_back(act); } { action act; act.account = N(txn.test.t); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.a\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}")); trx.actions.push_back(act); } { action act; act.account = N(txn.test.t); act.name = N(transfer); act.authorization = vector{{newaccountC,config::active_name}}; act.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string("{\"from\":\"txn.test.t\",\"to\":\"txn.test.b\",\"quantity\":\"200.0000 CUR\",\"memo\":\"\"}")); trx.actions.push_back(act); } trx.expiration = cc.head_block_time() + fc::seconds(30); trx.set_reference_block(cc.head_block_id()); trx.max_net_usage_words = 5000; trx.sign(txn_test_receiver_C_priv_key, chainid); push_transaction(trx); } } void start_generation(const std::string& salt, const uint64_t& period, const uint64_t& batch_size) { if(running) throw fc::exception(fc::invalid_operation_exception_code); if(period < 1 || period > 2500) throw fc::exception(fc::invalid_operation_exception_code); if(batch_size < 1 || batch_size > 250) throw fc::exception(fc::invalid_operation_exception_code); if(batch_size & 1) throw fc::exception(fc::invalid_operation_exception_code); running = true; //create the actions here act_a_to_b.account = N(txn.test.t); act_a_to_b.name = N(transfer); act_a_to_b.authorization = vector{{name("txn.test.a"),config::active_name}}; act_a_to_b.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string(fc::format_string("{\"from\":\"txn.test.a\",\"to\":\"txn.test.b\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt)))); act_b_to_a.account = N(txn.test.t); act_b_to_a.name = N(transfer); act_b_to_a.authorization = vector{{name("txn.test.b"),config::active_name}}; act_b_to_a.data = eosio_token_serializer.variant_to_binary("transfer", fc::json::from_string(fc::format_string("{\"from\":\"txn.test.b\",\"to\":\"txn.test.a\",\"quantity\":\"1.0000 CUR\",\"memo\":\"${l}\"}", fc::mutable_variant_object()("l", salt)))); timer_timeout = period; batch = batch_size/2; ilog("Started transaction test plugin; performing ${p} transactions every ${m}ms", ("p", batch_size)("m", period)); arm_timer(boost::asio::high_resolution_timer::clock_type::now()); } void arm_timer(boost::asio::high_resolution_timer::time_point s) { timer.expires_at(s + std::chrono::milliseconds(timer_timeout)); timer.async_wait([this](const boost::system::error_code& ec) { if(!running || ec) return; try { send_transaction(); } catch(fc::exception e) { elog("pushing transaction failed: ${e}", ("e", e.to_detail_string())); stop_generation(); return; } arm_timer(timer.expires_at()); }); } void send_transaction() { controller& cc = app().get_plugin().chain(); chain::chain_id_type chainid; app().get_plugin().get_chain_id(chainid); name sender("txn.test.a"); name recipient("txn.test.b"); fc::crypto::private_key a_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'a'))); fc::crypto::private_key b_priv_key = fc::crypto::private_key::regenerate(fc::sha256(std::string(64, 'b'))); static uint64_t nonce = static_cast(fc::time_point::now().sec_since_epoch()) << 32; abi_serializer eosio_serializer(cc.db().find(config::system_account_name)->get_abi()); uint32_t reference_block_num = cc.last_irreversible_block_num(); if (txn_reference_block_lag >= 0) { reference_block_num = cc.head_block_num(); if (reference_block_num <= (uint32_t)txn_reference_block_lag) { reference_block_num = 0; } else { reference_block_num -= (uint32_t)txn_reference_block_lag; } } block_id_type reference_block_id = cc.get_block_id_for_num(reference_block_num); for(unsigned int i = 0; i < batch; ++i) { { signed_transaction trx; trx.actions.push_back(act_a_to_b); trx.context_free_actions.emplace_back(action({}, config::null_account_name, "nonce", fc::raw::pack(nonce++))); trx.set_reference_block(reference_block_id); trx.expiration = cc.head_block_time() + fc::seconds(30); trx.max_net_usage_words = 100; trx.sign(a_priv_key, chainid); push_transaction(trx); } { signed_transaction trx; trx.actions.push_back(act_b_to_a); trx.context_free_actions.emplace_back(action({}, config::null_account_name, "nonce", fc::raw::pack(nonce++))); trx.set_reference_block(reference_block_id); trx.expiration = cc.head_block_time() + fc::seconds(30); trx.max_net_usage_words = 100; trx.sign(b_priv_key, chainid); push_transaction(trx); } } } void stop_generation() { if(!running) throw fc::exception(fc::invalid_operation_exception_code); timer.cancel(); running = false; ilog("Stopping transaction generation test"); } boost::asio::high_resolution_timer timer{app().get_io_service()}; bool running{false}; unsigned timer_timeout; unsigned batch; action act_a_to_b; action act_b_to_a; int32_t txn_reference_block_lag; abi_serializer eosio_token_serializer = fc::json::from_string(eosio_token_abi).as(); }; txn_test_gen_plugin::txn_test_gen_plugin() {} txn_test_gen_plugin::~txn_test_gen_plugin() {} void txn_test_gen_plugin::set_program_options(options_description&, options_description& cfg) { cfg.add_options() ("txn-reference-block-lag", bpo::value()->default_value(0), "Lag in number of blocks from the head block when selecting the reference block for transactions (-1 means Last Irreversible Block)") ; } void txn_test_gen_plugin::plugin_initialize(const variables_map& options) { my.reset(new txn_test_gen_plugin_impl); my->txn_reference_block_lag = options.at("txn-reference-block-lag").as(); } void txn_test_gen_plugin::plugin_startup() { app().get_plugin().add_api({ CALL(txn_test_gen, my, create_test_accounts, INVOKE_V_R_R(my, create_test_accounts, std::string, std::string), 200), CALL(txn_test_gen, my, stop_generation, INVOKE_V_V(my, stop_generation), 200), CALL(txn_test_gen, my, start_generation, INVOKE_V_R_R_R(my, start_generation, std::string, uint64_t, uint64_t), 200) }); } void txn_test_gen_plugin::plugin_shutdown() { try { my->stop_generation(); } catch(fc::exception e) { } } }