Skip to content

Commit 9381076

Browse files
s1naqianhh
authored andcommitted
internal/ethapi: fix tx.from in eth_simulateV1 (#31480)
Issue statement: when user requests eth_simulateV1 to return full transaction objects, these objects always had an empty `from` field. The reason is we lose the sender when translation the message into a types.Transaction which is then later on serialized. I did think of an alternative but opted to keep with this approach as it keeps complexity at the edge. The alternative would be to pass down a signer object to RPCMarshal* methods and define a custom signer which keeps the senders in its state and doesn't attempt the signature recovery.
1 parent 5d0f8c4 commit 9381076

File tree

2 files changed

+102
-12
lines changed

2 files changed

+102
-12
lines changed

internal/ethapi/api_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,77 @@ func TestSimulateV1ChainLinkage(t *testing.T) {
25032503
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
25042504
}
25052505

2506+
func TestSimulateV1TxSender(t *testing.T) {
2507+
var (
2508+
sender = common.Address{0xaa, 0xaa}
2509+
sender2 = common.Address{0xaa, 0xab}
2510+
sender3 = common.Address{0xaa, 0xac}
2511+
recipient = common.Address{0xbb, 0xbb}
2512+
gspec = &core.Genesis{
2513+
Config: params.MergedTestChainConfig,
2514+
Alloc: types.GenesisAlloc{
2515+
sender: {Balance: big.NewInt(params.Ether)},
2516+
sender2: {Balance: big.NewInt(params.Ether)},
2517+
sender3: {Balance: big.NewInt(params.Ether)},
2518+
},
2519+
}
2520+
ctx = context.Background()
2521+
)
2522+
backend := newTestBackend(t, 0, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {})
2523+
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
2524+
if err != nil {
2525+
t.Fatalf("failed to get state and header: %v", err)
2526+
}
2527+
2528+
sim := &simulator{
2529+
b: backend,
2530+
state: stateDB,
2531+
base: baseHeader,
2532+
chainConfig: backend.ChainConfig(),
2533+
gp: new(core.GasPool).AddGas(math.MaxUint64),
2534+
traceTransfers: false,
2535+
validate: false,
2536+
fullTx: true,
2537+
}
2538+
2539+
results, err := sim.execute(ctx, []simBlock{
2540+
{Calls: []TransactionArgs{
2541+
{From: &sender, To: &recipient, Value: (*hexutil.Big)(big.NewInt(1000))},
2542+
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(2000))},
2543+
{From: &sender3, To: &recipient, Value: (*hexutil.Big)(big.NewInt(3000))},
2544+
}},
2545+
{Calls: []TransactionArgs{
2546+
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(4000))},
2547+
}},
2548+
})
2549+
if err != nil {
2550+
t.Fatalf("simulation execution failed: %v", err)
2551+
}
2552+
require.Len(t, results, 2, "expected 2 simulated blocks")
2553+
require.Len(t, results[0].Block.Transactions(), 3, "expected 3 transaction in simulated block")
2554+
require.Len(t, results[1].Block.Transactions(), 1, "expected 1 transaction in 2nd simulated block")
2555+
enc, err := json.Marshal(results)
2556+
if err != nil {
2557+
t.Fatalf("failed to marshal results: %v", err)
2558+
}
2559+
type resultType struct {
2560+
Transactions []struct {
2561+
From common.Address `json:"from"`
2562+
}
2563+
}
2564+
var summary []resultType
2565+
if err := json.Unmarshal(enc, &summary); err != nil {
2566+
t.Fatalf("failed to unmarshal results: %v", err)
2567+
}
2568+
require.Len(t, summary, 2, "expected 2 simulated blocks")
2569+
require.Len(t, summary[0].Transactions, 3, "expected 3 transaction in simulated block")
2570+
require.Equal(t, sender, summary[0].Transactions[0].From, "sender address mismatch")
2571+
require.Equal(t, sender2, summary[0].Transactions[1].From, "sender address mismatch")
2572+
require.Equal(t, sender3, summary[0].Transactions[2].From, "sender address mismatch")
2573+
require.Len(t, summary[1].Transactions, 1, "expected 1 transaction in simulated block")
2574+
require.Equal(t, sender2, summary[1].Transactions[0].From, "sender address mismatch")
2575+
}
2576+
25062577
func TestSignTransaction(t *testing.T) {
25072578
t.Parallel()
25082579
// Initialize test accounts

internal/ethapi/simulate.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,25 @@ type simBlockResult struct {
7878
chainConfig *params.ChainConfig
7979
Block *types.Block
8080
Calls []simCallResult
81+
// senders is a map of transaction hashes to their senders.
82+
senders map[common.Hash]common.Address
8183
}
8284

8385
func (r *simBlockResult) MarshalJSON() ([]byte, error) {
8486
blockData := RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig)
8587
blockData["calls"] = r.Calls
88+
// Set tx sender if user requested full tx objects.
89+
if r.fullTx {
90+
if raw, ok := blockData["transactions"].([]any); ok {
91+
for _, tx := range raw {
92+
if tx, ok := tx.(*RPCTransaction); ok {
93+
tx.From = r.senders[tx.Hash]
94+
} else {
95+
return nil, errors.New("simulated transaction result has invalid type")
96+
}
97+
}
98+
}
99+
}
86100
return json.Marshal(blockData)
87101
}
88102

@@ -185,18 +199,18 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
185199
parent = sim.base
186200
)
187201
for bi, block := range blocks {
188-
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
202+
result, callResults, senders, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
189203
if err != nil {
190204
return nil, err
191205
}
192206
headers[bi] = result.Header()
193-
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
207+
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
194208
parent = result.Header()
195209
}
196210
return results, nil
197211
}
198212

199-
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, error) {
213+
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, map[common.Hash]common.Address, error) {
200214
// Set header fields that depend only on parent block.
201215
// Parent hash is needed for evm.GetHashFn to work.
202216
header.ParentHash = parent.Hash()
@@ -226,7 +240,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
226240
precompiles := sim.activePrecompiles(sim.base)
227241
// State overrides are applied prior to execution of a block
228242
if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil {
229-
return nil, nil, err
243+
return nil, nil, nil, err
230244
}
231245
var (
232246
gasUsed, blobGasUsed uint64
@@ -239,6 +253,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
239253
NoBaseFee: !sim.validate,
240254
Tracer: tracer.Hooks(),
241255
}
256+
// senders is a map of transaction hashes to their senders.
257+
// Transaction objects contain only the signature, and we lose track
258+
// of the sender when translating the arguments into a transaction object.
259+
senders = make(map[common.Hash]common.Address)
242260
)
243261
tracingStateDB := vm.StateDB(sim.state)
244262
if hooks := tracer.Hooks(); hooks != nil {
@@ -259,24 +277,25 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
259277
var allLogs []*types.Log
260278
for i, call := range block.Calls {
261279
if err := ctx.Err(); err != nil {
262-
return nil, nil, err
280+
return nil, nil, nil, err
263281
}
264282
if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil {
265-
return nil, nil, err
283+
return nil, nil, nil, err
266284
}
267285
var (
268286
tx = call.ToTransaction(types.DynamicFeeTxType)
269287
txHash = tx.Hash()
270288
)
271289
txes[i] = tx
290+
senders[txHash] = call.from()
272291
tracer.reset(txHash, uint(i))
273292
sim.state.SetTxContext(txHash, i)
274293
// EoA check is always skipped, even in validation mode.
275294
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
276295
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
277296
if err != nil {
278297
txErr := txValidationError(err)
279-
return nil, nil, txErr
298+
return nil, nil, nil, txErr
280299
}
281300
// Update the state with pending changes.
282301
var root []byte
@@ -315,15 +334,15 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
315334
requests = [][]byte{}
316335
// EIP-6110
317336
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
318-
return nil, nil, err
337+
return nil, nil, nil, err
319338
}
320339
// EIP-7002
321340
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
322-
return nil, nil, err
341+
return nil, nil, nil, err
323342
}
324343
// EIP-7251
325344
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
326-
return nil, nil, err
345+
return nil, nil, nil, err
327346
}
328347
}
329348
if requests != nil {
@@ -334,10 +353,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
334353
chainHeadReader := &simChainHeadReader{ctx, sim.b}
335354
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
336355
if err != nil {
337-
return nil, nil, err
356+
return nil, nil, nil, err
338357
}
339358
repairLogs(callResults, b.Hash())
340-
return b, callResults, nil
359+
return b, callResults, senders, nil
341360
}
342361

343362
// repairLogs updates the block hash in the logs present in the result of

0 commit comments

Comments
 (0)