fix block hash cache

This commit is contained in:
Sina Mahmoodi 2023-10-10 15:53:55 +02:00
parent cc1158ae6d
commit 2690445207
2 changed files with 118 additions and 23 deletions

View file

@ -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

View file

@ -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()