EVM: Faster create/create2#17806
Conversation
karalabe
left a comment
There was a problem hiding this comment.
Generally seems good to me. My two main questions is:
- Is it worthwhile to track
*common.Hash, or could we simply usecommon.Hash{}as the nil value? The rest of our code does that, seems simpler/more consistent. - You pass
codeAndHasharound by value. However, calling Hash on it changes it's contents, which if you passed by value into a nested method, won't propagate out. This is not an issue in the current code, because you Hash on the topmost context, but this is imho dangerous. Please pass it as a pointer everywhere (also create it as such).
| self ContractRef | ||
|
|
||
| jumpdests destinations // result of JUMPDEST analysis. | ||
| jumpdests destinations // Aggregated result of JUMPDEST analysis. |
There was a problem hiding this comment.
Could we drop the destinations typdef altogether? Previously we at least had a methof on it, but now it's just a map[common.Hash]bitvec. There's not much point really to keep a type just for this.
|
|
||
| Code []byte | ||
| CodeHash common.Hash | ||
| CodeHash *common.Hash |
There was a problem hiding this comment.
Wouldn't it be the same to keep this as it was and instead of checking for nil, we check for == (common.Hash{})?
|
|
||
| type codeAndHash struct { | ||
| code []byte | ||
| hash *common.Hash |
There was a problem hiding this comment.
Same here. Generally our code considers common.Hash{} to be the nil/uninited hash. Do we really need the pointers?
| codeAndHash := codeAndHash{code: code} | ||
| contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) | ||
| return evm.create(caller, code, gas, value, contractAddr) | ||
| return evm.create(caller, codeAndHash, gas, value, contractAddr) |
There was a problem hiding this comment.
You can probably inline codeAndHash{code: code} here., a bit shorter/cleaner (imho).
| } | ||
|
|
||
| func (c *codeAndHash) Hash() common.Hash { | ||
| if c.hash == emptyCodeHash { |
There was a problem hiding this comment.
This is not correct AFAIK. It should be common.Hash{}. The emptyCodeHash is Keccak256("").
| } | ||
| var analysis bitvec | ||
| // Do we have a contract hash already? | ||
| if c.CodeHash != emptyCodeHash { |
There was a problem hiding this comment.
This is not correct AFAIK. It should be common.Hash{}. The emptyCodeHash is Keccak256("").
| if OpCode(c.Code[udest]) != JUMPDEST { | ||
| return false | ||
| } | ||
| var analysis bitvec |
There was a problem hiding this comment.
You don't use this anywhere outside of the nested if below. You could delete it from here and instead of declaring exists explicitly below too, just do analysis, exist := c.jumpdests[c.CodeHash].
Did you want to use this for something and perhaps forgot?
There was a problem hiding this comment.
You're right, it can be simplified. I iterated a bit on that loop, and it's probably some leftover from that
|
@holiman @karalabe I noticed that after this change my execution tracer behaves differently when accessing the code hash of a contract that is currently being created. For instance, in function |
|
No, that's most definitely a regression. @holiman do you have time to take a look? |
|
Ah, crap. Probably trivial to fix, unsure if I'll be able to make it in time for release, if we release today though. |
|
Hm, actually.. The if c.CodeHash == (common.Hash{}) {
c.CodeHash = crypto.Keccak256Hash(c.code)
}Or did I misunderstand the usecase? |
|
The new intended behaviour is to not calculate the codehash for initcode. The only times we have the code hash is
We could specifically calculate it if debug tracing is turned on, but I'm not sure that's a better solution than hashing it lazily inside the specific tracer that needs it. |
|
In case anybody else stumbles across the problem I did:
v1.8.13: |
This PR improves
CREATEandCREATE2.CREATE2, reuse the hash of theinitcodefor laterCREATE, don't store the jumpdest map, instead always calculate the map if the codehash is not available. Calculating the map is a fairly trivial loop which is faster thansha, so doing ashato lookup a map is wasteful.debug.traceBadBlock(int), which can be used to trace a block which did not make it into the chain, but is available internally inbadBlocks.