Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/espresso-devnet-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
tests: "TestSmokeWithoutTEE|TestBatcherRestart"
tee: false
- group: 2
tests: "TestWithdrawal"
tests: "TestWithdrawal|TestBatcherSwitching"
tee: false
- group: 3
tests: "TestSmokeWithTEE|TestForcedTransaction"
Expand Down
1 change: 1 addition & 0 deletions README_ESPRESSO.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,4 @@ We are working on a set of scripts to handle the migration from a Celo Testnet t
Some relevant documents:
* [Documentation of configuration parameters](docs/README_ESPRESSO_DEPLOY_CONFIG.md)
* [Celo Testnet Migration Guide](docs/CELO_TESTNET_MIGRATION.md) (WIP)

87 changes: 87 additions & 0 deletions espresso/devnet-tests/batcher_switching_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package devnet_tests

import (
"context"
"testing"

"github.com/ethereum-optimism/optimism/op-batcher/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"
)

// TestBatcherSwitching tests that the batcher can be switched from the TEE-enabled
// batcher to a fallback non-TEE batcher using the BatchAuthenticator contract.
//
// This is the devnet equivalent of TestBatcherSwitching from the E2E tests.
// The test runs two batchers in parallel:
// - op-batcher: The primary batcher with Espresso enabled (initially active)
// - op-batcher-fallback: The fallback batcher without Espresso (initially stopped)
func TestBatcherSwitching(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Initialize devnet with NON_TEE profile (starts both batchers)
d := NewDevnet(ctx, t)
require.NoError(t, d.Up(NON_TEE))
defer func() {
require.NoError(t, d.Down())
}()

// Send initial transaction to verify everything has started up ok
require.NoError(t, d.RunSimpleL2Burn())

// Get rollup config to access BatchAuthenticator address
config, err := d.RollupConfig(ctx)
require.NoError(t, err)

// Get L1 chain ID for transaction signing
l1ChainID, err := d.L1.ChainID(ctx)
require.NoError(t, err)

// Create transactor options using the deployer key (owner of BatchAuthenticator)
deployerOpts, err := bind.NewKeyedTransactorWithChainID(d.secrets.Deployer, l1ChainID)
require.NoError(t, err)

// Bind to BatchAuthenticator contract
batchAuthenticator, err := bindings.NewBatchAuthenticator(config.BatchAuthenticatorAddress, d.L1)
require.NoError(t, err)

// Check current active batcher state before switching
activeIsTee, err := batchAuthenticator.ActiveIsTee(&bind.CallOpts{})
require.NoError(t, err)
t.Logf("Before switch: activeIsTee = %v", activeIsTee)

// Stop the primary "TEE" batcher (op-batcher with Espresso enabled)
require.NoError(t, d.StopBatcherSubmitting("op-batcher"))
t.Logf("Stopped op-batcher batch submission")

// Switch active batcher via BatchAuthenticator contract
tx, err := batchAuthenticator.SwitchBatcher(deployerOpts)
require.NoError(t, err)
t.Logf("Submitted switchBatcher transaction: %s", tx.Hash().Hex())

// Wait for transaction receipt
receipt, err := wait.ForReceiptOK(ctx, d.L1, tx.Hash())
require.NoError(t, err)
t.Logf("SwitchBatcher transaction confirmed in block %d", receipt.BlockNumber.Uint64())

// Verify the switch happened
activeIsTeeAfter, err := batchAuthenticator.ActiveIsTee(&bind.CallOpts{})
require.NoError(t, err)
require.NotEqual(t, activeIsTee, activeIsTeeAfter, "activeIsTee should have toggled")
t.Logf("After switch: activeIsTee = %v", activeIsTeeAfter)

// Start the fallback batcher
require.NoError(t, d.StartBatcherSubmitting("op-batcher-fallback"))
t.Logf("Started op-batcher-fallback batch submission")

// Verify everything still works with the fallback batcher
require.NoError(t, d.RunSimpleL2Burn())
t.Logf("Transaction verified with fallback batcher")

// Submit another transaction and verify system continues to work
d.SleepRecoveryDuration()
require.NoError(t, d.RunSimpleL2Burn())
t.Logf("System continues to work after batcher switch")
}
30 changes: 30 additions & 0 deletions espresso/devnet-tests/devnet_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,36 @@ func (d *Devnet) ServiceRestart(service string) error {
return nil
}

// callBatcherRPC calls a batcher RPC method on a running batcher service
func (d *Devnet) callBatcherRPC(service, method string) error {
cmd := exec.CommandContext(
d.ctx,
"docker", "compose", "exec", "-T", service,
"sh", "-c",
fmt.Sprintf("wget -q -O- --header='Content-Type: application/json' --post-data='{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":[],\"id\":1}' http://localhost:8545", method),
)
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to call %s (%w): %s", method, err, buf.String())
}
log.Info("RPC call successful", "service", service, "method", method, "response", buf.String())
return nil
}

// StartBatcherSubmitting starts batch submission on a running batcher service
func (d *Devnet) StartBatcherSubmitting(service string) error {
log.Info("starting batch submission", "service", service)
return d.callBatcherRPC(service, "admin_startBatcher")
}

// StopBatcherSubmitting stops batch submission on a running batcher service
func (d *Devnet) StopBatcherSubmitting(service string) error {
log.Info("stopping batch submission", "service", service)
return d.callBatcherRPC(service, "admin_stopBatcher")
}

func (d *Devnet) RollupConfig(ctx context.Context) (*rollup.Config, error) {
return d.L2SeqRollup.RollupConfig(ctx)
}
Expand Down
39 changes: 39 additions & 0 deletions espresso/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,45 @@ services:
- --altda.put-timeout=30s
- --altda.get-timeout=30s
- --data-availability-type=calldata
- --rpc.enable-admin

op-batcher-fallback:
profiles: ["default"]
build:
context: ../
dockerfile: espresso/docker/op-stack/Dockerfile
target: op-batcher-target
image: op-batcher:espresso
depends_on:
l1-geth:
condition: service_healthy
op-geth-sequencer:
condition: service_started
op-node-sequencer:
condition: service_started
l2-genesis:
condition: service_completed_successfully
environment:
L1_RPC: http://l1-geth:${L1_HTTP_PORT}
OP_BATCHER_L1_ETH_RPC: http://l1-geth:${L1_HTTP_PORT}
OP_BATCHER_L2_ETH_RPC: http://op-geth-sequencer:${OP_HTTP_PORT}
OP_BATCHER_ROLLUP_RPC: http://op-node-sequencer:${ROLLUP_PORT}
OP_BATCHER_MAX_CHANNEL_DURATION: ${MAX_CHANNEL_DURATION:-32}
OP_BATCHER_MAX_PENDING_TX: ${MAX_PENDING_TX:-32}
OP_BATCHER_STOPPED: "true"
volumes:
- ../packages/contracts-bedrock/lib/superchain-registry/ops/testdata/monorepo:/config
command:
- op-batcher
- --espresso.enabled=false
- --private-key=7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
- --stopped=true
- --throttle-threshold=0
- --max-channel-duration=2
- --target-num-frames=1
- --max-pending-tx=32
- --data-availability-type=calldata
- --rpc.enable-admin

op-batcher-tee:
profiles: ["tee"]
Expand Down
9 changes: 6 additions & 3 deletions espresso/scripts/prepare-allocs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,12 @@ op-deployer init --l1-chain-id "${L1_CHAIN_ID}" \

dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].espressoEnabled -t bool -v true

# Configure Espresso batchers for devnet. We reuse the operator address for both
# the non-TEE and TEE batchers to ensure they are non-zero and consistent.
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].nonTeeBatcher -v "${OPERATOR_ADDRESS}"
# Configure Espresso batchers for devnet. We reuse the operator address for the
# TEE batcher, but use a separate address for the non-TEE fallback batcher.
# We use Anvil test account #3 for the fallback batcher (already prefunded by Anvil):
# Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
# Address: 0x90F79bf6EB2c4f870365E785982E1f101E93b906
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].nonTeeBatcher -v "0x90F79bf6EB2c4f870365E785982E1f101E93b906"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].teeBatcher -v "${OPERATOR_ADDRESS}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .l1ContractsLocator -v "${ARTIFACTS_DIR}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .l2ContractsLocator -v "${ARTIFACTS_DIR}"
Expand Down
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ devnet-forced-transaction-test: build-devnet
devnet-withdraw-test: build-devnet
U_ID={{uid}} GID={{gid}} go test -timeout 30m -p 1 -count 1 -v -run TestWithdrawal ./espresso/devnet-tests/...

devnet-batcher-switching-test: build-devnet
U_ID={{uid}} GID={{gid}} go test -timeout 30m -p 1 -count 1 -v -run TestBatcherSwitching ./espresso/devnet-tests/...

build-devnet: compile-contracts
rm -Rf espresso/deployment
(cd op-deployer && just)
Expand Down
Loading