core/vm: avoid storing jumpdest analysis for initcode

This commit is contained in:
Martin Holst Swende 2018-10-01 20:55:58 +02:00
parent efc32877de
commit 67bad856b1
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
5 changed files with 68 additions and 36 deletions

View file

@ -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).

View file

@ -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()
} }

View file

@ -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
} }

View file

@ -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

View file

@ -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)
} }