-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dd7f0fd
commit 6cc317d
Showing
2 changed files
with
366 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
op_service "github.com/ethereum-optimism/optimism/op-service" | ||
"github.com/ethereum-optimism/optimism/op-service/cliapp" | ||
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" | ||
oplog "github.com/ethereum-optimism/optimism/op-service/log" | ||
"github.com/ethereum-optimism/optimism/op-service/retry" | ||
"github.com/ethereum-optimism/optimism/op-service/sources" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/consensus" | ||
"github.com/ethereum/go-ethereum/consensus/beacon" | ||
"github.com/ethereum/go-ethereum/consensus/misc" | ||
"github.com/ethereum/go-ethereum/core" | ||
gstate "github.com/ethereum/go-ethereum/core/state" | ||
"github.com/ethereum/go-ethereum/core/stateless" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
logger2 "github.com/ethereum/go-ethereum/eth/tracers/logger" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/ethereum/go-ethereum/ethdb/remotedb" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/params" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
"github.com/ethereum/go-ethereum/triedb" | ||
"github.com/urfave/cli/v2" | ||
"io" | ||
"math/big" | ||
"os" | ||
"time" | ||
) | ||
|
||
var EnvPrefix = "OP_RUN_BLOCK" | ||
|
||
var ( | ||
RPCFlag = &cli.StringFlag{ | ||
Name: "rpc", | ||
Usage: "RPC endpoint to fetch data from", | ||
EnvVars: op_service.PrefixEnvVar(EnvPrefix, "RPC"), | ||
Required: true, | ||
} | ||
BlockPathFlag = &cli.PathFlag{ | ||
Name: "block", | ||
Usage: "Path to local block", | ||
EnvVars: op_service.PrefixEnvVar(EnvPrefix, "BLOCK"), | ||
Required: true, | ||
} | ||
) | ||
|
||
func main() { | ||
flags := []cli.Flag{ | ||
RPCFlag, BlockPathFlag, | ||
} | ||
flags = append(flags, oplog.CLIFlags(EnvPrefix)...) | ||
|
||
app := cli.NewApp() | ||
app.Name = "op-run-block" | ||
app.Usage = "Simulate a block locally." | ||
app.Description = "Take a block JSON and simulate it locally." | ||
app.Flags = cliapp.ProtectFlags(flags) | ||
app.Action = mainAction | ||
app.Writer = os.Stdout | ||
app.ErrWriter = os.Stderr | ||
err := app.Run(os.Args) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "Application failed: %v", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func mainAction(c *cli.Context) error { | ||
ctx := ctxinterrupt.WithCancelOnInterrupt(c.Context) | ||
logCfg := oplog.ReadCLIConfig(c) | ||
logger := oplog.NewLogger(c.App.Writer, logCfg) | ||
|
||
rpcEndpoint := c.String(RPCFlag.Name) | ||
blockPath := c.String(BlockPathFlag.Name) | ||
|
||
cl, err := rpc.DialContext(ctx, rpcEndpoint) | ||
if err != nil { | ||
return fmt.Errorf("failed to dial RPC: %w", err) | ||
} | ||
defer cl.Close() | ||
|
||
ethCl := ethclient.NewClient(cl) | ||
|
||
db := remotedb.New(cl) | ||
|
||
var config *params.ChainConfig | ||
if err := cl.CallContext(ctx, &config, "debug_chainConfig"); err != nil { | ||
return fmt.Errorf("failed to fetch chain config: %w", err) | ||
} | ||
|
||
block, err := loadBlock(blockPath) | ||
if err != nil { | ||
return fmt.Errorf("failed to load block: %w", err) | ||
} | ||
if err := block.Verify(); err != nil { | ||
return fmt.Errorf("block content is invalid: %w", err) | ||
} | ||
logger.Info("Loaded block", | ||
"hash", block.Hash, "number", uint64(block.Number), "txs", len(block.Transactions)) | ||
|
||
parentBlock, err := ethCl.HeaderByHash(ctx, block.ParentHash) | ||
if err != nil { | ||
return fmt.Errorf("failed to fetch parent block: %w", err) | ||
} | ||
|
||
stateDB := gstate.NewDatabase(triedb.NewDatabase(db, &triedb.Config{ | ||
Preimages: true, | ||
}), nil) | ||
state, err := gstate.New(parentBlock.Root, stateDB) | ||
if err != nil { | ||
return fmt.Errorf("failed to create in-memory state: %w", err) | ||
} | ||
|
||
header := block.RPCHeader.CreateGethHeader() | ||
|
||
vmCfg := vm.Config{Tracer: nil} | ||
consensusEng := beacon.New(&beacon.OpLegacy{}) | ||
chCtx := &remoteChainCtx{ | ||
consensusEng: consensusEng, | ||
hdr: header, | ||
cfg: config, | ||
cl: ethCl, | ||
logger: logger, | ||
} | ||
|
||
outW, err := os.OpenFile("tx_17_dump.json", os.O_CREATE|os.O_WRONLY, 0755) | ||
if err != nil { | ||
return fmt.Errorf("failed to create/open dump file: %w", err) | ||
} | ||
defer outW.Close() | ||
|
||
vmCfg.Tracer = logger2.NewJSONLogger(&logger2.Config{ | ||
EnableMemory: false, | ||
DisableStack: false, | ||
DisableStorage: false, | ||
EnableReturnData: false, | ||
Debug: false, | ||
Limit: 0, | ||
Overrides: nil, | ||
}, outW) | ||
|
||
witness, err := stateless.NewWitness(header, chCtx) | ||
if err != nil { | ||
return fmt.Errorf("failed to prepare witness data collector: %w", err) | ||
} | ||
state.StartPrefetcher("debug", witness) | ||
defer func() { | ||
witnessDump := witness.ToExecutionWitness() | ||
out, err := json.MarshalIndent(witnessDump, "", " ") | ||
if err != nil { | ||
logger.Error("failed to encode witness", "err", err) | ||
return | ||
} | ||
if err := os.WriteFile("debug_witness.json", out, 0755); err != nil { | ||
logger.Error("Failed to write witness", "err", err) | ||
} | ||
}() | ||
logger.Info("Starting block processing") | ||
result, err := Process(logger, config, block, state, vmCfg, chCtx, outW) | ||
if err != nil { | ||
return fmt.Errorf("failed to process: %w", err) | ||
} | ||
logger.Info("Done", "gas_used", result.GasUsed) | ||
return nil | ||
} | ||
|
||
type WrappedDump struct { | ||
Block *sources.RPCBlock `json:"block"` | ||
} | ||
|
||
func loadBlock(blockPath string) (*sources.RPCBlock, error) { | ||
data, err := os.ReadFile(blockPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var out []WrappedDump | ||
if err := json.Unmarshal(data, &out); err != nil { | ||
return nil, err | ||
} | ||
if len(out) != 1 { | ||
return nil, fmt.Errorf("expected single block entry, got %d", len(out)) | ||
} | ||
blockData := out[0].Block | ||
return blockData, nil | ||
} | ||
|
||
type remoteChainCtx struct { | ||
consensusEng consensus.Engine | ||
hdr *types.Header | ||
cfg *params.ChainConfig | ||
cl *ethclient.Client | ||
logger log.Logger | ||
} | ||
|
||
var _ core.ChainContext = (*remoteChainCtx)(nil) | ||
var _ consensus.ChainHeaderReader = (*remoteChainCtx)(nil) | ||
|
||
func (r *remoteChainCtx) Config() *params.ChainConfig { | ||
return r.cfg | ||
} | ||
|
||
func (r remoteChainCtx) CurrentHeader() *types.Header { | ||
return r.hdr | ||
} | ||
|
||
func (r remoteChainCtx) GetHeaderByNumber(u uint64) *types.Header { | ||
if r.hdr.Number.Uint64() == u { | ||
return r.hdr | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) | ||
defer cancel() | ||
hdr, err := retry.Do[*types.Header](ctx, 10, retry.Exponential(), func() (*types.Header, error) { | ||
r.logger.Info("fetching block header", "num", u) | ||
return r.cl.HeaderByNumber(ctx, new(big.Int).SetUint64(u)) | ||
}) | ||
if err != nil { | ||
r.logger.Error("failed to get block header", "err", err, "num", u) | ||
return nil | ||
} | ||
if hdr == nil { | ||
r.logger.Warn("header not found", "num", u) | ||
} | ||
return hdr | ||
} | ||
|
||
func (r remoteChainCtx) GetHeaderByHash(hash common.Hash) *types.Header { | ||
if r.hdr.Hash() == hash { | ||
return r.hdr | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) | ||
defer cancel() | ||
hdr, err := retry.Do[*types.Header](ctx, 10, retry.Exponential(), func() (*types.Header, error) { | ||
r.logger.Info("fetching block header", "hash", hash) | ||
return r.cl.HeaderByHash(ctx, hash) | ||
}) | ||
if err != nil { | ||
r.logger.Error("failed to get block header", "err", err, "hash", hash) | ||
return nil | ||
} | ||
if hdr == nil { | ||
r.logger.Warn("header not found", "hash", hash) | ||
} | ||
return hdr | ||
} | ||
|
||
func (r remoteChainCtx) GetTd(hash common.Hash, number uint64) *big.Int { | ||
return big.NewInt(1) | ||
} | ||
|
||
func (r remoteChainCtx) Engine() consensus.Engine { | ||
return r.consensusEng | ||
} | ||
|
||
func (r remoteChainCtx) GetHeader(hash common.Hash, u uint64) *types.Header { | ||
if r.hdr.Hash() == hash { | ||
return r.hdr | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) | ||
defer cancel() | ||
hdr, err := retry.Do[*types.Header](ctx, 10, retry.Exponential(), func() (*types.Header, error) { | ||
r.logger.Info("fetching block header", "hash", hash, "num", u) | ||
return r.cl.HeaderByNumber(ctx, new(big.Int).SetUint64(u)) | ||
}) | ||
if err != nil { | ||
r.logger.Error("failed to get block header", "err", err, "hash", hash, "num", u) | ||
return nil | ||
} | ||
if hdr == nil { | ||
r.logger.Warn("header not found", "hash", hash, "num", u) | ||
} | ||
if got := hdr.Hash(); got != hash { | ||
r.logger.Error("fetched incompatible header", "expectedHash", hash, "fetchedHash", got, "num", u) | ||
} | ||
return hdr | ||
} | ||
|
||
func Process(logger log.Logger, config *params.ChainConfig, | ||
block *sources.RPCBlock, | ||
statedb *gstate.StateDB, cfg vm.Config, | ||
chainCtx *remoteChainCtx, outW io.Writer) (*core.ProcessResult, error) { | ||
var ( | ||
receipts types.Receipts | ||
usedGas = new(uint64) | ||
header = block.CreateGethHeader() | ||
blockHash = block.Hash | ||
blockNumber = new(big.Int).SetUint64(uint64(block.Number)) | ||
allLogs []*types.Log | ||
gp = new(core.GasPool).AddGas(uint64(block.GasLimit)) | ||
) | ||
|
||
// Mutate the block and state according to any hard-fork specs | ||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(blockNumber) == 0 { | ||
misc.ApplyDAOHardFork(statedb) | ||
} | ||
misc.EnsureCreate2Deployer(config, uint64(block.Time), statedb) | ||
var ( | ||
context vm.BlockContext | ||
signer = types.MakeSigner(config, header.Number, header.Time) | ||
err error | ||
) | ||
context = core.NewEVMBlockContext(header, chainCtx, nil, config, statedb) | ||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, config, cfg) | ||
if beaconRoot := block.ParentBeaconRoot; beaconRoot != nil { | ||
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) | ||
} | ||
if config.IsPrague(blockNumber, uint64(block.Time)) { | ||
core.ProcessParentBlockHash(block.ParentHash, vmenv, statedb) | ||
} | ||
logger.Info("Prepared EVM state") | ||
_, _ = fmt.Fprintf(outW, "# Prepared state\n") | ||
|
||
// Iterate over and process the individual transactions | ||
for i, tx := range block.Transactions { | ||
logger.Info("Processing tx", "i", i, "hash", tx.Hash()) | ||
_, _ = fmt.Fprintf(outW, "# Processing tx %d\n", i) | ||
msg, err := core.TransactionToMessage(tx, signer, header.BaseFee) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) | ||
} | ||
statedb.SetTxContext(tx.Hash(), i) | ||
|
||
receipt, err := core.ApplyTransactionWithEVM(msg, config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) | ||
} | ||
receipts = append(receipts, receipt) | ||
allLogs = append(allLogs, receipt.Logs...) | ||
} | ||
logger.Info("Done with transactions") | ||
_, _ = fmt.Fprintf(outW, "# Done with transactions\n") | ||
|
||
// Read requests if Prague is enabled. | ||
var requests types.Requests | ||
if config.IsPrague(new(big.Int).SetUint64(uint64(block.Number)), uint64(block.Time)) { | ||
requests, err = core.ParseDepositLogs(allLogs, config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
engine := chainCtx.Engine() | ||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) | ||
engine.Finalize(chainCtx, header, statedb, | ||
&types.Body{Transactions: block.Transactions, Withdrawals: *block.Withdrawals}) | ||
logger.Info("Completed block processing") | ||
_, _ = fmt.Fprintf(outW, "# Completed block processing\n") | ||
|
||
return &core.ProcessResult{ | ||
Receipts: receipts, | ||
Requests: requests, | ||
Logs: allLogs, | ||
GasUsed: *usedGas, | ||
}, nil | ||
} |
Oops, something went wrong.