Skip to content

Commit

Permalink
feat: interop invariant checks
Browse files Browse the repository at this point in the history
  • Loading branch information
tremarkley committed Jul 23, 2024
1 parent d84f3a8 commit 483de23
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 6 deletions.
13 changes: 13 additions & 0 deletions anvil/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,16 @@ func (a *Anvil) EthSendTransaction(ctx context.Context, tx *types.Transaction) e
func (a *Anvil) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
return a.ethClient.SubscribeFilterLogs(ctx, q, ch)
}

func (a *Anvil) DebugTraceCall(ctx context.Context, txArgs config.TransactionArgs) (config.TraceCallRaw, error) {
var result config.TraceCallRaw
if err := a.rpcClient.CallContext(ctx, &result, "debug_traceCall", txArgs, "latest", map[string]interface{}{
"tracer": "callTracer",
"tracerConfig": map[string]interface{}{
"withLog": true,
},
}); err != nil {
return config.TraceCallRaw{}, err
}
return result, nil
}
73 changes: 73 additions & 0 deletions config/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)

Expand Down Expand Up @@ -64,6 +66,76 @@ type ChainConfig struct {
ForkConfig *ForkConfig
}

type TransactionArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Data hexutil.Bytes `json:"data"`
Value *hexutil.Big `json:"value"`
}

type StructLog struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Memory []string `json:"memory,omitempty"`
MemorySize int `json:"memSize"`
Stack []string `json:"stack"`
ReturnData []byte `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
}

// type DebugTraceCallResponse struct {
// Failed bool `json:"failed"`
// Gas uint64 `json:"gas"`
// ReturnValue string `json:"returnValue"`
// StructLogs []StructLog `json:"structLogs"`
// }

// DebugTraceCall
// A response struct.
type DebugTraceCallResponse struct {
Calls []DebugTraceCallResponse `json:"calls"`
From common.Address `json:"from"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output"`
To common.Address `json:"to"`
Type string `json:"type"`
Value string `json:"value"`
}

type TraceCallRaw struct {
Type string `json:"type" gencodec:"required"`
From string `json:"from" gencodec:"required"`
To string `json:"to" gencodec:"required"`
Value string `json:"value" gencodec:"required"`
Gas string `json:"gas" gencodec:"required"`
GasUsed string `json:"gasUsed" gencodec:"required"`
Input string `json:"input" gencodec:"required"`
Output string `json:"output" gencodec:"required"`
Logs []*TraceCallRawLog `json:"logs" gencodec:"required"`
Calls []TraceCallRaw `json:"calls" gencodec:"required"`
}

type TraceCallRawLog struct {
Address common.Address `json:"address" gencodec:"required"`
Topics []common.Hash `json:"topics" gencodec:"required"`
Data string `json:"data" gencodec:"required"`
}

type EthCallResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result string `json:"result"`
}

type Chain interface {
Name() string
Endpoint() string
Expand All @@ -74,6 +146,7 @@ type Chain interface {
EthSendTransaction(ctx context.Context, tx *types.Transaction) error
EthGetLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
DebugTraceCall(ctx context.Context, txArgs TransactionArgs) (TraceCallRaw, error)
}

// Note: The default secrets config is used everywhere
Expand Down
118 changes: 112 additions & 6 deletions opsimulator/opsimulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package opsimulator
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand All @@ -17,7 +18,12 @@ import (

ophttp "github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/supersim/config"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)

const (
Expand All @@ -43,6 +49,12 @@ type JSONRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
}

type SimulatedLog struct {
Address common.Address
Topics []common.Hash
Data []byte
}

func New(log log.Logger, port uint64, l1Chain config.Chain, l2Chain config.Chain) *OpSimulator {
return &OpSimulator{
log: log,
Expand All @@ -59,7 +71,7 @@ func (opSim *OpSimulator) Start(ctx context.Context) error {
}

mux := http.NewServeMux()
mux.Handle("/", handler(proxy))
mux.Handle("/", opSim.handler(proxy, ctx))

hs, err := ophttp.StartHTTPServer(net.JoinHostPort(host, fmt.Sprintf("%d", opSim.port)), mux)
if err != nil {
Expand Down Expand Up @@ -96,7 +108,7 @@ func (a *OpSimulator) Stopped() bool {
return a.stopped.Load()
}

func handler(proxy *httputil.ReverseProxy) http.HandlerFunc {
func (a *OpSimulator) handler(proxy *httputil.ReverseProxy, ctx context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
Expand All @@ -115,7 +127,24 @@ func handler(proxy *httputil.ReverseProxy) http.HandlerFunc {
// TODO(https://github.com/ethereum-optimism/supersim/issues/55): support batch txs

if req.Method == "eth_sendRawTransaction" {
checkInteropInvariants()
fmt.Println("Call to eth_sendRawTransaction")
param := req.Params[0]
paramStr, ok := param.(string)
if !ok {
fmt.Println("Bad param to eth_sendRawTransaction")
http.Error(w, "Bad param to eth_sendRawTransaction", http.StatusBadRequest)
return
}
result, err := a.checkInteropInvariants(ctx, paramStr)
if err != nil {
fmt.Println("error checking interop invariants", err)
http.Error(w, "checking interop invariants failed", http.StatusInternalServerError)
return
}
if !result {
http.Error(w, "interop invriants not met", http.StatusBadRequest)
return
}
}

r.Body = io.NopCloser(bytes.NewReader(body))
Expand All @@ -124,9 +153,86 @@ func handler(proxy *httputil.ReverseProxy) http.HandlerFunc {
}
}

// TODO(https://github.com/ethereum-optimism/supersim/issues/19): add logic for checking that an interop transaction is valid.
func checkInteropInvariants() bool {
return true
func (a *OpSimulator) checkInteropInvariants(ctx context.Context, rawTx string) (bool, error) {
fmt.Println("Checking interop invariants")
tx, rawTxErr := a.decodeRawTransaction(rawTx)
if rawTxErr != nil {
fmt.Println("failed decodeRawTransaction", rawTxErr)
return false, fmt.Errorf("failed to check interop invariants: %v", rawTxErr)
}

from, fromAddErr := getFromAddress(tx)

if fromAddErr != nil {
fmt.Println("failed getFromAddress", fromAddErr)
return false, fmt.Errorf("failed to check interop invariants: %v", fromAddErr)
}

result, traceErr := a.l2Chain.DebugTraceCall(ctx, config.TransactionArgs{From: from, To: tx.To(), Gas: hexutil.Uint64(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), Data: tx.Data(), Value: (*hexutil.Big)(tx.Value())})
if traceErr != nil {
fmt.Println("traceErr", traceErr)
return false, fmt.Errorf("failed to simulate transaction: %v", traceErr)
}
simulatedLogs := ToSimulatedLogs(result)
fmt.Println("trace result:", result)
fmt.Println("simulated logs", simulatedLogs)

return true, nil
}

func ToSimulatedLogs(t config.TraceCallRaw) []SimulatedLog {
return toSimulatedLogs(t)
}

func toSimulatedLogs(call config.TraceCallRaw) []SimulatedLog {
var logs []SimulatedLog

// Recursively append the logs for each nested call
for _, call := range call.Calls {
logs = append(logs, toSimulatedLogs(call)...)
}

for _, log := range call.Logs {
fmt.Println("Log Address", log.Address)
fmt.Println("Log Topics", log.Topics)
fmt.Println("Data", common.FromHex(log.Data))
logs = append(logs, SimulatedLog{
Address: log.Address,
Topics: log.Topics,
Data: common.FromHex(log.Data),
})
}

return logs
}

func isExecutingMessage(l *ethTypes.Log) {

}

func (a *OpSimulator) decodeRawTransaction(rawTx string) (*ethTypes.Transaction, error) {
rawTxBytes, err := hex.DecodeString(rawTx[2:])
if err != nil {
return nil, fmt.Errorf("failed to decode raw transaction: %v", err)
}

var ethTx ethTypes.Transaction
if err := ethTx.UnmarshalBinary(rawTxBytes); err != nil {
// If UnmarshalBinary failed, we will try RLP in case user made mistake
e := rlp.DecodeBytes(rawTxBytes, &ethTx)
if e != nil {
return nil, fmt.Errorf("could not decode Ethereum transaction: %v", err)
}
fmt.Println("Ethereum transaction was in RLP format instead of binary," +
" transaction has been processed anyway, but it'd be best to use the Ethereum binary standard encoding")
}
return &ethTx, nil
}

func getFromAddress(tx *ethTypes.Transaction) (common.Address, error) {
from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx)

return from, err
}

func (opSim *OpSimulator) createReverseProxy() (*httputil.ReverseProxy, error) {
Expand Down

0 comments on commit 483de23

Please sign in to comment.