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

op-e2e: Add failing e2e test for proposals not supported by L1 data (#9662)

parent 9b8ebcec
...@@ -58,6 +58,24 @@ func (s Status) String() string { ...@@ -58,6 +58,24 @@ func (s Status) String() string {
} }
} }
type gameCfg struct {
allowUnsafe bool
}
type GameOpt interface {
Apply(cfg *gameCfg)
}
type gameOptFn func(c *gameCfg)
func (g gameOptFn) Apply(cfg *gameCfg) {
g(cfg)
}
func WithUnsafeProposal() GameOpt {
return gameOptFn(func(c *gameCfg) {
c.allowUnsafe = true
})
}
type DisputeSystem interface { type DisputeSystem interface {
L1BeaconEndpoint() string L1BeaconEndpoint() string
NodeEndpoint(name string) string NodeEndpoint(name string) string
...@@ -121,18 +139,28 @@ func (h *FactoryHelper) PreimageHelper(ctx context.Context) *preimage.Helper { ...@@ -121,18 +139,28 @@ func (h *FactoryHelper) PreimageHelper(ctx context.Context) *preimage.Helper {
return preimage.NewHelper(h.t, h.opts, h.client, oracleAddr) return preimage.NewHelper(h.t, h.opts, h.client, oracleAddr)
} }
func (h *FactoryHelper) StartOutputCannonGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64) *OutputCannonGameHelper { func newGameCfg(opts ...GameOpt) *gameCfg {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber) cfg := &gameCfg{}
for _, opt := range opts {
opt.Apply(cfg)
}
return cfg
}
func (h *FactoryHelper) StartOutputCannonGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64, opts ...GameOpt) *OutputCannonGameHelper {
cfg := newGameCfg(opts...)
h.waitForBlock(l2Node, l2BlockNumber, cfg)
output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber) output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber)
h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber) h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber)
return h.StartOutputCannonGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot)) return h.StartOutputCannonGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot), opts...)
} }
func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash) *OutputCannonGameHelper { func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash, opts ...GameOpt) *OutputCannonGameHelper {
cfg := newGameCfg(opts...)
logger := testlog.Logger(h.t, log.LevelInfo).New("role", "OutputCannonGameHelper") logger := testlog.Logger(h.t, log.LevelInfo).New("role", "OutputCannonGameHelper")
rollupClient := h.system.RollupClient(l2Node) rollupClient := h.system.RollupClient(l2Node)
extraData := h.createBisectionGameExtraData(l2Node, l2BlockNumber) extraData := h.createBisectionGameExtraData(l2Node, l2BlockNumber, cfg)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
...@@ -174,18 +202,20 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string ...@@ -174,18 +202,20 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
} }
} }
func (h *FactoryHelper) StartOutputAlphabetGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64) *OutputAlphabetGameHelper { func (h *FactoryHelper) StartOutputAlphabetGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64, opts ...GameOpt) *OutputAlphabetGameHelper {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber) cfg := newGameCfg(opts...)
h.waitForBlock(l2Node, l2BlockNumber, cfg)
output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber) output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber)
h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber) h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber)
return h.StartOutputAlphabetGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot)) return h.StartOutputAlphabetGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot))
} }
func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash) *OutputAlphabetGameHelper { func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash, opts ...GameOpt) *OutputAlphabetGameHelper {
cfg := newGameCfg(opts...)
logger := testlog.Logger(h.t, log.LevelInfo).New("role", "OutputAlphabetGameHelper") logger := testlog.Logger(h.t, log.LevelInfo).New("role", "OutputAlphabetGameHelper")
rollupClient := h.system.RollupClient(l2Node) rollupClient := h.system.RollupClient(l2Node)
extraData := h.createBisectionGameExtraData(l2Node, l2BlockNumber) extraData := h.createBisectionGameExtraData(l2Node, l2BlockNumber, cfg)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
...@@ -227,18 +257,23 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri ...@@ -227,18 +257,23 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
} }
} }
func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte { func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64, cfg *gameCfg) []byte {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber) h.waitForBlock(l2Node, l2BlockNumber, cfg)
h.t.Logf("Creating game with l2 block number: %v", l2BlockNumber) h.t.Logf("Creating game with l2 block number: %v", l2BlockNumber)
extraData := make([]byte, 32) extraData := make([]byte, 32)
binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber) binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber)
return extraData return extraData
} }
func (h *FactoryHelper) waitForBlockToBeSafe(l2Node string, l2BlockNumber uint64) { func (h *FactoryHelper) waitForBlock(l2Node string, l2BlockNumber uint64, cfg *gameCfg) {
l2Client := h.system.NodeClient(l2Node) l2Client := h.system.NodeClient(l2Node)
_, err := geth.WaitForBlockToBeSafe(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute) if cfg.allowUnsafe {
h.require.NoErrorf(err, "Block number %v did not become safe", l2BlockNumber) _, err := geth.WaitForBlock(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute)
h.require.NoErrorf(err, "Block number %v did not become unsafe", l2BlockNumber)
} else {
_, err := geth.WaitForBlockToBeSafe(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute)
h.require.NoErrorf(err, "Block number %v did not become safe", l2BlockNumber)
}
} }
func (h *FactoryHelper) StartChallenger(ctx context.Context, name string, options ...challenger.Option) *challenger.Helper { func (h *FactoryHelper) StartChallenger(ctx context.Context, name string, options ...challenger.Option) *challenger.Helper {
......
...@@ -18,13 +18,24 @@ type OutputHonestHelper struct { ...@@ -18,13 +18,24 @@ type OutputHonestHelper struct {
correctTrace types.TraceAccessor correctTrace types.TraceAccessor
} }
func (h *OutputHonestHelper) CounterClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper {
game, target := h.loadState(ctx, claim.index)
value, err := h.correctTrace.Get(ctx, game, target, target.Position)
h.require.NoErrorf(err, "Failed to determine correct claim at position %v with g index %v", target.Position, target.Position.ToGIndex())
if value == claim.claim {
return h.DefendClaim(ctx, claim, opts...)
} else {
return h.AttackClaim(ctx, claim, opts...)
}
}
func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper { func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper {
h.Attack(ctx, claim.index, opts...) h.Attack(ctx, claim.index, opts...)
return claim.WaitForCounterClaim(ctx) return claim.WaitForCounterClaim(ctx)
} }
func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper) *ClaimHelper { func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper {
h.Defend(ctx, claim.index) h.Defend(ctx, claim.index, opts...)
return claim.WaitForCounterClaim(ctx) return claim.WaitForCounterClaim(ctx)
} }
......
...@@ -660,3 +660,77 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) { ...@@ -660,3 +660,77 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) {
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx) game.LogGameData(ctx)
} }
func TestInvalidateUnsafeProposal(t *testing.T) {
// TODO(client-pod#540) Fix and enable TestInvalidateUnsafeProposal
t.Skip("Agreed head not correctly restricted yet")
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
tests := []struct {
name string
strategy func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper
}{
{
name: "Attack",
strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return correctTrace.AttackClaim(ctx, parent)
},
},
{
name: "Defend",
strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return correctTrace.DefendClaim(ctx, parent)
},
},
{
name: "Counter",
strategy: func(correctTrace *disputegame.OutputHonestHelper, parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return correctTrace.CounterClaim(ctx, parent)
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
sys, l1Client := startFaultDisputeSystem(t, withSequencerWindowSize(1000))
t.Cleanup(sys.Close)
// Wait for the safe head to advance at least one block to init the safe head database
require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), 1))
// Now stop the batcher so the safe head doesn't advance any further
require.NoError(t, sys.BatchSubmitter.Stop(ctx))
// Wait for the unsafe head to advance to be sure it is beyond the safe head
require.NoError(t, wait.ForNextBlock(ctx, sys.NodeClient("sequencer")))
blockNum, err := sys.NodeClient("sequencer").BlockNumber(ctx)
require.NoError(t, err)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is _dishonest_ because the required data is not available on L1
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal())
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger
game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
if parent.IsBottomGameRoot(ctx) {
return correctTrace.AttackClaim(ctx, parent)
}
return test.strategy(correctTrace, parent)
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
})
}
}
...@@ -41,6 +41,12 @@ func withEcotone() faultDisputeConfigOpts { ...@@ -41,6 +41,12 @@ func withEcotone() faultDisputeConfigOpts {
} }
} }
func withSequencerWindowSize(size uint64) faultDisputeConfigOpts {
return func(cfg *op_e2e.SystemConfig) {
cfg.DeployConfig.SequencerWindowSize = size
}
}
func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_e2e.System, *ethclient.Client) { func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_e2e.System, *ethclient.Client) {
cfg := op_e2e.DefaultSystemConfig(t) cfg := op_e2e.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