Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Merge pull request #7072 from EOSIO/6115-no-duplicate-deferred-id
Browse files Browse the repository at this point in the history
Implement NO_DUPLICATE_DEFERRED_ID protocol feature
  • Loading branch information
arhag committed Apr 11, 2019
2 parents 088a852 + a2d1927 commit bc9e0e5
Show file tree
Hide file tree
Showing 20 changed files with 394 additions and 91 deletions.
36 changes: 34 additions & 2 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,45 @@ void apply_context::execute_context_free_inline( action&& a ) {

void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing ) {
EOS_ASSERT( trx.context_free_actions.size() == 0, cfa_inside_generated_tx, "context free actions are not currently allowed in generated transactions" );
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

bool enforce_actor_whitelist_blacklist = trx_context.enforce_whiteblacklist && control.is_producing_block()
&& !control.sender_avoids_whitelist_blacklist_enforcement( receiver );
trx_context.validate_referenced_accounts( trx, enforce_actor_whitelist_blacklist );

if( control.is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) {
auto exts = trx.validate_and_extract_extensions();
if( exts.size() > 0 ) {
EOS_ASSERT( exts.size() == 1, invalid_transaction_extension,
"only one extension is currently supported for deferred transactions"
);
const auto& context = exts.front().get<deferred_transaction_generation_context>();
EOS_ASSERT( context.sender == receiver, ill_formed_deferred_transaction_generation_context,
"deferred transaction generaction context contains mismatching sender",
("expected", receiver)("actual", context.sender)
);
EOS_ASSERT( context.sender_id == sender_id, ill_formed_deferred_transaction_generation_context,
"deferred transaction generaction context contains mismatching sender_id",
("expected", sender_id)("actual", context.sender_id)
);
EOS_ASSERT( context.sender_trx_id == trx_context.id, ill_formed_deferred_transaction_generation_context,
"deferred transaction generaction context contains mismatching sender_trx_id",
("expected", trx_context.id)("actual", context.sender_trx_id)
);
} else {
FC_ASSERT( trx.transaction_extensions.size() == 0, "invariant failure" );
trx.transaction_extensions.emplace_back(
deferred_transaction_generation_context::extension_id(),
fc::raw::pack( deferred_transaction_generation_context( trx_context.id, sender_id, receiver ) )
);
}
trx.expiration = time_point_sec();
trx.ref_block_num = 0;
trx.ref_block_prefix = 0;
} else {
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
}

// 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;
Expand Down
28 changes: 24 additions & 4 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -956,8 +956,14 @@ struct controller_impl {
// Deliver onerror action containing the failed deferred transaction directly back to the sender.
etrx.actions.emplace_back( vector<permission_level>{{gtrx.sender, config::active_name}},
onerror( gtrx.sender_id, gtrx.packed_trx.data(), gtrx.packed_trx.size() ) );
etrx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to avoid appearing expired
etrx.set_reference_block( self.head_block_id() );
if( self.is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) {
etrx.expiration = time_point_sec();
etrx.ref_block_num = 0;
etrx.ref_block_prefix = 0;
} else {
etrx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired
etrx.set_reference_block( self.head_block_id() );
}

transaction_context trx_context( self, etrx, etrx.id(), start );
trx_context.deadline = deadline;
Expand All @@ -979,6 +985,8 @@ struct controller_impl {
trx_context.squash();
restore.cancel();
return trace;
} catch( const disallowed_transaction_extensions_bad_block_exception& ) {
throw;
} catch( const protocol_feature_bad_block_exception& ) {
throw;
} catch( const fc::exception& e ) {
Expand Down Expand Up @@ -1121,6 +1129,8 @@ struct controller_impl {
restore.cancel();

return trace;
} catch( const disallowed_transaction_extensions_bad_block_exception& ) {
throw;
} catch( const protocol_feature_bad_block_exception& ) {
throw;
} catch( const fc::exception& e ) {
Expand Down Expand Up @@ -1314,6 +1324,10 @@ struct controller_impl {
unapplied_transactions.erase( trx->signed_id );
}
return trace;
} catch( const disallowed_transaction_extensions_bad_block_exception& ) {
throw;
} catch( const protocol_feature_bad_block_exception& ) {
throw;
} catch (const fc::exception& e) {
trace->except = e;
trace->except_ptr = std::current_exception();
Expand Down Expand Up @@ -2150,8 +2164,14 @@ struct controller_impl {

signed_transaction trx;
trx.actions.emplace_back(std::move(on_block_act));
trx.set_reference_block(self.head_block_id());
trx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired
if( self.is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) {
trx.expiration = time_point_sec();
trx.ref_block_num = 0;
trx.ref_block_prefix = 0;
} else {
trx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired
trx.set_reference_block( self.head_block_id() );
}
return trx;
}

Expand Down
35 changes: 0 additions & 35 deletions libraries/chain/include/eosio/chain/block_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,6 @@
namespace eosio { namespace chain {

namespace detail {
struct extract_match {
bool enforce_unique = false;
};

template<typename... Ts>
struct decompose;

template<>
struct decompose<> {
template<typename ResultVariant>
static auto extract( uint16_t id, const vector<char>& data, ResultVariant& result )
-> fc::optional<extract_match>
{
return {};
}
};

template<typename T, typename... Rest>
struct decompose<T, Rest...> {
using head_t = T;
using tail_t = decompose< Rest... >;

template<typename ResultVariant>
static auto extract( uint16_t id, const vector<char>& data, ResultVariant& result )
-> fc::optional<extract_match>
{
if( id == head_t::extension_id() ) {
result = fc::raw::unpack<head_t>( data );
return { extract_match{ head_t::enforce_unique() } };
}

return tail_t::template extract<ResultVariant>( id, data, result );
}
};

template<typename... Ts>
struct block_header_extension_types {
using block_header_extensions_t = fc::static_variant< Ts... >;
Expand Down
6 changes: 6 additions & 0 deletions libraries/chain/include/eosio/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ namespace eosio { namespace chain {
3040013, "Transaction is too big" )
FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction_compression, transaction_exception,
3040014, "Unknown transaction compression" )
FC_DECLARE_DERIVED_EXCEPTION( invalid_transaction_extension, transaction_exception,
3040015, "Invalid transaction extension" )
FC_DECLARE_DERIVED_EXCEPTION( ill_formed_deferred_transaction_generation_context, transaction_exception,
3040016, "Transaction includes an ill-formed deferred transaction generation context extension" )
FC_DECLARE_DERIVED_EXCEPTION( disallowed_transaction_extensions_bad_block_exception, transaction_exception,
3250002, "Transaction includes disallowed extensions (invalid block)" )


FC_DECLARE_DERIVED_EXCEPTION( action_validate_exception, chain_exception,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum class builtin_protocol_feature_t : uint32_t {
preactivate_feature,
only_link_to_existing_permission,
replace_deferred,
no_duplicate_deferred_id,
fix_linkauth_restriction,
disallow_empty_producer_schedule,
restrict_action_to_self,
Expand Down
74 changes: 35 additions & 39 deletions libraries/chain/include/eosio/chain/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@

namespace eosio { namespace chain {

struct deferred_transaction_generation_context : fc::reflect_init {
static constexpr uint16_t extension_id() { return 0; }
static constexpr bool enforce_unique() { return true; }

deferred_transaction_generation_context() = default;

deferred_transaction_generation_context( const transaction_id_type& sender_trx_id, uint128_t sender_id, account_name sender )
:sender_trx_id( sender_trx_id )
,sender_id( sender_id )
,sender( sender )
{}

void reflector_init();

transaction_id_type sender_trx_id;
uint128_t sender_id;
account_name sender;
};

namespace detail {
template<typename... Ts>
struct transaction_extension_types {
using transaction_extensions_t = fc::static_variant< Ts... >;
using decompose_t = decompose< Ts... >;
};
}

using transaction_extension_types = detail::transaction_extension_types<
deferred_transaction_generation_context
>;

using transaction_extensions = transaction_extension_types::transaction_extensions_t;

/**
* The transaction header contains the fixed-sized data
* associated with each transaction. It is separated from
Expand Down Expand Up @@ -75,6 +108,7 @@ namespace eosio { namespace chain {
return account_name();
}

vector<eosio::chain::transaction_extensions> validate_and_extract_extensions()const;
};

struct signed_transaction : public transaction
Expand Down Expand Up @@ -174,53 +208,15 @@ namespace eosio { namespace chain {

using packed_transaction_ptr = std::shared_ptr<packed_transaction>;

/**
* When a transaction is generated it can be scheduled to occur
* in the future. It may also fail to execute for some reason in
* which case the sender needs to be notified. When the sender
* sends a transaction they will assign it an ID which will be
* passed back to the sender if the transaction fails for some
* reason.
*/
struct deferred_transaction : public signed_transaction
{
uint128_t sender_id; /// ID assigned by sender of generated, accessible via WASM api when executing normal or error
account_name sender; /// receives error handler callback
account_name payer;
time_point_sec execute_after; /// delayed execution

deferred_transaction() = default;

deferred_transaction(uint128_t sender_id, account_name sender, account_name payer,time_point_sec execute_after,
const signed_transaction& txn)
: signed_transaction(txn),
sender_id(sender_id),
sender(sender),
payer(payer),
execute_after(execute_after)
{}
};

struct deferred_reference {
deferred_reference(){}
deferred_reference( const account_name& sender, const uint128_t& sender_id)
:sender(sender),sender_id(sender_id)
{}

account_name sender;
uint128_t sender_id;
};

uint128_t transaction_id_to_sender_id( const transaction_id_type& tid );

} } /// namespace eosio::chain

FC_REFLECT(eosio::chain::deferred_transaction_generation_context, (sender_trx_id)(sender_id)(sender) )
FC_REFLECT( eosio::chain::transaction_header, (expiration)(ref_block_num)(ref_block_prefix)
(max_net_usage_words)(max_cpu_usage_ms)(delay_sec) )
FC_REFLECT_DERIVED( eosio::chain::transaction, (eosio::chain::transaction_header), (context_free_actions)(actions)(transaction_extensions) )
FC_REFLECT_DERIVED( eosio::chain::signed_transaction, (eosio::chain::transaction), (signatures)(context_free_data) )
FC_REFLECT_ENUM( eosio::chain::packed_transaction::compression_type, (none)(zlib))
// @ignore unpacked_trx
FC_REFLECT( eosio::chain::packed_transaction, (signatures)(compression)(packed_context_free_data)(packed_trx) )
FC_REFLECT_DERIVED( eosio::chain::deferred_transaction, (eosio::chain::signed_transaction), (sender_id)(sender)(payer)(execute_after) )
FC_REFLECT( eosio::chain::deferred_reference, (sender)(sender_id) )
2 changes: 2 additions & 0 deletions libraries/chain/include/eosio/chain/transaction_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ namespace eosio { namespace chain {

void validate_cpu_usage_to_bill( int64_t u, bool check_minimum = true )const;

void disallow_transaction_extensions( const char* error_msg )const;

/// Fields:
public:

Expand Down
37 changes: 37 additions & 0 deletions libraries/chain/include/eosio/chain/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,43 @@ namespace eosio { namespace chain {
};
// enum_hash needed to support old gcc compiler of Ubuntu 16.04

namespace detail {
struct extract_match {
bool enforce_unique = false;
};

template<typename... Ts>
struct decompose;

template<>
struct decompose<> {
template<typename ResultVariant>
static auto extract( uint16_t id, const vector<char>& data, ResultVariant& result )
-> fc::optional<extract_match>
{
return {};
}
};

template<typename T, typename... Rest>
struct decompose<T, Rest...> {
using head_t = T;
using tail_t = decompose< Rest... >;

template<typename ResultVariant>
static auto extract( uint16_t id, const vector<char>& data, ResultVariant& result )
-> fc::optional<extract_match>
{
if( id == head_t::extension_id() ) {
result = fc::raw::unpack<head_t>( data );
return { extract_match{ head_t::enforce_unique() } };
}

return tail_t::template extract<ResultVariant>( id, data, result );
}
};
}

} } // eosio::chain

FC_REFLECT( eosio::chain::void_t, )
13 changes: 13 additions & 0 deletions libraries/chain/protocol_feature_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ Also corrects the RAM usage of accounts affected by the replace deferred transac
*/
{}
} )
( builtin_protocol_feature_t::no_duplicate_deferred_id, builtin_protocol_feature_spec{
"NO_DUPLICATE_DEFERRED_ID",
fc::variant("45967387ee92da70171efd9fefd1ca8061b5efe6f124d269cd2468b47f1575a0").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: NO_DUPLICATE_DEFERRED_ID
Depends on: REPLACE_DEFERRED
Ensures transactions generated by contracts for deferred execution are adjusted to avoid transaction ID conflicts.
Also allows a contract to send a deferred transaction in a manner that enables the contract to know the transaction ID ahead of time.
*/
{builtin_protocol_feature_t::replace_deferred}
} )
( builtin_protocol_feature_t::fix_linkauth_restriction, builtin_protocol_feature_spec{
"FIX_LINKAUTH_RESTRICTION",
fc::variant("a98241c83511dc86c857221b9372b4aa7cea3aaebc567a48604e1d3db3557050").as<digest_type>(),
Expand Down
Loading

0 comments on commit bc9e0e5

Please sign in to comment.