原文連接:醒者呆的博客園,www.cnblogs.com/Evsward/p/t…html
本文內容本屬於《【精解】EOS TPS 多維實測》的內容,但因爲在編寫時篇幅過長,因此我決定將這一部分單獨成文撰寫,以便於理解。算法
關鍵字:eos, txn_test_gen_plugin, signed_transaction, ordered_action_result, C++, EOS插件json
這個插件是官方開發用來測試塊打包交易量的,這種方式因爲是直接系統內部調用來模擬transaction,沒有中間通信的損耗,所以效率是很是高的,官方稱經過這個插件測試到了8000的tps結果,而就個人測試結果來說,沒有這麼恐怖,但也能到2000了,熟不知,其餘的測試手段,例如cleos,eosjs可能只有百級的量。下面,咱們一同來研究一下這個插件是如何實現以上功能的,過程當中,咱們也會思考EOS插件的架構體系,以及實現方法。經過本文的學習,若是有好的想法,咱們也能夠本身開發一個功能強大的插件pr給eos,爲EOS社區作出咱們本身的貢獻。bash
關於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,內容爲帳戶建立:
這一部分的源碼展現以下:
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,爲帳戶轉帳爲以後的測試作準備
這一部分的源碼展現以下:
//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
參數列表爲:
第一個參數爲 salt,通常用於「加鹽」加密算法的值,這裏咱們能夠留空。
第二個參數爲 period,發送交易的間隔時間,單位爲ms,這裏是20。
第三個參數爲 batch_size,每一個發送間隔週期內打包交易的數量,這裏也是20。
翻譯過來就是:每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;
複製代碼
這部分代碼展現以下:
//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函數中的關閉計時器呼應)。具體內容可分爲兩部分:
設定計時器的過時時間,值爲start_generation 接口的參數period與now相加的值,即從如今開始,過period這麼久,當前計時器對象timer就過時。
設定計時器的異步定時任務,任務體直接調用send_transaction函數,對函數的返回值進行處理,若是有報錯信息(通常是服務停止)則調用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各項屬性的設置。具體步驟爲:
聲明trxs,併爲其設置大小爲start_generation 接口中batch_size的值。
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的表現。
圓方圓學院聚集大批區塊鏈名師,打造精品的區塊鏈技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:ke.qq.com/course/3451…