Skip to content

Commit 4757fde

Browse files
committed
Add test for devnet challenge game
1 parent 89f8c3a commit 4757fde

File tree

9 files changed

+292
-39
lines changed

9 files changed

+292
-39
lines changed

espresso/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ OPERATOR_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf
3434
# cast wallet address --private-key $OPERATOR_PRIVATE_KEY
3535
OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
3636

37+
# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/1"
38+
PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
39+
3740
L1_CHAIN_ID=11155111
3841
L2_CHAIN_ID=22266222
3942

espresso/devnet-tests/batcher_restart_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestBatcherRestart(t *testing.T) {
1313
defer cancel()
1414

1515
d := NewDevnet(ctx, t)
16-
require.NoError(t, d.Up(testing.Verbose()))
16+
require.NoError(t, d.Up())
1717
defer func() {
1818
require.NoError(t, d.Down())
1919
}()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package devnet_tests
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestChallengeGame(t *testing.T) {
12+
ctx, cancel := context.WithCancel(context.Background())
13+
defer cancel()
14+
15+
d := NewDevnet(ctx, t)
16+
require.NoError(t, d.Up())
17+
defer func() {
18+
require.NoError(t, d.Down())
19+
}()
20+
21+
// Wait for the proposer to make a claim.
22+
var games []ChallengeGame
23+
for len(games) == 0 {
24+
var err error
25+
t.Logf("waiting for a challenge game")
26+
time.Sleep(5 * time.Second)
27+
games, err = d.ListChallengeGames()
28+
require.NoError(t, err)
29+
}
30+
t.Logf("game created: %v", games[0])
31+
require.Equal(t, uint64(1), games[0].Claims)
32+
33+
// Attack the first claimed state.
34+
t.Logf("attacking game")
35+
require.NoError(t, d.OpChallenger("move", "--attack", "--game-address", games[0].Address.Hex()))
36+
37+
// Check that the proposer correctly responds.
38+
for {
39+
updatedGames, err := d.ListChallengeGames()
40+
require.NoError(t, err)
41+
if updatedGames[0].Claims == 3 {
42+
require.Equal(t, updatedGames[0].OutputRoot, games[0].OutputRoot)
43+
break
44+
}
45+
46+
t.Logf("waiting for a response")
47+
time.Sleep(time.Second)
48+
}
49+
}

espresso/devnet-tests/devnet_tools.go

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/hex"
77
"fmt"
8+
"io"
89
"math/big"
910
"os"
1011
"os/exec"
@@ -73,7 +74,7 @@ func NewDevnet(ctx context.Context, t *testing.T) *Devnet {
7374
return d
7475
}
7576

76-
func (d *Devnet) Up(verbose bool) (err error) {
77+
func (d *Devnet) Up() (err error) {
7778
cmd := exec.CommandContext(
7879
d.ctx,
7980
"docker", "compose", "up", "-d",
@@ -107,7 +108,7 @@ func (d *Devnet) Up(verbose bool) (err error) {
107108
}
108109
}()
109110

110-
if verbose {
111+
if testing.Verbose() {
111112
// Stream logs to stdout while the test runs. This goroutine will automatically exit when
112113
// the context is cancelled.
113114
go func() {
@@ -376,6 +377,158 @@ func (d *Devnet) Down() error {
376377
return cmd.Run()
377378
}
378379

380+
type TaggedWriter struct {
381+
inner io.Writer
382+
tag string
383+
newline bool
384+
}
385+
386+
func NewTaggedWriter(tag string, inner io.Writer) *TaggedWriter {
387+
return &TaggedWriter{
388+
inner: inner,
389+
tag: tag,
390+
newline: true,
391+
}
392+
}
393+
394+
func (w *TaggedWriter) Write(p []byte) (int, error) {
395+
if w.newline {
396+
if _, err := fmt.Fprintf(w.inner, "%s | ", w.tag); err != nil {
397+
return 0, err
398+
}
399+
w.newline = false
400+
}
401+
402+
written := 0
403+
for i := range len(p) {
404+
// Buffer bytes until we hit a newline.
405+
if p[i] == '\n' {
406+
// Print everything we've buffered up to and including the newline.
407+
line := p[written : i+1]
408+
n, err := w.inner.Write(line)
409+
written += n
410+
if err != nil || n < len(line) {
411+
return written, err
412+
}
413+
414+
// If that's the end of the output, return, but make a note that the buffer ended with a
415+
// newline and we need to print the tag before the next message.
416+
if written == len(p) {
417+
w.newline = true
418+
return written, nil
419+
}
420+
421+
// Otherwise print the tag now before proceeding with the next line in `p`.
422+
if _, err := fmt.Fprintf(w.inner, "%s | ", w.tag); err != nil {
423+
return written, err
424+
}
425+
}
426+
}
427+
428+
// Print anything that was buffered after the final newline.
429+
if written < len(p) {
430+
line := p[written:]
431+
n, err := w.inner.Write(line)
432+
written += n
433+
if err != nil || n < len(line) {
434+
return written, err
435+
}
436+
}
437+
438+
return written, nil
439+
}
440+
441+
func (d *Devnet) OpChallenger(opts ...string) error {
442+
return d.opChallengerCmd(opts...).Run()
443+
}
444+
445+
type ChallengeGame struct {
446+
Index uint64
447+
Address common.Address
448+
OutputRoot []byte
449+
Claims uint64
450+
}
451+
452+
func ParseChallengeGame(s string) (ChallengeGame, error) {
453+
fields := strings.Fields(s)
454+
if len(fields) < 8 {
455+
return ChallengeGame{}, fmt.Errorf("challenge game is missing fields; expected at least 8 but got only %v", len(fields))
456+
}
457+
458+
index, err := strconv.ParseUint(fields[0], 10, 64)
459+
if err != nil {
460+
return ChallengeGame{}, fmt.Errorf("index invalid: %w", err)
461+
}
462+
463+
address := common.HexToAddress(fields[1])
464+
465+
outputRoot := common.Hex2Bytes(fields[6])
466+
467+
claims, err := strconv.ParseUint(fields[7], 10, 64)
468+
if err != nil {
469+
return ChallengeGame{}, fmt.Errorf("claims count invalid: %w", err)
470+
}
471+
472+
return ChallengeGame{
473+
Index: index,
474+
Address: address,
475+
OutputRoot: outputRoot,
476+
Claims: claims,
477+
}, nil
478+
}
479+
480+
func (d *Devnet) ListChallengeGames() ([]ChallengeGame, error) {
481+
output, err := d.OpChallengerOutput("list-games")
482+
if err != nil {
483+
return nil, err
484+
}
485+
486+
var games []ChallengeGame
487+
for i, line := range strings.Split(output, "\n") {
488+
if i == 0 {
489+
// Ignore header.
490+
continue
491+
}
492+
line = strings.TrimSpace(line)
493+
if len(line) == 0 {
494+
// Ignore empty lines (e.g. trailing newline)
495+
continue
496+
}
497+
498+
game, err := ParseChallengeGame(line)
499+
if err != nil {
500+
return nil, fmt.Errorf("game %v is invalid: %w", i, err)
501+
}
502+
games = append(games, game)
503+
}
504+
return games, nil
505+
}
506+
507+
func (d *Devnet) OpChallengerOutput(opts ...string) (string, error) {
508+
cmd := d.opChallengerCmd(opts...)
509+
buf := new(bytes.Buffer)
510+
cmd.Stdout = buf
511+
if err := cmd.Run(); err != nil {
512+
return "", err
513+
}
514+
return buf.String(), nil
515+
}
516+
517+
func (d *Devnet) opChallengerCmd(opts ...string) *exec.Cmd {
518+
opts = append([]string{"compose", "exec", "op-challenger", "entrypoint.sh", "op-challenger"}, opts...)
519+
cmd := exec.CommandContext(
520+
d.ctx,
521+
"docker",
522+
opts...,
523+
)
524+
if testing.Verbose() {
525+
cmd.Stdout = NewTaggedWriter("op-challenger-cmd", os.Stdout)
526+
cmd.Stderr = NewTaggedWriter("op-challenger-cmd", os.Stderr)
527+
}
528+
log.Info("invoking op-challenger", "cmd", cmd)
529+
return cmd
530+
}
531+
379532
// Get the host port mapped to `privatePort` for the given Docker service.
380533
func (d *Devnet) hostPort(service string, privatePort uint16) (uint16, error) {
381534
buf := new(bytes.Buffer)

espresso/devnet-tests/key_rotation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestRotateBatcherKey(t *testing.T) {
1919
// We're going to change batcher key to Bob's, verify that it won't be a no-op
2020
require.NotEqual(t, d.secrets.Batcher, d.secrets.Bob)
2121

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

5858
d := NewDevnet(ctx, t)
5959

60-
require.NoError(t, d.Up(testing.Verbose()))
60+
require.NoError(t, d.Up())
6161
defer func() {
6262
require.NoError(t, d.Down())
6363
}()

0 commit comments

Comments
 (0)