我们在开发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节点。很快就会赶上,让可逆节点与不可逆节点保持一定距离。
希望能否帮助到大家。