Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2403,6 +2403,63 @@ 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}
recipient = common.Address{0xbb, 0xbb}
gspec = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{
sender: {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))},
}},
})
if err != nil {
t.Fatalf("simulation execution failed: %v", err)
}
require.Len(t, results, 1, "expected 1 simulated blocks")
require.Len(t, results[0].Block.Transactions(), 1, "expected 1 transaction in 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, 1, "expected 1 transaction in simulated block")
require.Len(t, summary[0].Transactions, 1, "expected 1 transaction in simulated block")
require.Equal(t, sender, summary[0].Transactions[0].From, "sender address mismatch")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're only having a single sender and a single transaction, it would make sense to have a few and check that the order is correct, lest they get mixed up somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now address this comment, sorry for the delay.

}

func TestSignTransaction(t *testing.T) {
t.Parallel()
// Initialize test accounts
Expand Down
37 changes: 28 additions & 9 deletions internal/ethapi/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,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)
}

Expand Down Expand Up @@ -141,18 +155,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()
Expand Down Expand Up @@ -182,7 +196,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
Expand All @@ -195,6 +209,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 {
Expand All @@ -212,24 +230,25 @@ 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.
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
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
Expand Down Expand Up @@ -264,7 +283,7 @@ 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
core.ProcessWithdrawalQueue(&requests, evm)
Expand All @@ -286,7 +305,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
}
b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil))
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
Expand Down