Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/espresso-devnet-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ jobs:
- name: Run Key Rotation test
run: go test -timeout 30m -p 1 -count 1 -run 'TestKeyRotation' -v ./espresso/devnet-tests/...

# - name: Run Change Batch Inbox Owner test
# run: go test -timeout 30m -p 1 -count 1 -run 'TestChangeBatchInboxOwner -v ./espresso/devnet-tests/...
- name: Run Change Batch Inbox Owner test
run: go test -timeout 30m -p 1 -count 1 -run 'TestChangeBatchInboxOwner' -v ./espresso/devnet-tests/...

- name: Save Nix cache
uses: nix-community/cache-nix-action/save@v6
Expand Down
6 changes: 6 additions & 0 deletions espresso/.env
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ OPERATOR_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf
# cast wallet address --private-key $OPERATOR_PRIVATE_KEY
OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

# BatchAuthenticator owner address for contract ownership (index 3 from mnemonic)
# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/3"
BATCH_AUTHENTICATOR_OWNER_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906
# cast wallet private-key --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/3"
BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6

# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/1"
PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8

Expand Down
14 changes: 14 additions & 0 deletions espresso/devnet-tests/devnet_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
Expand All @@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/joho/godotenv"

env "github.com/ethereum-optimism/optimism/espresso/environment"
"github.com/ethereum-optimism/optimism/op-e2e/config/secrets"
Expand All @@ -44,6 +46,18 @@ type Devnet struct {
L2VerifRollup *sources.RollupClient
}

// LoadEnvFile loads environment variables from a .env file
func LoadEnvFile(filename string) error {
return godotenv.Load(filename)
}

// LoadDevnetEnv loads the espresso/.env file for devnet tests
func LoadDevnetEnv() error {
// Get the path to the espresso/.env file relative to the test directory
envPath := filepath.Join("..", ".env")
return LoadEnvFile(envPath)
}

func NewDevnet(ctx context.Context, t *testing.T) *Devnet {

if testing.Short() {
Expand Down
43 changes: 39 additions & 4 deletions espresso/devnet-tests/key_rotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ package devnet_tests

import (
"context"
"os"
"strings"
"testing"

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

func TestRotateBatcherKey(t *testing.T) {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand Down Expand Up @@ -52,6 +57,10 @@ func TestRotateBatcherKey(t *testing.T) {
}

func TestChangeBatchInboxOwner(t *testing.T) {
// Load environment variables from .env file
err := LoadDevnetEnv()
require.NoError(t, err, "Failed to load .env file")

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand All @@ -68,18 +77,44 @@ func TestChangeBatchInboxOwner(t *testing.T) {
config, err := d.RollupConfig(ctx)
require.NoError(t, err)

// Change the BatchAuthenticator's owner
batchAuthenticator, err := bindings.NewBatchAuthenticator(config.BatchAuthenticatorAddress, d.L1)
require.NoError(t, err)
tx, err := batchAuthenticator.TransferOwnership(&bind.TransactOpts{}, d.secrets.Addresses().Bob)

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

// Check current owner first
currentOwner, err := batchAuthenticator.Owner(&bind.CallOpts{})
require.NoError(t, err)

// Check that the new owner is different from the current one
bobAddress := d.secrets.Addresses().Bob
require.NotEqual(t, currentOwner, bobAddress)

// Use batch authenticator owner key to sign the transaction
batchAuthenticatorPrivateKeyHex := os.Getenv("BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY")
require.NotEmpty(t, batchAuthenticatorPrivateKeyHex, "BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY must be set")
t.Logf("Using BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY from environment: %s...", batchAuthenticatorPrivateKeyHex[:10])

batchAuthenticatorKey, err := crypto.HexToECDSA(strings.TrimPrefix(batchAuthenticatorPrivateKeyHex, "0x"))
require.NoError(t, err)

batchAuthenticatorOwnerOpts, err := bind.NewKeyedTransactorWithChainID(batchAuthenticatorKey, l1ChainID)
require.NoError(t, err)

// Call TransferOwnership
tx, err := batchAuthenticator.TransferOwnership(batchAuthenticatorOwnerOpts, bobAddress)
require.NoError(t, err)

// Wait for transaction receipt and check if it succeeded
_, err = wait.ForReceiptOK(ctx, d.L1, tx.Hash())
require.NoError(t, err)

// Ensure the owner has been changed
newOwner, err := batchAuthenticator.Owner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, newOwner, d.secrets.Addresses().Bob)
require.Equal(t, newOwner, bobAddress)

// Check that everything still functions
require.NoError(t, d.RunSimpleL2Burn())
Expand Down
134 changes: 134 additions & 0 deletions espresso/environment/6_batch_inbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,89 @@ package environment_test

import (
"context"
"crypto/ecdsa"
"math/big"
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-batcher/bindings"
env "github.com/ethereum-optimism/optimism/espresso/environment"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/setuputils"
"github.com/ethereum-optimism/optimism/op-e2e/system/e2esys"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

// Test private key for PreApprovedBatcher (TEE batcher)
const preApprovedBatcherPrivateKey = "5fede428b9506dee864b0d85aefb2409f4728313eb41da4121409299c487f816"

func setupBatchInboxEnv(ctx context.Context, t *testing.T) (*e2esys.System, *bindings.BatchInbox, *big.Int) {
t.Helper()
launcher := new(env.EspressoDevNodeLauncherDocker)
system, _, err := launcher.StartE2eDevnet(ctx, t,
env.Config(func(cfg *e2esys.SystemConfig) {
cfg.DisableBatcher = true
}),
)
require.NoError(t, err)

l1 := system.NodeClient(e2esys.RoleL1)
chainID, err := l1.ChainID(ctx)
require.NoError(t, err)

inbox, err := bindings.NewBatchInbox(system.RollupConfig.BatchInboxAddress, l1)
require.NoError(t, err)

// Fund the PreApprovedBatcher account if needed
pk, _ := crypto.HexToECDSA(preApprovedBatcherPrivateKey)
addr := crypto.PubkeyToAddress(pk.PublicKey)
if balance, _ := l1.BalanceAt(ctx, addr, nil); balance.Sign() == 0 {
nonce, _ := l1.PendingNonceAt(ctx, crypto.PubkeyToAddress(system.Cfg.Secrets.Deployer.PublicKey))
gasPrice, _ := l1.SuggestGasPrice(ctx)
tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &addr,
Value: big.NewInt(1e18),
Gas: 21000,
GasPrice: gasPrice,
})
signedTx, _ := types.SignTx(tx, types.NewEIP155Signer(chainID), system.Cfg.Secrets.Deployer)
l1.SendTransaction(ctx, signedTx)
bind.WaitMined(ctx, l1, signedTx)
}

return system, inbox, chainID
}

func authForAddress(t *testing.T, system *e2esys.System, chainID *big.Int, addr common.Address) *bind.TransactOpts {
t.Helper()
for _, secret := range []*ecdsa.PrivateKey{
system.Cfg.Secrets.Deployer,
system.Cfg.Secrets.Batcher,
system.Cfg.Secrets.Bob,
} {
if crypto.PubkeyToAddress(secret.PublicKey) == addr {
auth, _ := bind.NewKeyedTransactorWithChainID(secret, chainID)
return auth
}
}
// Check PreApprovedBatcher
if pk, _ := crypto.HexToECDSA(preApprovedBatcherPrivateKey); crypto.PubkeyToAddress(pk.PublicKey) == addr {
auth, _ := bind.NewKeyedTransactorWithChainID(pk, chainID)
return auth
}
t.Fatalf("no auth available for address %s", addr)
return nil
}

// TestE2eDevnetWithoutAuthenticatingBatches verifies BatchInboxContract behaviour when batches
// aren't attested before being posted to batch inbox. To do this, we substitute BatchAuthenticatorAddress
// in batcher config with a zero address, which will never revert as it has no contract deployed.
Expand Down Expand Up @@ -108,6 +174,74 @@ func TestE2eDevnetWithoutAuthenticatingBatches(t *testing.T) {
require.Error(t, err)
}

func TestBatchInbox_SwitchActiveBatcher(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
system, inbox, chainID := setupBatchInboxEnv(ctx, t)
deployerAuth, _ := bind.NewKeyedTransactorWithChainID(system.Cfg.Secrets.Deployer, chainID)
tx, err := inbox.SwitchBatcher(deployerAuth)
require.NoError(t, err)
_, err = bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx)
require.NoError(t, err)
}

func TestBatchInbox_ActiveNonTeeBatcherAllowsPosting(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
system, inbox, chainID := setupBatchInboxEnv(ctx, t)
deployerAuth, _ := bind.NewKeyedTransactorWithChainID(system.Cfg.Secrets.Deployer, chainID)
tx, err := inbox.SwitchBatcher(deployerAuth)
require.NoError(t, err)
_, err = bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx)
require.NoError(t, err)
// Determine non-TEE batcher from contract and post with its key
nonTeeAddr, err := inbox.NonTeeBatcher(&bind.CallOpts{Context: ctx})
require.NoError(t, err)
nonTeeAuth := authForAddress(t, system, chainID, nonTeeAddr)
tx2, err := inbox.PostCalldata(nonTeeAuth, []byte("hello"))
require.NoError(t, err)
receipt, err := bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx2)
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
}

func TestBatchInbox_InactiveBatcherReverts(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
system, inbox, chainID := setupBatchInboxEnv(ctx, t)
deployerAuth, _ := bind.NewKeyedTransactorWithChainID(system.Cfg.Secrets.Deployer, chainID)
tx, err := inbox.SwitchBatcher(deployerAuth)
require.NoError(t, err)
_, err = bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx)
require.NoError(t, err)
teeAddr, err := inbox.TeeBatcher(&bind.CallOpts{Context: ctx})
require.NoError(t, err)
teeAuth := authForAddress(t, system, chainID, teeAddr)
teeAuth.GasLimit = 100000 // Bypass gas estimation
tx2, err := inbox.PostCalldata(teeAuth, []byte("unauth"))
require.NoError(t, err)
receipt, err := bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx2)
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusFailed, receipt.Status)
}

func TestBatchInbox_TEEBatcherRequiresAuthentication(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
system, inbox, chainID := setupBatchInboxEnv(ctx, t)
teeAddr, err := inbox.TeeBatcher(&bind.CallOpts{Context: ctx})
require.NoError(t, err)
teeAuth := authForAddress(t, system, chainID, teeAddr)
// Disable gas estimation to force sending a transaction that will revert
teeAuth.GasLimit = 100000
teeAuth.NoSend = false
tx, err := inbox.PostCalldata(teeAuth, []byte("needs-auth"))
require.NoError(t, err)
receipt, err := bind.WaitMined(ctx, system.NodeClient(e2esys.RoleL1), tx)
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusFailed, receipt.Status)
}

// A wrapper for testing that proxies all calls to ETHBackend unchanged,
// except EstimateGas and CallContract calls, which always "succeed"
// without making any actual RPC calls.
Expand Down
2 changes: 1 addition & 1 deletion espresso/scripts/prepare-allocs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].roles.proposer -v "${P
# contract addresses are deterministic.
dasel put -f "${DEPLOYER_DIR}/state.json" -s create2Salt -v "0xaecea4f57fadb2097ccd56594f2f22715ac52f92971c5913b70a7f1134b68feb"

op-deployer apply --l1-rpc-url "${ANVIL_URL}" \
BATCH_AUTHENTICATOR_OWNER_ADDRESS="${BATCH_AUTHENTICATOR_OWNER_ADDRESS}" op-deployer apply --l1-rpc-url "${ANVIL_URL}" \
--workdir "${DEPLOYER_DIR}" \
--private-key="${OPERATOR_PRIVATE_KEY}"

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/joho/godotenv v1.5.1 // indirect

require (
codeberg.org/go-fonts/liberation v0.5.0 // indirect
codeberg.org/go-latex/latex v0.1.0 // indirect
Expand Down Expand Up @@ -106,7 +108,6 @@ require (
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/coder/websocket v1.8.13
github.com/consensys/bavard v0.1.27 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
Expand Down
142 changes: 4 additions & 138 deletions op-batcher/bindings/batch_authenticator.go

Large diffs are not rendered by default.

Loading