mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
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.
This commit is contained in:
parent
fc2ba1fb2e
commit
bca0646ede
2 changed files with 102 additions and 12 deletions
|
|
@ -2488,6 +2488,77 @@ func TestSimulateV1ChainLinkage(t *testing.T) {
|
|||
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
|
||||
}
|
||||
|
||||
func TestSimulateV1TxSender(t *testing.T) {
|
||||
var (
|
||||
sender = common.Address{0xaa, 0xaa}
|
||||
sender2 = common.Address{0xaa, 0xab}
|
||||
sender3 = common.Address{0xaa, 0xac}
|
||||
recipient = common.Address{0xbb, 0xbb}
|
||||
gspec = &core.Genesis{
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: big.NewInt(params.Ether)},
|
||||
sender2: {Balance: big.NewInt(params.Ether)},
|
||||
sender3: {Balance: big.NewInt(params.Ether)},
|
||||
},
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
backend := newTestBackend(t, 0, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {})
|
||||
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: true,
|
||||
}
|
||||
|
||||
results, err := sim.execute(ctx, []simBlock{
|
||||
{Calls: []TransactionArgs{
|
||||
{From: &sender, To: &recipient, Value: (*hexutil.Big)(big.NewInt(1000))},
|
||||
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(2000))},
|
||||
{From: &sender3, To: &recipient, Value: (*hexutil.Big)(big.NewInt(3000))},
|
||||
}},
|
||||
{Calls: []TransactionArgs{
|
||||
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(4000))},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("simulation execution failed: %v", err)
|
||||
}
|
||||
require.Len(t, results, 2, "expected 2 simulated blocks")
|
||||
require.Len(t, results[0].Block.Transactions(), 3, "expected 3 transaction in simulated block")
|
||||
require.Len(t, results[1].Block.Transactions(), 1, "expected 1 transaction in 2nd simulated block")
|
||||
enc, err := json.Marshal(results)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal results: %v", err)
|
||||
}
|
||||
type resultType struct {
|
||||
Transactions []struct {
|
||||
From common.Address `json:"from"`
|
||||
}
|
||||
}
|
||||
var summary []resultType
|
||||
if err := json.Unmarshal(enc, &summary); err != nil {
|
||||
t.Fatalf("failed to unmarshal results: %v", err)
|
||||
}
|
||||
require.Len(t, summary, 2, "expected 2 simulated blocks")
|
||||
require.Len(t, summary[0].Transactions, 3, "expected 3 transaction in simulated block")
|
||||
require.Equal(t, sender, summary[0].Transactions[0].From, "sender address mismatch")
|
||||
require.Equal(t, sender2, summary[0].Transactions[1].From, "sender address mismatch")
|
||||
require.Equal(t, sender3, summary[0].Transactions[2].From, "sender address mismatch")
|
||||
require.Len(t, summary[1].Transactions, 1, "expected 1 transaction in simulated block")
|
||||
require.Equal(t, sender2, summary[1].Transactions[0].From, "sender address mismatch")
|
||||
}
|
||||
|
||||
func TestSignTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
|
|
|||
|
|
@ -78,11 +78,25 @@ type simBlockResult struct {
|
|||
chainConfig *params.ChainConfig
|
||||
Block *types.Block
|
||||
Calls []simCallResult
|
||||
// senders is a map of transaction hashes to their senders.
|
||||
senders map[common.Hash]common.Address
|
||||
}
|
||||
|
||||
func (r *simBlockResult) MarshalJSON() ([]byte, error) {
|
||||
blockData := RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig)
|
||||
blockData["calls"] = r.Calls
|
||||
// Set tx sender if user requested full tx objects.
|
||||
if r.fullTx {
|
||||
if raw, ok := blockData["transactions"].([]any); ok {
|
||||
for _, tx := range raw {
|
||||
if tx, ok := tx.(*RPCTransaction); ok {
|
||||
tx.From = r.senders[tx.Hash]
|
||||
} else {
|
||||
return nil, errors.New("simulated transaction result has invalid type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Marshal(blockData)
|
||||
}
|
||||
|
||||
|
|
@ -181,18 +195,18 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
|
|||
parent = sim.base
|
||||
)
|
||||
for bi, block := range blocks {
|
||||
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
|
||||
result, callResults, senders, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers[bi] = result.Header()
|
||||
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
|
||||
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
|
||||
parent = result.Header()
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, error) {
|
||||
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) {
|
||||
// Set header fields that depend only on parent block.
|
||||
// Parent hash is needed for evm.GetHashFn to work.
|
||||
header.ParentHash = parent.Hash()
|
||||
|
|
@ -222,7 +236,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
precompiles := sim.activePrecompiles(sim.base)
|
||||
// State overrides are applied prior to execution of a block
|
||||
if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
var (
|
||||
gasUsed, blobGasUsed uint64
|
||||
|
|
@ -235,6 +249,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
NoBaseFee: !sim.validate,
|
||||
Tracer: tracer.Hooks(),
|
||||
}
|
||||
// senders is a map of transaction hashes to their senders.
|
||||
// Transaction objects contain only the signature, and we lose track
|
||||
// of the sender when translating the arguments into a transaction object.
|
||||
senders = make(map[common.Hash]common.Address)
|
||||
)
|
||||
tracingStateDB := vm.StateDB(sim.state)
|
||||
if hooks := tracer.Hooks(); hooks != nil {
|
||||
|
|
@ -255,16 +273,17 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
var allLogs []*types.Log
|
||||
for i, call := range block.Calls {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
var (
|
||||
tx = call.ToTransaction(types.DynamicFeeTxType)
|
||||
txHash = tx.Hash()
|
||||
)
|
||||
txes[i] = tx
|
||||
senders[txHash] = call.from()
|
||||
tracer.reset(txHash, uint(i))
|
||||
sim.state.SetTxContext(txHash, i)
|
||||
// EoA check is always skipped, even in validation mode.
|
||||
|
|
@ -272,7 +291,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
|
||||
if err != nil {
|
||||
txErr := txValidationError(err)
|
||||
return nil, nil, txErr
|
||||
return nil, nil, nil, txErr
|
||||
}
|
||||
// Update the state with pending changes.
|
||||
var root []byte
|
||||
|
|
@ -311,15 +330,15 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
if requests != nil {
|
||||
|
|
@ -330,10 +349,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
chainHeadReader := &simChainHeadReader{ctx, sim.b}
|
||||
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
repairLogs(callResults, b.Hash())
|
||||
return b, callResults, nil
|
||||
return b, callResults, senders, nil
|
||||
}
|
||||
|
||||
// repairLogs updates the block hash in the logs present in the result of
|
||||
|
|
|
|||
Loading…
Reference in a new issue