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/docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: nightly-654c8f01721e43dbc8a53c7a3b022548cb82b2f9 # same as for the nix environment

- name: Install dasel
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/espresso-devnet-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
docker compose build

# - name: Run Devnet tests
# run: go test -timeout 30m -p 1 -count 1 -v ./espresso/devnet-tests/...
# run: go test -timeout 30m -p 1 -count 1 -v ./espresso/devnet-tests/...

- name: Save Nix cache
uses: nix-community/cache-nix-action/save@v6
Expand Down
6 changes: 6 additions & 0 deletions README_ESPRESSO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Provide Docker with the PAT.
> echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
```

Run docker as a non root user:
```console
> sudo add group docker
> sudo usermod -aG docker $USER
```

### Run the tests

Run the Espresso smoke tests:
Expand Down
3 changes: 3 additions & 0 deletions espresso/.env
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ OPERATOR_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf
# cast wallet address --private-key $OPERATOR_PRIVATE_KEY
OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

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

L1_CHAIN_ID=11155111
L2_CHAIN_ID=22266222

Expand Down
2 changes: 1 addition & 1 deletion espresso/devnet-tests/batcher_restart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestBatcherRestart(t *testing.T) {
defer cancel()

d := NewDevnet(ctx, t)
require.NoError(t, d.Up(testing.Verbose()))
require.NoError(t, d.Up())
defer func() {
require.NoError(t, d.Down())
}()
Expand Down
50 changes: 50 additions & 0 deletions espresso/devnet-tests/challenge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package devnet_tests

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestChallengeGame(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

d := NewDevnet(ctx, t)
require.NoError(t, d.Up())
defer func() {
require.NoError(t, d.Down())
}()

// Wait for the proposer to make a claim.
var games []ChallengeGame
for len(games) == 0 {
var err error
t.Logf("waiting for a challenge game")
time.Sleep(5 * time.Second)
games, err = d.ListChallengeGames()
require.NoError(t, err)
}
t.Logf("game created: %v", games[0])
require.Equal(t, uint64(1), games[0].Claims)

// Attack the first claimed state.
t.Logf("attacking game")
require.NoError(t, d.OpChallenger("move", "--attack", "--game-address", games[0].Address.Hex()))

// Check that the proposer correctly responds.
CLAIMS_NUMBER := uint64(3) // First claim by the proposer + attack + response
for {
updatedGames, err := d.ListChallengeGames()
require.NoError(t, err)
if updatedGames[0].Claims == CLAIMS_NUMBER {
require.Equal(t, updatedGames[0].OutputRoot, games[0].OutputRoot)
break
}

t.Logf("waiting for a response")
time.Sleep(time.Second)
}
}
170 changes: 166 additions & 4 deletions espresso/devnet-tests/devnet_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/hex"
"fmt"
"io"
"math/big"
"os"
"os/exec"
Expand Down Expand Up @@ -50,9 +51,15 @@ func NewDevnet(ctx context.Context, t *testing.T) *Devnet {

d := new(Devnet)
d.ctx = ctx
d.secrets = *secrets.DefaultSecrets

var err error
mnemonics := *secrets.DefaultMnemonicConfig
mnemonics.Batcher = "m/44'/60'/0'/0/0"
secrets, err := mnemonics.Secrets()
if err != nil {
panic(fmt.Sprintf("failed to create default secrets: %e", err))
}
d.secrets = *secrets

if outageTime, ok := os.LookupEnv("ESPRESSO_DEVNET_TESTS_OUTAGE_PERIOD"); ok {
d.outageTime, err = time.ParseDuration(outageTime)
if err != nil {
Expand All @@ -73,7 +80,7 @@ func NewDevnet(ctx context.Context, t *testing.T) *Devnet {
return d
}

func (d *Devnet) Up(verbose bool) (err error) {
func (d *Devnet) Up() (err error) {
cmd := exec.CommandContext(
d.ctx,
"docker", "compose", "up", "-d",
Expand Down Expand Up @@ -107,7 +114,7 @@ func (d *Devnet) Up(verbose bool) (err error) {
}
}()

if verbose {
if testing.Verbose() {
// Stream logs to stdout while the test runs. This goroutine will automatically exit when
// the context is cancelled.
go func() {
Expand Down Expand Up @@ -376,6 +383,161 @@ func (d *Devnet) Down() error {
return cmd.Run()
}

type TaggedWriter struct {
inner io.Writer
tag string
newline bool
}

func NewTaggedWriter(tag string, inner io.Writer) *TaggedWriter {
return &TaggedWriter{
inner: inner,
tag: tag,
newline: true,
}
}

// Implementation of io.Write interface for TaggedWriter.
// Allows to prepend a tag to each line of output.
// The `p` parameter is the tag to add at the beginning of each line.
func (w *TaggedWriter) Write(p []byte) (int, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this function do? Where does it write and why?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the generic go io writer interface. This type is just a wrapper around an inner IO stream that prepends output with a tag (e.g. op-challenger | ...). I'll add commenets

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added in 9373be8.

if w.newline {
if _, err := fmt.Fprintf(w.inner, "%s | ", w.tag); err != nil {
return 0, err
}
w.newline = false
}

written := 0
for i := range len(p) {
// Buffer bytes until we hit a newline.
if p[i] == '\n' {
// Print everything we've buffered up to and including the newline.
line := p[written : i+1]
n, err := w.inner.Write(line)
written += n
if err != nil || n < len(line) {
return written, err
}

// If that's the end of the output, return, but make a note that the buffer ended with a
// newline and we need to print the tag before the next message.
if written == len(p) {
w.newline = true
return written, nil
}

// Otherwise print the tag now before proceeding with the next line in `p`.
if _, err := fmt.Fprintf(w.inner, "%s | ", w.tag); err != nil {
return written, err
}
}
}

// Print anything that was buffered after the final newline.
if written < len(p) {
line := p[written:]
n, err := w.inner.Write(line)
written += n
if err != nil || n < len(line) {
return written, err
}
}

return written, nil
}

func (d *Devnet) OpChallenger(opts ...string) error {
return d.opChallengerCmd(opts...).Run()
}

type ChallengeGame struct {
Index uint64
Address common.Address
OutputRoot []byte
Claims uint64
}

func ParseChallengeGame(s string) (ChallengeGame, error) {
fields := strings.Fields(s)
if len(fields) < 8 {
return ChallengeGame{}, fmt.Errorf("challenge game is missing fields; expected at least 8 but got only %v", len(fields))
}

index, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return ChallengeGame{}, fmt.Errorf("index invalid: %w", err)
}

address := common.HexToAddress(fields[1])

outputRoot := common.Hex2Bytes(fields[6])

claims, err := strconv.ParseUint(fields[7], 10, 64)
if err != nil {
return ChallengeGame{}, fmt.Errorf("claims count invalid: %w", err)
}

return ChallengeGame{
Index: index,
Address: address,
OutputRoot: outputRoot,
Claims: claims,
}, nil
}

func (d *Devnet) ListChallengeGames() ([]ChallengeGame, error) {
output, err := d.OpChallengerOutput("list-games")
if err != nil {
return nil, err
}

var games []ChallengeGame
for i, line := range strings.Split(output, "\n") {
if i == 0 {
// Ignore header.
continue
}
line = strings.TrimSpace(line)
if len(line) == 0 {
// Ignore empty lines (e.g. trailing newline)
continue
}

game, err := ParseChallengeGame(line)
if err != nil {
return nil, fmt.Errorf("game %v is invalid: %w", i, err)
}
games = append(games, game)
}
return games, nil
}

func (d *Devnet) OpChallengerOutput(opts ...string) (string, error) {
cmd := d.opChallengerCmd(opts...)
buf := new(bytes.Buffer)
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
return "", err
}
return buf.String(), nil
}

func (d *Devnet) opChallengerCmd(opts ...string) *exec.Cmd {
opts = append([]string{"compose", "exec", "op-challenger", "entrypoint.sh", "op-challenger"}, opts...)
cmd := exec.CommandContext(
d.ctx,
"docker",
opts...,
)
if testing.Verbose() {
cmd.Stdout = NewTaggedWriter("op-challenger-cmd", os.Stdout)
cmd.Stderr = NewTaggedWriter("op-challenger-cmd", os.Stderr)
}
log.Info("invoking op-challenger", "cmd", cmd)
return cmd
}

// Get the host port mapped to `privatePort` for the given Docker service.
func (d *Devnet) hostPort(service string, privatePort uint16) (uint16, error) {
buf := new(bytes.Buffer)
Expand Down
4 changes: 2 additions & 2 deletions espresso/devnet-tests/key_rotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestRotateBatcherKey(t *testing.T) {
// We're going to change batcher key to Bob's, verify that it won't be a no-op
require.NotEqual(t, d.secrets.Batcher, d.secrets.Bob)

require.NoError(t, d.Up(testing.Verbose()))
require.NoError(t, d.Up())
defer func() {
require.NoError(t, d.Down())
}()
Expand Down Expand Up @@ -57,7 +57,7 @@ func TestChangeBatchInboxOwner(t *testing.T) {

d := NewDevnet(ctx, t)

require.NoError(t, d.Up(testing.Verbose()))
require.NoError(t, d.Up())
defer func() {
require.NoError(t, d.Down())
}()
Expand Down
Loading