原文连接:醒者呆的博客园,https://www.cnblogs.com/Evswa...html
本文内容本属于《【精解】EOS TPS 多维实测》的内容,但因为在编写时篇幅过长,因此我决定将这一部分单独成文撰写,以便于理解。关键字:eos, txn_test_gen_plugin, signed_transaction, ordered_action_result, C++, EOS插件算法
这个插件是官方开发用来测试块打包交易量的,这种方式因为是直接系统内部调用来模拟transaction,没有中间通信的损耗,所以效率是很是高的,官方称经过这个插件测试到了8000的tps结果,而就个人测试结果来说,没有这么恐怖,但也能到2000了,熟不知,其余的测试手段,例如cleos,eosjs可能只有百级的量。下面,咱们一同来研究一下这个插件是如何实现以上功能的,过程当中,咱们也会思考EOS插件的架构体系,以及实现方法。经过本文的学习,若是有好的想法,咱们也能够本身开发一个功能强大的插件pr给eos,为EOS社区作出咱们本身的贡献。json
关于txn_test_gen_plugin插件的使用,很是易于上手,本文不作分析,这方面能够直接参考官方文档。
插件代码总体结构中,咱们上面介绍的核心功能的实现函数都是包含在一个结构体struct txn_test_gen_plugin_impl中。剩余的其余代码都是对插件自己的通信进行描述,包括如何调用,如何响应等,以及整个插件的生命周期的控制:网络
下面是对外暴露的三个接口之一的stop_generation函数的源码:架构
void stop_generation() { if(!running) throw fc::exception(fc::invalid_operation_exception_code); timer.cancel(); running = false; ilog("Stopping transaction generation test"); }
接下来,咱们主要集中精力在结构体txn_test_gen_plugin_impl上,研究路线是以剩余两个接口分别为入口进行逐一分析。app
关于这个接口,调用方法是curl
curl --data-binary '["eosio", "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"]' http://localhost:8888/v1/txn_test_gen/create_test_accounts
传入的参数是eosio以及其私钥。咱们进入到函数create_test_accounts中去分析源码。异步
首先,整个函数涉及到的全部transaction都是打包存入到一个vector集合std::vector中去。async
trxs是一个事务集,它包含不少的trx,而其中每个trx包含一个actions集合vector
trxs的第一个trx,内容为帐户建立:函数
trx的actions成员已经设置完毕,完成剩余trx的组装工做,包括
sign,签名,使用的是建立者eosio的私钥对象,上面咱们已经准备好了,签名的数据是data的摘要
这一部分的源码展现以下:
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<abi_def>(); controller& cc = app().get_plugin<chain_plugin>().chain(); auto chainid = app().get_plugin<chain_plugin>().get_chain_id(); 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<chain::permission_level>{{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<chain::permission_level>{{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<chain::permission_level>{{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); trxs.emplace_back(std::move(trx)); }
trxs的第二个trx,内容为token建立和issue,为帐户转帐为以后的测试作准备
为帐户txn.test.t设置eosio.token合约,以前在操做cleos set contract的时候能够经过打印结果发现,是有setcode和setabi两个步骤的。
setcode handler:
setabi handler:
trx的actions成员已经设置完毕,完成剩余trx的组装工做(同上),这里只介绍不一样的部分
这一部分的源码展现以下:
//set txn.test.t contract to eosio.token & initialize it { signed_transaction trx; vector<uint8_t> 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<chain::permission_level>{{newaccountC,"active"}}, handler); { setabi handler; handler.account = newaccountC; handler.abi = fc::raw::pack(json::from_string(eosio_token_abi).as<abi_def>()); trx.actions.emplace_back( vector<chain::permission_level>{{newaccountC,"active"}}, handler); } { action act; act.account = N(txn.test.t); act.name = N(create); act.authorization = vector<permission_level>{{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<permission_level>{{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<permission_level>{{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<permission_level>{{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); trxs.emplace_back(std::move(trx)); }
目前trxs集合已经包含了两个trx元素,其中每一个trx包含了多个action。下面要将trxs推送到链上执行
这部分代码比较杂,分为几个部分:
push_transactions函数:
void push_transactions( std::vector<signed_transaction>&& trxs, const std::function<void(fc::exception_ptr)>& next ) { auto trxs_copy = std::make_shared<std::decay_t<decltype(trxs)>>(std::move(trxs)); push_next_transaction(trxs_copy, 0, next); }
push_next_transaction函数:
static void push_next_transaction(const std::shared_ptr<std::vector<signed_transaction>>& trxs, size_t index, const std::function<void(const fc::exception_ptr&)>& next ) { chain_plugin& cp = app().get_plugin<chain_plugin>(); cp.accept_transaction( packed_transaction(trxs->at(index)), [=](const fc::static_variant<fc::exception_ptr, transaction_trace_ptr>& result){ if (result.contains<fc::exception_ptr>()) { next(result.get<fc::exception_ptr>()); } else { if (index + 1 < trxs->size()) { push_next_transaction(trxs, index + 1, next); } else { next(nullptr); } } }); }
packed_transaction函数,set_transaction函数以及pack_transaction函数的代码都属于本插件源码以外的EOS库源码,因为自己代码量也较少,含义在上面已经彻底解释过了,这里再也不粘贴源码。
accept_transaction函数也是EOS的库源码
void chain_plugin::accept_transaction(const chain::packed_transaction& trx, next_function<chain::transaction_trace_ptr> next) { my->incoming_transaction_async_method(std::make_shared<packed_transaction>(trx), false, std::forward<decltype(next)>(next)); } incoming_transaction_async_method(app().get_method<incoming::methods::transaction_async>())
该接口的调用方法是:
curl --data-binary '["", 20, 20]' http://localhost:8888/v1/txn_test_gen/start_generation
参数列表为:
翻译过来就是:每20ms提交20笔交易。
接下来,以start_generation 函数为入口进行源码分析。
校验:
这部分代码展现以下:
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;
定义两个action,分别是:
这部分代码展现以下:
//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<permission_level>{{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<permission_level>{{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))));
接下来,是对参数period和batch_size的储存为结构体做用域的变量以供结构体内其余函数调用,而后打印日志,最后调用arm_timer函数。
timer_timeout = period; // timer_timeout是结构体的成员变量 batch = batch_size/2; // batch是结构体的成员变量 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());
从start_generation 函数过来,传入的参数是当前时间now,该函数主要功能是对计时器的初始化操做(计时器与文首的stop_generation函数中的关闭计时器呼应)。具体内容可分为两部分:
注意stop_generation函数关闭的是定时任务的无限递归,停止定时任务,中止发送测试交易。但它并无中止插件服务,咱们仍旧能够经过再次请求插件接口启动无限测试交易。
这部分代码以下:
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; send_transaction([this](const fc::exception_ptr& e){ if (e) { elog("pushing transaction failed: ${e}", ("e", e->to_detail_string())); stop_generation(); } else { // 若是没有终止报错,则无限递归调用arm_timer函数,递归时传入的参数代替上面的now是当前timer对象的过时时间,这样在新的递归调用中,timer的建立会以这个时间再加上period,无间隔继续执行。 arm_timer(timer.expires_at()); } }); }); }
这个函数是本插件的核心功能部分,主要是发送测试交易,对transaction的处理,将咱们上面start_generation 函数中设置的两个action打包到transaction中去,以及对transaction各项属性的设置。具体步骤为:
std::vector<signed_transaction> trxs;
trxs.reserve(2*batch);
接下来,与上面介绍的create_test_accounts 接口的帐户准备过程相同,准备私钥公钥,很少介绍。继续准备trx的参数:
这部分代码以下:
controller& cc = app().get_plugin<chain_plugin>().chain(); auto chainid = app().get_plugin<chain_plugin>().get_chain_id(); 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<uint64_t>(fc::time_point::now().sec_since_epoch()) << 32; abi_serializer eosio_serializer(cc.db().find<account_object, by_name>(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);
接下来,就是循环打包trx,咱们设置的batch_size比如是20,如今咱们已有两个action,每一个action对应一个trx,则循环只须要执行10次,每次执行两个trx便可实现,每一个trx相关的属性在上一阶段都已准备好。直接看代码吧。
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); trxs.emplace_back(std::move(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); trxs.emplace_back(std::move(trx)); } }
最后,执行
push_transactions(std::move(trxs), next);
这个部分与create_test_accounts 接口发起请求的部分一致,这里再也不重复展现。
到这里为止,咱们已经彻底分析透了txn_test_gen_plugin 插件的内容。本文首先从大致上介绍了插件的架构,生命周期,通信请求与返回。接着介绍了核心结构体的内容,而后以对外接口为入口,沿着一条线将每一个功能的实现完整地研究清楚。经过本文的学习,咱们对于EOS插件的体系有了初步深入的理解,同时咱们也彻底搞清楚了txn_test_gen_plugin 插件的功能,以及它为何会达到一个比较高的tps的表现。
圆方圆学院聚集大批区块链名师,打造精品的区块链技术课程。 在各大平台都长期有优质免费公开课,欢迎报名收看。