Commit 8d8597a2 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #7135 from ethereum-optimism/aj/e2e-duplication

op-e2e: Remove duplicate logic for playing through a game
parents 28a6c6cc 0b828f3d
...@@ -142,6 +142,59 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status ...@@ -142,6 +142,59 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
g.require.NoErrorf(err, "wait for game status. Game state: \n%v", g.gameData(ctx)) g.require.NoErrorf(err, "wait for game status. Game state: \n%v", g.gameData(ctx))
} }
// Mover is a function that either attacks or defends the claim at parentClaimIdx
type Mover func(parentClaimIdx int64)
// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx
type Stepper func(parentClaimIdx int64)
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim.
// It is assumed that the output root being disputed is valid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
func (g *FaultGameHelper) DefendRootClaim(ctx context.Context, performMove Mover) {
maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
g.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter
g.WaitForClaimCount(ctx, claimCount)
// Respond with our own move
performMove(claimCount - 1)
claimCount++
g.WaitForClaimCount(ctx, claimCount)
}
// Wait for the challenger to call step and counter our invalid claim
g.WaitForClaimAtMaxDepth(ctx, true)
}
// ChallengeRootClaim uses the supplied Mover and Stepper to perform moves and steps in an attempt to challenge the root claim.
// It is assumed that the output root being disputed is invalid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it calls the Stepper to attempt to counter the leaf claim.
// Since the output root is invalid, it should not be possible for the Stepper to call step successfully.
func (g *FaultGameHelper) ChallengeRootClaim(ctx context.Context, performMove Mover, attemptStep Stepper) {
maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
g.LogGameData(ctx)
// Perform our move
performMove(claimCount - 1)
claimCount++
g.WaitForClaimCount(ctx, claimCount)
// Wait for the challenger to counter
claimCount++
g.WaitForClaimCount(ctx, claimCount)
}
// Confirm the game has reached max depth and the last claim hasn't been countered
g.WaitForClaimAtMaxDepth(ctx, false)
g.LogGameData(ctx)
// It's on us to call step if we want to win but shouldn't be possible
attemptStep(maxDepth)
}
func (g *FaultGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) { func (g *FaultGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) {
tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim) tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim)
g.require.NoError(err, "Attack transaction did not send") g.require.NoError(err, "Attack transaction did not send")
......
...@@ -245,7 +245,7 @@ func TestCannonDisputeGame(t *testing.T) { ...@@ -245,7 +245,7 @@ func TestCannonDisputeGame(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
defendAtClaim int64 defendClaimCount int64
}{ }{
{"StepFirst", 0}, {"StepFirst", 0},
{"StepMiddle", 28}, {"StepMiddle", 28},
...@@ -271,27 +271,15 @@ func TestCannonDisputeGame(t *testing.T) { ...@@ -271,27 +271,15 @@ func TestCannonDisputeGame(t *testing.T) {
challenger.WithPrivKey(sys.cfg.Secrets.Alice), challenger.WithPrivKey(sys.cfg.Secrets.Alice),
) )
maxDepth := game.MaxDepth(ctx) game.DefendRootClaim(
for claimCount := int64(1); claimCount < maxDepth; { ctx,
game.LogGameData(ctx) func(parentClaimIdx int64) {
claimCount++ if parentClaimIdx+1 == test.defendClaimCount {
// Wait for the challenger to counter game.Defend(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)})
game.WaitForClaimCount(ctx, claimCount)
// Post our own counter to the latest challenger claim
if claimCount == test.defendAtClaim {
// Defend one claim so we don't wind up executing from the absolute pre-state
game.Defend(ctx, claimCount-1, common.Hash{byte(claimCount)})
} else { } else {
game.Attack(ctx, claimCount-1, common.Hash{byte(claimCount)}) game.Attack(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)})
}
claimCount++
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)) sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client)) require.NoError(t, wait.ForNextBlock(ctx, l1Client))
...@@ -326,28 +314,15 @@ func TestCannonDefendStep(t *testing.T) { ...@@ -326,28 +314,15 @@ func TestCannonDefendStep(t *testing.T) {
challenger.WithPrivKey(sys.cfg.Secrets.Mallory), challenger.WithPrivKey(sys.cfg.Secrets.Mallory),
) )
maxDepth := game.MaxDepth(ctx) game.DefendRootClaim(ctx, func(parentClaimIdx int64) {
for claimCount := int64(1); claimCount < maxDepth; {
game.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount)
// Post invalid claims for most steps to get down into the early part of the trace // Post invalid claims for most steps to get down into the early part of the trace
if claimCount < 28 { if parentClaimIdx < 27 {
game.Attack(ctx, claimCount-1, common.Hash{byte(claimCount)}) game.Attack(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)})
} else { } else {
// Post our own counter but using the correct hash in low levels to force a defense step // Post our own counter but using the correct hash in low levels to force a defense step
correctTrace.Attack(ctx, claimCount-1) correctTrace.Attack(ctx, parentClaimIdx)
}
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)) sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client)) require.NoError(t, wait.ForNextBlock(ctx, l1Client))
...@@ -358,36 +333,60 @@ func TestCannonDefendStep(t *testing.T) { ...@@ -358,36 +333,60 @@ func TestCannonDefendStep(t *testing.T) {
func TestCannonProposedOutputRootInvalid(t *testing.T) { func TestCannonProposedOutputRootInvalid(t *testing.T) {
InitParallel(t) InitParallel(t)
honestStepsFail := func(ctx context.Context, correctTrace *disputegame.HonestHelper, parentClaimIdx int64) {
// Attack step should fail
correctTrace.StepFails(ctx, parentClaimIdx, true)
// Defending should fail too
correctTrace.StepFails(ctx, parentClaimIdx, false)
}
tests := []struct {
name string
outputRoot common.Hash
performMove func(ctx context.Context, correctTrace *disputegame.HonestHelper, parentClaimIdx int64)
performStep func(ctx context.Context, correctTrace *disputegame.HonestHelper, parentClaimIdx int64)
}{
{
name: "AttackWithCorrectTrace",
outputRoot: common.Hash{0xab},
performMove: func(ctx context.Context, correctTrace *disputegame.HonestHelper, parentClaimIdx int64) {
// Attack everything but oddly using the correct hash.
correctTrace.Attack(ctx, parentClaimIdx)
},
performStep: honestStepsFail,
},
{
name: "DefendWithCorrectTrace",
outputRoot: common.Hash{0xab},
performMove: func(ctx context.Context, correctTrace *disputegame.HonestHelper, parentClaimIdx int64) {
// Can only attack the root claim
if parentClaimIdx == 0 {
correctTrace.Attack(ctx, parentClaimIdx)
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) {
InitParallel(t)
ctx := context.Background() ctx := context.Background()
sys, l1Client, game, correctTrace := setupDisputeGameForInvalidOutputRoot(t, common.Hash{0x01, 0xab}) sys, l1Client, game, correctTrace := setupDisputeGameForInvalidOutputRoot(t, common.Hash{0xab})
t.Cleanup(sys.Close) t.Cleanup(sys.Close)
maxDepth := game.MaxDepth(ctx)
// Now maliciously play the game and it should be impossible to win // Now maliciously play the game and it should be impossible to win
game.ChallengeRootClaim(ctx,
for claimCount := int64(1); claimCount < maxDepth; { func(parentClaimIdx int64) {
// Attack everything but oddly using the correct hash. test.performMove(ctx, correctTrace, parentClaimIdx)
correctTrace.Attack(ctx, claimCount-1) },
claimCount++ func(parentClaimIdx int64) {
game.LogGameData(ctx) test.performStep(ctx, correctTrace, parentClaimIdx)
game.WaitForClaimCount(ctx, claimCount) })
game.LogGameData(ctx)
// Wait for the challenger to counter
claimCount++
game.WaitForClaimCount(ctx, claimCount)
}
game.LogGameData(ctx)
// Wait for the challenger to call step and counter our invalid claim
game.WaitForClaimAtMaxDepth(ctx, false)
// It's on us to call step if we want to win but shouldn't be possible
correctTrace.StepFails(ctx, maxDepth, true)
// Defending should fail too
correctTrace.StepFails(ctx, maxDepth, false)
// Time travel past when the game will be resolvable. // Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx)) sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
...@@ -395,6 +394,8 @@ func TestCannonProposedOutputRootInvalid(t *testing.T) { ...@@ -395,6 +394,8 @@ func TestCannonProposedOutputRootInvalid(t *testing.T) {
game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins) game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins)
game.LogGameData(ctx) game.LogGameData(ctx)
})
}
} }
// setupDisputeGameForInvalidOutputRoot sets up an L2 chain with at least one valid output root followed by an invalid output root. // setupDisputeGameForInvalidOutputRoot sets up an L2 chain with at least one valid output root followed by an invalid output root.
...@@ -413,7 +414,7 @@ func setupDisputeGameForInvalidOutputRoot(t *testing.T, outputRoot common.Hash) ...@@ -413,7 +414,7 @@ func setupDisputeGameForInvalidOutputRoot(t *testing.T, outputRoot common.Hash)
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
sys.L2OutputSubmitter = nil sys.L2OutputSubmitter = nil
// Submit an invalid output rooot // Submit an invalid output root
l2oo.PublishNextOutput(ctx, outputRoot) l2oo.PublishNextOutput(ctx, outputRoot)
l1Endpoint := sys.NodeEndpoint("l1") l1Endpoint := sys.NodeEndpoint("l1")
...@@ -458,23 +459,10 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) { ...@@ -458,23 +459,10 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) {
challenger.WithPrivKey(sys.cfg.Secrets.Alice), challenger.WithPrivKey(sys.cfg.Secrets.Alice),
) )
maxDepth := game.MaxDepth(ctx) game.DefendRootClaim(ctx, func(parentClaimIdx int64) {
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 // Defend everything because we have the same trace as the honest proposer
correctTrace.Defend(ctx, claimCount-1) correctTrace.Defend(ctx, parentClaimIdx)
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)) sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client)) require.NoError(t, wait.ForNextBlock(ctx, l1Client))
......
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