mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/vm: avoid storing jumpdest analysis for initcode
This commit is contained in:
parent
efc32877de
commit
67bad856b1
5 changed files with 68 additions and 36 deletions
|
|
@ -17,33 +17,13 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// destinations stores one map per contract (keyed by hash of code).
|
// destinations stores one bitmap per contract (keyed by hash of code).
|
||||||
// The maps contain an entry for each location of a JUMPDEST
|
// The bitmaps mark code and data-sections for a piece of contract code
|
||||||
// instruction.
|
|
||||||
type destinations map[common.Hash]bitvec
|
type destinations map[common.Hash]bitvec
|
||||||
|
|
||||||
// has checks whether code has a JUMPDEST at dest.
|
|
||||||
func (d destinations) has(codehash common.Hash, code []byte, dest *big.Int) bool {
|
|
||||||
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
|
|
||||||
// Don't bother checking for JUMPDEST in that case.
|
|
||||||
udest := dest.Uint64()
|
|
||||||
if dest.BitLen() >= 63 || udest >= uint64(len(code)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
m, analysed := d[codehash]
|
|
||||||
if !analysed {
|
|
||||||
m = codeBitmap(code)
|
|
||||||
d[codehash] = m
|
|
||||||
}
|
|
||||||
return OpCode(code[udest]) == JUMPDEST && m.codeSegment(udest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bitvec is a bit vector which maps bytes in a program.
|
// bitvec is a bit vector which maps bytes in a program.
|
||||||
// An unset bit means the byte is an opcode, a set bit means
|
// An unset bit means the byte is an opcode, a set bit means
|
||||||
// it's data (i.e. argument of PUSHxx).
|
// it's data (i.e. argument of PUSHxx).
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestJumpDestAnalysis(t *testing.T) {
|
func TestJumpDestAnalysis(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -49,5 +52,23 @@ func TestJumpDestAnalysis(t *testing.T) {
|
||||||
t.Fatalf("expected %x, got %02x", test.exp, ret[test.which])
|
t.Fatalf("expected %x, got %02x", test.exp, ret[test.which])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
|
||||||
|
// 1.4 ms
|
||||||
|
code := make([]byte, 1200000)
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
codeBitmap(code)
|
||||||
|
}
|
||||||
|
bench.StopTimer()
|
||||||
|
}
|
||||||
|
func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
|
||||||
|
// 4 ms
|
||||||
|
code := make([]byte, 1200000)
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
crypto.Keccak256Hash(code)
|
||||||
|
}
|
||||||
|
bench.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,10 @@ type Contract struct {
|
||||||
caller ContractRef
|
caller ContractRef
|
||||||
self ContractRef
|
self ContractRef
|
||||||
|
|
||||||
jumpdests destinations // result of JUMPDEST analysis.
|
jumpdests destinations // Aggregated result of JUMPDEST analysis.
|
||||||
|
|
||||||
Code []byte
|
Code []byte
|
||||||
CodeHash common.Hash
|
CodeHash *common.Hash
|
||||||
CodeAddr *common.Address
|
CodeAddr *common.Address
|
||||||
Input []byte
|
Input []byte
|
||||||
|
|
||||||
|
|
@ -84,6 +84,37 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Contract) validJumpdest(dest *big.Int) bool {
|
||||||
|
udest := dest.Uint64()
|
||||||
|
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
|
||||||
|
// Don't bother checking for JUMPDEST in that case.
|
||||||
|
if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Only JUMPDESTs allowed for destinations
|
||||||
|
if OpCode(c.Code[udest]) != JUMPDEST {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var analysis bitvec
|
||||||
|
// Do we have a contract hash already?
|
||||||
|
if c.CodeHash != nil {
|
||||||
|
var exist bool
|
||||||
|
// Does parent context have the analysis?
|
||||||
|
analysis, exist = c.jumpdests[*c.CodeHash]
|
||||||
|
if !exist {
|
||||||
|
// Do the analysis
|
||||||
|
analysis = codeBitmap(c.Code)
|
||||||
|
// Save in parent context
|
||||||
|
c.jumpdests[*c.CodeHash] = analysis
|
||||||
|
}
|
||||||
|
return analysis.codeSegment(udest)
|
||||||
|
}
|
||||||
|
//Don't have the hash, most likely a piece of initcode not already in state trie
|
||||||
|
analysis = codeBitmap(c.Code)
|
||||||
|
// Don't bother saving this
|
||||||
|
return analysis.codeSegment(udest)
|
||||||
|
}
|
||||||
|
|
||||||
// AsDelegate sets the contract to be a delegate call and returns the current
|
// AsDelegate sets the contract to be a delegate call and returns the current
|
||||||
// contract (for chaining calls)
|
// contract (for chaining calls)
|
||||||
func (c *Contract) AsDelegate() *Contract {
|
func (c *Contract) AsDelegate() *Contract {
|
||||||
|
|
@ -138,16 +169,16 @@ func (c *Contract) Value() *big.Int {
|
||||||
return c.value
|
return c.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCode sets the code to the contract
|
|
||||||
func (c *Contract) SetCode(hash common.Hash, code []byte) {
|
|
||||||
c.Code = code
|
|
||||||
c.CodeHash = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCallCode sets the code of the contract and address of the backing data
|
// SetCallCode sets the code of the contract and address of the backing data
|
||||||
// object
|
// object
|
||||||
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
||||||
c.Code = code
|
c.Code = code
|
||||||
c.CodeHash = hash
|
c.CodeHash = &hash
|
||||||
|
c.CodeAddr = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash codeAndHash) {
|
||||||
|
c.Code = codeAndHash.code
|
||||||
|
c.CodeHash = codeAndHash.hash
|
||||||
c.CodeAddr = addr
|
c.CodeAddr = addr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash codeAndHash, gas uint64,
|
||||||
// EVM. The contract is a scoped environment for this execution context
|
// EVM. The contract is a scoped environment for this execution context
|
||||||
// only.
|
// only.
|
||||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||||
contract.SetCallCode(&address, codeAndHash.Hash(), codeAndHash.code)
|
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||||
|
|
||||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||||
return nil, address, gas, nil
|
return nil, address, gas, nil
|
||||||
|
|
|
||||||
|
|
@ -620,7 +620,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor
|
||||||
|
|
||||||
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
pos := stack.pop()
|
pos := stack.pop()
|
||||||
if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
|
if !contract.validJumpdest(pos) {
|
||||||
nop := contract.GetOp(pos.Uint64())
|
nop := contract.GetOp(pos.Uint64())
|
||||||
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
||||||
}
|
}
|
||||||
|
|
@ -633,7 +633,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
|
||||||
func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
pos, cond := stack.pop(), stack.pop()
|
pos, cond := stack.pop(), stack.pop()
|
||||||
if cond.Sign() != 0 {
|
if cond.Sign() != 0 {
|
||||||
if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
|
if !contract.validJumpdest(pos) {
|
||||||
nop := contract.GetOp(pos.Uint64())
|
nop := contract.GetOp(pos.Uint64())
|
||||||
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue