Skip to content

Commit 8a14362

Browse files
s1nalightclient
andauthored
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 <[email protected]>
1 parent 5552ada commit 8a14362

File tree

3 files changed

+116
-8
lines changed

3 files changed

+116
-8
lines changed

internal/ethapi/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockN
775775
//
776776
// Note, this function doesn't make any changes in the state/blockchain and is
777777
// useful to execute and retrieve values.
778-
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
778+
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]*simBlockResult, error) {
779779
if len(opts.BlockStateCalls) == 0 {
780780
return nil, &invalidParamsError{message: "empty input"}
781781
} else if len(opts.BlockStateCalls) > maxSimulateBlocks {

internal/ethapi/api_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/json"
2525
"errors"
2626
"fmt"
27+
"math"
2728
"math/big"
2829
"os"
2930
"path/filepath"
@@ -2309,6 +2310,101 @@ func TestSimulateV1(t *testing.T) {
23092310
}
23102311
}
23112312

2313+
func TestSimulateV1ChainLinkage(t *testing.T) {
2314+
var (
2315+
acc = newTestAccount()
2316+
sender = acc.addr
2317+
contractAddr = common.Address{0xaa, 0xaa}
2318+
recipient = common.Address{0xbb, 0xbb}
2319+
gspec = &core.Genesis{
2320+
Config: params.MergedTestChainConfig,
2321+
Alloc: types.GenesisAlloc{
2322+
sender: {Balance: big.NewInt(params.Ether)},
2323+
contractAddr: {Code: common.Hex2Bytes("5f35405f8114600f575f5260205ff35b5f80fd")},
2324+
},
2325+
}
2326+
signer = types.LatestSigner(params.MergedTestChainConfig)
2327+
)
2328+
backend := newTestBackend(t, 1, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
2329+
tx := types.MustSignNewTx(acc.key, signer, &types.LegacyTx{
2330+
Nonce: uint64(i),
2331+
GasPrice: b.BaseFee(),
2332+
Gas: params.TxGas,
2333+
To: &recipient,
2334+
Value: big.NewInt(500),
2335+
})
2336+
b.AddTx(tx)
2337+
})
2338+
2339+
ctx := context.Background()
2340+
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
2341+
if err != nil {
2342+
t.Fatalf("failed to get state and header: %v", err)
2343+
}
2344+
2345+
sim := &simulator{
2346+
b: backend,
2347+
state: stateDB,
2348+
base: baseHeader,
2349+
chainConfig: backend.ChainConfig(),
2350+
gp: new(core.GasPool).AddGas(math.MaxUint64),
2351+
traceTransfers: false,
2352+
validate: false,
2353+
fullTx: false,
2354+
}
2355+
2356+
var (
2357+
call1 = TransactionArgs{
2358+
From: &sender,
2359+
To: &recipient,
2360+
Value: (*hexutil.Big)(big.NewInt(1000)),
2361+
}
2362+
call2 = TransactionArgs{
2363+
From: &sender,
2364+
To: &recipient,
2365+
Value: (*hexutil.Big)(big.NewInt(2000)),
2366+
}
2367+
call3a = TransactionArgs{
2368+
From: &sender,
2369+
To: &contractAddr,
2370+
Input: uint256ToBytes(uint256.NewInt(baseHeader.Number.Uint64() + 1)),
2371+
Gas: newUint64(1000000),
2372+
}
2373+
call3b = TransactionArgs{
2374+
From: &sender,
2375+
To: &contractAddr,
2376+
Input: uint256ToBytes(uint256.NewInt(baseHeader.Number.Uint64() + 2)),
2377+
Gas: newUint64(1000000),
2378+
}
2379+
blocks = []simBlock{
2380+
{Calls: []TransactionArgs{call1}},
2381+
{Calls: []TransactionArgs{call2}},
2382+
{Calls: []TransactionArgs{call3a, call3b}},
2383+
}
2384+
)
2385+
2386+
results, err := sim.execute(ctx, blocks)
2387+
if err != nil {
2388+
t.Fatalf("simulation execution failed: %v", err)
2389+
}
2390+
require.Equal(t, 3, len(results), "expected 3 simulated blocks")
2391+
2392+
// Check linkages of simulated blocks:
2393+
// Verify that block2's parent hash equals block1's hash.
2394+
block1 := results[0].Block
2395+
block2 := results[1].Block
2396+
block3 := results[2].Block
2397+
require.Equal(t, block1.ParentHash(), baseHeader.Hash(), "parent hash of block1 should equal hash of base block")
2398+
require.Equal(t, block1.Hash(), block2.Header().ParentHash, "parent hash of block2 should equal hash of block1")
2399+
require.Equal(t, block2.Hash(), block3.Header().ParentHash, "parent hash of block3 should equal hash of block2")
2400+
2401+
// In block3, two calls were executed to our contract.
2402+
// The first call in block3 should return the blockhash for block1 (i.e. block1.Hash()),
2403+
// whereas the second call should return the blockhash for block2 (i.e. block2.Hash()).
2404+
require.Equal(t, block1.Hash().Bytes(), []byte(results[2].Calls[0].ReturnValue), "returned blockhash for block1 does not match")
2405+
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
2406+
}
2407+
23122408
func TestSignTransaction(t *testing.T) {
23132409
t.Parallel()
23142410
// Initialize test accounts

internal/ethapi/simulate.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ func (r *simCallResult) MarshalJSON() ([]byte, error) {
7373
return json.Marshal((*callResultAlias)(r))
7474
}
7575

76+
// simBlockResult is the result of a simulated block.
77+
type simBlockResult struct {
78+
fullTx bool
79+
chainConfig *params.ChainConfig
80+
Block *types.Block
81+
Calls []simCallResult
82+
}
83+
84+
func (r *simBlockResult) MarshalJSON() ([]byte, error) {
85+
blockData := RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig)
86+
blockData["calls"] = r.Calls
87+
return json.Marshal(blockData)
88+
}
89+
7690
// simOpts are the inputs to eth_simulateV1.
7791
type simOpts struct {
7892
BlockStateCalls []simBlock
@@ -95,7 +109,7 @@ type simulator struct {
95109
}
96110

97111
// execute runs the simulation of a series of blocks.
98-
func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[string]interface{}, error) {
112+
func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlockResult, error) {
99113
if err := ctx.Err(); err != nil {
100114
return nil, err
101115
}
@@ -123,19 +137,17 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]map[str
123137
return nil, err
124138
}
125139
var (
126-
results = make([]map[string]interface{}, len(blocks))
140+
results = make([]*simBlockResult, len(blocks))
127141
parent = sim.base
128142
)
129143
for bi, block := range blocks {
130144
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
131145
if err != nil {
132146
return nil, err
133147
}
134-
enc := RPCMarshalBlock(result, true, sim.fullTx, sim.chainConfig)
135-
enc["calls"] = callResults
136-
results[bi] = enc
137-
138-
parent = headers[bi]
148+
headers[bi] = result.Header()
149+
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
150+
parent = result.Header()
139151
}
140152
return results, nil
141153
}

0 commit comments

Comments
 (0)