-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add deprecate_deferred_transactions
protocol feature
#8697
base: develop
Are you sure you want to change the base?
Changes from 32 commits
03ad47d
6493043
5a4c580
738859e
318c668
f5469bb
e0a19c3
fcf6d85
3a56333
d05174b
2c2fdc0
d80ae1f
9ecb930
4a855aa
cd6e39f
7c57d60
efd7640
45c073d
a99c7c1
5fa34b8
bbaf657
963cfbb
01a34aa
6c9f0c1
c9e0439
1a4af45
f6a3d85
6fe80c4
9cb5a21
b7d4e2c
310154a
91ec090
36bdc74
d619846
460d80b
084e7bf
ba86dd6
e671033
a89b9b7
fe76dc6
418f91b
ee0efc0
1ef5c18
203e725
531f005
ae25ee7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1244,7 +1244,10 @@ struct controller_impl { | |
trx->accepted = true; | ||
|
||
transaction_trace_ptr trace; | ||
if( gtrx.expiration < self.pending_block_time() ) { | ||
|
||
bool stop_deferred_transactions_activated = self.is_builtin_activated(builtin_protocol_feature_t::stop_deferred_transactions); | ||
|
||
if( gtrx.expiration < self.pending_block_time() || stop_deferred_transactions_activated ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to match the spec. "Only if a deferred transaction is expired can it be pushed" You're instead treating all transactions as if they were expired. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The behavior now matches the spec; if the transaction has expired then the transaction shall expire. Then we check to see if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does producer_plugin know how to handle this outcome? |
||
trace = std::make_shared<transaction_trace>(); | ||
trace->id = gtrx.trx_id; | ||
trace->block_num = self.head_block_num() + 1; | ||
|
@@ -1467,6 +1470,11 @@ struct controller_impl { | |
|
||
trx_context.delay = fc::seconds(trn.delay_sec); | ||
|
||
if ( trx_context.delay.count() > 0 ) { | ||
bool stop_deferred_transactions_activated = self.is_builtin_activated(builtin_protocol_feature_t::stop_deferred_transactions); | ||
EOS_ASSERT( !stop_deferred_transactions_activated, stop_deferred_tx, "delay seconds must be 0" ); | ||
} | ||
|
||
if( check_auth ) { | ||
authorization.check_authorization( | ||
trn.actions, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,7 +62,7 @@ namespace eosio { namespace testing { | |
old_bios_only, | ||
preactivate_feature_only, | ||
preactivate_feature_and_new_bios, | ||
full | ||
complete | ||
swatanabe-b1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
std::vector<uint8_t> read_wasm( const char* fn ); | ||
|
@@ -146,13 +146,12 @@ namespace eosio { namespace testing { | |
typedef string action_result; | ||
|
||
static const uint32_t DEFAULT_EXPIRATION_DELTA = 6; | ||
|
||
static const uint32_t DEFAULT_BILLED_CPU_TIME_US = 2000; | ||
static const fc::microseconds abi_serializer_max_time; | ||
|
||
virtual ~base_tester() {}; | ||
|
||
void init(const setup_policy policy = setup_policy::full, db_read_mode read_mode = db_read_mode::SPECULATIVE); | ||
void init(const setup_policy policy = setup_policy::complete, db_read_mode read_mode = db_read_mode::SPECULATIVE); | ||
void init(controller::config config, const snapshot_reader_ptr& snapshot); | ||
void init(controller::config config, const genesis_state& genesis); | ||
void init(controller::config config); | ||
|
@@ -379,7 +378,7 @@ namespace eosio { namespace testing { | |
|
||
void schedule_protocol_features_wo_preactivation(const vector<digest_type> feature_digests); | ||
void preactivate_protocol_features(const vector<digest_type> feature_digests); | ||
void preactivate_all_builtin_protocol_features(); | ||
void preactivate_builtin_protocol_features(const std::vector<builtin_protocol_feature_t>& ignored_features); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name will be confusing at the call site. If I see:
I would assume that it meant the features to activate, not the features to exclude. I wouldn't even bother looking for the declaration because the meaning is "obvious." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how the new name expresses the meaning of the function better than the old name. I think part of the problem is that the function's behavior is odd, which makes it hard to come up with a good name. |
||
|
||
static genesis_state default_genesis() { | ||
genesis_state genesis; | ||
|
@@ -432,15 +431,16 @@ namespace eosio { namespace testing { | |
controller::config cfg; | ||
map<transaction_id_type, transaction_receipt> chain_transactions; | ||
map<account_name, block_id_type> last_produced_block; | ||
unapplied_transaction_queue unapplied_transactions; | ||
unapplied_transaction_queue unapplied_transactions; | ||
|
||
public: | ||
vector<builtin_protocol_feature_t> ignored_features; | ||
vector<digest_type> protocol_features_to_be_activated_wo_preactivation; | ||
}; | ||
|
||
class tester : public base_tester { | ||
public: | ||
tester(setup_policy policy = setup_policy::full, db_read_mode read_mode = db_read_mode::SPECULATIVE) { | ||
tester(setup_policy policy = setup_policy::complete, db_read_mode read_mode = db_read_mode::SPECULATIVE) { | ||
init(policy, read_mode); | ||
} | ||
|
||
|
@@ -516,49 +516,30 @@ namespace eosio { namespace testing { | |
wdump((e.to_detail_string())); | ||
} | ||
} | ||
controller::config vcfg; | ||
|
||
validating_tester(const flat_set<account_name>& trusted_producers = flat_set<account_name>()) { | ||
auto def_conf = default_config(tempdir); | ||
|
||
validating_tester(const ::boost::container::flat_set<::eosio::chain::account_name>& trusted_producers = ::boost::container::flat_set<::eosio::chain::account_name>(), | ||
const ::std::vector<::eosio::chain::builtin_protocol_feature_t>& ignored_features = {}, | ||
const ::eosio::testing::setup_policy& policy = ::eosio::testing::setup_policy::complete) { | ||
::std::pair<::eosio::chain::controller::config, ::eosio::chain::genesis_state> def_conf = default_config(tempdir); | ||
::eosio::testing::base_tester::ignored_features = ignored_features; | ||
swatanabe-b1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
vcfg = def_conf.first; | ||
config_validator(vcfg); | ||
vcfg.trusted_producers = trusted_producers; | ||
|
||
validating_node = create_validating_node(vcfg, def_conf.second, true); | ||
|
||
init(def_conf.first, def_conf.second); | ||
execute_setup_policy(setup_policy::full); | ||
} | ||
|
||
static void config_validator(controller::config& vcfg) { | ||
FC_ASSERT( vcfg.blocks_dir.filename().generic_string() != "." | ||
&& vcfg.state_dir.filename().generic_string() != ".", "invalid path names in controller::config" ); | ||
|
||
vcfg.blocks_dir = vcfg.blocks_dir.parent_path() / std::string("v_").append( vcfg.blocks_dir.filename().generic_string() ); | ||
vcfg.state_dir = vcfg.state_dir.parent_path() / std::string("v_").append( vcfg.state_dir.filename().generic_string() ); | ||
|
||
vcfg.contracts_console = false; | ||
} | ||
|
||
static unique_ptr<controller> create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis) { | ||
unique_ptr<controller> validating_node = std::make_unique<controller>(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); | ||
validating_node->add_indices(); | ||
if (use_genesis) { | ||
validating_node->startup( [](){}, []() { return false; }, genesis ); | ||
} | ||
else { | ||
validating_node->startup( [](){}, []() { return false; } ); | ||
} | ||
return validating_node; | ||
execute_setup_policy(policy); | ||
} | ||
|
||
validating_tester(const fc::temp_directory& tempdir, bool use_genesis) { | ||
// Delete this one after I fix the default one | ||
validating_tester(const fc::temp_directory& tempdir, bool use_genesis, const std::vector<builtin_protocol_feature_t>& ignored_features = {}) { | ||
base_tester::ignored_features = ignored_features; | ||
|
||
auto def_conf = default_config(tempdir); | ||
vcfg = def_conf.first; | ||
config_validator(vcfg); | ||
|
||
validating_node = create_validating_node(vcfg, def_conf.second, use_genesis); | ||
|
||
if (use_genesis) { | ||
init(def_conf.first, def_conf.second); | ||
} | ||
|
@@ -573,6 +554,7 @@ namespace eosio { namespace testing { | |
conf_edit(def_conf.first); | ||
vcfg = def_conf.first; | ||
config_validator(vcfg); | ||
|
||
validating_node = create_validating_node(vcfg, def_conf.second, use_genesis); | ||
|
||
if (use_genesis) { | ||
|
@@ -583,6 +565,28 @@ namespace eosio { namespace testing { | |
} | ||
} | ||
|
||
static void config_validator(controller::config& vcfg) { | ||
FC_ASSERT( vcfg.blocks_dir.filename().generic_string() != "." | ||
&& vcfg.state_dir.filename().generic_string() != ".", "invalid path names in controller::config" ); | ||
|
||
vcfg.blocks_dir = vcfg.blocks_dir.parent_path() / std::string("v_").append( vcfg.blocks_dir.filename().generic_string() ); | ||
vcfg.state_dir = vcfg.state_dir.parent_path() / std::string("v_").append( vcfg.state_dir.filename().generic_string() ); | ||
|
||
vcfg.contracts_console = false; | ||
} | ||
|
||
static unique_ptr<controller> create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis) { | ||
unique_ptr<controller> validating_node = std::make_unique<controller>(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); | ||
validating_node->add_indices(); | ||
if (use_genesis) { | ||
validating_node->startup( [](){}, []() { return false; }, genesis ); | ||
} | ||
else { | ||
validating_node->startup( [](){}, []() { return false; } ); | ||
} | ||
return validating_node; | ||
} | ||
|
||
signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { | ||
auto sb = _produce_block(skip_time, false); | ||
auto bsf = validating_node->create_block_state_future( sb ); | ||
|
@@ -614,8 +618,6 @@ namespace eosio { namespace testing { | |
} | ||
|
||
bool validate() { | ||
|
||
|
||
auto hbh = control->head_block_state()->header; | ||
auto vn_hbh = validating_node->head_block_state()->header; | ||
bool ok = control->head_block_id() == validating_node->head_block_id() && | ||
|
@@ -633,9 +635,10 @@ namespace eosio { namespace testing { | |
return ok; | ||
} | ||
|
||
unique_ptr<controller> validating_node; | ||
uint32_t num_blocks_to_producer_before_shutdown = 0; | ||
bool skip_validate = false; | ||
controller::config vcfg; | ||
std::unique_ptr<controller> validating_node; | ||
uint32_t num_blocks_to_producer_before_shutdown = 0; | ||
bool skip_validate = false; | ||
}; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -214,11 +214,11 @@ namespace eosio { namespace testing { | |
set_before_producer_authority_bios_contract(); | ||
break; | ||
} | ||
case setup_policy::full: { | ||
case setup_policy::complete: { | ||
schedule_preactivate_protocol_feature(); | ||
produce_block(); | ||
set_before_producer_authority_bios_contract(); | ||
preactivate_all_builtin_protocol_features(); | ||
preactivate_builtin_protocol_features(ignored_features); | ||
produce_block(); | ||
set_bios_contract(); | ||
break; | ||
|
@@ -1116,24 +1116,47 @@ namespace eosio { namespace testing { | |
} | ||
} | ||
|
||
void base_tester::preactivate_all_builtin_protocol_features() { | ||
void base_tester::preactivate_builtin_protocol_features(const std::vector<builtin_protocol_feature_t>& ignored_features) { | ||
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); | ||
const auto current_block_time = ( control->is_building_block() ? control->pending_block_time() | ||
: control->head_block_time() + fc::milliseconds(config::block_interval_ms) ); | ||
|
||
set<digest_type> preactivation_set; | ||
vector<digest_type> preactivations; | ||
set<digest_type> preactivation_set; | ||
vector<digest_type> preactivations; | ||
std::vector<digest_type> ignored_digests; | ||
|
||
ignored_digests.reserve(ignored_features.size()); | ||
for ( const auto& feature : ignored_features ) { | ||
ignored_digests.push_back(*pfs.get_builtin_digest( feature ) ); | ||
} | ||
|
||
std::function<void(const digest_type&)> add_digests = | ||
[&pfm, &pfs, current_block_num, current_block_time, &preactivation_set, &preactivations, &add_digests] | ||
[&ignored_digests, &pfm, &pfs, current_block_num, current_block_time, &preactivation_set, &preactivations, &add_digests] | ||
( const digest_type& feature_digest ) { | ||
const auto& pf = pfs.get_protocol_feature( feature_digest ); | ||
FC_ASSERT( pf.builtin_feature, "called add_digests on a non-builtin protocol feature" ); | ||
if( !pf.enabled || pf.earliest_allowed_activation_time > current_block_time | ||
|| pfm.is_builtin_activated( *pf.builtin_feature, current_block_num ) ) return; | ||
|
||
std::function<void(const digest_type&)> check_dependencies = | ||
[&check_dependencies, &ignored_digests, &pf] ( const digest_type& feature ) { | ||
const auto dependency_is_ignored = std::find( ignored_digests.cbegin(), ignored_digests.cend(), feature ); | ||
const auto dependency_has_dependency = std::find( pf.dependencies.cbegin(), pf.dependencies.cend(), feature ); | ||
if ( dependency_is_ignored != ignored_digests.cend() && dependency_has_dependency != pf.dependencies.cend() ) { | ||
check_dependencies(*dependency_has_dependency); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this causes infinite recursion? *dependency_has_dependency should be equal to feature, right? I can't quite figure out what you're trying to do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should now be checking protocol feature dependencies correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only handles the simple case of A depends on B, ignore B. Does not handle the possibility of chaining ignores: A -> B -> C -> D, ignore D, which needs to cause all of A, B, C, and D to be ignored. Also I'm on the fence about whether it's better to ignore additional features or whether it's better to issue an error. On the one hand, it's bad if adding a new protocol feature breaks existing tests. On the other hand, silently disabling more protocol features than were given may be surprising. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I definitely considered that case. But for the time being we only have the case of A depends on B. And I definitely agree with you there on the last point. If it were up to me, I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Best practice is to handle the public API of protocol features. Since arbitrary protocol feature dependencies are allowed, you should support them. Tester should not need any specific knowledge of what protocol features are actually defined. The existing code in tester to determine the activation order of protocol features is designed to handle arbitrary dependencies correctly, so I think it's reasonable for you to do so as well. Now, with all of that having been said, being fully general is not always worth the effort. If you do decide to take shortcuts, you should definitely call it out in a comment, to help anyone who adds a new protocol feature and gets confused by mysterious failures. |
||
} else { | ||
FC_ASSERT( pf.builtin_feature, "ignoring a protocol feature that is a dependency for another protocol feature" ); | ||
swatanabe-b1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
}; | ||
|
||
const auto digest_is_ignored = std::find(ignored_digests.cbegin(), ignored_digests.cend(), feature_digest); | ||
swatanabe-b1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if( digest_is_ignored != ignored_digests.cend() ) { | ||
check_dependencies(*digest_is_ignored); | ||
return; | ||
} | ||
|
||
auto res = preactivation_set.emplace( feature_digest ); | ||
if( !res.second ) return; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a no-op, which doesn't seem consistent with the fact that send_deferred skips schedule_deferred_transaction entirely. Actually, I don't understand the motivation for allowing replace_existing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My assumption, based off of the specification, is that there are two ways to enter deferred transaction logic:
transaction_api::send_deferred
.apply_context::schedule_deferred_transaction
.Therefore two individual (seemingly redundant) checks must be made. Am I wrong in this assumption?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think apply_context::schedule_deferred_transaction should ever be called outside of wasm. Also, the main point that I was trying to make was that the behavior is not the same in the two cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe they now have the same behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The behavior is still different in the case of
replace_existing = true
.transaction_api::send_deferred
short-circuits this case to a no-op, butapply_context::schedule_deferred_transaction
handles it in a more complex way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is what I was told to implement.
Hence, the assertion in
apply_context::schedule_deferred_transaction
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain the motivation for this difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation is unbeknownst to me. I just took it at face-value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm somewhat concerned that this may be a misunderstanding. The spec for
schedule_deferred_transaction
sounds to me like reasonable behavior for both of them. @arhag