internal/ethapi: fix prev hashes in eth_simulate (#31122)
Shout-out to @Gabriel-Trintinalia for discovering this issue. The gist of it as follows: When processing a block, we should provide the parent block as well as the last 256 block hashes. Some of these parents data (specifically the hash) was incorrect because even though during the processing of the parent block we have updated the header, that header was not updating the TransactionsRoot and ReceiptsRoot fields (types.NewBlock makes a new copy of the header and changes it only on that instance). --------- Co-authored-by: lightclient <lightclient@protonmail.com>
This commit is contained in:
parent
5552ada486
commit
8a14362bf7
|
@ -775,7 +775,7 @@ func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockN
|
|||
//
|
||||
// Note, this function doesn't make any changes in the state/blockchain and is
|
||||
// useful to execute and retrieve values.
|
||||
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]*simBlockResult, error) {
|
||||
if len(opts.BlockStateCalls) == 0 {
|
||||
return nil, &invalidParamsError{message: "empty input"}
|
||||
} else if len(opts.BlockStateCalls) > maxSimulateBlocks {
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -2309,6 +2310,101 @@ func TestSimulateV1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSimulateV1ChainLinkage(t *testing.T) {
|
||||
var (
|
||||
acc = newTestAccount()
|
||||
sender = acc.addr
|
||||
contractAddr = common.Address{0xaa, 0xaa}
|
||||
recipient = common.Address{0xbb, 0xbb}
|
||||
gspec = &core.Genesis{
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: big.NewInt(params.Ether)},
|
||||
contractAddr: {Code: common.Hex2Bytes("5f35405f8114600f575f5260205ff35b5f80fd")},
|
||||
},
|
||||
}
|
||||
signer = types.LatestSigner(params.MergedTestChainConfig)
|
||||
)
|
||||
backend := newTestBackend(t, 1, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
||||
tx := types.MustSignNewTx(acc.key, signer, &types.LegacyTx{
|
||||
Nonce: uint64(i),
|
||||
GasPrice: b.BaseFee(),
|
||||
Gas: params.TxGas,
|
||||
To: &recipient,
|
||||
Value: big.NewInt(500),
|
||||
})
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get state and header: %v", err)
|
||||
}
|
||||
|
||||
sim := &simulator{
|
||||
b: backend,
|
||||
state: stateDB,
|
||||
base: baseHeader,
|
||||
chainConfig: backend.ChainConfig(),
|
||||
gp: new(core.GasPool).AddGas(math.MaxUint64),
|
||||
traceTransfers: false,
|
||||
validate: false,
|
||||
fullTx: false,
|
||||
}
|
||||
|
||||
var (
|
||||
call1 = TransactionArgs{
|
||||
From: &sender,
|
||||
To: &recipient,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
}
|
||||
call2 = TransactionArgs{
|
||||
From: &sender,
|
||||
To: &recipient,
|
||||
Value: (*hexutil.Big)(big.NewInt(2000)),
|
||||
}
|
||||
call3a = TransactionArgs{
|
||||
From: &sender,
|
||||
To: &contractAddr,
|
||||
Input: uint256ToBytes(uint256.NewInt(baseHeader.Number.Uint64() + 1)),
|
||||
Gas: newUint64(1000000),
|
||||
}
|
||||
call3b = TransactionArgs{
|
||||
From: &sender,
|
||||
To: &contractAddr,
|
||||
Input: uint256ToBytes(uint256.NewInt(baseHeader.Number.Uint64() + 2)),
|
||||
Gas: newUint64(1000000),
|
||||
}
|
||||
blocks = []simBlock{
|
||||
{Calls: []TransactionArgs{call1}},
|
||||
{Calls: []TransactionArgs{call2}},
|
||||
{Calls: []TransactionArgs{call3a, call3b}},
|
||||
}
|
||||
)
|
||||
|
||||
results, err := sim.execute(ctx, blocks)
|
||||
if err != nil {
|
||||
t.Fatalf("simulation execution failed: %v", err)
|
||||
}
|
||||
require.Equal(t, 3, len(results), "expected 3 simulated blocks")
|
||||
|
||||
// Check linkages of simulated blocks:
|
||||
// Verify that block2's parent hash equals block1's hash.
|
||||
block1 := results[0].Block
|
||||
block2 := results[1].Block
|
||||
block3 := results[2].Block
|
||||
require.Equal(t, block1.ParentHash(), baseHeader.Hash(), "parent hash of block1 should equal hash of base block")
|
||||
require.Equal(t, block1.Hash(), block2.Header().ParentHash, "parent hash of block2 should equal hash of block1")
|
||||
require.Equal(t, block2.Hash(), block3.Header().ParentHash, "parent hash of block3 should equal hash of block2")
|
||||
|
||||
// In block3, two calls were executed to our contract.
|
||||
// The first call in block3 should return the blockhash for block1 (i.e. block1.Hash()),
|
||||
// whereas the second call should return the blockhash for block2 (i.e. block2.Hash()).
|
||||
require.Equal(t, block1.Hash().Bytes(), []byte(results[2].Calls[0].ReturnValue), "returned blockhash for block1 does not match")
|
||||
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
|
||||
}
|
||||
|
||||
func TestSignTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
|
|
@ -73,6 +73,20 @@ func (r *simCallResult) MarshalJSON() ([]byte, error) {
|
|||
return json.Marshal((*callResultAlias)(r))
|
||||
}
|
||||
|
||||
// simBlockResult is the result of a simulated block.
|
||||
type simBlockResult struct {
|
||||
fullTx bool
|
||||
chainConfig *params.ChainConfig
|
||||
Block *types.Block
|
||||
Calls []simCallResult
|
||||
}
|
||||
|
||||
func (r *simBlockResult) MarshalJSON() ([]byte, error) {
|
||||
blockData := RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig)
|
||||
blockData["calls"] = r.Calls
|
||||
return json.Marshal(blockData)
|
||||
}
|
||||
|
||||
// simOpts are the inputs to eth_simulateV1.
|
||||
type simOpts struct {
|
||||
BlockStateCalls []simBlock
|
||||
|
@ -95,7 +109,7 @@ type simulator struct {
|
|||
}
|
||||
|
||||
// execute runs the simulation of a series of blocks.
|
||||
func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[string]interface{}, error) {
|
||||
func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlockResult, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -123,7 +137,7 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[str
|
|||
return nil, err
|
||||
}
|
||||
var (
|
||||
results = make([]map[string]interface{}, len(blocks))
|
||||
results = make([]*simBlockResult, len(blocks))
|
||||
parent = sim.base
|
||||
)
|
||||
for bi, block := range blocks {
|
||||
|
@ -131,11 +145,9 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[str
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc := RPCMarshalBlock(result, true, sim.fullTx, sim.chainConfig)
|
||||
enc["calls"] = callResults
|
||||
results[bi] = enc
|
||||
|
||||
parent = headers[bi]
|
||||
headers[bi] = result.Header()
|
||||
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
|
||||
parent = result.Header()
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue