Skip to content

Commit bca0646

Browse files
authored
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 fc2ba1f commit bca0646

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
@@ -2488,6 +2488,77 @@ func TestSimulateV1ChainLinkage(t *testing.T) {
24882488
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
24892489
}
24902490

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

@@ -181,18 +195,18 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
181195
parent = sim.base
182196
)
183197
for bi, block := range blocks {
184-
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
198+
result, callResults, senders, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
185199
if err != nil {
186200
return nil, err
187201
}
188202
headers[bi] = result.Header()
189-
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
203+
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
190204
parent = result.Header()
191205
}
192206
return results, nil
193207
}
194208

195-
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, error) {
209+
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) {
196210
// Set header fields that depend only on parent block.
197211
// Parent hash is needed for evm.GetHashFn to work.
198212
header.ParentHash = parent.Hash()
@@ -222,7 +236,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
222236
precompiles := sim.activePrecompiles(sim.base)
223237
// State overrides are applied prior to execution of a block
224238
if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil {
225-
return nil, nil, err
239+
return nil, nil, nil, err
226240
}
227241
var (
228242
gasUsed, blobGasUsed uint64
@@ -235,6 +249,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
235249
NoBaseFee: !sim.validate,
236250
Tracer: tracer.Hooks(),
237251
}
252+
// senders is a map of transaction hashes to their senders.
253+
// Transaction objects contain only the signature, and we lose track
254+
// of the sender when translating the arguments into a transaction object.
255+
senders = make(map[common.Hash]common.Address)
238256
)
239257
tracingStateDB := vm.StateDB(sim.state)
240258
if hooks := tracer.Hooks(); hooks != nil {
@@ -255,24 +273,25 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
255273
var allLogs []*types.Log
256274
for i, call := range block.Calls {
257275
if err := ctx.Err(); err != nil {
258-
return nil, nil, err
276+
return nil, nil, nil, err
259277
}
260278
if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil {
261-
return nil, nil, err
279+
return nil, nil, nil, err
262280
}
263281
var (
264282
tx = call.ToTransaction(types.DynamicFeeTxType)
265283
txHash = tx.Hash()
266284
)
267285
txes[i] = tx
286+
senders[txHash] = call.from()
268287
tracer.reset(txHash, uint(i))
269288
sim.state.SetTxContext(txHash, i)
270289
// EoA check is always skipped, even in validation mode.
271290
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
272291
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
273292
if err != nil {
274293
txErr := txValidationError(err)
275-
return nil, nil, txErr
294+
return nil, nil, nil, txErr
276295
}
277296
// Update the state with pending changes.
278297
var root []byte
@@ -311,15 +330,15 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
311330
requests = [][]byte{}
312331
// EIP-6110
313332
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
314-
return nil, nil, err
333+
return nil, nil, nil, err
315334
}
316335
// EIP-7002
317336
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
318-
return nil, nil, err
337+
return nil, nil, nil, err
319338
}
320339
// EIP-7251
321340
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
322-
return nil, nil, err
341+
return nil, nil, nil, err
323342
}
324343
}
325344
if requests != nil {
@@ -330,10 +349,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
330349
chainHeadReader := &simChainHeadReader{ctx, sim.b}
331350
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
332351
if err != nil {
333-
return nil, nil, err
352+
return nil, nil, nil, err
334353
}
335354
repairLogs(callResults, b.Hash())
336-
return b, callResults, nil
355+
return b, callResults, senders, nil
337356
}
338357

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

0 commit comments

Comments
 (0)