-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Provide read-only access to storage during tracing #9244
Conversation
It looks like @udoprog signed our Contributor License Agreement. 👍 Many thanks, Parity Technologies CLA Bot |
9bde809
to
afd65e8
Compare
Gonna fix the error when I have a moment. I realized I also have to add another hook for when the execution context of a contract is done that is not |
cb8714f
to
03ee0b6
Compare
@udoprog Can you explain more on the use-case here? If you are doing a normal tracing via full Would this cover your use-case? |
@sorpaas IIUC, Is this correct? |
Yes.
|
@sorpaas what do you mean with "before execution". Can you show what you mean in code? Note that StorageAccess as introduced here provides uniform access to both committed and uncommitted storage as a contract is being executed. I think what you propose would require me to keep a reference to state_db around for when other contracts are called, get a snapshot of storage as it was before the transaction, and overlay any modifications I observe through store_diff. Is that correct? It might be difficult to share state_db during a trace since I think it's already mutably borrowed to run the transaction. But I would have to check that. Overall it seems a fair bit harder to get right. |
please rebase on master |
03ee0b6
to
2144b78
Compare
@5chdn done |
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.
Looks ok to me, but I haven't really touch this code, @sorpaas should know better.
evmbin/src/display/std_json.rs
Outdated
@@ -148,7 +148,7 @@ impl<Trace: Writer, Out: Writer> trace::VMTracer for Informant<Trace, Out> { | |||
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) { | |||
} | |||
|
|||
fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { | |||
fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>, storage: &storage::StorageAccess) { |
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.
_storage
ethcore/src/trace/mod.rs
Outdated
/// Trace the end of a subtrace. | ||
/// | ||
/// After this, no more instructions will be executed for this subtrace. | ||
fn trace_done(&mut self, _storage_access: &StorageAccess) {} |
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 have a strong opinion on naming, but we already have done_subtrace
, so maybe trace_finalized
?
evmbin/src/display/json.rs
Outdated
@@ -100,7 +100,7 @@ impl trace::VMTracer for Informant { | |||
self.gas_cost = gas_cost; | |||
} | |||
|
|||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { | |||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>, storage: &storage::StorageAccess) { |
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.
_storage
@ordian We don't need this tracer anywhere for ourselves, and it's not exposed in RPC. So this is solely about using |
@udoprog Sorry I realized that I missed your last message!
Sharing the reference would indeed be hard. However, if your usecase is debugging and you can bare a little bit of performance, you can clone it! |
2144b78
to
3804196
Compare
@sorpaas OK! I tried cloning, and with the current amount of storage that I'm using in tests it seems to work fine. I haven't tried overlaying the changes, but all the APIs are there, so I don't think that should be a problem. I'm a bit concerned that I have to do it for every call, and that might not scale long term if I ever build tests with a bit more state. So I'd still be interested to do it without cloning at some point. |
3804196
to
ffc2dc9
Compare
@udoprog It's actually not as expensive as you think. As a result, for your usecase, if you clone Using |
@sorpaas Not sure I follow. This is how I currently set up storage, so I'm not sharing anything between my VM instances so how would caching work? Cloning has to give me access to an isolated instance of state in which I overlay mutations. At the very least it needs to do bookeeping to separate the underlying state for each instance in case they are mutated right? I'm trying to do what you've proposed so far, but it's turning it to be a fair bit of extra bookkeeping and synchronization I have to do for my tracers. It would be really helpful to have something like this instead. I can fix the conflicts, but I'd like to check before if this is even something you want to merge? |
@udoprog I tried to look into the code but still don't yet understand the use case (probably I haven't looked this hard enough!). It would be great if you can further explain what you tries to do:
|
@sorpaas I trace the execution of a contract, record each time storage is accessed. If a revert occurs, I lookup as much context as possible at the point it was reverted. That includes the value of most expressions that have been evaluated. If any of these expressions are references into storage, I lookup and decode their value from storage. Right now I'm trying to rebase on the latest version of ethereum, and a fair bit of the tracing stuff has changed (e.g. subtracers no longer seem to be a thing). This is a gif showcasing the state dumping in general: https://github.com/PrimaBlock/parables/blob/master/images/expression-dump.gif That works because the access to storage is an immediate expression that I can decode, but if I for example have a loop I'm recording all storage accesses in order to dump that as part of the context. These storage slots then need to be decoded at a later time, by accessing storage directly. |
self.vm_tracer.trace_executed(gas_used, stack_push, mem_diff, store_diff) | ||
let Externalities { ref state, ref mut vm_tracer, .. } = *self; | ||
|
||
let access = ExtStorageAccess(*state, &self.origin_info.address); |
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.
Would it be possible to change to a design where if NoopTracer
is used, we don't create this struct? For example, this can be done by directly passing State
reference into tracer.
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 benefit is pairing the storage access with the address of the contract so that we don't have to do this, making the storage access API more convenient.
What are the worries about from setting up this struct? The overhead should be negligible since we are mostly dealing with pointers.
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.
Yeah looks like the overhead is negligible. However, I wonder about other tracer usage for ethcore-as-a-library. People may want to build other tracer implementation that access to other state information (like other account's storage slot, account code, nonce, etc). So I'm just thinking maybe directly passing State
would be more generalized and potentially fit more usage?
@5chdn I'll rebase after it's been reviewed or not if it's rejected if that's fine by you? I've already rebased a couple of times. |
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.
Mostly LGTM. Sorry for the delay.
Still have a question but otherwise, as the overhead is really small, I think it's fine to merge this. Because I still don't fully understand internals of how this tracer feature is used, I would still appreciate another review, or any other ideas how we can better support those ethcore-as-a-library use cases.
ffc2dc9
to
78e7474
Compare
I don't think this applies cleanly since 1e9aebb If I'm reading that correctly, calls can now be suspended and resumed (the details of which are not entirely clear to me yet). When this happens a new externality is created and finalized. This messes with the
Does this make sense? |
Needs rebase, and minor interface change.
@udoprog Yeah. It's possible to get the same interface as of now, but also not sure if it suits you need still -- there's no concept of subtracers any more. |
|
Are you still working on this @udoprog ? |
@5chdn No, unfortunately I currently don't have the time to familiarize myself with the changes done while this was pending. Either someone else can pick it up, or feel free to close it and I'll open a new one if I end up finding the time. |
Ok, no worries. |
I'm putting together a debugger based on Parity and need access to storage during tracing to be able to decode solidity variables.