-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Restructure transaction traces to disambiguate and provide more information #6897
Comments
@arhag I'm sure you saw this thing https://www.dfuse.io/en/blog/eosq-removes-ambiguity-when-debugging-your-smart-contract .. wondering if we had the complete picture or if we missed something? Is this change going to swap the "Creation Tree" view for the "Execution Tree" view forever? or will it provide both views in the transaction_trace ? |
Yes, great job with that. I believe your "Creation Tree" view is the same as the structure I am proposing for the transaction trace in this issue, although it is difficult to tell for sure with just the examples given on the web page. I assume I believe there should be enough information in the proposed transaction trace structure combined with basic rules for how action dispatching works on EOSIO to reliably convert the proposed transaction trace structure into the old transaction trace structure (equivalent to the "Execution Tree" view). |
re Why not do the contrary? Things build up during execution, like database operations build upon things created in a certain order. Always storing a creation tree will make it more difficult to reliably know how things built up... or explain certain database modifications. Why not add a bit of metadata about who creates what when? You think creation tree is more valuable than execution tree? |
The tree structure proposed in the issue provides more information than the current tree structure of transaction traces to the point where the latter can be fully derived from the former (that's even without the additional metadata of I'm not sure I understand what alternative you are proposing, but are we in agreement that the existing trace structure (with all its ambiguities that, assuming no special knowledge of the contract sources, can only be resolved with modifications made to the core of nodeos) is less useful than the proposed alternative in this issue? Is the concern about needing to do a transformation on the proposed tree (essentially a "Creation Tree") to get the "Execution Tree" view which may be viewed as the more useful one? |
Yes, agreed there's missing bits and pieces to stitch it all. I'm just wondering about the expectations of nesting people have today.. if it suddenly swaps around, they might be confused. But it's a good question.. is creation what people are really after when they inspect the nesting? That might the default assumption. And it can be violated (which is never good :). One could extract stats about such confusion in today's chains through our endpoint, see when creation trees and execution trees are different. I don't have the stats today.. I know nodeos spits out a flattened version of traces, sort of merged with the nested ones.. maybe having all the necessary info in a flat version, for both things would be interesting. Like a |
Now compiles, but the tests fail due to bugs.
…e_traces #6897 Also correct state history ABI for action_trace_v0 to reflect that inline_traces are no longer included.
A few changes have been made to the design described above. First, Second,
Third, Fourth, |
Due to the changes in the It is desirable to continue returning the current modified trace results in a new RPC endpoint, This means that the action traces that are not for top-level actions should be moved into the The execution tree can be reconstructed from the set of |
Resolved by #7044. |
The current transaction trace is structured in a manner that prevents determining some potentially useful information about transaction execution due to ambiguities in the structure. For example, if an action notifies another contract, and there is another inline action in the
inline_traces
of the original action, it is not clear whether that inline action was sent by the original action or the notified contract. This information can be useful if an application wishes to react to the event of the inline action but only if sent by the appropriate contract. Adding authorizations can help, but it does not fully resolve the potential ambiguities (also authorizations are not available for context-free actions).Consider a transaction with a single action
alice::act1
which initiates the following sequence of operations:alice::act1
executes in thealice
contract which:require_recipient
withbob
;alice::act2
and thenalice::act3
;require_recipient
withcharlie
.alice::act1
executes in thebob
contract which:bob::foo
;require_recipient
withdavid
.alice::act1
executes in thecharlie
contract which:charlie::bar
.alice::act1
executes in thedavid
contract which does nothing.alice::act2
executes in thealice
contract which:require_recipient
withdavid
and thenerin
;alice::act4
.alice::act2
executes in thedavid
contract which does nothing.alice::act2
executes in theerin
contract which does nothing.alice::act4
executes in thealice
contract which does nothing.alice::act3
executes in thealice
contract which does nothing.bob::foo
executes in thebob
contract which does nothing.charlie::bar
executes in thecharlie
contract which does nothing.The current implementation of nodeos will generate a transaction trace with the following structure:
The numbers in the bracket act as convenient labels to refer to the individual actions but also show the order of execution of the actions. This would correspond to the order of the global sequence numbers included in each action receipt.
One problem with the above structure is that action traces 9 and 10 both appear as children of action trace 1, however inline action 9 was sent by the execution of action 1 while inline action 10 was sent by the execution of action 2.
Another example of the problem of the above structure is that action trace 4 appears as a notification of action 1 to
david
in a similar manner as the notifications show in action traces 2 and 3; however, action traces 2 and 3 are there because of arequire_recipient
calls made during the execution of action 1, while action trace 4 is there because of arequire_recipient
call made during the execution of action 2.Finally consider what the returned trace would look like if a failure was encountered during execution of action 5 in between the
require_recipient
calls that notifieddavid
anderin
:However, up to the point of encountering the error, more information was known that was simply discarded because of the way transaction traces are structured. For example, it was known that action 2 sent inline action 10 and action 3 sent inline action 11, even though actions 10 and 11 did not have a chance to execute yet. It was also known that action 5 has notified
david
, however the notification toerin
and inline action 8 being sent would not have been known.The transaction trace is structured in a way that makes it easy to follow the order of execution. However, enough information already exists in the action receipts of the traces (specifically the
global_sequence
) to determine the exact order in which the actions executed. What does not exist is the information of the type described in the paragraphs above.So this issue proposes changing the transaction trace structure to provide more information about the order in which things were scheduled/notified as well as the scheduling/notifying/dispatching relationship between actions. Since the
global_sequence
information would still exist, clients could transform the returned trace structure back into the original structure, thus giving users a choice of which representation they wish to see.Under this new system the transaction trace of the example above would instead be:
Furthermore, the scenario of encountering a failure during execution of action 5 described earlier would now result in a transaction trace of the following:
More information is provided in the above structure which could help contract developers debugging their contracts. Notice that the actions that did not have a chance to execute fully (or at all) would have an empty receipt. Child traces of an action trace with an empty receipt would not necessarily be complete, but would be as accurate as possible with the information known by the time of the failure.
The
base_action_trace
structure would be modified as follows:receipt
field fromaction_receipt
tooptional<action_receipt>
.parent_seq_num
of typeoptional<uint64_t>
which be set to theglobal_sequence
number of the parent action (the action that sent this inline action or calledrequire_recipient
causing this action to be dispatched). This field would be empty for the original top-level actions of the transaction.Implementation
The goal is to only change the transaction trace structure as described above, and not to actually change the way actions get executed. Even if there is a more sensible way to do action dispatching, the order cannot be changed without breaking existing consensus rules (and this issue is not about a consensus protocol upgrade). Thus, the core code of the dispatch logic of actions, in
apply_context::exec
, will remain the same.However, the general pattern of allocating new space in the
inline_traces
with anemplace_back
at the time right before executing will be replaced by instead doing that trace allocation (as well as a copy of the action data to be eventually dispatched) during the time of scheduling or notification.The
_notified
field ofapply_context
should be modified to be avector<pair<account_name, size_t>>
. The_inline_actions
and_cfa_inline_actions
fields ofapply_context
should be modified to be avector<size_t>
. A new field_trace
of typeaction_trace*
should be added toapply_context
. On execution ofapply_context::exec( action_trace& trace )
this field will be set to&trace
.In
apply_context::require_recipient
, instead of pushing backrecipient
into_notified
, the code should addaction_trace
initialized with the currentact
to the end oftrace->inline_traces
and then do_notified.emplace_back( recipient, trace->inline_traces.size() - 1 )
.In
apply_context::execute_inline
andapply_context::execute_context_free_inline
, instead of doing anemplace_back( move(a) )
on the_inline_action
or_cfa_inline_actions
field, respectively, the code should addaction_trace
initialized with the moveda
to the end oftrace->inline_traces
and then call the methodemplace_back( trace->inline_traces.size() - 1 )
on the_inline_action
or_cfa_inline_actions
field, respectively.When the new
action_trace
is initialized as described above, the fieldsact
,context_free
,trx_id
,block_num
,block_time
,producer_block_id
can and should all be properly initialized (and would never have to modified again). The remaining fields should be initialized with their default constructed values and, with the exception ofparent_seq_num
, may only be changed during/after execution of the action inapply_context::exec_one
. Theparent_seq_num
field of an action will be modified during theapply_context::exec_one
execution for the parent action by iterating through all of the action traces intrace.inline_traces
afterr.global_sequence
is determined.In
apply_context::exec
, wherevertrace.inline_traces.emplace_back()
andtrace.inline_traces.back()
exist, they will have to be replaced with a lookup in thetrace.inline_traces
vector with the appropriate index retrieved either from one of the three vectors:_retrieved
,_cfa_inline_actions
, or_inline_actions
. Furthermore, when looping through the actions in_cfa_inline_actions
or_inline_actions
, theinline_action
variable can instead be replaced by theact
field of theaction_trace
found via the index.The text was updated successfully, but these errors were encountered: