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

Commit

Permalink
implement NO_DUPLICATE_DEFERRED_ID protocol feature #6115
Browse files Browse the repository at this point in the history
  • Loading branch information
arhag committed Apr 4, 2019
1 parent c8d8271 commit 324260d
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 81 deletions.
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 = {};
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
20 changes: 16 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 = {};
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 Down Expand Up @@ -2138,8 +2144,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 = {};
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
4 changes: 4 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,10 @@ 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( 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) )
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
49 changes: 49 additions & 0 deletions libraries/chain/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ typedef multi_index_container<
>
> recovery_cache_type;

void deferred_transaction_generation_context::reflector_init() {
static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types,
"deferred_transaction_generation_context expects FC to support reflector_init" );


EOS_ASSERT( sender != account_name(), ill_formed_deferred_transaction_generation_context,
"Deferred transaction generation context extension must have a non-empty sender account",
);
}

void transaction_header::set_reference_block( const block_id_type& reference_block ) {
ref_block_num = fc::endian_reverse_u32(reference_block._hash[0]);
ref_block_prefix = reference_block._hash[1];
Expand Down Expand Up @@ -134,6 +144,45 @@ fc::microseconds transaction::get_signature_keys( const vector<signature_type>&
return sig_cpu_usage;
} FC_CAPTURE_AND_RETHROW() }

vector<transaction_extensions> transaction::validate_and_extract_extensions()const {
using transaction_extensions_t = transaction_extension_types::transaction_extensions_t;
using decompose_t = transaction_extension_types::decompose_t;

static_assert( std::is_same<transaction_extensions_t, eosio::chain::transaction_extensions>::value,
"transaction_extensions is not setup as expected" );

vector<transaction_extensions_t> results;

uint16_t id_type_lower_bound = 0;

for( size_t i = 0; i < transaction_extensions.size(); ++i ) {
const auto& e = transaction_extensions[i];
auto id = e.first;

EOS_ASSERT( id >= id_type_lower_bound, invalid_transaction_extension,
"Transaction extensions are not in the correct order (ascending id types required)"
);

results.emplace_back();

auto match = decompose_t::extract<transaction_extensions_t>( id, e.second, results.back() );
EOS_ASSERT( match, invalid_transaction_extension,
"Transaction extension with id type ${id} is not supported",
("id", id)
);

if( match->enforce_unique ) {
EOS_ASSERT( i == 0 || id > id_type_lower_bound, invalid_transaction_extension,
"Transaction extension with id type ${id} is not allowed to repeat",
("id", id)
);
}

id_type_lower_bound = id;
}

return results;
}

const signature_type& signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id) {
signatures.push_back(key.sign(sig_digest(chain_id, context_free_data)));
Expand Down
Loading

0 comments on commit 324260d

Please sign in to comment.