55 "context"
66 "encoding/hex"
77 "fmt"
8+ "io"
89 "math/big"
910 "os"
1011 "os/exec"
@@ -50,9 +51,15 @@ func NewDevnet(ctx context.Context, t *testing.T) *Devnet {
5051
5152 d := new (Devnet )
5253 d .ctx = ctx
53- d .secrets = * secrets .DefaultSecrets
5454
55- var err error
55+ mnemonics := * secrets .DefaultMnemonicConfig
56+ mnemonics .Batcher = "m/44'/60'/0'/0/0"
57+ secrets , err := mnemonics .Secrets ()
58+ if err != nil {
59+ panic (fmt .Sprintf ("failed to create default secrets: %e" , err ))
60+ }
61+ d .secrets = * secrets
62+
5663 if outageTime , ok := os .LookupEnv ("ESPRESSO_DEVNET_TESTS_OUTAGE_PERIOD" ); ok {
5764 d .outageTime , err = time .ParseDuration (outageTime )
5865 if err != nil {
@@ -73,7 +80,7 @@ func NewDevnet(ctx context.Context, t *testing.T) *Devnet {
7380 return d
7481}
7582
76- func (d * Devnet ) Up (verbose bool ) (err error ) {
83+ func (d * Devnet ) Up () (err error ) {
7784 cmd := exec .CommandContext (
7885 d .ctx ,
7986 "docker" , "compose" , "up" , "-d" ,
@@ -107,7 +114,7 @@ func (d *Devnet) Up(verbose bool) (err error) {
107114 }
108115 }()
109116
110- if verbose {
117+ if testing . Verbose () {
111118 // Stream logs to stdout while the test runs. This goroutine will automatically exit when
112119 // the context is cancelled.
113120 go func () {
@@ -376,6 +383,161 @@ func (d *Devnet) Down() error {
376383 return cmd .Run ()
377384}
378385
386+ type TaggedWriter struct {
387+ inner io.Writer
388+ tag string
389+ newline bool
390+ }
391+
392+ func NewTaggedWriter (tag string , inner io.Writer ) * TaggedWriter {
393+ return & TaggedWriter {
394+ inner : inner ,
395+ tag : tag ,
396+ newline : true ,
397+ }
398+ }
399+
400+ // Implementation of io.Write interface for TaggedWriter.
401+ // Allows to prepend a tag to each line of output.
402+ // The `p` parameter is the tag to add at the beginning of each line.
403+ func (w * TaggedWriter ) Write (p []byte ) (int , error ) {
404+ if w .newline {
405+ if _ , err := fmt .Fprintf (w .inner , "%s | " , w .tag ); err != nil {
406+ return 0 , err
407+ }
408+ w .newline = false
409+ }
410+
411+ written := 0
412+ for i := range len (p ) {
413+ // Buffer bytes until we hit a newline.
414+ if p [i ] == '\n' {
415+ // Print everything we've buffered up to and including the newline.
416+ line := p [written : i + 1 ]
417+ n , err := w .inner .Write (line )
418+ written += n
419+ if err != nil || n < len (line ) {
420+ return written , err
421+ }
422+
423+ // If that's the end of the output, return, but make a note that the buffer ended with a
424+ // newline and we need to print the tag before the next message.
425+ if written == len (p ) {
426+ w .newline = true
427+ return written , nil
428+ }
429+
430+ // Otherwise print the tag now before proceeding with the next line in `p`.
431+ if _ , err := fmt .Fprintf (w .inner , "%s | " , w .tag ); err != nil {
432+ return written , err
433+ }
434+ }
435+ }
436+
437+ // Print anything that was buffered after the final newline.
438+ if written < len (p ) {
439+ line := p [written :]
440+ n , err := w .inner .Write (line )
441+ written += n
442+ if err != nil || n < len (line ) {
443+ return written , err
444+ }
445+ }
446+
447+ return written , nil
448+ }
449+
450+ func (d * Devnet ) OpChallenger (opts ... string ) error {
451+ return d .opChallengerCmd (opts ... ).Run ()
452+ }
453+
454+ type ChallengeGame struct {
455+ Index uint64
456+ Address common.Address
457+ OutputRoot []byte
458+ Claims uint64
459+ }
460+
461+ func ParseChallengeGame (s string ) (ChallengeGame , error ) {
462+ fields := strings .Fields (s )
463+ if len (fields ) < 8 {
464+ return ChallengeGame {}, fmt .Errorf ("challenge game is missing fields; expected at least 8 but got only %v" , len (fields ))
465+ }
466+
467+ index , err := strconv .ParseUint (fields [0 ], 10 , 64 )
468+ if err != nil {
469+ return ChallengeGame {}, fmt .Errorf ("index invalid: %w" , err )
470+ }
471+
472+ address := common .HexToAddress (fields [1 ])
473+
474+ outputRoot := common .Hex2Bytes (fields [6 ])
475+
476+ claims , err := strconv .ParseUint (fields [7 ], 10 , 64 )
477+ if err != nil {
478+ return ChallengeGame {}, fmt .Errorf ("claims count invalid: %w" , err )
479+ }
480+
481+ return ChallengeGame {
482+ Index : index ,
483+ Address : address ,
484+ OutputRoot : outputRoot ,
485+ Claims : claims ,
486+ }, nil
487+ }
488+
489+ func (d * Devnet ) ListChallengeGames () ([]ChallengeGame , error ) {
490+ output , err := d .OpChallengerOutput ("list-games" )
491+ if err != nil {
492+ return nil , err
493+ }
494+
495+ var games []ChallengeGame
496+ for i , line := range strings .Split (output , "\n " ) {
497+ if i == 0 {
498+ // Ignore header.
499+ continue
500+ }
501+ line = strings .TrimSpace (line )
502+ if len (line ) == 0 {
503+ // Ignore empty lines (e.g. trailing newline)
504+ continue
505+ }
506+
507+ game , err := ParseChallengeGame (line )
508+ if err != nil {
509+ return nil , fmt .Errorf ("game %v is invalid: %w" , i , err )
510+ }
511+ games = append (games , game )
512+ }
513+ return games , nil
514+ }
515+
516+ func (d * Devnet ) OpChallengerOutput (opts ... string ) (string , error ) {
517+ cmd := d .opChallengerCmd (opts ... )
518+ buf := new (bytes.Buffer )
519+ cmd .Stdout = buf
520+ if err := cmd .Run (); err != nil {
521+ return "" , err
522+ }
523+ return buf .String (), nil
524+ }
525+
526+ func (d * Devnet ) opChallengerCmd (opts ... string ) * exec.Cmd {
527+ opts = append ([]string {"compose" , "exec" , "op-challenger" , "entrypoint.sh" , "op-challenger" }, opts ... )
528+ cmd := exec .CommandContext (
529+ d .ctx ,
530+ "docker" ,
531+ opts ... ,
532+ )
533+ if testing .Verbose () {
534+ cmd .Stdout = NewTaggedWriter ("op-challenger-cmd" , os .Stdout )
535+ cmd .Stderr = NewTaggedWriter ("op-challenger-cmd" , os .Stderr )
536+ }
537+ log .Info ("invoking op-challenger" , "cmd" , cmd )
538+ return cmd
539+ }
540+
379541// Get the host port mapped to `privatePort` for the given Docker service.
380542func (d * Devnet ) hostPort (service string , privatePort uint16 ) (uint16 , error ) {
381543 buf := new (bytes.Buffer )
0 commit comments