我們在開發EOS項目中,在高TPS下可能會出現交易從可逆的block無法打包到不可以的block中,造成交易回滾,就此問題。我們提出解決方案。
在交易過程中,我們需要確認這筆交易,從可逆的狀態到不可逆的狀態。才確認這筆交易完成。
一、問題提出:
1、如何確認交易;
2、如果發現,可逆block與不可逆的差距很大,如何解決。
二、實現辦法:
針對如何確認交易問題,我們通過以下方法解決。
1、同步一個mongodb。
2、在一筆交易完成後,檢測mongodb中,transactions document ,中一個字段irreversible爲true,才能確認這筆交易完成。
{
"_id" : ObjectId("5d8da6b63f6b7ad62a74534c"),
"trx_id" : "10d9841055e4e8517df6519e3e02d02bfa0ec209b121b551e6e875c85b238adb",
"accepted" : true,
"actions" : [
{
"account" : "token.abcd",
"name" : "transfer",
"authorization" : [
{
"actor" : "antreceipt",
"permission" : "active"
}
],
"data" : {
"from" : "antreceipt",
"to" : "tyre",
"quantity" : "1.9600 ABCD",
"memo" : "第三方調用轉賬"
},
"hex_data" : "0040ae4e2175f3340000000000a0aecf904c00000000000004494b454300000015e7acace4b889e696b9e8b083e794a8e8bdace8b4a6"
}
],
"context_free_actions" : [],
"context_free_data" : [],
"createdAt" : ISODate("2019-09-27T06:05:43.048Z"),
"delay_sec" : 0,
"expiration" : "2019-09-27T06:06:12",
"implicit" : false,
"max_cpu_usage_ms" : 0,
"max_net_usage_words" : 0,
"ref_block_num" : 26827,
"ref_block_prefix" : NumberLong(2692015748),
"scheduled" : false,
"signatures" : [
"SIG_K1_K1AaPDVagBgdAxqwYCGQempDJ5FJn1RRAaZEMfqJpeXLAJebAUFWaguai4v2H1cnAqm5aHiwxdRQWk5kU9ycxUQGK6Bm4V"
],
"signing_keys" : {
"0" : "EOS6Kv7BREsnXjzHGkjtjDR5FDbTDTNvkdExcsjumufUEvR5UvQFC"
},
"transaction_extensions" : [],
"block_id" : "012168cd8439207b3a1105b1b5dd3f2d95f883210b705e213c8a0fa2395f03b9",
"block_num" : 18966733,
"irreversible" : true,
"updatedAt" : ISODate("2019-09-27T06:07:21.532Z")
}
確認這個字段 ,“irreversible” : true,
如果,這個同步的mongodb出現問題,我們需要重新同步。就會導致新的問題產生。
可逆block與不可逆的差距很大,我們通過以下辦法解決
可逆節點位置 | 不可逆節點位置 | 備註 |
---|---|---|
2000 | 1000 | 啓動節點 |
2000+1 | 1000 +1 | 收到一個新包 |
差距一直存在 | ||
問題 | 如何解決 | |
解決辦法 | 加速執行不可逆節點速度 |
解決這個問題,我們需要分析源代碼
文件路徑
eos\libraries\chain\controller.cpp
...
void push_block( std::future<block_state_ptr>& block_state_future ) {
controller::block_status s = controller::block_status::complete;
EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block");
auto reset_prod_light_validation = fc::make_scoped_exit([old_value=trusted_producer_light_validation, this]() {
trusted_producer_light_validation = old_value;
});
try {
block_state_ptr new_header_state = block_state_future.get();
auto& b = new_header_state->block;
emit( self.pre_accepted_block, b );
fork_db.add( new_header_state, false );
if (conf.trusted_producers.count(b->producer)) {
trusted_producer_light_validation = true;
};
emit( self.accepted_block_header, new_header_state );
if ( read_mode != db_read_mode::IRREVERSIBLE ) {
maybe_switch_forks( s );
}
} FC_LOG_AND_RETHROW( )
}
...
這個函數的作用,就是保存block,在這裏fork_db.add( new_header_state, false );就是保存block的地方,我們需要找到這個方法。
文件位置
eos\libraries\chain\fork_database.cpp
block_state_ptr fork_database::add( const block_state_ptr& n, bool skip_validate_previous ) {
EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" );
EOS_ASSERT( my->head, fork_db_block_not_found, "no head block set" );
if( !skip_validate_previous ) {
auto prior = my->index.find( n->block->previous );
EOS_ASSERT( prior != my->index.end(), unlinkable_block_exception,
"unlinkable block", ("id", n->block->id())("previous", n->block->previous) );
}
auto inserted = my->index.insert(n);
EOS_ASSERT( inserted.second, fork_database_exception, "duplicate block added?" );
my->head = *my->index.get<by_lib_block_num>().begin();
auto lib = my->head->dpos_irreversible_blocknum;
auto oldest = *my->index.get<by_block_num>().begin();
if( oldest->block_num < lib ) {
prune( oldest );
}
return n;
}
我們分析上面代碼,如果 oldest->block_num < lib 的時候,就確認這個塊, prune( oldest );
這樣就是我上面說的,2000+1 ,1000+1,這樣,永遠追不上。
我們需要再這裏改造。
if( oldest->block_num < lib ) {
prune( oldest );
}
修改如下:
在距離差距很遠的情況下,我們加速確認塊,每次確認12個blcok,在趕上後,按照既定規則確認。
auto destsize = 168; //21個生產節點,2/3的節點確認 12 * 21*2/3 = 168
if( ( oldest->block_num + destsize ) < lib )
{
uint8_t tmp = 12; //每次加速確認12個block
while(tmp)
{
auto lib = my->head->dpos_irreversible_blocknum;
auto oldest = *my->index.get<by_block_num>().begin();
prune( oldest );
tmp--;
}
}
else{
if( oldest->block_num < lib ) {
prune( oldest );
}
}
這個函數完整的代碼
block_state_ptr fork_database::add( const block_state_ptr& n, bool skip_validate_previous ) {
EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" );
EOS_ASSERT( my->head, fork_db_block_not_found, "no head block set" );
if( !skip_validate_previous ) {
auto prior = my->index.find( n->block->previous );
EOS_ASSERT( prior != my->index.end(), unlinkable_block_exception,
"unlinkable block", ("id", n->block->id())("previous", n->block->previous) );
}
auto inserted = my->index.insert(n);
EOS_ASSERT( inserted.second, fork_database_exception, "duplicate block added?" );
my->head = *my->index.get<by_lib_block_num>().begin();
auto lib = my->head->dpos_irreversible_blocknum;
auto oldest = *my->index.get<by_block_num>().begin();
//ilog("lib =${lib},oldest =${oldest}",("lib",lib)("oldest",oldest->block_num));
// add by cjb 20190927
auto destsize = 168; //21個生產節點,2/3的節點確認 12 * 21*2/3 = 168
if( ( oldest->block_num + destsize ) < lib )
{
uint8_t tmp = 12; //每次加速確認12個block
while(tmp)
{
auto lib = my->head->dpos_irreversible_blocknum;
auto oldest = *my->index.get<by_block_num>().begin();
//ilog("lib =${lib},oldest =${oldest}",("lib",lib)("oldest",oldest->block_num));
prune( oldest );
tmp--;
}
}
else{
if( oldest->block_num < lib ) {
prune( oldest );
}
}
return n;
}
執行
./eosio_install.sh
啓動mongog節點。很快就會趕上,讓可逆節點與不可逆節點保持一定距離。
希望能否幫助到大家。