From c616dcf1118699af938002689cc4e5a817822544 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 19 Mar 2019 10:26:21 +0100 Subject: [PATCH] core/vm/runtime: add test for blockhash --- core/vm/runtime/env.go | 8 ++- core/vm/runtime/runtime_test.go | 113 ++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 31c9b9cf9d..42a5b0f801 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -23,11 +23,15 @@ import ( ) func NewEnv(cfg *Config) *vm.EVM { + getHash := cfg.GetHashFn + if getHash == nil { + getHash = func(uint64) common.Hash { return common.Hash{} } + } + context := vm.Context{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, - GetHash: func(uint64) common.Hash { return common.Hash{} }, - + GetHash: getHash, Origin: cfg.Origin, Coinbase: cfg.Coinbase, BlockNumber: cfg.BlockNumber, diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 15f545ddca..503e7057cb 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -17,6 +17,9 @@ package runtime import ( + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "math/big" "strings" "testing" @@ -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: big.NewInt(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 chain.counter != 255 { + t.Logf("Suboptimal; too much chain iteration, expected 255, got %d", chain.counter) + } +}