-
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?
Conversation
johndebord
commented
Feb 24, 2020
•
edited
Loading
edited
ccf3f18
to
6c9f0c1
Compare
…ix `currency_tests`
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.
Haven't reviewed the tests yet.
@@ -512,29 +519,33 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a | |||
|
|||
// Use remove and create rather than modify because mutating the trx_id field in a modifier is unsafe. | |||
db.remove( *ptr ); |
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:
- Through
transaction_api::send_deferred
. - Through a user extending his/her code via a plugin and calling
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, but apply_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.
[XX]
send_deferred
[XX] - Ifreplace_existing
== true insend_deferred
; it shall be a noop
[XX]
schedule_deferred_transaction
[XX] - If there is no transaction to replace: fail
[XX] - If there is a transaction to replace: continue to cancel the old one but do not schedule the new one
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
libraries/chain/controller.cpp
Outdated
|
||
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 comment
The 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 comment
The 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 builtin_protocol_feature_t::stop_deferred_transactions
has been activated; if so, then the trace shall be returned, and no further operations are done on the 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.
Does producer_plugin know how to handle this outcome?
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
The name will be confusing at the call site. If I see:
t.preactivate_builtin_protocol_features({wtmsig_block_signatures, replace_deferred});
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to preactivate_selected_protocol_features
.
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 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.
libraries/testing/tester.cpp
Outdated
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 comment
The 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 comment
The 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 comment
The 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 comment
The 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.
Would it be best practice to account for future cases, such as chaining ignores? Or is the best practice only to account for what is currently in use?
And I definitely agree with you there on the last point. If it were up to me, I think tester
is due for a re-write; to consider, from the ground up, an architecture where an arbitrary number of protocol features that may or may not depend on each other.
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.
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.
unittests/api_tests.cpp
Outdated
set_code( N(testapi), contracts::test_api_wasm() ); | ||
produce_blocks(1); | ||
BOOST_AUTO_TEST_CASE(action_receipt) { try { | ||
validating_tester chain( {}, {::eosio::chain::builtin_protocol_feature_t::stop_deferred_transactions} ); |
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 original code uses TESTER. Can you make tester define the same constructors as validating_tester?
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.
Made matching constructors for class tester
.
libraries/chain/apply_context.cpp
Outdated
@@ -522,8 +528,22 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a | |||
gtx.expiration = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window); | |||
|
|||
trx_size = gtx.set( trx ); | |||
|
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.
So this now allows deferred transactions to be replaced. I'm not so sure that that's a good idea, since it allows the expiration to be arbitrarily extended as long as someone keeps replacing it.