mirror of https://github.com/status-im/op-geth.git
core/evm: less iteration in blockhash (#20589)
* core/vm/runtime: add test for blockhash * core/evm: less iteration in blockhash * core/vm/runtime: nitpickfix Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
parent
33791dbeb5
commit
058a4ac5f1
36
core/evm.go
36
core/evm.go
|
@ -60,24 +60,32 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
|
||||||
|
|
||||||
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||||
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
||||||
var cache map[uint64]common.Hash
|
// Cache will initially contain [refHash.parent],
|
||||||
|
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...]
|
||||||
|
var cache []common.Hash
|
||||||
|
|
||||||
return func(n uint64) common.Hash {
|
return func(n uint64) common.Hash {
|
||||||
// If there's no hash cache yet, make one
|
// If there's no hash cache yet, make one
|
||||||
if cache == nil {
|
if len(cache) == 0 {
|
||||||
cache = map[uint64]common.Hash{
|
cache = append(cache, ref.ParentHash)
|
||||||
ref.Number.Uint64() - 1: ref.ParentHash,
|
}
|
||||||
|
if idx := ref.Number.Uint64() - n - 1; idx < uint64(len(cache)) {
|
||||||
|
return cache[idx]
|
||||||
|
}
|
||||||
|
// No luck in the cache, but we can start iterating from the last element we already know
|
||||||
|
lastKnownHash := cache[len(cache)-1]
|
||||||
|
lastKnownNumber := ref.Number.Uint64() - uint64(len(cache))
|
||||||
|
|
||||||
|
for {
|
||||||
|
header := chain.GetHeader(lastKnownHash, lastKnownNumber)
|
||||||
|
if header == nil {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
cache = append(cache, header.ParentHash)
|
||||||
// Try to fulfill the request from the cache
|
lastKnownHash = header.ParentHash
|
||||||
if hash, ok := cache[n]; ok {
|
lastKnownNumber = header.Number.Uint64() - 1
|
||||||
return hash
|
if n == lastKnownNumber {
|
||||||
}
|
return lastKnownHash
|
||||||
// Not cached, iterate the blocks and cache the hashes
|
|
||||||
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
|
|
||||||
cache[header.Number.Uint64()-1] = header.ParentHash
|
|
||||||
if n == header.Number.Uint64()-1 {
|
|
||||||
return header.ParentHash
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
)
|
)
|
||||||
|
@ -26,8 +25,7 @@ func NewEnv(cfg *Config) *vm.EVM {
|
||||||
context := vm.Context{
|
context := vm.Context{
|
||||||
CanTransfer: core.CanTransfer,
|
CanTransfer: core.CanTransfer,
|
||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
GetHash: func(uint64) common.Hash { return common.Hash{} },
|
GetHash: cfg.GetHashFn,
|
||||||
|
|
||||||
Origin: cfg.Origin,
|
Origin: cfg.Origin,
|
||||||
Coinbase: cfg.Coinbase,
|
Coinbase: cfg.Coinbase,
|
||||||
BlockNumber: cfg.BlockNumber,
|
BlockNumber: cfg.BlockNumber,
|
||||||
|
|
|
@ -23,8 +23,11 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
@ -203,3 +206,113 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
|
||||||
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
|
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
|
||||||
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
|
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fakeHeader(n uint64, parentHash common.Hash) *types.Header {
|
||||||
|
header := types.Header{
|
||||||
|
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),
|
||||||
|
Number: big.NewInt(int64(n)),
|
||||||
|
ParentHash: parentHash,
|
||||||
|
Time: 1000,
|
||||||
|
Nonce: types.BlockNonce{0x1},
|
||||||
|
Extra: []byte{},
|
||||||
|
Difficulty: big.NewInt(0),
|
||||||
|
GasLimit: 100000,
|
||||||
|
}
|
||||||
|
return &header
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyChain struct {
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine retrieves the chain's consensus engine.
|
||||||
|
func (d *dummyChain) Engine() consensus.Engine {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader returns the hash corresponding to their hash.
|
||||||
|
func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header {
|
||||||
|
d.counter++
|
||||||
|
parentHash := common.Hash{}
|
||||||
|
s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32)
|
||||||
|
copy(parentHash[:], s)
|
||||||
|
|
||||||
|
//parentHash := common.Hash{byte(n - 1)}
|
||||||
|
//fmt.Printf("GetHeader(%x, %d) => header with parent %x\n", h, n, parentHash)
|
||||||
|
return fakeHeader(n, parentHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockhash tests the blockhash operation. It's a bit special, since it internally
|
||||||
|
// requires access to a chain reader.
|
||||||
|
func TestBlockhash(t *testing.T) {
|
||||||
|
// Current head
|
||||||
|
n := uint64(1000)
|
||||||
|
parentHash := common.Hash{}
|
||||||
|
s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32)
|
||||||
|
copy(parentHash[:], s)
|
||||||
|
header := fakeHeader(n, parentHash)
|
||||||
|
|
||||||
|
// This is the contract we're using. It requests the blockhash for current num (should be all zeroes),
|
||||||
|
// then iteratively fetches all blockhashes back to n-260.
|
||||||
|
// It returns
|
||||||
|
// 1. the first (should be zero)
|
||||||
|
// 2. the second (should be the parent hash)
|
||||||
|
// 3. the last non-zero hash
|
||||||
|
// By making the chain reader return hashes which correlate to the number, we can
|
||||||
|
// verify that it obtained the right hashes where it should
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
pragma solidity ^0.5.3;
|
||||||
|
contract Hasher{
|
||||||
|
|
||||||
|
function test() public view returns (bytes32, bytes32, bytes32){
|
||||||
|
uint256 x = block.number;
|
||||||
|
bytes32 first;
|
||||||
|
bytes32 last;
|
||||||
|
bytes32 zero;
|
||||||
|
zero = blockhash(x); // Should be zeroes
|
||||||
|
first = blockhash(x-1);
|
||||||
|
for(uint256 i = 2 ; i < 260; i++){
|
||||||
|
bytes32 hash = blockhash(x - i);
|
||||||
|
if (uint256(hash) != 0){
|
||||||
|
last = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (zero, first, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
// The contract above
|
||||||
|
data := common.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820462d71b510c1725ff35946c20b415b0d50b468ea157c8c77dff9466c9cb85f560029")
|
||||||
|
// The method call to 'test()'
|
||||||
|
input := common.Hex2Bytes("f8a8fd6d")
|
||||||
|
chain := &dummyChain{}
|
||||||
|
ret, _, err := Execute(data, input, &Config{
|
||||||
|
GetHashFn: core.GetHashFn(header, chain),
|
||||||
|
BlockNumber: new(big.Int).Set(header.Number),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if len(ret) != 96 {
|
||||||
|
t.Fatalf("expected returndata to be 96 bytes, got %d", len(ret))
|
||||||
|
}
|
||||||
|
|
||||||
|
zero := new(big.Int).SetBytes(ret[0:32])
|
||||||
|
first := new(big.Int).SetBytes(ret[32:64])
|
||||||
|
last := new(big.Int).SetBytes(ret[64:96])
|
||||||
|
if zero.BitLen() != 0 {
|
||||||
|
t.Fatalf("expected zeroes, got %x", ret[0:32])
|
||||||
|
}
|
||||||
|
if first.Uint64() != 999 {
|
||||||
|
t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64])
|
||||||
|
}
|
||||||
|
if last.Uint64() != 744 {
|
||||||
|
t.Fatalf("last block should be 744, got %d (%x)", last, ret[64:96])
|
||||||
|
}
|
||||||
|
if exp, got := 255, chain.counter; exp != got {
|
||||||
|
t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue