diff --git a/go.mod b/go.mod index ee4ce36..868092a 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.22.6 require ( github.com/aws/aws-sdk-go v1.55.5 + github.com/base-org/op-enclave/op-enclave v0.0.0 github.com/ethereum-optimism/optimism v1.9.3 github.com/ethereum/go-ethereum v1.14.11 github.com/hashicorp/go-multierror v1.1.1 - github.com/base-org/op-enclave/op-enclave v0.0.0 github.com/prometheus/client_golang v1.20.4 github.com/urfave/cli/v2 v2.27.4 ) diff --git a/op-proposer/flags/flags.go b/op-proposer/flags/flags.go index d266e43..c928a50 100644 --- a/op-proposer/flags/flags.go +++ b/op-proposer/flags/flags.go @@ -17,6 +17,13 @@ var ( EnvVars: prefixEnvVar("L2_ETH_RPC"), Required: true, } + L2RethFlag = &cli.BoolFlag{ + Name: "l2-reth", + Usage: "Is the L2 HTTP provider running Reth?", + EnvVars: prefixEnvVar("L2_RETH"), + Value: false, + Required: false, + } EnclaveRpcFlag = &cli.StringFlag{ Name: "enclave-rpc", Usage: "HTTP provider URL for the enclave service", @@ -33,6 +40,7 @@ var ( var requiredFlags = []cli.Flag{ L2EthRpcFlag, + L2RethFlag, EnclaveRpcFlag, MinProposalIntervalFlag, } diff --git a/op-proposer/proposer/clients.go b/op-proposer/proposer/clients.go index e70bbf8..494d499 100644 --- a/op-proposer/proposer/clients.go +++ b/op-proposer/proposer/clients.go @@ -55,6 +55,10 @@ type ethClient struct { } func NewClient(client *ethclient.Client, metrics caching.Metrics) Client { + return newClient(client, metrics) +} + +func newClient(client *ethclient.Client, metrics caching.Metrics) *ethClient { cacheSize := 1000 return ðClient{ client: client, diff --git a/op-proposer/proposer/config.go b/op-proposer/proposer/config.go index bbe31bc..284ab72 100644 --- a/op-proposer/proposer/config.go +++ b/op-proposer/proposer/config.go @@ -9,6 +9,7 @@ import ( type CLIConfig struct { *proposer.CLIConfig L2EthRpc string + L2Reth bool EnclaveRpc string MinProposalInterval uint64 } @@ -17,6 +18,7 @@ func NewConfig(ctx *cli.Context) *CLIConfig { return &CLIConfig{ CLIConfig: proposer.NewConfig(ctx), L2EthRpc: ctx.String(flags.L2EthRpcFlag.Name), + L2Reth: ctx.Bool(flags.L2RethFlag.Name), EnclaveRpc: ctx.String(flags.EnclaveRpcFlag.Name), MinProposalInterval: ctx.Uint64(flags.MinProposalIntervalFlag.Name), } diff --git a/op-proposer/proposer/reth_client.go b/op-proposer/proposer/reth_client.go new file mode 100644 index 0000000..0dc8368 --- /dev/null +++ b/op-proposer/proposer/reth_client.go @@ -0,0 +1,73 @@ +package proposer + +import ( + "bytes" + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/sources/caching" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/ethclient" +) + +// see https://github.com/alloy-rs/alloy/blob/main/crates/rpc-types-debug/src/debug.rs +type RethExecutionWitness struct { + State map[common.Hash]hexutil.Bytes + Codes map[common.Hash]hexutil.Bytes + Keys map[common.Hash]hexutil.Bytes +} + +type rethClient struct { + ethClient +} + +func NewRethClient(client *ethclient.Client, metrics caching.Metrics) Client { + ethClient := newClient(client, metrics) + return &rethClient{ + ethClient: *ethClient, + } +} + +func (e *rethClient) ExecutionWitness(ctx context.Context, hash common.Hash) ([]byte, error) { + header, err := e.HeaderByHash(ctx, hash) + if err != nil { + return nil, err + } + + var witness RethExecutionWitness + // TODO it would be nice if reth accepted a tx hash parameter here + if err := e.client.Client().CallContext(ctx, &witness, "debug_executionWitness", "0x"+header.Number.Text(16)); err != nil { + return nil, err + } + + w := &stateless.Witness{ + Codes: make(map[string]struct{}), + State: make(map[string]struct{}), + } + for _, code := range witness.Codes { + w.Codes[string(code)] = struct{}{} + } + for _, state := range witness.State { + w.State[string(state)] = struct{}{} + } + + // reth doesn't return required headers (for BLOCKHASH), so eagerly populate them all: + parentHash := header.ParentHash + history := int(min(256, header.Number.Uint64())) + for i := 0; i < history; i++ { + parent, err := e.HeaderByHash(ctx, parentHash) + if err != nil { + return nil, err + } + w.Headers = append(w.Headers, parent) + parentHash = parent.ParentHash + } + + var buf bytes.Buffer + if err = w.EncodeRLP(&buf); err != nil { + return nil, fmt.Errorf("failed to encode witness: %w", err) + } + return buf.Bytes(), nil +} diff --git a/op-proposer/proposer/service.go b/op-proposer/proposer/service.go index b041403..6ca6415 100644 --- a/op-proposer/proposer/service.go +++ b/op-proposer/proposer/service.go @@ -57,6 +57,7 @@ type ProposerService struct { TxManager txmgr.TxManager L1Client *ethclient.Client L2Client *ethclient.Client + L2Reth bool RollupClient *gethrpc.Client EnclaveClient *gethrpc.Client @@ -135,6 +136,7 @@ func (ps *ProposerService) initRPCClients(ctx context.Context, cfg *CLIConfig) e return fmt.Errorf("failed to dial L2 RPC: %w", err) } ps.L2Client = l2Client + ps.L2Reth = cfg.L2Reth rollupClient, err := dial.DialRPCClientWithTimeout(ctx, dial.DefaultDialTimeout, ps.Log, cfg.RollupRpc) if err != nil { @@ -214,13 +216,19 @@ func (ps *ProposerService) initL2ooAddress(cfg *CLIConfig) { } func (ps *ProposerService) initDriver() error { + var l2Client L2Client + if ps.L2Reth { + l2Client = NewRethClient(ps.L2Client, ps.Metrics.L2Cache) + } else { + l2Client = NewClient(ps.L2Client, ps.Metrics.L2Cache) + } driver, err := NewL2OutputSubmitter(DriverSetup{ Log: ps.Log, Metr: ps.Metrics, Cfg: ps.ProposerConfig, Txmgr: ps.TxManager, L1Client: NewClient(ps.L1Client, ps.Metrics.L1Cache), - L2Client: NewClient(ps.L2Client, ps.Metrics.L2Cache), + L2Client: l2Client, RollupClient: NewRollupClient(ps.RollupClient, ps.Metrics.WitnessCache), EnclaveClient: &enclave.Client{Client: ps.EnclaveClient}, }) diff --git a/testnet/.env.example b/testnet/.env.example index fa9ab04..5eeb7fb 100644 --- a/testnet/.env.example +++ b/testnet/.env.example @@ -1,5 +1,5 @@ # per deploy -OP_GETH_GENESIS_FILE_PATH=./deployments/84532--genesis.json +GENESIS_FILE_PATH=./deployments/84532--genesis.json OP_NODE_ROLLUP_CONFIG=./deployments/84532--rollup-config.json DEPLOYED_JSON=./deployments/84532--deployed.json @@ -53,6 +53,7 @@ OP_BATCHER_TXMGR_RECEIPT_QUERY_INTERVAL=1s # op-proposer OP_PROPOSER_L2_ETH_RPC=http://op-geth:8545 +OP_PROPOSER_L2_RETH=false OP_PROPOSER_ROLLUP_RPC=http://op-node:8545 OP_PROPOSER_ENCLAVE_RPC=http://op-enclave:1234 OP_PROPOSER_ALLOW_NON_FINALIZED=true diff --git a/testnet/Dockerfile b/testnet/Dockerfile index 7dada4b..55fa5df 100644 --- a/testnet/Dockerfile +++ b/testnet/Dockerfile @@ -49,6 +49,25 @@ RUN git clone $REPO --branch $VERSION --single-branch . && \ RUN go run build/ci.go install -static ./cmd/geth +FROM ubuntu:22.04 AS op-reth + +WORKDIR /app + +RUN apt-get update && apt-get -y upgrade && apt-get install -y git libclang-dev pkg-config curl build-essential +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="/root/.cargo/bin:${PATH}" + +ENV REPO=https://github.com/paradigmxyz/reth.git +ENV VERSION=v1.1.0 +ENV COMMIT=1ba631ba9581973e7c6cadeea92cfe1802aceb4a +RUN git clone $REPO --branch $VERSION --single-branch . && \ + git switch -c branch-$VERSION && \ + bash -c '[ "$(git rev-parse HEAD)" = "$COMMIT" ]' + +RUN cargo build --bin op-reth --features jemalloc,asm-keccak,optimism --manifest-path crates/optimism/bin/Cargo.toml + + FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl jq @@ -57,6 +76,7 @@ WORKDIR /app COPY --from=op-node /app/op-node/bin/op-node ./ COPY --from=op-geth /app/build/bin/geth ./ +COPY --from=op-reth /app/target/debug/op-reth ./reth COPY --from=op-enclave /app/bin/op-enclave ./ COPY --from=op-enclave /app/bin/op-batcher ./ COPY --from=op-enclave /app/bin/op-proposer ./ @@ -67,4 +87,5 @@ COPY testnet/entrypoint-enclave.sh ./ COPY testnet/entrypoint-geth.sh ./ COPY testnet/entrypoint-node.sh ./ COPY testnet/entrypoint-proposer.sh ./ +COPY testnet/entrypoint-reth.sh ./ COPY deployments/ deployments/ diff --git a/testnet/docker-compose.yml b/testnet/docker-compose.yml index 196d892..2acb932 100644 --- a/testnet/docker-compose.yml +++ b/testnet/docker-compose.yml @@ -10,6 +10,17 @@ services: - ./data/geth:/data env_file: - .env +# op-reth: +# build: +# context: .. +# dockerfile: testnet/Dockerfile +# ports: +# - "8545:8545" +# command: [ "bash", "./entrypoint-reth.sh" ] +# volumes: +# - ./data/reth:/data +# env_file: +# - .env op-node: build: context: .. diff --git a/testnet/entrypoint-geth.sh b/testnet/entrypoint-geth.sh index e0c773a..8508b74 100755 --- a/testnet/entrypoint-geth.sh +++ b/testnet/entrypoint-geth.sh @@ -3,14 +3,14 @@ GETH_DATA_DIR=${GETH_DATA_DIR:-/data} GETH_CHAINDATA_DIR="$GETH_DATA_DIR/geth/chaindata" -mkdir -p $GETH_DATA_DIR +mkdir -p "$GETH_DATA_DIR" if [ ! -d "$GETH_CHAINDATA_DIR" ]; then echo "$GETH_CHAINDATA_DIR missing, running init" echo "Initializing genesis." ./geth init \ --datadir="$GETH_DATA_DIR" \ --state.scheme=hash \ - "$OP_GETH_GENESIS_FILE_PATH" + "$GENESIS_FILE_PATH" else echo "$GETH_CHAINDATA_DIR exists." fi @@ -18,7 +18,7 @@ fi echo "$L2_ENGINE_JWT" > /tmp/engine.jwt exec ./geth \ - --datadir=/data \ + --datadir="$GETH_DATA_DIR" \ --http \ --http.corsdomain="*" \ --http.vhosts="*" \ diff --git a/testnet/entrypoint-reth.sh b/testnet/entrypoint-reth.sh new file mode 100755 index 0000000..2384c7d --- /dev/null +++ b/testnet/entrypoint-reth.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +RETH_DATA_DIR=${RETH_DATA_DIR:-/data} +echo "$L2_ENGINE_JWT" > /tmp/engine.jwt + +exec ./reth node \ + --datadir="$RETH_DATA_DIR" \ + --chain="$GENESIS_FILE_PATH" \ + --http \ + --http.corsdomain="*" \ + --http.addr=0.0.0.0 \ + --http.port=8545 \ + --http.api=web3,debug,eth,net \ + --authrpc.addr=0.0.0.0 \ + --authrpc.port=8551 \ + --authrpc.jwtsecret=/tmp/engine.jwt \ + --port=30303 \ + --rpc.eth-proof-window=10000