Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved blockhash caching #20589

Merged
merged 3 commits into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,32 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author

// GetHashFn returns a GetHashFunc which retrieves header hashes by number
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 {
// If there's no hash cache yet, make one
if cache == nil {
cache = map[uint64]common.Hash{
ref.Number.Uint64() - 1: ref.ParentHash,
}
if len(cache) == 0 {
cache = append(cache, ref.ParentHash)
}
// Try to fulfill the request from the cache
if hash, ok := cache[n]; ok {
return hash
if idx := ref.Number.Uint64() - n - 1; idx < uint64(len(cache)) {
return cache[idx]
}
// 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
// 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))
holiman marked this conversation as resolved.
Show resolved Hide resolved

for {
header := chain.GetHeader(lastKnownHash, lastKnownNumber)
if header == nil {
break
}
cache = append(cache, header.ParentHash)
lastKnownHash = header.ParentHash
lastKnownNumber = header.Number.Uint64() - 1
if n == lastKnownNumber {
return lastKnownHash
}
}
return common.Hash{}
Expand Down
4 changes: 1 addition & 3 deletions core/vm/runtime/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package runtime

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
)
Expand All @@ -26,8 +25,7 @@ func NewEnv(cfg *Config) *vm.EVM {
context := vm.Context{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
GetHash: func(uint64) common.Hash { return common.Hash{} },

GetHash: cfg.GetHashFn,
Origin: cfg.Origin,
Coinbase: cfg.Coinbase,
BlockNumber: cfg.BlockNumber,
Expand Down
113 changes: 113 additions & 0 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
"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/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
Expand Down Expand Up @@ -203,3 +206,113 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
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)
}
}