From 2690445207baa73783024c7d5201e466984ba415 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Oct 2023 15:53:55 +0200 Subject: [PATCH] fix block hash cache --- internal/ethapi/api.go | 46 ++++++++++-------- internal/ethapi/api_test.go | 95 +++++++++++++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 23 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 49d2d54f9c..5fe169cb4d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1322,8 +1322,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo blockContext = core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil) rules = s.b.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time) precompiles = vm.ActivePrecompiledContracts(rules).Copy() - numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() - // Cache for simulated and phantom block hashes. + numHashes = headers[len(headers)-1].Number.Uint64() - base.Number.Uint64() + 256 + // Cache for the block hashes. hashes = make([]common.Hash, numHashes) ) for bi, block := range blocks { @@ -1426,21 +1426,35 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo // Note getHash assumes `n` is smaller than the last already simulated block // and smaller than the last block to be simulated. func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, []common.Hash, error) { + // getIndex returns the index of the hash in the hashes cache. + // The cache potentially includes 255 blocks prior to the base. getIndex := func(n uint64) int { - return int(n - base.Number.Uint64()) + first := base.Number.Uint64() - 255 + return int(n - first) } index := getIndex(n) if h := hashes[index]; h != (common.Hash{}) { return h, hashes, nil } + h, err := computeHash(ctx, n, base, headers, backend, hashes) + if err != nil { + return common.Hash{}, hashes, err + } + if h != (common.Hash{}) { + hashes[index] = h + } + return h, hashes, nil +} + +func computeHash(ctx context.Context, n uint64, base *types.Header, headers []*types.Header, backend Backend, hashes []common.Hash) (common.Hash, error) { if n == base.Number.Uint64() { - return base.Hash(), hashes, nil + return base.Hash(), nil } else if n < base.Number.Uint64() { h, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(n)) if err != nil { - return common.Hash{}, hashes, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) + return common.Hash{}, fmt.Errorf("failed to load block hash for number %d. Err: %v\n", n, err) } - return h.Hash(), hashes, nil + return h.Hash(), nil } h := base for i, _ := range headers { @@ -1449,29 +1463,23 @@ func getHash(ctx context.Context, n uint64, base *types.Header, headers []*types // so no need to check that condition. if tmp.Number.Uint64() == n { hash := tmp.Hash() - hashes[index] = hash - return hash, hashes, nil + return hash, nil } else if tmp.Number.Uint64() > n { // Phantom block. - var lastNonPhantomHash common.Hash - if hash := hashes[getIndex(h.Number.Uint64())]; hash != (common.Hash{}) { - lastNonPhantomHash = hash - } else { - lastNonPhantomHash = h.Hash() - hashes[getIndex(h.Number.Uint64())] = lastNonPhantomHash + lastNonPhantomHash, _, err := getHash(ctx, h.Number.Uint64(), base, headers, backend, hashes) + if err != nil { + return common.Hash{}, err } // keccak(rlp(lastNonPhantomBlockHash, blockNumber)) hashData, err := rlp.EncodeToBytes([][]byte{lastNonPhantomHash.Bytes(), big.NewInt(int64(n)).Bytes()}) if err != nil { - return common.Hash{}, hashes, err + return common.Hash{}, err } - hash := crypto.Keccak256Hash(hashData) - hashes[index] = hash - return hash, hashes, nil + return crypto.Keccak256Hash(hashData), nil } h = tmp } - return common.Hash{}, hashes, errors.New("requested block is in future") + return common.Hash{}, errors.New("requested block is in future") } // repairLogs updates the block hash in the logs present in a multicall diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index cd0e912a76..82375f32d5 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1640,6 +1640,91 @@ func TestMulticallV1(t *testing.T) { }}, }}, }, + { + name: "blockhash-opcode", + tag: latest, + blocks: []CallBatch{{ + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(12)), + }, + StateOverrides: &StateOverride{ + randomAccounts[2].addr: { + Code: hex2Bytes("600035804060008103601057600080fd5b5050"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Phantom block after base. + Input: uint256ToBytes(uint256.NewInt(11)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Canonical block. + Input: uint256ToBytes(uint256.NewInt(8)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // base block. + Input: uint256ToBytes(uint256.NewInt(10)), + }}, + }, { + BlockOverrides: &BlockOverrides{ + Number: (*hexutil.Big)(big.NewInt(16)), + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // blocks[0] + Input: uint256ToBytes(uint256.NewInt(12)), + }, { + From: &accounts[0].addr, + To: &randomAccounts[2].addr, + // Phantom after blocks[0] + Input: uint256ToBytes(uint256.NewInt(13)), + }}, + }}, + want: []blockRes{{ + Number: "0xc", + GasLimit: "0x47e7c4", + GasUsed: "0xf864", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }}, + }, { + Number: "0x10", + GasLimit: "0x47e7c4", + GasUsed: "0xa598", + FeeRecipient: coinbase, + Calls: []callRes{{ + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }, { + + ReturnValue: "0x", + GasUsed: "0x52cc", + Logs: []log{}, + Status: "0x1", + }}, + }}, + }, } for _, tc := range testSuite { @@ -1715,6 +1800,12 @@ func newBytes(b []byte) *hexutil.Bytes { return &rpcBytes } +func uint256ToBytes(v *uint256.Int) *hexutil.Bytes { + b := v.Bytes32() + r := hexutil.Bytes(b[:]) + return &r +} + func TestRPCMarshalBlock(t *testing.T) { t.Parallel() var ( @@ -1924,10 +2015,6 @@ func TestRPCMarshalBlock(t *testing.T) { } } -func hex2Hash(s string) common.Hash { - return common.BytesToHash(common.FromHex(s)) -} - func TestRPCGetBlockOrHeader(t *testing.T) { t.Parallel()