Commit 0a6023cb authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Add e2e test that disputes an output root by agreeing with it

Tests the corner case where both sides have the same trace that says the output root is valid but the malicious user has disputed the original output root anyway.
parent ee860521
...@@ -32,7 +32,7 @@ type Executor struct { ...@@ -32,7 +32,7 @@ type Executor struct {
logger log.Logger logger log.Logger
l1 string l1 string
l2 string l2 string
inputs localGameInputs inputs LocalGameInputs
cannon string cannon string
server string server string
network string network string
...@@ -45,7 +45,7 @@ type Executor struct { ...@@ -45,7 +45,7 @@ type Executor struct {
cmdExecutor cmdExecutor cmdExecutor cmdExecutor
} }
func NewExecutor(logger log.Logger, cfg *config.Config, inputs localGameInputs) *Executor { func NewExecutor(logger log.Logger, cfg *config.Config, inputs LocalGameInputs) *Executor {
return &Executor{ return &Executor{
logger: logger, logger: logger,
l1: cfg.L1EthRpc, l1: cfg.L1EthRpc,
...@@ -92,11 +92,11 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -92,11 +92,11 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
"--l1", e.l1, "--l1", e.l1,
"--l2", e.l2, "--l2", e.l2,
"--datadir", dataDir, "--datadir", dataDir,
"--l1.head", e.inputs.l1Head.Hex(), "--l1.head", e.inputs.L1Head.Hex(),
"--l2.head", e.inputs.l2Head.Hex(), "--l2.head", e.inputs.L2Head.Hex(),
"--l2.outputroot", e.inputs.l2OutputRoot.Hex(), "--l2.outputroot", e.inputs.L2OutputRoot.Hex(),
"--l2.claim", e.inputs.l2Claim.Hex(), "--l2.claim", e.inputs.L2Claim.Hex(),
"--l2.blocknumber", e.inputs.l2BlockNumber.Text(10), "--l2.blocknumber", e.inputs.L2BlockNumber.Text(10),
) )
if e.network != "" { if e.network != "" {
args = append(args, "--network", e.network) args = append(args, "--network", e.network)
......
...@@ -29,12 +29,12 @@ func TestGenerateProof(t *testing.T) { ...@@ -29,12 +29,12 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonL2 = "http://localhost:9999" cfg.CannonL2 = "http://localhost:9999"
cfg.CannonSnapshotFreq = 500 cfg.CannonSnapshotFreq = 500
inputs := localGameInputs{ inputs := LocalGameInputs{
l1Head: common.Hash{0x11}, L1Head: common.Hash{0x11},
l2Head: common.Hash{0x22}, L2Head: common.Hash{0x22},
l2OutputRoot: common.Hash{0x33}, L2OutputRoot: common.Hash{0x33},
l2Claim: common.Hash{0x44}, L2Claim: common.Hash{0x44},
l2BlockNumber: big.NewInt(3333), L2BlockNumber: big.NewInt(3333),
} }
captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) { captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs) executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs)
...@@ -94,10 +94,10 @@ func TestGenerateProof(t *testing.T) { ...@@ -94,10 +94,10 @@ func TestGenerateProof(t *testing.T) {
require.NotContains(t, args, "--l2.genesis") require.NotContains(t, args, "--l2.genesis")
// Local game inputs // Local game inputs
require.Equal(t, inputs.l1Head.Hex(), args["--l1.head"]) require.Equal(t, inputs.L1Head.Hex(), args["--l1.head"])
require.Equal(t, inputs.l2Head.Hex(), args["--l2.head"]) require.Equal(t, inputs.L2Head.Hex(), args["--l2.head"])
require.Equal(t, inputs.l2OutputRoot.Hex(), args["--l2.outputroot"]) require.Equal(t, inputs.L2OutputRoot.Hex(), args["--l2.outputroot"])
require.Equal(t, inputs.l2Claim.Hex(), args["--l2.claim"]) require.Equal(t, inputs.L2Claim.Hex(), args["--l2.claim"])
require.Equal(t, "3333", args["--l2.blocknumber"]) require.Equal(t, "3333", args["--l2.blocknumber"])
}) })
......
...@@ -11,12 +11,12 @@ import ( ...@@ -11,12 +11,12 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
type localGameInputs struct { type LocalGameInputs struct {
l1Head common.Hash L1Head common.Hash
l2Head common.Hash L2Head common.Hash
l2OutputRoot common.Hash L2OutputRoot common.Hash
l2Claim common.Hash L2Claim common.Hash
l2BlockNumber *big.Int L2BlockNumber *big.Int
} }
type L2DataSource interface { type L2DataSource interface {
...@@ -32,30 +32,30 @@ type GameInputsSource interface { ...@@ -32,30 +32,30 @@ type GameInputsSource interface {
}, error) }, error)
} }
func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameInputsSource, l2Client L2DataSource) (localGameInputs, error) { func fetchLocalInputs(ctx context.Context, gameAddr common.Address, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) {
opts := &bind.CallOpts{Context: ctx} opts := &bind.CallOpts{Context: ctx}
l1Head, err := caller.L1Head(opts) l1Head, err := caller.L1Head(opts)
if err != nil { if err != nil {
return localGameInputs{}, fmt.Errorf("fetch L1 head for game %v: %w", gameAddr, err) return LocalGameInputs{}, fmt.Errorf("fetch L1 head for game %v: %w", gameAddr, err)
} }
proposals, err := caller.Proposals(opts) proposals, err := caller.Proposals(opts)
if err != nil { if err != nil {
return localGameInputs{}, fmt.Errorf("fetch proposals: %w", err) return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err)
} }
claimedOutput := proposals.Disputed claimedOutput := proposals.Disputed
agreedOutput := proposals.Starting agreedOutput := proposals.Starting
agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber) agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil { if err != nil {
return localGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err) return LocalGameInputs{}, fmt.Errorf("fetch L2 block header %v: %w", agreedOutput.L2BlockNumber, err)
} }
l2Head := agreedHeader.Hash() l2Head := agreedHeader.Hash()
return localGameInputs{ return LocalGameInputs{
l1Head: l1Head, L1Head: l1Head,
l2Head: l2Head, L2Head: l2Head,
l2OutputRoot: agreedOutput.OutputRoot, L2OutputRoot: agreedOutput.OutputRoot,
l2Claim: claimedOutput.OutputRoot, L2Claim: claimedOutput.OutputRoot,
l2BlockNumber: claimedOutput.L2BlockNumber, L2BlockNumber: claimedOutput.L2BlockNumber,
}, nil }, nil
} }
...@@ -39,11 +39,11 @@ func TestFetchLocalInputs(t *testing.T) { ...@@ -39,11 +39,11 @@ func TestFetchLocalInputs(t *testing.T) {
inputs, err := fetchLocalInputs(ctx, gameAddr, l1Client, l2Client) inputs, err := fetchLocalInputs(ctx, gameAddr, l1Client, l2Client)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Client.l1Head, inputs.l1Head) require.Equal(t, l1Client.l1Head, inputs.L1Head)
require.Equal(t, l2Client.header.Hash(), inputs.l2Head) require.Equal(t, l2Client.header.Hash(), inputs.L2Head)
require.EqualValues(t, l1Client.starting.OutputRoot, inputs.l2OutputRoot) require.EqualValues(t, l1Client.starting.OutputRoot, inputs.L2OutputRoot)
require.EqualValues(t, l1Client.disputed.OutputRoot, inputs.l2Claim) require.EqualValues(t, l1Client.disputed.OutputRoot, inputs.L2Claim)
require.Equal(t, l1Client.disputed.L2BlockNumber, inputs.l2BlockNumber) require.Equal(t, l1Client.disputed.L2BlockNumber, inputs.L2BlockNumber)
} }
type mockGameInputsSource struct { type mockGameInputsSource struct {
......
...@@ -60,16 +60,20 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -60,16 +60,20 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
if err != nil { if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", cfg.GameAddress, err) return nil, fmt.Errorf("create caller for game %v: %w", cfg.GameAddress, err)
} }
l1Head, err := fetchLocalInputs(ctx, cfg.GameAddress, gameCaller, l2Client) localInputs, err := fetchLocalInputs(ctx, cfg.GameAddress, gameCaller, l2Client)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
return NewTraceProviderFromInputs(logger, cfg, localInputs), nil
}
func NewTraceProviderFromInputs(logger log.Logger, cfg *config.Config, localInputs LocalGameInputs) *CannonTraceProvider {
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger, logger: logger,
dir: cfg.CannonDatadir, dir: cfg.CannonDatadir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, cfg, l1Head), generator: NewExecutor(logger, cfg, localInputs),
}, nil }
} }
func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) { func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
......
...@@ -36,6 +36,11 @@ func (s *Solver) NextMove(ctx context.Context, claim types.Claim, agreeWithClaim ...@@ -36,6 +36,11 @@ func (s *Solver) NextMove(ctx context.Context, claim types.Claim, agreeWithClaim
if claim.Depth() == s.gameDepth { if claim.Depth() == s.gameDepth {
return nil, types.ErrGameDepthReached return nil, types.ErrGameDepthReached
} }
if claim.IsRoot() {
// We can't defend the root claim so attack regardless of whether we agree or not
// This isn't necessarily the right solution...
return s.attack(ctx, claim)
}
agree, err := s.agreeWithClaim(ctx, claim.ClaimData) agree, err := s.agreeWithClaim(ctx, claim.ClaimData)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon" "github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
...@@ -12,8 +13,10 @@ import ( ...@@ -12,8 +13,10 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"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/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
) )
type CannonGameHelper struct { type CannonGameHelper struct {
...@@ -21,7 +24,7 @@ type CannonGameHelper struct { ...@@ -21,7 +24,7 @@ type CannonGameHelper struct {
} }
func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, name string, options ...challenger.Option) *challenger.Helper { func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{g.createConfigOption(rollupCfg, l2Genesis, l2Endpoint)} opts := []challenger.Option{createConfigOption(g.t, rollupCfg, l2Genesis, g.addr, l2Endpoint)}
opts = append(opts, options...) opts = append(opts, options...)
c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...) c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
g.t.Cleanup(func() { g.t.Cleanup(func() {
...@@ -31,10 +34,10 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu ...@@ -31,10 +34,10 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
} }
func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client bind.ContractCaller, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper { func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Client bind.ContractCaller, l1Endpoint string, l2Endpoint string, options ...challenger.Option) *HonestHelper {
opts := []challenger.Option{g.createConfigOption(rollupCfg, l2Genesis, l2Endpoint)} opts := []challenger.Option{createConfigOption(g.t, rollupCfg, l2Genesis, g.addr, l2Endpoint)}
opts = append(opts, options...) opts = append(opts, options...)
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...) cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlTrace).New("role", "CorrectTrace"), cfg, l1Client) provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client)
g.require.NoError(err, "create cannon trace provider") g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{ return &HonestHelper{
...@@ -45,28 +48,29 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -45,28 +48,29 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
} }
} }
func (g *CannonGameHelper) createConfigOption(rollupCfg *rollup.Config, l2Genesis *core.Genesis, l2Endpoint string) challenger.Option { func createConfigOption(t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis, gameAddr common.Address, l2Endpoint string) challenger.Option {
return func(c *config.Config) { return func(c *config.Config) {
c.GameAddress = g.addr require := require.New(t)
c.GameAddress = gameAddr
c.TraceType = config.TraceTypeCannon c.TraceType = config.TraceTypeCannon
c.AgreeWithProposedOutput = false c.AgreeWithProposedOutput = false
c.CannonL2 = l2Endpoint c.CannonL2 = l2Endpoint
c.CannonBin = "../cannon/bin/cannon" c.CannonBin = "../cannon/bin/cannon"
c.CannonDatadir = g.t.TempDir() c.CannonDatadir = t.TempDir()
c.CannonServer = "../op-program/bin/op-program" c.CannonServer = "../op-program/bin/op-program"
c.CannonAbsolutePreState = "../op-program/bin/prestate.json" c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
c.CannonSnapshotFreq = 10_000_000 c.CannonSnapshotFreq = 10_000_000
genesisBytes, err := json.Marshal(l2Genesis) genesisBytes, err := json.Marshal(l2Genesis)
g.require.NoError(err, "marshall l2 genesis config") require.NoError(err, "marshall l2 genesis config")
genesisFile := filepath.Join(c.CannonDatadir, "l2-genesis.json") genesisFile := filepath.Join(c.CannonDatadir, "l2-genesis.json")
g.require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644)) require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644))
c.CannonL2GenesisPath = genesisFile c.CannonL2GenesisPath = genesisFile
rollupBytes, err := json.Marshal(rollupCfg) rollupBytes, err := json.Marshal(rollupCfg)
g.require.NoError(err, "marshall rollup config") require.NoError(err, "marshall rollup config")
rollupFile := filepath.Join(c.CannonDatadir, "rollup.json") rollupFile := filepath.Join(c.CannonDatadir, "rollup.json")
g.require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644)) require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644))
c.CannonRollupConfigPath = rollupFile c.CannonRollupConfigPath = rollupFile
} }
} }
...@@ -42,7 +42,7 @@ func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) { ...@@ -42,7 +42,7 @@ func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr) g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr)
return actual.Cmp(big.NewInt(count)) == 0, nil return actual.Cmp(big.NewInt(count)) == 0, nil
}) })
g.require.NoError(err) g.require.NoErrorf(err, "Did not find expected claim count %v", count)
} }
type ContractClaim struct { type ContractClaim struct {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"math"
"math/big" "math/big"
"testing" "testing"
"time" "time"
...@@ -13,11 +14,16 @@ import ( ...@@ -13,11 +14,16 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"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"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -125,9 +131,57 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -125,9 +131,57 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
} }
func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Hash) *CannonGameHelper { func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Hash) *CannonGameHelper {
l2BlockNumber := h.waitForProposals(ctx) l2BlockNumber, l1Head := h.prepareCannonGame(ctx)
l1Head := h.checkpointL1Block(ctx) return h.createCannonGame(ctx, l2BlockNumber, l1Head, rootClaim)
}
func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, options ...challenger.Option) (*CannonGameHelper, *HonestHelper) {
l2BlockNumber, l1Head := h.prepareCannonGame(ctx)
challengerOpts := []challenger.Option{createConfigOption(h.t, rollupCfg, l2Genesis, common.Address{0xaa}, l2Endpoint)}
challengerOpts = append(challengerOpts, options...)
cfg := challenger.NewChallengerConfig(h.t, l1Endpoint, challengerOpts...)
opts := &bind.CallOpts{Context: ctx}
outputIdx, err := h.l2oo.GetL2OutputIndexAfter(opts, new(big.Int).SetUint64(l2BlockNumber))
h.require.NoError(err, "Fetch challenged output index")
challengedOutput, err := h.l2oo.GetL2Output(opts, outputIdx)
h.require.NoError(err, "Fetch challenged output")
agreedOutput, err := h.l2oo.GetL2Output(opts, new(big.Int).Sub(outputIdx, common.Big1))
h.require.NoError(err, "Fetch agreed output")
l1BlockInfo, err := h.blockOracle.Load(opts, l1Head)
h.require.NoError(err, "Fetch L1 block info")
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
h.require.NoErrorf(err, "Failed to dial l2 client %v", l2Endpoint)
}
defer l2Client.Close()
agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil {
h.require.NoErrorf(err, "Failed to fetch L2 block header %v", agreedOutput.L2BlockNumber)
}
inputs := cannon.LocalGameInputs{
L1Head: l1BlockInfo.Hash,
L2Head: agreedHeader.Hash(),
L2OutputRoot: agreedOutput.OutputRoot,
L2Claim: challengedOutput.OutputRoot,
L2BlockNumber: challengedOutput.L2BlockNumber,
}
provider := cannon.NewTraceProviderFromInputs(testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, inputs)
rootClaim, err := provider.Get(ctx, math.MaxUint64)
h.require.NoError(err, "Compute correct root hash")
game := h.createCannonGame(ctx, l2BlockNumber, l1Head, rootClaim)
honestHelper := &HonestHelper{
t: h.t,
require: h.require,
game: &game.FaultGameHelper,
correctTrace: provider,
}
return game, honestHelper
}
func (h *FactoryHelper) createCannonGame(ctx context.Context, l2BlockNumber uint64, l1Head *big.Int, rootClaim common.Hash) *CannonGameHelper {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
...@@ -171,6 +225,12 @@ func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, ...@@ -171,6 +225,12 @@ func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string,
return c return c
} }
func (h *FactoryHelper) prepareCannonGame(ctx context.Context) (uint64, *big.Int) {
l2BlockNumber := h.waitForProposals(ctx)
l1Head := h.checkpointL1Block(ctx)
return l2BlockNumber, l1Head
}
// waitForProposals waits until there are at least two proposals in the output oracle // waitForProposals waits until there are at least two proposals in the output oracle
// This is the minimum required for creating a game. // This is the minimum required for creating a game.
// Returns the l2 block number of the latest available proposal // Returns the l2 block number of the latest available proposal
......
...@@ -290,6 +290,53 @@ func TestCannonDefendStep(t *testing.T) { ...@@ -290,6 +290,53 @@ func TestCannonDefendStep(t *testing.T) {
game.LogGameData(ctx) game.LogGameData(ctx)
} }
func TestCannonChallengeWithCorrectRoot(t *testing.T) {
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
l1Endpoint := sys.NodeEndpoint("l1")
l2Endpoint := sys.NodeEndpoint("sequencer")
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game, correctTrace := disputeGameFactory.StartCannonGameWithCorrectRoot(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, func(c *config.Config) {
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Mallory)
})
require.NotNil(t, game)
game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Challenger", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
maxDepth := game.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
game.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount)
// Defend everything because we have the same trace as the honest proposer
correctTrace.Defend(ctx, claimCount-1)
claimCount++
game.LogGameData(ctx)
game.WaitForClaimCount(ctx, claimCount)
}
game.LogGameData(ctx)
// Wait for the challenger to call step and counter our invalid claim
game.WaitForClaimAtMaxDepth(ctx, true)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
}
func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) { func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier") delete(cfg.Nodes, "verifier")
......
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