eosio::transaction out;
out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
out.delay_sec = refund_delay;
cancel_deferred( from ); // TODO: Remove this line when replacing deferred trxs is fixed
out.send( from, from, true );
先声明一个transaction. Push action之后,设置延迟的时间就可以了, cancel_deferred是用来取消一个deferred_transaction的。 一个deferred_transaction 通过send_id 和 contract_account来作为唯一标识符, payer 为该 deferred_trx RAM资源消耗的支付者(即该 trx 所占的内存,执行完后返回), deferred_trx 的 CPU NET 由当前 trx 中 actions 的 author 支付。 最后用send函数就可以执行一个延迟交易了
//eosiolib/transaction.hpp
void send(const uint128_t& sender_id, account_name payer, bool replace_existing = false) const {
auto serialize = pack(*this);
send_deferred(sender_id, payer, serialize.data(), serialize.size(), replace_existing);
}
send函数将数据打包后调用wasm提供的接口。
//libraries/chain/wasm_interface.cpp
void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr
try {
transaction trx;
fc::raw::unpack
//调用apply_context的函数。
context.schedule_deferred_transaction(sender_id, payer, std::move(trx), replace_existing);
} FC_RETHROW_EXCEPTIONS(warn, "data as hex: ${data}", ("data", fc::to_hex(data, data_len)))
}
void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing ) {
//deferred_transaction 不需要有上下文无关的action。
EOS_ASSERT( trx.context_free_actions.size() == 0, cfa_inside_generated_tx, "context free actions are not currently allowed in generated transactions" );
//给trx添加必要的参数
trx.expiration = control.pending_block_time() + fc::microseconds(999'999); // Rounds up to nearest second (makes expiration check unnecessary)
trx.set_reference_block(control.head_block_id()); // No TaPoS check necessary
control.validate_referenced_accounts( trx );
// Charge ahead of time for the additional net usage needed to retire the deferred transaction
// whether that be by successfully executing, soft failure, hard failure, or expiration.
const auto& cfg = control.get_global_properties().configuration;
trx_context.add_net_usage( static_cast
+ static_cast
auto delay = fc::seconds(trx.delay_sec);
if( !control.skip_auth_check() && !privileged ) { // Do not need to check authorization if replayng irreversible block or if contract is privileged
if( payer != receiver ) {
require_authorization(payer); /// uses payer's storage
}
// if a contract is deferring only actions to itself then there is no need
// to check permissions, it could have done everything anyway.
bool check_auth = false;
for( const auto& act : trx.actions ) {
if( act.account != receiver ) {
check_auth = true;
break;
}
}
if( check_auth ) {
control.get_authorization_manager()
.check_authorization( trx.actions,
{},
{{receiver, config::eosio_code_name}},
delay,
std::bind(&transaction_context::checktime, &this->trx_context),
false
);
}
}
uint32_t trx_size = 0;
auto& d = control.db();
//当deferred_transaction不存在时,往数据库添加,存在时,如果不允许替换则中断,否则更新。
if ( auto ptr = d.find
EOS_ASSERT( replace_existing, deferred_tx_duplicate, "deferred transaction with the same sender_id and payer already exists" );
// TODO: Remove the following subjective check when the deferred trx replacement RAM bug has been fixed with a hard fork.
EOS_ASSERT( !control.is_producing_block(), subjective_block_production_exception,
"Replacing a deferred transaction is temporarily disabled." );
// TODO: The logic of the next line needs to be incorporated into the next hard fork.
// trx_context.add_ram_usage( ptr->payer, -(config::billable_size_v
d.modify
gtx.sender = receiver;
gtx.sender_id = sender_id;
gtx.payer = payer;
gtx.published = control.pending_block_time();
gtx.delay_until = gtx.published + delay;
gtx.expiration = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window);
trx_size = gtx.set( trx );
});
} else {
d.create
gtx.trx_id = trx.id();
gtx.sender = receiver;
gtx.sender_id = sender_id;
gtx.payer = payer;
gtx.published = control.pending_block_time();
gtx.delay_until = gtx.published + delay;
gtx.expiration = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window);
trx_size = gtx.set( trx );
});
}
trx_context.add_ram_usage( payer, (config::billable_size_v
}
好了, 数据库已经有deferred_transaction的数据了,那么是在什么时候执行它呢。先看看generated_transaction_object 用的是什么索引。
using generated_transaction_multi_index = chainbase::shared_multi_index_container<
generated_transaction_object,
indexed_by<
ordered_unique< tag
ordered_unique< tag
ordered_unique< tag
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, expiration),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)
>
>,
ordered_unique< tag
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, delay_until),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)
>
>,
ordered_unique< tag
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, account_name, sender),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, uint128_t, sender_id)
>
>
>
>;
看看哪里引用了这个索引。通过查找我们会在controller.cpp发现get_scheduled_transactions函数。
//获取所有delay transaction的索引
vector
const auto& idx = db().get_index
vector
static const size_t max_reserve = 64;
result.reserve(std::min(idx.size(), max_reserve));
auto itr = idx.begin();
while( itr != idx.end() && itr->delay_until <= pending_block_time() ) {
result.emplace_back(itr->trx_id);
++itr;
}
return result;
}
这个函数会在producer_plugin使用。在start_block生产区块的时候执行delay_transaction 并打包进块里。
if (_pending_block_mode == pending_block_mode::producing) {
auto& blacklist_by_id = _blacklisted_transactions.get
auto& blacklist_by_expiry = _blacklisted_transactions.get
auto now = fc::time_point::now();
while (!blacklist_by_expiry.empty() && blacklist_by_expiry.begin()->expiry <= now) {
blacklist_by_expiry.erase(blacklist_by_expiry.begin());
}
//获取delay transaction的id
auto scheduled_trxs = chain.get_scheduled_transactions();
for (const auto& trx : scheduled_trxs) {
if (block_time <= fc::time_point::now()) exhausted = true;
if (exhausted) {
break;
}
// configurable ratio of incoming txns vs deferred txns
while (_incoming_trx_weight >= 1.0 && orig_pending_txn_size && _pending_incoming_transactions.size()) {
auto e = _pending_incoming_transactions.front();
_pending_incoming_transactions.pop_front();
--orig_pending_txn_size;
_incoming_trx_weight -= 1.0;
on_incoming_transaction_async(std::get<0>(e), std::get<1>(e), std::get<2>(e));
}
if (block_time <= fc::time_point::now()) {
exhausted = true;
break;
}
if (blacklist_by_id.find(trx) != blacklist_by_id.end()) {
continue;
}
try {
auto deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms);
bool deadline_is_subjective = false;
if (_max_transaction_time_ms < 0 || (_pending_block_mode == pending_block_mode::producing && block_time < deadline)) {
deadline_is_subjective = true;
deadline = block_time;
}
//执行
auto trace = chain.push_scheduled_transaction(trx, deadline);
if (trace->except) {
if (failure_is_subjective(*trace->except, deadline_is_subjective)) {
exhausted = true;
} else {
auto expiration = fc::time_point::now() + fc::seconds(chain.get_global_properties().configuration.deferred_trx_expiration_window);
// this failed our configured maximum transaction time, we don't want to replay it add it to a blacklist
_blacklisted_transactions.insert(transaction_id_with_expiry{trx, expiration});
}
}
} catch ( const guard_exception& e ) {
app().get_plugin
return start_block_result::failed;
} FC_LOG_AND_DROP();
_incoming_trx_weight += _incoming_defer_ratio;
if (!orig_pending_txn_size) _incoming_trx_weight = 0.0;
}
}
push_scheduled_transaction 的逻辑跟push_transaction 类似, 这里就不再赘述了。
值得一提的是,通过合约建立的延时交易,在block上只会存他的transaction_id。 不会存详情。 通过push_transaction的会把整个transaction都存在block上不会只存transaction_id。