Skip to content

Commit

Permalink
Expand e2e tests to cover the true end-to-end flow (#267)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshklop authored Nov 7, 2024
1 parent b30e677 commit 53ab57a
Show file tree
Hide file tree
Showing 19 changed files with 3,016 additions and 915 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: foundry-rs/foundry-toolchain@v1
- run: make setup-e2e
- run: make e2e
- uses: actions/upload-artifact@v4
if: always()
Expand Down
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
e2e/artifacts
opdevnet/artifacts

# Ignore go workspaces
go.work
go.work.sum

# Ignore the forge artifacts and cache.
bindings/artifacts
bindings/cache
Expand Down
16 changes: 1 addition & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ BIN ?= bin
GO_WRAPPER ?= $(SCRIPTS_PATH)/go-wrapper.sh

E2E_ARTIFACTS_PATH ?= e2e/artifacts
E2E_STATE_SETUP_PATH ?= e2e/optimism/.devnet
E2E_CONFIG_SETUP_PATH ?= e2e/optimism/packages/contracts-bedrock/deploy-config/devnetL1.json
FOUNDRY_ARTIFACTS_PATH ?= bindings/artifacts
FOUNDRY_CACHE_PATH ?= bindings/cache

Expand All @@ -21,11 +19,7 @@ test-all:

.PHONY: e2e
e2e:
$(GO_WRAPPER) test -v ./e2e \
-l1-allocs ./optimism/.devnet/allocs-l1.json \
-l2-allocs-dir ./optimism/.devnet/ \
-l1-deployments ./optimism/.devnet/addresses.json \
-deploy-config ./optimism/packages/contracts-bedrock/deploy-config/devnetL1.json
$(GO_WRAPPER) test -v ./e2e

.PHONY: install-golangci-lint
install-golangci-lint:
Expand Down Expand Up @@ -92,14 +86,6 @@ clean:
if [ -f $(COVER_OUT) ]; then rm $(COVER_OUT); fi
if [ -f $(COVER_HTML) ]; then rm $(COVER_HTML); fi
if [ -d ${E2E_ARTIFACTS_PATH} ]; then rm -r ${E2E_ARTIFACTS_PATH}; fi
if [ -d ${E2E_STATE_SETUP_PATH} ]; then rm -r ${E2E_STATE_SETUP_PATH}; fi
if [ -f $(E2E_CONFIG_SETUP_PATH) ]; then rm $(E2E_CONFIG_SETUP_PATH); fi
if [ -d ${FOUNDRY_ARTIFACTS_PATH} ]; then rm -r ${FOUNDRY_ARTIFACTS_PATH}; fi
if [ -d ${FOUNDRY_CACHE_PATH} ]; then rm -r ${FOUNDRY_CACHE_PATH}; fi
if [ -d $(BIN) ]; then rm -r $(BIN); fi

.PHONY: setup-e2e
setup-e2e:
$(MAKE) -C e2e/optimism install-geth && \
$(MAKE) -C e2e/optimism cannon-prestate && \
$(MAKE) -C e2e/optimism devnet-allocs
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@ From the [OP stack](https://specs.optimism.io/protocol/overview.html#components)

## Development

We use Go 1.22. To run the e2e tests, you'll need the submodules and a configured L1 backend. We use [`buf`](https://buf.build/) to manage protobufs.
We use Go 1.22. We use [`buf`](https://buf.build/) to manage protobufs.

### Prerequisites

1. Install [go](https://go.dev/) 1.22 or higher.
1. Initialize submodules:
```sh
git submodule update --init --recursive
```
1. Install [jq](https://jqlang.github.io/jq/download/)
1. Install [foundry](https://book.getfoundry.sh/getting-started/installation)
1. Install buf:
Expand Down
Binary file modified cmd/monogen/testapp.zip
Binary file not shown.
7 changes: 5 additions & 2 deletions cmd/monogen/testapp/setup-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ set -e
# Build the app.
# Because we transitively depend on github.com/fjl/memsize, we need to disable checklinkname in go1.23.0 and higher.
goVersion=$(go env GOVERSION)
[[ $goVersion > go1.23.0 || $goVersion == go1.23.0 ]] && ldflags="-ldflags=-checklinkname=0"
go build $ldflags -o testappd ./cmd/testappd
if [[ $goVersion > go1.23.0 || $goVersion == go1.23.0 ]]; then
GOFLAGS="$GOFLAGS -ldflags=-checklinkname=0"
fi
export GOFLAGS
go build -o testappd ./cmd/testappd

# The following process is identical for a standard Cosmos SDK chain.
# The Cosmos SDK documentation has more information in addition to what is presented here:
Expand Down
4 changes: 4 additions & 0 deletions e2e/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (m *MonomerClient) BlockByNumber(ctx context.Context, number *big.Int) (*et
return block, nil
}

func (m *MonomerClient) ChainID(ctx context.Context) (*big.Int, error) {
return m.ethclient.ChainID(ctx)
}

// GetProof returns the account and storage values of the specified account including the Merkle-proof.
// The block number can be nil, in which case the value is taken from the latest known block.
func (m *MonomerClient) GetProof(
Expand Down
99 changes: 99 additions & 0 deletions e2e/e2e.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package e2e

import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"

bftclient "github.com/cometbft/cometbft/rpc/client/http"
opbindings "github.com/ethereum-optimism/optimism/op-bindings/bindings"
opgenesis "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-node/bindings"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/polymerdao/monomer/environment"
)

type StackConfig struct {
Ctx context.Context
Users []*ecdsa.PrivateKey
L1Client *L1Client
L1Deployments *opgenesis.L1Deployments
OptimismPortal *bindings.OptimismPortal
L1StandardBridge *opbindings.L1StandardBridge
L2OutputOracleCaller *bindings.L2OutputOracleCaller
L2Client *bftclient.HTTP
MonomerClient *MonomerClient
RollupConfig *rollup.Config
WaitL1 func(numBlocks int) error
WaitL2 func(numBlocks int) error
}

func Run(
ctx context.Context,
env *environment.Env,
outDir string,
) error {
monomerPath, err := filepath.Abs("..")
if err != nil {
return fmt.Errorf("absolute path to local monomer directory: %v", err)
}
const appName = "e2eapp"
appDirPath := filepath.Join(outDir, appName)
//nolint:gosec // We aren't worried about tainted cmd args.
monogenCmd := setupCmd(exec.CommandContext(ctx,
filepath.Join("..", "scripts", "go-wrapper.sh"),
"run", filepath.Join("..", "cmd", "monogen"),
"--app-dir-path", appDirPath,
"--gomod-path", "github.com/e2e/"+appName,
"--address-prefix", "e2e",
"--monomer-path", monomerPath,
))
if err := monogenCmd.Run(); err != nil {
return fmt.Errorf("run monogen: %v", err)
}

setupHelperCmd := setupCmd(exec.CommandContext(ctx, filepath.Join(appDirPath, "setup-helper.sh"))) //nolint:gosec
setupHelperCmd.Dir = appDirPath
// Add the "GOFLAGS='-gcflags=all=-N -l'" environment variable to disable optimizations and make debugging easier.
setupHelperCmd.Env = append(os.Environ(), "e2eapp_HOME="+outDir)
if err := setupHelperCmd.Run(); err != nil {
return fmt.Errorf("run setup helper: %v", err)
}

//nolint:gosec // We aren't worried about tainted cmd args.
appCmd := setupCmd(exec.CommandContext(ctx,
filepath.Join(appDirPath, appName+"d"),
"monomer",
"start",
"--minimum-gas-prices", "0.001wei",
"--monomer.dev-start",
))
appCmd.Dir = appDirPath
appCmd.Env = append(os.Environ(), "e2eapp_HOME="+outDir)
if err := appCmd.Start(); err != nil {
return fmt.Errorf("run app: %v", err)
}
env.DeferErr("wait for app", func() error {
err := appCmd.Wait()
if errors.Is(err, context.Canceled) {
return nil
}
return err
})

return nil
}

func setupCmd(cmd *exec.Cmd) *exec.Cmd {
cmd.Cancel = func() error {
return cmd.Process.Signal(syscall.SIGTERM)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd
}
147 changes: 147 additions & 0 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package e2e_test

import (
"context"
"crypto/ecdsa"
"errors"
"os"
"path/filepath"
"testing"
"time"

bftclient "github.com/cometbft/cometbft/rpc/client/http"
opbindings "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/bindings"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/rpc"
"github.com/polymerdao/monomer/e2e"
"github.com/polymerdao/monomer/e2e/url"
"github.com/polymerdao/monomer/environment"
"github.com/polymerdao/monomer/opdevnet"
"github.com/stretchr/testify/require"
)

var e2eTests = []struct {
name string
run func(t *testing.T, stack *e2e.StackConfig)
}{
{
name: "ETH L1 Deposits and L2 Withdrawals",
run: ethRollupFlow,
},
{
name: "ERC-20 L1 Deposits",
run: erc20RollupFlow,
},
{
name: "AttributesTX",
run: containsAttributesTx,
},
{
name: "No Rollbacks",
run: checkForRollbacks,
},
}

func TestE2E(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e tests in short mode")
}

env := environment.New()
defer func() {
require.NoError(t, env.Close())
}()
artifactsDir, err := filepath.Abs("artifacts")
require.NoError(t, err)

if err := os.Mkdir(artifactsDir, 0o755); !errors.Is(err, os.ErrExist) {
require.NoError(t, err)
}

stdoutFile, err := os.OpenFile(filepath.Join(artifactsDir, "stdout"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o666)
require.NoError(t, err)
env.DeferErr("close stdout file", stdoutFile.Close)
stdout := os.Stdout
os.Stdout = stdoutFile
defer func() {
os.Stdout = stdout
}()
stderrFile, err := os.OpenFile(filepath.Join(artifactsDir, "stderr"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o666)
require.NoError(t, err)
env.DeferErr("close stderr file", stderrFile.Close)
stderr := os.Stderr
os.Stderr = stderrFile
defer func() {
os.Stderr = stderr
}()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
require.NoError(t, e2e.Run(ctx, env, t.TempDir()))

for _, test := range e2eTests {
t.Run(test.name, func(t *testing.T) {
test.run(t, newStackConfig(t))
})
}
}

func newStackConfig(t *testing.T) *e2e.StackConfig {
secrets, err := opdevnet.DefaultMnemonicConfig.Secrets()
require.NoError(t, err)

engineURL, err := url.ParseString("ws://127.0.0.1:9000")
require.NoError(t, err)
require.True(t, engineURL.IsReachable(context.Background()))
monomerRPCClient, err := rpc.Dial(engineURL.String())
require.NoError(t, err)
monomerClient := e2e.NewMonomerClient(monomerRPCClient)

bftClient, err := bftclient.New("tcp://127.0.0.1:26657", "/websocket")
require.NoError(t, err)
require.NoError(t, bftClient.Start())

l1URL, err := url.ParseString("ws://127.0.0.1:9001")
require.NoError(t, err)
require.True(t, l1URL.IsReachable(context.Background()))
l1RPCClient, err := rpc.Dial(l1URL.String())
require.NoError(t, err)
l1Client := e2e.NewL1Client(l1RPCClient)

l1Deployments, err := opdevnet.DefaultL1Deployments()
require.NoError(t, err)

opPortal, err := bindings.NewOptimismPortal(l1Deployments.OptimismPortalProxy, l1Client)
require.NoError(t, err)
l1StandardBridge, err := opbindings.NewL1StandardBridge(l1Deployments.L1StandardBridgeProxy, l1Client)
require.NoError(t, err)
l2OutputOracleCaller, err := bindings.NewL2OutputOracleCaller(l1Deployments.L2OutputOracleProxy, l1Client)
require.NoError(t, err)

deployConfig, err := opdevnet.DefaultDeployConfig(l1Deployments)
require.NoError(t, err)

return &e2e.StackConfig{
Ctx: context.Background(),
Users: []*ecdsa.PrivateKey{secrets.Alice, secrets.Bob},
L1Client: l1Client,
L1Deployments: l1Deployments,
OptimismPortal: opPortal,
L1StandardBridge: l1StandardBridge,
L2OutputOracleCaller: l2OutputOracleCaller,
L2Client: bftClient,
MonomerClient: monomerClient,
RollupConfig: &rollup.Config{
SeqWindowSize: deployConfig.SequencerWindowSize,
},
WaitL1: func(numBlocks int) error {
time.Sleep(time.Second * time.Duration(deployConfig.L1BlockTime*uint64(numBlocks)))
return nil
},
WaitL2: func(numBlocks int) error {
time.Sleep(time.Second * time.Duration(deployConfig.L2BlockTime*uint64(numBlocks)))
return nil
},
}
}
41 changes: 0 additions & 41 deletions e2e/gethdevnet.go

This file was deleted.

Loading

0 comments on commit 53ab57a

Please sign in to comment.