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:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						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 | ||||
| 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) | ||||
| 		} | ||||
| 		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 | ||||
| 			} | ||||
| 		} | ||||
| 		// Try to fulfill the request from the cache | ||||
| 		if hash, ok := cache[n]; ok { | ||||
| 			return hash | ||||
| 		} | ||||
| 		// 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 | ||||
| 			cache = append(cache, header.ParentHash) | ||||
| 			lastKnownHash = header.ParentHash | ||||
| 			lastKnownNumber = header.Number.Uint64() - 1 | ||||
| 			if n == lastKnownNumber { | ||||
| 				return lastKnownHash | ||||
| 			} | ||||
| 		} | ||||
| 		return common.Hash{} | ||||
|   | ||||
| @@ -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" | ||||
| ) | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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" | ||||
| ) | ||||
| @@ -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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user