Skip to content

EVM: Faster create/create2#17806

Merged
karalabe merged 11 commits into
ethereum:masterfrom
holiman:faster_create
Oct 4, 2018
Merged

EVM: Faster create/create2#17806
karalabe merged 11 commits into
ethereum:masterfrom
holiman:faster_create

Conversation

@holiman
Copy link
Copy Markdown
Contributor

@holiman holiman commented Oct 2, 2018

This PR improves CREATE and CREATE2.

  • For CREATE2, reuse the hash of the initcode for later
  • For CREATE, 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 than sha, so doing a sha to lookup a map is wasteful.
  • It also adds a method, debug.traceBadBlock(int), which can be used to trace a block which did not make it into the chain, but is available internally in badBlocks.

Copy link
Copy Markdown
Member

@karalabe karalabe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally seems good to me. My two main questions is:

  • Is it worthwhile to track *common.Hash, or could we simply use common.Hash{} as the nil value? The rest of our code does that, seems simpler/more consistent.
  • You pass codeAndHash around 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).

Comment thread core/vm/contract.go Outdated
self ContractRef

jumpdests destinations // result of JUMPDEST analysis.
jumpdests destinations // Aggregated result of JUMPDEST analysis.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread core/vm/contract.go Outdated

Code []byte
CodeHash common.Hash
CodeHash *common.Hash
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be the same to keep this as it was and instead of checking for nil, we check for == (common.Hash{})?

Comment thread core/vm/evm.go Outdated

type codeAndHash struct {
code []byte
hash *common.Hash
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Generally our code considers common.Hash{} to be the nil/uninited hash. Do we really need the pointers?

Comment thread core/vm/evm.go Outdated
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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably inline codeAndHash{code: code} here., a bit shorter/cleaner (imho).

Comment thread eth/api_tracer.go
Comment thread eth/api_tracer.go
Comment thread core/vm/evm.go Outdated
}

func (c *codeAndHash) Hash() common.Hash {
if c.hash == emptyCodeHash {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct AFAIK. It should be common.Hash{}. The emptyCodeHash is Keccak256("").

Comment thread core/vm/contract.go Outdated
}
var analysis bitvec
// Do we have a contract hash already?
if c.CodeHash != emptyCodeHash {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct AFAIK. It should be common.Hash{}. The emptyCodeHash is Keccak256("").

Comment thread core/vm/contract.go Outdated
if OpCode(c.Code[udest]) != JUMPDEST {
return false
}
var analysis bitvec
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it can be simplified. I iterated a bit on that loop, and it's probably some leftover from that

@karalabe karalabe added this to the 1.8.17 milestone Oct 4, 2018
Copy link
Copy Markdown
Member

@karalabe karalabe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@karalabe karalabe merged commit 89a3245 into ethereum:master Oct 4, 2018
@wuestholz
Copy link
Copy Markdown
Contributor

wuestholz commented Oct 8, 2018

@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 CaptureState, contract.CodeHash (where contract is of type *vm.contract) now seems to return an empty hash. Is this the new intended behavior?

@karalabe
Copy link
Copy Markdown
Member

karalabe commented Oct 8, 2018

No, that's most definitely a regression. @holiman do you have time to take a look?

@holiman
Copy link
Copy Markdown
Contributor Author

holiman commented Oct 8, 2018

Ah, crap. Probably trivial to fix, unsure if I'll be able to make it in time for release, if we release today though.

@holiman
Copy link
Copy Markdown
Contributor Author

holiman commented Oct 8, 2018

Hm, actually.. The contract.codeHash is not exposed to javascript tracers, are they? If you're writing your own tracer in go, you could more easily solve it by hashing it yourself.

if c.CodeHash == (common.Hash{}) {
 c.CodeHash = crypto.Keccak256Hash(c.code)
}

Or did I misunderstand the usecase?

@holiman
Copy link
Copy Markdown
Contributor Author

holiman commented Oct 8, 2018

The new intended behaviour is to not calculate the codehash for initcode. The only times we have the code hash is

  1. When the code is loaded from the trie
  2. When the code is created via CREATE2, which has a penalty-fee since we're forced to hash the code.

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.

@wuestholz
Copy link
Copy Markdown
Contributor

@karalabe @holiman Thank you very much for looking into this.

I see. Yes, I'm using my own tracer in Go. I was going to change my code to compute the hash myself, but wasn't sure if this change in behavior was intended. :) In that case, I will just change my code.

@saracen
Copy link
Copy Markdown

saracen commented Jun 28, 2019

In case anybody else stumbles across the problem I did:

CreateAddress2's signature remains the same, CreateAddress2(common.Address, [32]byte, []byte), across multiple versions, but the result is different. :)

v1.8.13: return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt.Bytes(), code)[12:])
v1.8.14: return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], Keccak256(code))[12:])
v1.8.17: return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants