Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DISABLE_DEFERRED_TRXS_STAGE_1 and DISABLE_DEFERRED_TRXS_STAGE_2 protocol features #1697

Merged
merged 27 commits into from
Oct 4, 2023
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5523fd5
implement DISABLE_DEFERRED_TRXS_STAGE_1 and DISABLE_DEFERRED_TRXS_STA…
linh2931 Sep 29, 2023
d8f975a
make send_deferred, cancel_deferred and canceldelay no-op after DISAB…
linh2931 Sep 29, 2023
cb966dd
enforce deferred trxs retirement rules after disable_deferred_trxs_st…
linh2931 Sep 29, 2023
648b016
Exclude DISABLE_DEFERRED_TRXS_STAGE_1 and DISABLE_DEFERRED_TRXS_STAGE…
linh2931 Sep 29, 2023
83a7cd6
use a new setup_policy::full_but_disable_deferrd_trx for tests which …
linh2931 Sep 30, 2023
bdb607d
use centralized testers for setup_policy::full_but_disable_deferrd_trx
linh2931 Sep 30, 2023
28ef3ca
add tests for disable_deferred_trxs_stage_1 protocol feature
linh2931 Sep 30, 2023
5d61a8a
add tests for disable_deferred_trxs_stage_2
linh2931 Oct 1, 2023
015ff42
use tester_no_disable_deferrd_trx instead of validating_tester_no_dis…
linh2931 Oct 1, 2023
3a11565
enfore the rule of disable_deferred_trxs_stage_2 dependency on disabl…
linh2931 Oct 2, 2023
3a3e059
add a disable_deferred_trxs_stage_2 dependency test
linh2931 Oct 2, 2023
efb067a
add missing disable_deferred_trxs_stage_2's dependency to its definit…
linh2931 Oct 2, 2023
49bff9b
enfore deferred trx retirement rule correctly and add a test for it
linh2931 Oct 2, 2023
4e8e1a9
use system-contracts from main instead of release/3.1
linh2931 Oct 2, 2023
a515033
move is_builtin_activated( builtin_protocol_feature_t::disable_deferr…
linh2931 Oct 2, 2023
db52566
add cancelcall action to deferred_test contract to exercise cancel_de…
linh2931 Oct 3, 2023
391a992
add tests for cancel_deferred host function before and after disable_…
linh2931 Oct 3, 2023
779108a
use eos-system-contracts release/3.2 branch
linh2931 Oct 3, 2023
b344239
remove non-needed activation handler for disable_deferred_trxs_stage_…
linh2931 Oct 3, 2023
51bea7b
improve prototol features descriptions and update feature digests acc…
linh2931 Oct 3, 2023
7b3a7c5
fix typos in deferrd instances (should be deferred)
linh2931 Oct 3, 2023
cf15dd8
refactor preactivate_builtin_protocol_features and revert back to ori…
linh2931 Oct 3, 2023
28e9d3e
revert to use full protococol features for currency_tests not involve…
linh2931 Oct 3, 2023
058ed39
make sure all protocol features are activated in the same order such …
linh2931 Oct 3, 2023
da6ad0a
consistently activating protocol features in the same order
linh2931 Oct 3, 2023
4c38e86
make api_tests::transaction_tests for before and after disable_trxs_p…
linh2931 Oct 4, 2023
eb232a7
Merge branch 'main' into disable_deferred_trxs_prot_features
linh2931 Oct 4, 2023
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
2 changes: 1 addition & 1 deletion .cicd/defaults.json
Original file line number Diff line number Diff line change
@@ -4,6 +4,6 @@
"prerelease":false
},
"eossystemcontracts":{
"ref":"release/3.1"
"ref":"release/3.2"
}
}
10 changes: 10 additions & 0 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
@@ -423,6 +423,11 @@ 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 ) {
// no-op after DISABLE_DEFERRED_TRXS_STAGE_1 is activated
if( control.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) ) {
return;
}

EOS_ASSERT( !trx_context.is_read_only(), transaction_exception, "cannot schedule a deferred transaction from within a readonly transaction" );
EOS_ASSERT( trx.context_free_actions.size() == 0, cfa_inside_generated_tx, "context free actions are not currently allowed in generated transactions" );

@@ -624,6 +629,11 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a
}

bool apply_context::cancel_deferred_transaction( const uint128_t& sender_id, account_name sender ) {
// no-op after DISABLE_DEFERRED_TRXS_STAGE_1 is activated
if( control.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) ) {
return false;
}

EOS_ASSERT( !trx_context.is_read_only(), transaction_exception, "cannot cancel a deferred transaction from within a readonly transaction" );
auto& generated_transaction_idx = db.get_mutable_index<generated_transaction_multi_index>();
const auto* gto = db.find<generated_transaction_object,by_sender_id>(boost::make_tuple(sender, sender_id));
22 changes: 19 additions & 3 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
@@ -339,6 +339,7 @@ struct controller_impl {
set_activation_handler<builtin_protocol_feature_t::get_block_num>();
set_activation_handler<builtin_protocol_feature_t::crypto_primitives>();
set_activation_handler<builtin_protocol_feature_t::bls_primitives>();
set_activation_handler<builtin_protocol_feature_t::disable_deferred_trxs_stage_2>();

self.irreversible_block.connect([this](const block_state_ptr& bsp) {
wasmif.current_lib(bsp->block_num);
@@ -1302,8 +1303,11 @@ struct controller_impl {

fc::datastream<const char*> ds( gtrx.packed_trx.data(), gtrx.packed_trx.size() );

EOS_ASSERT( gtrx.delay_until <= self.pending_block_time(), transaction_exception, "this transaction isn't ready",
("gtrx.delay_until",gtrx.delay_until)("pbt",self.pending_block_time()) );
// check delay_until only before disable_deferred_trxs_stage_1 is activated.
if( !self.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) ) {
EOS_ASSERT( gtrx.delay_until <= self.pending_block_time(), transaction_exception, "this transaction isn't ready",
("gtrx.delay_until",gtrx.delay_until)("pbt",self.pending_block_time()) );
}

signed_transaction dtrx;
fc::raw::unpack(ds,static_cast<transaction&>(dtrx) );
@@ -1312,8 +1316,11 @@ struct controller_impl {
transaction_metadata::trx_type::scheduled );
trx->accepted = true;

// After disable_deferred_trxs_stage_1 is activated, a deferred transaction
// can only be retired as expired, and it can be retired as expired
// regardless of whether its delay_util or expiration times have been reached.
transaction_trace_ptr trace;
if( gtrx.expiration < self.pending_block_time() ) {
if( self.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) || gtrx.expiration < self.pending_block_time() ) {
trace = std::make_shared<transaction_trace>();
trace->id = gtrx.trx_id;
trace->block_num = self.head_block_num() + 1;
@@ -3835,6 +3842,15 @@ void controller_impl::on_activation<builtin_protocol_feature_t::bls_primitives>(
} );
}

template<>
void controller_impl::on_activation<builtin_protocol_feature_t::disable_deferred_trxs_stage_2>() {
const auto& idx = db.get_index<generated_transaction_multi_index, by_trx_id>();
// remove all deferred trxs and refund their payers
for( auto itr = idx.begin(); itr != idx.end(); itr = idx.begin() ) {
remove_scheduled_transaction(*itr);
}
}

/// End of protocol feature activation handlers

} } /// eosio::chain
Original file line number Diff line number Diff line change
@@ -36,6 +36,8 @@ enum class builtin_protocol_feature_t : uint32_t {
crypto_primitives = 19,
get_block_num = 20,
bls_primitives = 21,
disable_deferred_trxs_stage_1 = 22,
disable_deferred_trxs_stage_2 = 23,
reserved_private_fork_protocol_features = 500000,
};

35 changes: 35 additions & 0 deletions libraries/chain/protocol_feature_manager.cpp
Original file line number Diff line number Diff line change
@@ -272,6 +272,41 @@ Adds new cryptographic host functions
*/
{}
} )
( builtin_protocol_feature_t::disable_deferred_trxs_stage_1, builtin_protocol_feature_spec{
"DISABLE_DEFERRED_TRXS_STAGE_1",
fc::variant("440c3efaaab212c387ce967c574dc813851cf8332d041beb418dfaf55facd5a9").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: DISABLE_DEFERRED_TRXS_STAGE_1

Once this first disabling deferred transactions protocol feature is activated,
the behavior of the send_deferred and cancel_deferred host functions and
canceldelay native action changes so that they become no-ops.

In addition, any block that retires a deferred transaction with a status other
than expired is invalid.

Also, a deferred transaction can only be retired as expired, and it can be
retired as expired regardless of whether its delay_util or expiration times
have been reached.
*/
{}
} )
( builtin_protocol_feature_t::disable_deferred_trxs_stage_2, builtin_protocol_feature_spec{
"DISABLE_DEFERRED_TRXS_STAGE_2",
fc::variant("a857eeb932774c511a40efb30346ec01bfb7796916b54c3c69fe7e5fb70d5cba").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: DISABLE_DEFERRED_TRXS_STAGE_2
Depends on: DISABLE_DEFERRED_TRXS_STAGE_1

On activation of this second disabling deferred transactions protocol feature,
all pending deferred transactions are removed from state and the RAM paid by
the sender of each deferred transaction is refunded. Also, any block that
retires a deferred transaction is invalid.
*/
{builtin_protocol_feature_t::disable_deferred_trxs_stage_1}
} )
;


17 changes: 17 additions & 0 deletions libraries/testing/include/eosio/testing/tester.hpp
Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@ namespace eosio { namespace testing {
preactivate_feature_only,
preactivate_feature_and_new_bios,
old_wasm_parser,
full_except_do_not_disable_deferred_trx,
full
};

@@ -386,6 +387,7 @@ namespace eosio { namespace testing {
void preactivate_protocol_features(const vector<digest_type> feature_digests);
void preactivate_builtin_protocol_features(const std::vector<builtin_protocol_feature_t>& features);
void preactivate_all_builtin_protocol_features();
void preactivate_all_but_disable_deferred_trx();

static genesis_state default_genesis() {
genesis_state genesis;
@@ -445,6 +447,9 @@ namespace eosio { namespace testing {

public:
vector<digest_type> protocol_features_to_be_activated_wo_preactivation;

private:
std::vector<builtin_protocol_feature_t> get_all_builtin_protocol_features();
};

class tester : public base_tester {
@@ -512,6 +517,12 @@ namespace eosio { namespace testing {
bool validate() { return true; }
};

class tester_no_disable_deferred_trx : public tester {
public:
tester_no_disable_deferred_trx(): tester(setup_policy::full_except_do_not_disable_deferred_trx) {
}
};

class validating_tester : public base_tester {
public:
virtual ~validating_tester() {
@@ -657,6 +668,12 @@ namespace eosio { namespace testing {
bool skip_validate = false;
};

class validating_tester_no_disable_deferred_trx : public validating_tester {
public:
validating_tester_no_disable_deferred_trx(): validating_tester({}, nullptr, setup_policy::full_except_do_not_disable_deferred_trx) {
}
};

/**
* Utility predicate to check whether an fc::exception message is equivalent to a given string
*/
67 changes: 45 additions & 22 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
@@ -265,11 +265,16 @@ namespace eosio { namespace testing {
set_bios_contract();
break;
}
case setup_policy::full: {
case setup_policy::full:
case setup_policy::full_except_do_not_disable_deferred_trx: {
schedule_preactivate_protocol_feature();
produce_block();
set_before_producer_authority_bios_contract();
preactivate_all_builtin_protocol_features();
if( policy == setup_policy::full ) {
preactivate_all_builtin_protocol_features();
} else {
preactivate_all_but_disable_deferred_trx();
}
produce_block();
set_bios_contract();
break;
@@ -1184,20 +1189,7 @@ namespace eosio { namespace testing {
}
}

void base_tester::preactivate_builtin_protocol_features(const std::vector<builtin_protocol_feature_t>& builtin_features) {
const auto& pfs = control->get_protocol_feature_manager().get_protocol_feature_set();

// This behavior is disabled by configurable_wasm_limits
std::vector<digest_type> features;
for(builtin_protocol_feature_t feature : builtin_features ) {
if( auto digest = pfs.get_builtin_digest( feature ) ) {
features.push_back( *digest );
}
}
preactivate_protocol_features(features);
}

void base_tester::preactivate_all_builtin_protocol_features() {
void base_tester::preactivate_builtin_protocol_features(const std::vector<builtin_protocol_feature_t>& builtins) {
const auto& pfm = control->get_protocol_feature_manager();
const auto& pfs = pfm.get_protocol_feature_set();
const auto current_block_num = control->head_block_num() + (control->is_building_block() ? 1 : 0);
@@ -1225,12 +1217,7 @@ namespace eosio { namespace testing {
preactivations.emplace_back( feature_digest );
};

std::vector<builtin_protocol_feature_t> ordered_builtins;
for( const auto& f : builtin_protocol_feature_codenames ) {
ordered_builtins.push_back( f.first );
}
std::sort( ordered_builtins.begin(), ordered_builtins.end() );
for( const auto& f : ordered_builtins ) {
for( const auto& f : builtins ) {
auto digest = pfs.get_builtin_digest( f);
if( !digest ) continue;
add_digests( *digest );
@@ -1239,6 +1226,42 @@ namespace eosio { namespace testing {
preactivate_protocol_features( preactivations );
}

std::vector<builtin_protocol_feature_t> base_tester::get_all_builtin_protocol_features() {
std::vector<builtin_protocol_feature_t> builtins;
for( const auto& f : builtin_protocol_feature_codenames ) {
builtins.push_back( f.first );
}

// Sorting is here to ensure a consistent order across platforms given that it is
// pulling the items from an std::unordered_map. This order is important because
// it impacts the block IDs generated and written out to logs for some tests such
// as the deep-mind tests.
std::sort( builtins.begin(), builtins.end() );

return builtins;
}

void base_tester::preactivate_all_builtin_protocol_features() {
preactivate_builtin_protocol_features( get_all_builtin_protocol_features() );
}

void base_tester::preactivate_all_but_disable_deferred_trx() {
std::vector<builtin_protocol_feature_t> builtins;
for( const auto& f : get_all_builtin_protocol_features() ) {
// Before deferred trxs feature is fully disabled, existing tests involving
// deferred trxs need to be exercised to make sure existing behaviors are
// maintained. Excluding DISABLE_DEFERRED_TRXS_STAGE_1 and DISABLE_DEFERRED_TRXS_STAGE_2
// from full protocol feature list such that existing tests can run.
if( f == builtin_protocol_feature_t::disable_deferred_trxs_stage_1 || f == builtin_protocol_feature_t::disable_deferred_trxs_stage_2 ) {
continue;
}

builtins.push_back( f );
}

preactivate_builtin_protocol_features( builtins );
}

tester::tester(const std::function<void(controller&)>& control_setup, setup_policy policy, db_read_mode read_mode) {
auto def_conf = default_config(tempdir);
def_conf.first.read_mode = read_mode;
30 changes: 22 additions & 8 deletions plugins/producer_plugin/producer_plugin.cpp
Original file line number Diff line number Diff line change
@@ -373,7 +373,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
bool remove_expired_trxs(const fc::time_point& deadline);
bool remove_expired_blacklisted_trxs(const fc::time_point& deadline);
bool process_unapplied_trxs(const fc::time_point& deadline);
void retire_expired_deferred_trxs(const fc::time_point& deadline);
bool retire_deferred_trxs(const fc::time_point& deadline);
bool process_incoming_trxs(const fc::time_point& deadline, unapplied_transaction_queue::iterator& itr);

struct push_result {
@@ -1948,9 +1948,15 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() {
if (!process_unapplied_trxs(preprocess_deadline))
return start_block_result::exhausted;

// Hard-code the deadline to retire expired deferred trxs to 10ms
auto deferred_trxs_deadline = std::min<fc::time_point>(preprocess_deadline, fc::time_point::now() + fc::milliseconds(10));
retire_expired_deferred_trxs(deferred_trxs_deadline);
// after DISABLE_DEFERRED_TRXS_STAGE_2 is activated,
// no deferred trxs are allowed to be retired
if (!chain.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_2) ) {
// Hard-code the deadline to retire expired deferred trxs to 10ms
auto deferred_trxs_deadline = std::min<fc::time_point>(preprocess_deadline, fc::time_point::now() + fc::milliseconds(10));
if (!retire_deferred_trxs(deferred_trxs_deadline)) {
return start_block_result::failed;
}
}
}

repost_exhausted_transactions(preprocess_deadline);
@@ -2293,7 +2299,7 @@ bool producer_plugin_impl::process_unapplied_trxs(const fc::time_point& deadline
return !exhausted;
}

void producer_plugin_impl::retire_expired_deferred_trxs(const fc::time_point& deadline) {
bool producer_plugin_impl::retire_deferred_trxs(const fc::time_point& deadline) {
int num_applied = 0;
int num_failed = 0;
int num_processed = 0;
@@ -2304,10 +2310,13 @@ void producer_plugin_impl::retire_expired_deferred_trxs(const fc::time_point& de
const auto& expired_idx = chain.db().get_index<generated_transaction_multi_index, by_expiration>();
const auto expired_size = expired_idx.size();
auto expired_itr = expired_idx.begin();
bool stage_1_activated = chain.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1);

while (expired_itr != expired_idx.end()) {
if (expired_itr->expiration >= pending_block_time) {
break; // not expired yet
// * Before disable_deferred_trxs_stage_1 is activated, retire only expired deferred trxs.
// * After disable_deferred_trxs_stage_1, retire any deferred trxs in any order
if (!stage_1_activated && expired_itr->expiration >= pending_block_time) { // before stage_1 and not expired yet
break;
}

if (exhausted || deadline <= fc::time_point::now()) {
@@ -2374,9 +2383,14 @@ void producer_plugin_impl::retire_expired_deferred_trxs(const fc::time_point& de
}

if (expired_size > 0) {
fc_dlog(_log, "Processed ${m} of ${n} expired scheduled transactions, Applied ${applied}, Failed/Dropped ${failed}",
fc_dlog(_log, "Processed ${m} of ${n} scheduled transactions, Applied ${applied}, Failed/Dropped ${failed}",
("m", num_processed)("n", expired_size)("applied", num_applied)("failed", num_failed));
}

if (stage_1_activated && num_failed > 0) {
return false;
}
return true;
}

bool producer_plugin_impl::process_incoming_trxs(const fc::time_point& deadline, unapplied_transaction_queue::iterator& itr) {
160 changes: 89 additions & 71 deletions unittests/api_tests.cpp
Original file line number Diff line number Diff line change
@@ -1360,14 +1360,15 @@ BOOST_FIXTURE_TEST_CASE(checktime_start, validating_tester) try {
} FC_LOG_AND_RETHROW()

/*************************************************************************************
* transaction_tests test case
* transaction_tests common function
*************************************************************************************/
BOOST_FIXTURE_TEST_CASE(transaction_tests, validating_tester) { try {
produce_blocks(2);
create_account( "testapi"_n );
produce_blocks(100);
set_code( "testapi"_n, test_contracts::test_api_wasm() );
produce_blocks(1);
template<typename T>
void transaction_tests(T& chain) {
chain.produce_blocks(2);
chain.create_account( "testapi"_n );
chain.produce_blocks(100);
chain.set_code( "testapi"_n, test_contracts::test_api_wasm() );
chain.produce_blocks(1);

// test for zero auth
{
@@ -1376,111 +1377,113 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, validating_tester) { try {
action act({}, tm);
trx.actions.push_back(act);

set_transaction_headers(trx);
BOOST_CHECK_EXCEPTION(push_transaction(trx), transaction_exception,
chain.set_transaction_headers(trx);
BOOST_CHECK_EXCEPTION(chain.push_transaction(trx), transaction_exception,
[](const fc::exception& e) {
return expect_assert_message(e, "transaction must have at least one authorization");
}
);
}

// test send_action
CALL_TEST_FUNCTION(*this, "test_transaction", "send_action", {});
CALL_TEST_FUNCTION(chain, "test_transaction", "send_action", {});

// test send_action_empty
CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_empty", {});
CALL_TEST_FUNCTION(chain, "test_transaction", "send_action_empty", {});

// test send_action_large (512k)
CALL_TEST_FUNCTION( *this, "test_transaction", "send_action_512k", {});
CALL_TEST_FUNCTION( chain, "test_transaction", "send_action_512k", {});

// test send_many_actions_512k (512k)
CALL_TEST_FUNCTION( *this, "test_transaction", "send_many_actions_512k", {});
CALL_TEST_FUNCTION( chain, "test_transaction", "send_many_actions_512k", {});

// test send_action_large (512k + 1)
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_large", {}), inline_action_too_big,
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(chain, "test_transaction", "send_action_large", {}), inline_action_too_big,
[](const fc::exception& e) {
return expect_assert_message(e, "inline action too big");
}
);

// test send_action_inline_fail
BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_inline_fail", {}),
BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION(chain, "test_transaction", "send_action_inline_fail", {}),
eosio_assert_message_exception,
eosio_assert_message_is("test_action::assert_false") );

// test send_transaction
CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction", {});

// test send_transaction_empty
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_empty", {}), tx_no_auths,
[](const fc::exception& e) {
return expect_assert_message(e, "transaction must have at least one authorization");
}
);

{
produce_blocks(10);
transaction_trace_ptr trace;
auto c = control->applied_transaction.connect([&](std::tuple<const transaction_trace_ptr&, const packed_transaction_ptr&> x) {
auto& t = std::get<0>(x);
if (t && t->receipt && t->receipt->status != transaction_receipt::executed) { trace = t; }
} );
block_state_ptr bsp;
auto c2 = control->accepted_block.connect([&](const block_state_ptr& b) { bsp = b; });
CALL_TEST_FUNCTION(chain, "test_transaction", "send_transaction", {});

// test error handling on deferred transaction failure
auto test_trace = CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_trigger_error_handler", {});
if (std::is_same<T, validating_tester_no_disable_deferred_trx>::value) {
// test send_transaction_empty
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(chain, "test_transaction", "send_transaction_empty", {}), tx_no_auths,
[](const fc::exception& e) {
return expect_assert_message(e, "transaction must have at least one authorization");
}
);

BOOST_REQUIRE(trace);
BOOST_CHECK_EQUAL(trace->receipt->status, transaction_receipt::soft_fail);

std::set<transaction_id_type> block_ids;
for( const auto& receipt : bsp->block->transactions ) {
transaction_id_type id;
if( std::holds_alternative<packed_transaction>(receipt.trx) ) {
const auto& pt = std::get<packed_transaction>(receipt.trx);
id = pt.id();
} else {
id = std::get<transaction_id_type>(receipt.trx);
{
chain.produce_blocks(10);
transaction_trace_ptr trace;
auto c = chain.control->applied_transaction.connect([&](std::tuple<const transaction_trace_ptr&, const packed_transaction_ptr&> x) {
auto& t = std::get<0>(x);
if (t && t->receipt && t->receipt->status != transaction_receipt::executed) { trace = t; }
} );
block_state_ptr bsp;
auto c2 = chain.control->accepted_block.connect([&](const block_state_ptr& b) { bsp = b; });

// test error handling on deferred transaction failure
auto test_trace = CALL_TEST_FUNCTION(chain, "test_transaction", "send_transaction_trigger_error_handler", {});

BOOST_REQUIRE(trace);
BOOST_CHECK_EQUAL(trace->receipt->status, transaction_receipt::soft_fail);

std::set<transaction_id_type> block_ids;
for( const auto& receipt : bsp->block->transactions ) {
transaction_id_type id;
if( std::holds_alternative<packed_transaction>(receipt.trx) ) {
const auto& pt = std::get<packed_transaction>(receipt.trx);
id = pt.id();
} else {
id = std::get<transaction_id_type>(receipt.trx);
}
block_ids.insert( id );
}
block_ids.insert( id );
}

BOOST_CHECK_EQUAL(2, block_ids.size() ); // originating trx and deferred
BOOST_CHECK_EQUAL(1, block_ids.count( test_trace->id ) ); // originating
BOOST_CHECK( !test_trace->failed_dtrx_trace );
BOOST_CHECK_EQUAL(0, block_ids.count( trace->id ) ); // onerror id, not in block
BOOST_CHECK_EQUAL(1, block_ids.count( trace->failed_dtrx_trace->id ) ); // deferred id since trace moved to failed_dtrx_trace
BOOST_CHECK( trace->action_traces.at(0).act.name == "onerror"_n );
BOOST_CHECK_EQUAL(2, block_ids.size() ); // originating trx and deferred
BOOST_CHECK_EQUAL(1, block_ids.count( test_trace->id ) ); // originating
BOOST_CHECK( !test_trace->failed_dtrx_trace );
BOOST_CHECK_EQUAL(0, block_ids.count( trace->id ) ); // onerror id, not in block
BOOST_CHECK_EQUAL(1, block_ids.count( trace->failed_dtrx_trace->id ) ); // deferred id since trace moved to failed_dtrx_trace
BOOST_CHECK( trace->action_traces.at(0).act.name == "onerror"_n );

c.disconnect();
c2.disconnect();
c.disconnect();
c2.disconnect();
}
}

// test test_transaction_size
CALL_TEST_FUNCTION(*this, "test_transaction", "test_transaction_size", fc::raw::pack(54) ); // TODO: Need a better way to test this.
CALL_TEST_FUNCTION(chain, "test_transaction", "test_transaction_size", fc::raw::pack(54) ); // TODO: Need a better way to test this.

// test test_read_transaction
// this is a bit rough, but I couldn't figure out a better way to compare the hashes
auto tx_trace = CALL_TEST_FUNCTION( *this, "test_transaction", "test_read_transaction", {} );
auto tx_trace = CALL_TEST_FUNCTION( chain, "test_transaction", "test_read_transaction", {} );
string sha_expect = tx_trace->id;
BOOST_TEST_MESSAGE( "tx_trace->action_traces.front().console: = " << tx_trace->action_traces.front().console );
BOOST_TEST_MESSAGE( "sha_expect = " << sha_expect );
BOOST_CHECK_EQUAL(tx_trace->action_traces.front().console == sha_expect, true);
// test test_tapos_block_num
CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_num", fc::raw::pack(control->head_block_num()) );
CALL_TEST_FUNCTION(chain, "test_transaction", "test_tapos_block_num", fc::raw::pack(chain.control->head_block_num()) );

// test test_tapos_block_prefix
CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_prefix", fc::raw::pack(control->head_block_id()._hash[1]) );
CALL_TEST_FUNCTION(chain, "test_transaction", "test_tapos_block_prefix", fc::raw::pack(chain.control->head_block_id()._hash[1]) );

// test send_action_recurse
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_recurse", {}), eosio::chain::transaction_exception,
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(chain, "test_transaction", "send_action_recurse", {}), eosio::chain::transaction_exception,
[](const eosio::chain::transaction_exception& e) {
return expect_assert_message(e, "max inline action depth per transaction reached");
}
);

BOOST_REQUIRE_EQUAL( validate(), true );
BOOST_REQUIRE_EQUAL( chain.validate(), true );

// test read_transaction only returns packed transaction
{
@@ -1492,8 +1495,8 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, validating_tester) { try {
act.authorization = {{"testapi"_n, config::active_name}};
trx.actions.push_back( act );

set_transaction_headers( trx, DEFAULT_EXPIRATION_DELTA );
auto sigs = trx.sign( get_private_key( "testapi"_n, "active" ), control->get_chain_id() );
chain.set_transaction_headers( trx, chain.DEFAULT_EXPIRATION_DELTA );
auto sigs = trx.sign( chain.get_private_key( "testapi"_n, "active" ), chain.control->get_chain_id() );

auto time_limit = fc::microseconds::maximum();
auto ptrx = std::make_shared<packed_transaction>( signed_transaction(trx), packed_transaction::compression_type::none );
@@ -1508,16 +1511,31 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, validating_tester) { try {
BOOST_CHECK(pkt.get_packed_transaction() == packed_copy);
ptrx = std::make_shared<packed_transaction>( pkt );

auto fut = transaction_metadata::start_recover_keys( std::move( ptrx ), control->get_thread_pool(), control->get_chain_id(), time_limit, transaction_metadata::trx_type::input );
auto r = control->push_transaction( fut.get(), fc::time_point::maximum(), fc::microseconds::maximum(), DEFAULT_BILLED_CPU_TIME_US, true, 0 );
auto fut = transaction_metadata::start_recover_keys( std::move( ptrx ), chain.control->get_thread_pool(), chain.control->get_chain_id(), time_limit, transaction_metadata::trx_type::input );
auto r = chain.control->push_transaction( fut.get(), fc::time_point::maximum(), fc::microseconds::maximum(), T::DEFAULT_BILLED_CPU_TIME_US, true, 0 );
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except) throw *r->except;
tx_trace = r;
produce_block();
chain.produce_block();
BOOST_CHECK(tx_trace->action_traces.front().console == sha_expect);
}
}

} FC_LOG_AND_RETHROW() }
/*************************************************************************************
* transaction tests for before disable_trxs_protocol_features are activated
*************************************************************************************/
BOOST_AUTO_TEST_CASE(transaction_tests_before_disable_trxs_protocol_features) { try {
validating_tester_no_disable_deferred_trx chain;
transaction_tests<validating_tester>(chain);
} FC_LOG_AND_RETHROW() }

/*************************************************************************************
* transaction tests after before disable_trxs_protocol_features are activated
*************************************************************************************/
BOOST_AUTO_TEST_CASE(transaction_tests_after_disable_trxs_protocol_features) { try {
validating_tester chain;
transaction_tests<validating_tester>(chain);
} FC_LOG_AND_RETHROW() }

/*************************************************************************************
* verify objective limit test case
@@ -1574,8 +1592,8 @@ BOOST_AUTO_TEST_CASE(inline_action_objective_limit) { try {

BOOST_AUTO_TEST_CASE(deferred_inline_action_limit) { try {
const uint32_t _4k = 4 * 1024;
tester chain(setup_policy::full, db_read_mode::HEAD, {_4k + 100});
tester chain2(setup_policy::full, db_read_mode::HEAD, {_4k + 100});
tester chain(setup_policy::full_except_do_not_disable_deferred_trx, db_read_mode::HEAD, {_4k + 100});
tester chain2(setup_policy::full_except_do_not_disable_deferred_trx, db_read_mode::HEAD, {_4k + 100});
signed_block_ptr block;
for (int n=0; n < 2; ++n) {
block = chain.produce_block();
@@ -1613,7 +1631,7 @@ BOOST_AUTO_TEST_CASE(deferred_inline_action_limit) { try {

} FC_LOG_AND_RETHROW() }

BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester) { try {
BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable_deferred_trx) { try {
produce_blocks(2);
create_accounts( {"testapi"_n, "testapi2"_n, "alice"_n} );
set_code( "testapi"_n, test_contracts::test_api_wasm() );
15 changes: 10 additions & 5 deletions unittests/currency_tests.cpp
Original file line number Diff line number Diff line change
@@ -63,8 +63,8 @@ class currency_tester : public validating_tester {
return trace;
}

currency_tester()
:validating_tester(),abi_ser(json::from_string(test_contracts::eosio_token_abi()).as<abi_def>(), abi_serializer::create_yield_function( abi_serializer_max_time ))
currency_tester(setup_policy p = setup_policy::full)
:validating_tester({}, nullptr, p), abi_ser(json::from_string(test_contracts::eosio_token_abi()).as<abi_def>(), abi_serializer::create_yield_function( abi_serializer_max_time ))
{
create_account( "eosio.token"_n);
set_code( "eosio.token"_n, test_contracts::eosio_token_wasm() );
@@ -91,6 +91,11 @@ class currency_tester : public validating_tester {
static const name eosio_token;
};

class pre_disable_deferred_trx_currency_tester : public currency_tester {
public:
pre_disable_deferred_trx_currency_tester() : currency_tester(setup_policy::full_except_do_not_disable_deferred_trx) {}
};

const name currency_tester::eosio_token = "eosio.token"_n;

BOOST_AUTO_TEST_SUITE(currency_tests)
@@ -389,7 +394,7 @@ BOOST_FIXTURE_TEST_CASE(test_symbol, validating_tester) try {

} FC_LOG_AND_RETHROW() /// test_symbol

BOOST_FIXTURE_TEST_CASE( test_proxy, currency_tester ) try {
BOOST_FIXTURE_TEST_CASE( test_proxy_deferred, pre_disable_deferred_trx_currency_tester ) try {
produce_blocks(2);

create_accounts( {"alice"_n, "proxy"_n} );
@@ -442,9 +447,9 @@ BOOST_FIXTURE_TEST_CASE( test_proxy, currency_tester ) try {
BOOST_REQUIRE_EQUAL(get_balance( "proxy"_n), asset::from_string("0.0000 CUR"));
BOOST_REQUIRE_EQUAL(get_balance( "alice"_n), asset::from_string("5.0000 CUR"));

} FC_LOG_AND_RETHROW() /// test_currency
} FC_LOG_AND_RETHROW() /// test_proxy_deferred

BOOST_FIXTURE_TEST_CASE( test_deferred_failure, currency_tester ) try {
BOOST_FIXTURE_TEST_CASE( test_deferred_failure, pre_disable_deferred_trx_currency_tester ) try {
produce_blocks(2);

create_accounts( {"alice"_n, "bob"_n, "proxy"_n} );
98 changes: 54 additions & 44 deletions unittests/deep-mind/deep-mind.log

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion unittests/deep_mind_tests.cpp
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ struct deep_mind_log_fixture

struct deep_mind_tester : deep_mind_log_fixture, validating_tester
{
deep_mind_tester() : validating_tester({}, &deep_mind_logger) {}
deep_mind_tester() : validating_tester({}, &deep_mind_logger, setup_policy::full) {}
};

namespace {
4 changes: 2 additions & 2 deletions unittests/delay_tests.cpp
Original file line number Diff line number Diff line change
@@ -1388,7 +1388,7 @@ BOOST_AUTO_TEST_CASE( link_delay_link_change_heirarchy_test ) { try {

// test canceldelay action cancelling a delayed transaction
BOOST_AUTO_TEST_CASE( canceldelay_test ) { try {
validating_tester chain;
validating_tester_no_disable_deferred_trx chain;
chain.produce_block();

const auto& contract_account = account_name("defcontract");
@@ -1439,7 +1439,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try {

// test canceldelay action under different permission levels
BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try {
validating_tester chain;
validating_tester_no_disable_deferred_trx chain;
chain.produce_block();

const auto& contract_account = account_name("defcontract");
263 changes: 263 additions & 0 deletions unittests/protocol_feature_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/generated_transaction_object.hpp>
#include <eosio/testing/tester.hpp>
@@ -1920,4 +1921,266 @@ BOOST_AUTO_TEST_CASE( set_parameters_packed_test ) { try {
c.error("alice does not have permission to call this API"));
} FC_LOG_AND_RETHROW() }

// native action hardcodes sender empty and builds sender_id from trx id.
// modify_gto_for_canceldelay_test modifies those two fields for contract
// generated deferred trxs so canceldelay can be used. defined in delay_tests.cpp
namespace eosio::chain { extern void modify_gto_for_canceldelay_test(controller& control, const transaction_id_type& trx_id) ; }

BOOST_AUTO_TEST_CASE( disable_deferred_trxs_stage_1_no_op_test ) { try {
tester_no_disable_deferred_trx c;

c.produce_block();
c.create_accounts( {"alice"_n, "bob"_n, "test"_n} );
c.set_code( "test"_n, test_contracts::deferred_test_wasm() );
c.set_abi( "test"_n, test_contracts::deferred_test_abi() );
c.produce_block();

auto gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(0u, gen_size);

// verify send_deferred host function works before disable_deferred_trxs_stage_1 is activated
c.push_action( "test"_n, "delayedcall"_n, "alice"_n, fc::mutable_variant_object()
("payer", "alice")
("sender_id", 1)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.produce_block();
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(1u, gen_size);

// verify cancel_deferred host function works before disable_deferred_trxs_stage_1 is activated
c.push_action( "test"_n, "cancelcall"_n, "alice"_n, fc::mutable_variant_object()
("sender_id", 1)
);
c.produce_block();
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(0u, gen_size);

// generate a new deferred trx for the rest of the test
c.push_action( "test"_n, "delayedcall"_n, "alice"_n, fc::mutable_variant_object()
("payer", "alice")
("sender_id", 1)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.produce_block();
const auto& idx = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>();
gen_size = idx.size();
BOOST_REQUIRE_EQUAL(1u, gen_size);
BOOST_REQUIRE_EQUAL(idx.begin()->payer, "alice"_n);
auto alice_trx_id = idx.begin()->trx_id;

// activate disable_deferred_trxs_stage_1
const auto& pfm = c.control->get_protocol_feature_manager();
auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 );
BOOST_REQUIRE( d );
c.preactivate_protocol_features( {*d} );
c.produce_block();

// verify send_deferred host function is no-op
c.push_action( "test"_n, "delayedcall"_n, "bob"_n, fc::mutable_variant_object()
("payer", "bob")
("sender_id", 2)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.produce_block();

// verify bob's deferred trx is not made to generated_transaction_multi_index
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(1u, gen_size);
// verify alice's deferred trx is still in generated_transaction_multi_index
auto gto = c.control->db().find<generated_transaction_object, by_trx_id>(alice_trx_id);
BOOST_REQUIRE(gto != nullptr);

// verify cancel_deferred host function is no-op
BOOST_REQUIRE_EXCEPTION(
c.push_action( "test"_n, "cancelcall"_n, "alice"_n, fc::mutable_variant_object()
("sender_id", 1)),
eosio_assert_message_exception,
eosio_assert_message_is( "cancel_deferred failed" ) );
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(1u, gen_size);
// verify alice's deferred trx is not removed
gto = c.control->db().find<generated_transaction_object, by_trx_id>(alice_trx_id);
BOOST_REQUIRE( gto );

// verify canceldelay native action is no-op

// canceldelay assumes sender and sender_id to be a specific format
modify_gto_for_canceldelay_test(*(c.control.get()), alice_trx_id);
// call canceldelay native action
signed_transaction trx;
trx.actions.emplace_back(
vector<permission_level>{{"test"_n, config::active_name}},
canceldelay{{"test"_n, config::active_name}, alice_trx_id}
);
c.set_transaction_headers(trx);
trx.sign(c.get_private_key("test"_n, "active"), c.control->get_chain_id());
c.push_transaction(trx);
c.produce_block();

// verify canceldelay is no-op
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL(1u, gen_size);
// verify alice's deferred trx is not removed
gto = c.control->db().find<generated_transaction_object, by_trx_id>(alice_trx_id);
BOOST_REQUIRE( gto );
} FC_LOG_AND_RETHROW() } /// disable_deferred_trxs_stage_1_no_op_test

// verify a deferred transaction can be retired as expired at any time regardless of
// whether its delay_until or expiration have been reached
BOOST_AUTO_TEST_CASE( disable_deferred_trxs_stage_1_retire_test ) { try {
tester_no_disable_deferred_trx c;

c.produce_block();
c.create_accounts( {"alice"_n, "test"_n} );
c.set_code( "test"_n, test_contracts::deferred_test_wasm() );
c.set_abi( "test"_n, test_contracts::deferred_test_abi() );
c.produce_block();

// verify number of deferred trxs is 0
auto gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 0u, gen_size );
auto alice_ram_usage_before = c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n );

// alice schedules a deferred trx
c.push_action( "test"_n, "delayedcall"_n, "alice"_n, fc::mutable_variant_object()
("payer", "alice")
("sender_id", 1)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.produce_block();

// the deferred trx was added into generated_transaction_multi_index
const auto& idx = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>();
gen_size = idx.size();
BOOST_REQUIRE_EQUAL( 1u, gen_size );
auto trx_id = idx.begin()->trx_id;
auto delay_until = idx.begin()->delay_until;
// alice's RAM were charged
BOOST_CHECK_GT(c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n ), alice_ram_usage_before);

// activate disable_deferred_trxs_stage_1
const auto& pfm = c.control->get_protocol_feature_manager();
auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 );
BOOST_REQUIRE( d );
c.preactivate_protocol_features( {*d} );
c.produce_block();

// verify generated_transaction_multi_index still has 1 entry
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 1u, gen_size );

// at this time, delay_sec has not reached,
// neither does expiration which is "deferred_trx_expiration_window + delay_sec".
// BOOST_CHECK_LT does not work on time_point. Need to compare explicitly
if ( delay_until <= c.control->pending_block_time() ) { // not reached
BOOST_REQUIRE(false);
}

// attemp to retire the trx
auto deadline = fc::time_point::now() + fc::milliseconds(10); // 10ms more than enough
auto trace = c.control->push_scheduled_transaction(trx_id, deadline, fc::microseconds::maximum(), 0, false);

// the trx was retired as "expired" and RAM was refunded even though delay_until not reached
BOOST_REQUIRE_EQUAL( trace->receipt->status, transaction_receipt::expired );
// all scheduled deferred trxs are removed upon activation of disable_deferred_trxs_stage_2
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 0u, gen_size );
// payers' RAM are refunded
BOOST_CHECK_EQUAL( c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n ), alice_ram_usage_before );
} FC_LOG_AND_RETHROW() } /// disable_deferred_trxs_stage_1_retire_test

BOOST_AUTO_TEST_CASE( disable_deferred_trxs_stage_2_test ) { try {
tester_no_disable_deferred_trx c;

c.produce_block();
c.create_accounts( {"alice"_n, "bob"_n, "test"_n} );
c.set_code( "test"_n, test_contracts::deferred_test_wasm() );
c.set_abi( "test"_n, test_contracts::deferred_test_abi() );
c.produce_block();

// verify number of deferred trxs starts at 0
auto gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 0u, gen_size );
auto alice_ram_usage_before = c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n );
auto bob_ram_usage_before = c.control->get_resource_limits_manager().get_account_ram_usage( "bob"_n );

// schedule 2 deferred trxs
c.push_action( "test"_n, "delayedcall"_n, "alice"_n, fc::mutable_variant_object()
("payer", "alice")
("sender_id", 1)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.push_action( "test"_n, "delayedcall"_n, "bob"_n, fc::mutable_variant_object()
("payer", "bob")
("sender_id", 2)
("contract", "test")
("payload", 100)
("delay_sec", 120)
("replace_existing", false)
);
c.produce_block();

// trxs were added into generated_transaction_multi_index
const auto& idx = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>();
gen_size = idx.size();
BOOST_REQUIRE_EQUAL( 2u, gen_size );

// payers' RAM were charged
BOOST_CHECK_GT(c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n ), alice_ram_usage_before);
BOOST_CHECK_GT(c.control->get_resource_limits_manager().get_account_ram_usage( "bob"_n ), bob_ram_usage_before);

const auto& pfm = c.control->get_protocol_feature_manager();
auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 );
BOOST_REQUIRE( d );
c.preactivate_protocol_features( {*d} );

// before disable_deferred_trxs_stage_2 is activated, generated_transaction_multi_index
// should still have 2 entries
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 2u, gen_size );

d = pfm.get_builtin_digest( builtin_protocol_feature_t::disable_deferred_trxs_stage_2 );
BOOST_REQUIRE( d );
c.preactivate_protocol_features( {*d} );
c.produce_block();

// all scheduled deferred trxs are removed upon activation of disable_deferred_trxs_stage_2
gen_size = c.control->db().get_index<generated_transaction_multi_index,by_trx_id>().size();
BOOST_REQUIRE_EQUAL( 0u, gen_size );

// payers' RAM are refunded
BOOST_CHECK_EQUAL( c.control->get_resource_limits_manager().get_account_ram_usage( "alice"_n ), alice_ram_usage_before );
BOOST_CHECK_EQUAL( c.control->get_resource_limits_manager().get_account_ram_usage( "bob"_n ), bob_ram_usage_before );
} FC_LOG_AND_RETHROW() } /// disable_deferred_trxs_stage_2_test

BOOST_AUTO_TEST_CASE( disable_deferred_trxs_stage_2_dependency_test ) { try {
tester_no_disable_deferred_trx c;

c.produce_block();

// disable_deferred_trxs_stage_2 cannot be activated before disable_deferred_trxs_stage_1
const auto& pfm = c.control->get_protocol_feature_manager();
auto d = pfm.get_builtin_digest( builtin_protocol_feature_t::disable_deferred_trxs_stage_2 );
BOOST_REQUIRE( d );
BOOST_REQUIRE_EXCEPTION( c.preactivate_protocol_features( {*d} ),
protocol_feature_exception,
fc_exception_message_starts_with("not all dependencies of protocol feature with digest"));
} FC_LOG_AND_RETHROW() } /// disable_deferred_trxs_stage_2_dependency_test

BOOST_AUTO_TEST_SUITE_END()
2 changes: 1 addition & 1 deletion unittests/state_history_tests.cpp
Original file line number Diff line number Diff line change
@@ -571,7 +571,7 @@ BOOST_AUTO_TEST_CASE(test_deltas_resources_history) {
}

BOOST_AUTO_TEST_CASE(test_trace_log_with_transaction_extensions) {
tester c(setup_policy::full);
tester_no_disable_deferred_trx c;

fc::temp_directory state_history_dir;
eosio::state_history::trace_converter log;
15 changes: 15 additions & 0 deletions unittests/test-contracts/deferred_test/deferred_test.abi
Original file line number Diff line number Diff line change
@@ -3,6 +3,16 @@
"version": "eosio::abi/1.2",
"types": [],
"structs": [
{
"name": "cancelcall",
"base": "",
"fields": [
{
"name": "sender_id",
"type": "uint64"
}
]
},
{
"name": "defercall",
"base": "",
@@ -90,6 +100,11 @@
}
],
"actions": [
{
"name": "cancelcall",
"type": "cancelcall",
"ricardian_contract": ""
},
{
"name": "defercall",
"type": "defercall",
5 changes: 5 additions & 0 deletions unittests/test-contracts/deferred_test/deferred_test.cpp
Original file line number Diff line number Diff line change
@@ -52,6 +52,11 @@ void deferred_test::delayedcall( name payer, uint64_t sender_id, name contract,
trx.send( sender_id, payer, replace_existing );
}

void deferred_test::cancelcall(uint64_t sender_id) {
auto r = eosio::cancel_deferred( sender_id );
check( (bool)r, "cancel_deferred failed" );
}

void deferred_test::deferfunc( uint64_t payload ) {
print( "deferfunc called on ", get_self(), " with payload = ", payload, "\n" );
check( payload != 13, "value 13 not allowed in payload" );
3 changes: 3 additions & 0 deletions unittests/test-contracts/deferred_test/deferred_test.hpp
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@ class [[eosio::contract]] deferred_test : public eosio::contract {
void delayedcall( eosio::name payer, uint64_t sender_id, eosio::name contract,
uint64_t payload, uint32_t delay_sec, bool replace_existing );

[[eosio::action]]
void cancelcall( uint64_t sender_id );

[[eosio::action]]
void deferfunc( uint64_t payload );
using deferfunc_action = eosio::action_wrapper<"deferfunc"_n, &deferred_test::deferfunc>;
Binary file modified unittests/test-contracts/deferred_test/deferred_test.wasm
Binary file not shown.