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
}
}
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 {
logger := testlog.Logger(h.t, log.LvlInfo).New("role", "OutputCannonGameHelper")
rollupClient := h.system.RollupClient(l2Node)
......@@ -353,15 +360,19 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.H
}
func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte {
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)
h.waitForBlockToBeSafe(l2Node, l2BlockNumber)
h.t.Logf("Creating game with l2 block number: %v", l2BlockNumber)
extraData := make([]byte, 32)
binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber)
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) {
l2BlockNumber = h.waitForProposals(ctx)
l1Head = h.checkpointL1Block(ctx)
......
......@@ -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.
func (g *OutputGameHelper) DefendRootClaim(ctx context.Context, performMove Mover) {
maxDepth := g.MaxDepth(ctx)
claimCount, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "Failed to get current claim count")
for claimCount := claimCount.Int64(); claimCount < maxDepth; {
for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; {
g.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter
......@@ -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.
func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove Mover, attemptStep Stepper) {
maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; {
g.LogGameData(ctx)
// Perform our move
performMove(claimCount - 1)
......@@ -368,6 +367,12 @@ func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove M
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) {
return g.waitForNewClaim(ctx, checkPoint, defaultTimeout)
}
......
......@@ -27,7 +27,7 @@ func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) {
game, claim := h.loadState(ctx, claimIdx)
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)
h.require.NoErrorf(err, "Get correct claim at position %v with g index %v", attackPos, attackPos.ToGIndex())
h.t.Log("Performing attack")
......
......@@ -174,3 +174,98 @@ func TestOutputCannonDefendStep(t *testing.T) {
game.LogGameData(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