Commit 150f704d authored by Adrian Sutton's avatar Adrian Sutton

op-e2e: Add output_cannon e2e test for disputing a valid output root.

parent f8de1ed1
...@@ -157,6 +157,13 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -157,6 +157,13 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
} }
} }
func (h *FactoryHelper) StartOutputCannonGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64) *OutputCannonGameHelper {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber)
output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber)
h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber)
return h.StartOutputCannonGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot))
}
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) *OutputCannonGameHelper {
logger := testlog.Logger(h.t, log.LvlInfo).New("role", "OutputCannonGameHelper") logger := testlog.Logger(h.t, log.LvlInfo).New("role", "OutputCannonGameHelper")
rollupClient := h.system.RollupClient(l2Node) rollupClient := h.system.RollupClient(l2Node)
...@@ -353,15 +360,19 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.H ...@@ -353,15 +360,19 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.H
} }
func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte { func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte {
l2Client := h.system.NodeClient(l2Node) h.waitForBlockToBeSafe(l2Node, l2BlockNumber)
_, err := geth.WaitForBlockToBeSafe(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute)
h.require.NoErrorf(err, "Block number %v did not become safe", l2BlockNumber)
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) {
l2Client := h.system.NodeClient(l2Node)
_, 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) createDisputeGameExtraData(ctx context.Context) (extraData []byte, l1Head *big.Int, l2BlockNumber uint64) { func (h *FactoryHelper) createDisputeGameExtraData(ctx context.Context) (extraData []byte, l1Head *big.Int, l2BlockNumber uint64) {
l2BlockNumber = h.waitForProposals(ctx) l2BlockNumber = h.waitForProposals(ctx)
l1Head = h.checkpointL1Block(ctx) l1Head = h.checkpointL1Block(ctx)
......
...@@ -323,9 +323,7 @@ type Stepper func(parentClaimIdx int64) ...@@ -323,9 +323,7 @@ type Stepper func(parentClaimIdx int64)
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step. // When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
func (g *OutputGameHelper) DefendRootClaim(ctx context.Context, performMove Mover) { func (g *OutputGameHelper) DefendRootClaim(ctx context.Context, performMove Mover) {
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
claimCount, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx}) for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; {
g.require.NoError(err, "Failed to get current claim count")
for claimCount := claimCount.Int64(); claimCount < maxDepth; {
g.LogGameData(ctx) g.LogGameData(ctx)
claimCount++ claimCount++
// Wait for the challenger to counter // Wait for the challenger to counter
...@@ -348,7 +346,8 @@ func (g *OutputGameHelper) DefendRootClaim(ctx context.Context, performMove Move ...@@ -348,7 +346,8 @@ func (g *OutputGameHelper) DefendRootClaim(ctx context.Context, performMove Move
// Since the output root is invalid, it should not be possible for the Stepper to call step successfully. // Since the output root is invalid, it should not be possible for the Stepper to call step successfully.
func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove Mover, attemptStep Stepper) { func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove Mover, attemptStep Stepper) {
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; {
g.LogGameData(ctx) g.LogGameData(ctx)
// Perform our move // Perform our move
performMove(claimCount - 1) performMove(claimCount - 1)
...@@ -368,6 +367,12 @@ func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove M ...@@ -368,6 +367,12 @@ func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove M
attemptStep(maxDepth) attemptStep(maxDepth)
} }
func (g *OutputGameHelper) getClaimCount(ctx context.Context) int64 {
claimCount, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "Failed to get current claim count")
return claimCount.Int64()
}
func (g *OutputGameHelper) WaitForNewClaim(ctx context.Context, checkPoint int64) (int64, error) { func (g *OutputGameHelper) WaitForNewClaim(ctx context.Context, checkPoint int64) (int64, error) {
return g.waitForNewClaim(ctx, checkPoint, defaultTimeout) return g.waitForNewClaim(ctx, checkPoint, defaultTimeout)
} }
......
...@@ -27,7 +27,7 @@ func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) { ...@@ -27,7 +27,7 @@ func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) {
game, claim := h.loadState(ctx, claimIdx) game, claim := h.loadState(ctx, claimIdx)
attackPos := claim.Position.Attack() attackPos := claim.Position.Attack()
h.t.Logf("Attacking at position %v with g index %v", attackPos, attackPos.ToGIndex()) h.t.Logf("Attacking claim %v at position %v with g index %v", claimIdx, attackPos, attackPos.ToGIndex())
value, err := h.correctTrace.Get(ctx, game, claim, attackPos) value, err := h.correctTrace.Get(ctx, game, claim, attackPos)
h.require.NoErrorf(err, "Get correct claim at position %v with g index %v", attackPos, attackPos.ToGIndex()) h.require.NoErrorf(err, "Get correct claim at position %v with g index %v", attackPos, attackPos.ToGIndex())
h.t.Log("Performing attack") h.t.Log("Performing attack")
......
...@@ -174,3 +174,98 @@ func TestOutputCannonDefendStep(t *testing.T) { ...@@ -174,3 +174,98 @@ func TestOutputCannonDefendStep(t *testing.T) {
game.LogGameData(ctx) game.LogGameData(ctx)
require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx)) require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx))
} }
func TestOutputCannonProposedOutputRootValid(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(outputCannonTestExecutor))
// honestStepsFail attempts to perform both an attack and defend step using the correct trace.
honestStepsFail := func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) {
// Attack step should fail
correctTrace.StepFails(ctx, parentClaimIdx, true)
// Defending should fail too
correctTrace.StepFails(ctx, parentClaimIdx, false)
}
tests := []struct {
// name is the name of the test
name string
// performMove is called to respond to each claim posted by the honest op-challenger.
// It should either attack or defend the claim at parentClaimIdx
performMove func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64)
// performStep is called once the maximum game depth is reached. It should perform a step to counter the
// claim at parentClaimIdx. Since the proposed output root is invalid, the step call should always revert.
performStep func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64)
}{
{
name: "AttackWithCorrectTrace",
performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) {
// Attack everything but oddly using the correct hash.
// Except the root of the cannon game must have an invalid VM status code.
splitDepth := game.SplitDepth(ctx)
if splitDepth == parentClaimIdx {
// TODO(client-pod#262): Verify that an attack with a valid status code is rejected
game.Attack(ctx, parentClaimIdx, common.Hash{0x01})
return
}
correctTrace.Attack(ctx, parentClaimIdx)
},
performStep: honestStepsFail,
},
{
name: "DefendWithCorrectTrace",
performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) {
splitDepth := game.SplitDepth(ctx)
// Can only attack the root claim or the first cannon claim
if parentClaimIdx == 0 {
correctTrace.Attack(ctx, parentClaimIdx)
return
}
// The root of the cannon game must have an invalid VM status code
// Attacking ensure we're running the cannon trace between two different blocks
// instead of being in the trace extension of the output root bisection
if splitDepth == parentClaimIdx {
// TODO(client-pod#262): Verify that an attack with a valid status code is rejected
game.Attack(ctx, parentClaimIdx, common.Hash{0x01})
return
}
// Otherwise, defend everything using the correct hash.
correctTrace.Defend(ctx, parentClaimIdx)
},
performStep: honestStepsFail,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UseExecutor(0))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1)
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory))
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Now maliciously play the game and it should be impossible to win
game.ChallengeRootClaim(ctx,
func(parentClaimIdx int64) {
test.performMove(ctx, game, correctTrace, parentClaimIdx)
},
func(parentClaimIdx int64) {
test.performStep(ctx, game, correctTrace, parentClaimIdx)
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, disputegame.StatusDefenderWins, game.Status(ctx))
})
}
}
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