mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
ethclient: add support for eth_simulateV1
This commit is contained in:
parent
e42af536c5
commit
5691d733a3
2 changed files with 347 additions and 0 deletions
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -819,3 +820,103 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress {
|
||||||
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimulateOptions represents the options for eth_simulateV1.
|
||||||
|
type SimulateOptions struct {
|
||||||
|
BlockStateCalls []SimulateBlock `json:"blockStateCalls"`
|
||||||
|
TraceTransfers bool `json:"traceTransfers"`
|
||||||
|
Validation bool `json:"validation"`
|
||||||
|
ReturnFullTransactions bool `json:"returnFullTransactions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateBlock represents a batch of calls to be simulated.
|
||||||
|
type SimulateBlock struct {
|
||||||
|
BlockOverrides *BlockOverrides `json:"blockOverrides,omitempty"`
|
||||||
|
StateOverrides *StateOverride `json:"stateOverrides,omitempty"`
|
||||||
|
Calls []CallArgs `json:"calls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockOverrides is a set of header fields to override.
|
||||||
|
type BlockOverrides struct {
|
||||||
|
Number *hexutil.Big `json:"number,omitempty"`
|
||||||
|
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
||||||
|
Time *hexutil.Uint64 `json:"time,omitempty"`
|
||||||
|
GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty"`
|
||||||
|
FeeRecipient *common.Address `json:"feeRecipient,omitempty"`
|
||||||
|
PrevRandao *common.Hash `json:"prevRandao,omitempty"`
|
||||||
|
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||||
|
BlobBaseFee *hexutil.Big `json:"blobBaseFee,omitempty"`
|
||||||
|
BeaconRoot *common.Hash `json:"beaconRoot,omitempty"`
|
||||||
|
Withdrawals *types.Withdrawals `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateOverride is the collection of overridden accounts.
|
||||||
|
type StateOverride map[common.Address]OverrideAccount
|
||||||
|
|
||||||
|
// OverrideAccount indicates the overriding fields of account during the execution
|
||||||
|
// of a message call.
|
||||||
|
type OverrideAccount struct {
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce,omitempty"`
|
||||||
|
Code *hexutil.Bytes `json:"code,omitempty"`
|
||||||
|
Balance *hexutil.Big `json:"balance,omitempty"`
|
||||||
|
State map[common.Hash]common.Hash `json:"state,omitempty"`
|
||||||
|
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
|
||||||
|
MovePrecompileTo *common.Address `json:"movePrecompileToAddress,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallArgs represents the arguments to construct a transaction call.
|
||||||
|
type CallArgs struct {
|
||||||
|
From *common.Address `json:"from,omitempty"`
|
||||||
|
To *common.Address `json:"to,omitempty"`
|
||||||
|
Gas *hexutil.Uint64 `json:"gas,omitempty"`
|
||||||
|
GasPrice *hexutil.Big `json:"gasPrice,omitempty"`
|
||||||
|
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas,omitempty"`
|
||||||
|
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
|
||||||
|
Value *hexutil.Big `json:"value,omitempty"`
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce,omitempty"`
|
||||||
|
Data *hexutil.Bytes `json:"data,omitempty"`
|
||||||
|
Input *hexutil.Bytes `json:"input,omitempty"`
|
||||||
|
AccessList *types.AccessList `json:"accessList,omitempty"`
|
||||||
|
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||||
|
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
|
||||||
|
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
|
||||||
|
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
|
||||||
|
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
|
||||||
|
Proofs []kzg4844.Proof `json:"proofs,omitempty"`
|
||||||
|
AuthorizationList []types.SetCodeAuthorization `json:"authorizationList,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateCallResult is the result of a simulated call.
|
||||||
|
type SimulateCallResult struct {
|
||||||
|
ReturnValue hexutil.Bytes `json:"returnData"`
|
||||||
|
Logs []*types.Log `json:"logs"`
|
||||||
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
Status hexutil.Uint64 `json:"status"`
|
||||||
|
Error *CallError `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallError represents an error from a simulated call.
|
||||||
|
type CallError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateBlockResult represents the result of a simulated block.
|
||||||
|
type SimulateBlockResult struct {
|
||||||
|
Number hexutil.Uint64 `json:"number"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
Timestamp hexutil.Uint64 `json:"timestamp"`
|
||||||
|
GasLimit hexutil.Uint64 `json:"gasLimit"`
|
||||||
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
FeeRecipient common.Address `json:"miner"`
|
||||||
|
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||||
|
Calls []SimulateCallResult `json:"calls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateV1 executes transactions on top of a base state.
|
||||||
|
func (ec *Client) SimulateV1(ctx context.Context, opts SimulateOptions, blockNrOrHash *rpc.BlockNumberOrHash) ([]SimulateBlockResult, error) {
|
||||||
|
var result []SimulateBlockResult
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_simulateV1", opts, blockNrOrHash)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -754,3 +755,248 @@ func ExampleRevertErrorData() {
|
||||||
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
||||||
// message: user error
|
// message: user error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple test: transfer ETH from one account to another
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := hexutil.Big(*big.NewInt(100))
|
||||||
|
gas := hexutil.Uint64(100000)
|
||||||
|
maxFeePerGas := hexutil.Big(*new(big.Int).Mul(header.BaseFee, big.NewInt(2)))
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
Calls: []ethclient.CallArgs{
|
||||||
|
{
|
||||||
|
From: &from,
|
||||||
|
To: &to,
|
||||||
|
Value: &value,
|
||||||
|
Gas: &gas,
|
||||||
|
MaxFeePerGas: &maxFeePerGas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := client.SimulateV1(ctx, opts, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SimulateV1 failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("expected 1 block result, got %d", len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results[0].Calls) != 1 {
|
||||||
|
t.Fatalf("expected 1 call result, got %d", len(results[0].Calls))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the transaction succeeded
|
||||||
|
if results[0].Calls[0].Status != 1 {
|
||||||
|
t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if results[0].Calls[0].Error != nil {
|
||||||
|
t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1WithBlockOverrides(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := hexutil.Big(*big.NewInt(100))
|
||||||
|
gas := hexutil.Uint64(100000)
|
||||||
|
maxFeePerGas := hexutil.Big(*new(big.Int).Mul(header.BaseFee, big.NewInt(2)))
|
||||||
|
|
||||||
|
// Override timestamp only
|
||||||
|
timestamp := hexutil.Uint64(1234567890)
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
BlockOverrides: ðclient.BlockOverrides{
|
||||||
|
Time: ×tamp,
|
||||||
|
},
|
||||||
|
Calls: []ethclient.CallArgs{
|
||||||
|
{
|
||||||
|
From: &from,
|
||||||
|
To: &to,
|
||||||
|
Value: &value,
|
||||||
|
Gas: &gas,
|
||||||
|
MaxFeePerGas: &maxFeePerGas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := client.SimulateV1(ctx, opts, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SimulateV1 with block overrides failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("expected 1 block result, got %d", len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the timestamp was overridden
|
||||||
|
if results[0].Timestamp != timestamp {
|
||||||
|
t.Errorf("expected timestamp %d, got %d", timestamp, results[0].Timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1WithStateOverrides(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := hexutil.Big(*big.NewInt(1000000000000000000)) // 1 ETH
|
||||||
|
gas := hexutil.Uint64(100000)
|
||||||
|
maxFeePerGas := hexutil.Big(*new(big.Int).Mul(header.BaseFee, big.NewInt(2)))
|
||||||
|
|
||||||
|
// Override the balance of the 'from' address
|
||||||
|
balanceStr := "1000000000000000000000"
|
||||||
|
balance := new(big.Int)
|
||||||
|
balance.SetString(balanceStr, 10)
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
StateOverrides: ðclient.StateOverride{
|
||||||
|
from: ethclient.OverrideAccount{
|
||||||
|
Balance: (*hexutil.Big)(balance),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Calls: []ethclient.CallArgs{
|
||||||
|
{
|
||||||
|
From: &from,
|
||||||
|
To: &to,
|
||||||
|
Value: &value,
|
||||||
|
Gas: &gas,
|
||||||
|
MaxFeePerGas: &maxFeePerGas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := client.SimulateV1(ctx, opts, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SimulateV1 with state overrides failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("expected 1 block result, got %d", len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
if results[0].Calls[0].Status != 1 {
|
||||||
|
t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1WithBlockNumberOrHash(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := hexutil.Big(*big.NewInt(100))
|
||||||
|
gas := hexutil.Uint64(100000)
|
||||||
|
maxFeePerGas := hexutil.Big(*new(big.Int).Mul(header.BaseFee, big.NewInt(2)))
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
Calls: []ethclient.CallArgs{
|
||||||
|
{
|
||||||
|
From: &from,
|
||||||
|
To: &to,
|
||||||
|
Value: &value,
|
||||||
|
Gas: &gas,
|
||||||
|
MaxFeePerGas: &maxFeePerGas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate on the latest block
|
||||||
|
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
|
results, err := client.SimulateV1(ctx, opts, &latest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SimulateV1 with latest block failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("expected 1 block result, got %d", len(results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue