Commit b8196518 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Add helper to run op-challenger in e2e tests (#6366)

Enhance test by running a challenger that defends the output and confirm it wins.
parent 54de2b5f
package op_challenger package op_challenger
import ( import (
"context"
"fmt" "fmt"
"time" "time"
...@@ -14,7 +15,7 @@ import ( ...@@ -14,7 +15,7 @@ import (
) )
// Main is the programmatic entry-point for running op-challenger // Main is the programmatic entry-point for running op-challenger
func Main(logger log.Logger, cfg *config.Config) error { func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error {
client, err := ethclient.Dial(cfg.L1EthRpc) client, err := ethclient.Dial(cfg.L1EthRpc)
if err != nil { if err != nil {
return fmt.Errorf("failed to dial L1: %w", err) return fmt.Errorf("failed to dial L1: %w", err)
...@@ -50,6 +51,12 @@ func Main(logger log.Logger, cfg *config.Config) error { ...@@ -50,6 +51,12 @@ func Main(logger log.Logger, cfg *config.Config) error {
logger.Info("Performing action") logger.Info("Performing action")
_ = agent.Act() _ = agent.Act()
caller.LogGameInfo() caller.LogGameInfo()
time.Sleep(300 * time.Millisecond) select {
// nosemgrep: dgryski.semgrep-go.timeafter.leaky-time-after
case <-time.After(300 * time.Millisecond):
// Continue
case <-ctx.Done():
return ctx.Err()
}
} }
} }
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
...@@ -41,7 +42,7 @@ func main() { ...@@ -41,7 +42,7 @@ func main() {
} }
} }
type ConfigAction func(log log.Logger, config *config.Config) error type ConfigAction func(ctx context.Context, log log.Logger, config *config.Config) error
func run(args []string, action ConfigAction) error { func run(args []string, action ConfigAction) error {
oplog.SetupDefaults() oplog.SetupDefaults()
...@@ -63,7 +64,7 @@ func run(args []string, action ConfigAction) error { ...@@ -63,7 +64,7 @@ func run(args []string, action ConfigAction) error {
if err != nil { if err != nil {
return err return err
} }
return action(logger, cfg) return action(ctx.Context, logger, cfg)
} }
return app.Run(args) return app.Run(args)
} }
......
package main package main
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
...@@ -137,7 +138,7 @@ func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) { ...@@ -137,7 +138,7 @@ func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
cfg := new(config.Config) cfg := new(config.Config)
var logger log.Logger var logger log.Logger
fullArgs := append([]string{"op-challenger"}, cliArgs...) fullArgs := append([]string{"op-challenger"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error { err := run(fullArgs, func(ctx context.Context, log log.Logger, config *config.Config) error {
logger = log logger = log
cfg = config cfg = config
return nil return nil
......
package challenger
import (
"context"
"errors"
"testing"
"time"
op_challenger "github.com/ethereum-optimism/optimism/op-challenger"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
type Helper struct {
log log.Logger
cancel func()
errors chan error
}
type Option func(config2 *config.Config)
func NewChallenger(t *testing.T, ctx context.Context, l1Endpoint string, name string, options ...Option) *Helper {
log := testlog.Logger(t, log.LvlInfo).New("role", name)
log.Info("Creating challenger", "l1", l1Endpoint)
txmgrCfg := txmgr.NewCLIConfig(l1Endpoint)
txmgrCfg.NumConfirmations = 1
txmgrCfg.ReceiptQueryInterval = 1 * time.Second
cfg := &config.Config{
L1EthRpc: l1Endpoint,
AlphabetTrace: "",
AgreeWithProposedOutput: true,
TxMgrConfig: txmgrCfg,
}
for _, option := range options {
option(cfg)
}
require.NotEmpty(t, cfg.TxMgrConfig.PrivateKey, "Missing private key for TxMgrConfig")
errCh := make(chan error, 1)
ctx, cancel := context.WithCancel(ctx)
go func() {
defer close(errCh)
errCh <- op_challenger.Main(ctx, log, cfg)
}()
return &Helper{
log: log,
cancel: cancel,
errors: errCh,
}
}
func (h *Helper) Close() error {
h.cancel()
select {
case <-time.After(1 * time.Minute):
return errors.New("timed out while stopping challenger")
case err := <-h.errors:
if !errors.Is(err, context.Canceled) {
return err
}
return nil
}
}
...@@ -3,12 +3,15 @@ package disputegame ...@@ -3,12 +3,15 @@ package disputegame
import ( import (
"context" "context"
"math" "math"
"math/big"
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault" "github.com/ethereum-optimism/optimism/op-challenger/fault"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-service/client/utils" "github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -32,6 +35,7 @@ var alphaExtraData = common.Hex2Bytes("10000000000000000000000000000000000000000 ...@@ -32,6 +35,7 @@ var alphaExtraData = common.Hex2Bytes("10000000000000000000000000000000000000000
var alphabetVMAbsolutePrestate = uint256.NewInt(140).Bytes32() var alphabetVMAbsolutePrestate = uint256.NewInt(140).Bytes32()
type FactoryHelper struct { type FactoryHelper struct {
t *testing.T
require *require.Assertions require *require.Assertions
client *ethclient.Client client *ethclient.Client
opts *bind.TransactOpts opts *bind.TransactOpts
...@@ -48,6 +52,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Clien ...@@ -48,6 +52,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Clien
factory := deployDisputeGameContracts(require, ctx, client, opts, gameDuration) factory := deployDisputeGameContracts(require, ctx, client, opts, gameDuration)
return &FactoryHelper{ return &FactoryHelper{
t: t,
require: require, require: require,
client: client, client: client,
opts: opts, opts: opts,
...@@ -55,7 +60,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Clien ...@@ -55,7 +60,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Clien
} }
} }
func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultHelper { func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultGameHelper {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
trace := fault.NewAlphabetProvider(claimedAlphabet, 4) trace := fault.NewAlphabetProvider(claimedAlphabet, 4)
...@@ -70,22 +75,57 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -70,22 +75,57 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
h.require.NoError(err) h.require.NoError(err)
game, err := bindings.NewFaultDisputeGame(createdEvent.DisputeProxy, h.client) game, err := bindings.NewFaultDisputeGame(createdEvent.DisputeProxy, h.client)
h.require.NoError(err) h.require.NoError(err)
return &FaultHelper{ return &FaultGameHelper{
require: h.require, t: h.t,
client: h.client, require: h.require,
opts: h.opts, client: h.client,
game: game, opts: h.opts,
game: game,
addr: createdEvent.DisputeProxy,
claimedAlphabet: claimedAlphabet,
} }
} }
type FaultHelper struct { type FaultGameHelper struct {
require *require.Assertions t *testing.T
client *ethclient.Client require *require.Assertions
opts *bind.TransactOpts client *ethclient.Client
game *bindings.FaultDisputeGame opts *bind.TransactOpts
game *bindings.FaultDisputeGame
addr common.Address
claimedAlphabet string
} }
func (g *FaultHelper) Resolve(ctx context.Context) { func (g *FaultGameHelper) StartChallenger(ctx context.Context, l1Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{
func(c *config.Config) {
c.GameAddress = g.addr
c.GameDepth = alphabetGameDepth
// By default the challenger agrees with the root claim (thus disagrees with the proposed output)
// This can be overridden by passing in options
c.AlphabetTrace = g.claimedAlphabet
c.AgreeWithProposedOutput = false
},
}
opts = append(opts, options...)
return challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
}
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return false, err
}
g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr)
return actual.Cmp(big.NewInt(count)) == 0, nil
})
g.require.NoError(err)
}
func (g *FaultGameHelper) Resolve(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
tx, err := g.game.Resolve(g.opts) tx, err := g.game.Resolve(g.opts)
...@@ -94,10 +134,10 @@ func (g *FaultHelper) Resolve(ctx context.Context) { ...@@ -94,10 +134,10 @@ func (g *FaultHelper) Resolve(ctx context.Context) {
g.require.NoError(err) g.require.NoError(err)
} }
func (g *FaultHelper) AssertStatusEquals(expected Status) { func (g *FaultGameHelper) AssertStatusEquals(ctx context.Context, expected Status) {
status, err := g.game.Status(&bind.CallOpts{ ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
From: g.opts.From, defer cancel()
}) status, err := g.game.Status(&bind.CallOpts{Context: ctx})
g.require.NoError(err) g.require.NoError(err)
g.require.Equal(expected, Status(status)) g.require.Equal(expected, Status(status))
} }
...@@ -5,8 +5,11 @@ import ( ...@@ -5,8 +5,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-service/client/utils" "github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -26,15 +29,25 @@ func TestResolveDisputeGame(t *testing.T) { ...@@ -26,15 +29,25 @@ func TestResolveDisputeGame(t *testing.T) {
l1Client := sys.Clients["l1"] l1Client := sys.Clients["l1"]
gameDuration := 24 * time.Hour gameDuration := 24 * time.Hour
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, l1Client, uint64(gameDuration.Seconds())) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, l1Client, uint64(gameDuration.Seconds()))
game := disputeGameFactory.StartAlphabetGame(ctx, "abcdefg") game := disputeGameFactory.StartAlphabetGame(ctx, "zyxwvut")
require.NotNil(t, game) require.NotNil(t, game)
game.AssertStatusEquals(disputegame.StatusInProgress) game.AssertStatusEquals(ctx, disputegame.StatusInProgress)
honest := game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "honestAlice", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
c.AlphabetTrace = "abcdefg"
c.TxMgrConfig.PrivateKey = hexutil.Encode(e2eutils.EncodePrivKey(cfg.Secrets.Alice))
})
defer honest.Close()
game.WaitForClaimCount(ctx, 2)
sys.TimeTravelClock.AdvanceTime(gameDuration) sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, utils.WaitNextBlock(ctx, l1Client)) require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
game.Resolve(ctx) game.Resolve(ctx)
game.AssertStatusEquals(disputegame.StatusDefenderWins) game.AssertStatusEquals(ctx, disputegame.StatusChallengerWins)
require.NoError(t, honest.Close())
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment