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

Implement NO_DUPLICATE_DEFERRED_ID protocol feature #7072

Merged
merged 10 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,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 @@ -954,8 +954,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 @@ -978,6 +984,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 @@ -1112,6 +1120,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 @@ -1302,6 +1312,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 @@ -2138,8 +2152,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
};
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 @@ -74,6 +107,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 @@ -173,53 +207,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 @@ -73,6 +73,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