Commit 1740df6a authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Convert output_cannon tests to use claim helper (#8743)

parent 21fa288c
...@@ -3,6 +3,7 @@ package disputegame ...@@ -3,6 +3,7 @@ package disputegame
import ( import (
"context" "context"
"fmt" "fmt"
"slices"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
...@@ -35,6 +36,10 @@ func (c *ClaimHelper) AgreesWithOutputRoot() bool { ...@@ -35,6 +36,10 @@ func (c *ClaimHelper) AgreesWithOutputRoot() bool {
return c.position.Depth()%2 == 0 return c.position.Depth()%2 == 0
} }
func (c *ClaimHelper) IsRootClaim() bool {
return c.position.IsRootPosition()
}
func (c *ClaimHelper) IsOutputRoot(ctx context.Context) bool { func (c *ClaimHelper) IsOutputRoot(ctx context.Context) bool {
splitDepth := c.game.SplitDepth(ctx) splitDepth := c.game.SplitDepth(ctx)
return int64(c.position.Depth()) <= splitDepth return int64(c.position.Depth()) <= splitDepth
...@@ -45,16 +50,25 @@ func (c *ClaimHelper) IsOutputRootLeaf(ctx context.Context) bool { ...@@ -45,16 +50,25 @@ func (c *ClaimHelper) IsOutputRootLeaf(ctx context.Context) bool {
return int64(c.position.Depth()) == splitDepth return int64(c.position.Depth()) == splitDepth
} }
func (c *ClaimHelper) IsBottomGameRoot(ctx context.Context) bool {
splitDepth := c.game.SplitDepth(ctx)
return int64(c.position.Depth()) == splitDepth+1
}
func (c *ClaimHelper) IsMaxDepth(ctx context.Context) bool { func (c *ClaimHelper) IsMaxDepth(ctx context.Context) bool {
maxDepth := c.game.MaxDepth(ctx) maxDepth := c.game.MaxDepth(ctx)
return int64(c.position.Depth()) == maxDepth return int64(c.position.Depth()) == maxDepth
} }
func (c *ClaimHelper) Depth() int64 {
return int64(c.position.Depth())
}
// WaitForCounterClaim waits for the claim to be countered by another claim being posted. // WaitForCounterClaim waits for the claim to be countered by another claim being posted.
// It returns a helper for the claim that countered this one. // It returns a helper for the claim that countered this one.
func (c *ClaimHelper) WaitForCounterClaim(ctx context.Context) *ClaimHelper { func (c *ClaimHelper) WaitForCounterClaim(ctx context.Context, ignoreClaims ...*ClaimHelper) *ClaimHelper {
counterIdx, counterClaim := c.game.waitForClaim(ctx, fmt.Sprintf("failed to find claim with parent idx %v", c.index), func(claim ContractClaim) bool { counterIdx, counterClaim := c.game.waitForClaim(ctx, fmt.Sprintf("failed to find claim with parent idx %v", c.index), func(claimIdx int64, claim ContractClaim) bool {
return int64(claim.ParentIndex) == c.index return int64(claim.ParentIndex) == c.index && !containsClaim(claimIdx, ignoreClaims)
}) })
return newClaimHelper(c.game, counterIdx, counterClaim) return newClaimHelper(c.game, counterIdx, counterClaim)
} }
...@@ -92,3 +106,23 @@ func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash) *ClaimHelpe ...@@ -92,3 +106,23 @@ func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash) *ClaimHelpe
func (c *ClaimHelper) RequireDifferentClaimValue(other *ClaimHelper) { func (c *ClaimHelper) RequireDifferentClaimValue(other *ClaimHelper) {
c.require.NotEqual(c.claim, other.claim, "should have posted different claims") c.require.NotEqual(c.claim, other.claim, "should have posted different claims")
} }
func (c *ClaimHelper) RequireOnlyCounteredBy(ctx context.Context, expected ...*ClaimHelper) {
claims := c.game.getAllClaims(ctx)
for idx, claim := range claims {
if int64(claim.ParentIndex) != c.index {
// Doesn't counter this claim, so ignore
continue
}
if !containsClaim(int64(idx), expected) {
// Found a countering claim not in the expected list. Fail.
c.require.FailNowf("Found unexpected countering claim", "Parent claim index: %v Game state:\n%v", c.index, c.game.gameData(ctx))
}
}
}
func containsClaim(claimIdx int64, haystack []*ClaimHelper) bool {
return slices.ContainsFunc(haystack, func(candidate *ClaimHelper) bool {
return candidate.index == claimIdx
})
}
...@@ -246,7 +246,7 @@ func (g *FaultGameHelper) WaitForInactivity(ctx context.Context, numInactiveBloc ...@@ -246,7 +246,7 @@ func (g *FaultGameHelper) WaitForInactivity(ctx context.Context, numInactiveBloc
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim. // 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. // 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. // 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) { func (g *FaultGameHelper) DefendRootClaim(ctx context.Context, performMove func(parentClaimIdx int64)) {
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; { for claimCount := int64(1); claimCount < maxDepth; {
g.LogGameData(ctx) g.LogGameData(ctx)
...@@ -268,7 +268,7 @@ func (g *FaultGameHelper) DefendRootClaim(ctx context.Context, performMove Mover ...@@ -268,7 +268,7 @@ func (g *FaultGameHelper) DefendRootClaim(ctx context.Context, performMove Mover
// It is assumed that the output root being disputed is invalid and that an honest op-challenger is already running. // 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. // 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. // 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) { func (g *FaultGameHelper) ChallengeRootClaim(ctx context.Context, performMove func(parentClaimIdx int64), attemptStep Stepper) {
maxDepth := g.MaxDepth(ctx) maxDepth := g.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; { for claimCount := int64(1); claimCount < maxDepth; {
g.LogGameData(ctx) g.LogGameData(ctx)
......
...@@ -145,7 +145,7 @@ func (g *OutputGameHelper) MaxDepth(ctx context.Context) int64 { ...@@ -145,7 +145,7 @@ func (g *OutputGameHelper) MaxDepth(ctx context.Context) int64 {
return depth.Int64() return depth.Int64()
} }
func (g *OutputGameHelper) waitForClaim(ctx context.Context, errorMsg string, predicate func(claim ContractClaim) bool) (int64, ContractClaim) { func (g *OutputGameHelper) waitForClaim(ctx context.Context, errorMsg string, predicate func(claimIdx int64, claim ContractClaim) bool) (int64, ContractClaim) {
timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel() defer cancel()
var matchedClaim ContractClaim var matchedClaim ContractClaim
...@@ -161,7 +161,7 @@ func (g *OutputGameHelper) waitForClaim(ctx context.Context, errorMsg string, pr ...@@ -161,7 +161,7 @@ func (g *OutputGameHelper) waitForClaim(ctx context.Context, errorMsg string, pr
if err != nil { if err != nil {
return false, fmt.Errorf("retrieve claim %v: %w", i, err) return false, fmt.Errorf("retrieve claim %v: %w", i, err)
} }
if predicate(claimData) { if predicate(i, claimData) {
matchClaimIdx = i matchClaimIdx = i
matchedClaim = claimData matchedClaim = claimData
return true, nil return true, nil
...@@ -206,10 +206,13 @@ func (g *OutputGameHelper) GetClaimValue(ctx context.Context, claimIdx int64) co ...@@ -206,10 +206,13 @@ func (g *OutputGameHelper) GetClaimValue(ctx context.Context, claimIdx int64) co
return claim.Claim return claim.Claim
} }
func (g *OutputGameHelper) GetClaimPosition(ctx context.Context, claimIdx int64) types.Position { func (g *OutputGameHelper) getAllClaims(ctx context.Context) []ContractClaim {
g.WaitForClaimCount(ctx, claimIdx+1) count := g.getClaimCount(ctx)
claim := g.getClaim(ctx, claimIdx) var claims []ContractClaim
return types.NewPositionFromGIndex(claim.Position) for i := int64(0); i < count; i++ {
claims = append(claims, g.getClaim(ctx, i))
}
return claims
} }
// getClaim retrieves the claim data for a specific index. // getClaim retrieves the claim data for a specific index.
...@@ -226,7 +229,7 @@ func (g *OutputGameHelper) WaitForClaimAtDepth(ctx context.Context, depth int) { ...@@ -226,7 +229,7 @@ func (g *OutputGameHelper) WaitForClaimAtDepth(ctx context.Context, depth int) {
g.waitForClaim( g.waitForClaim(
ctx, ctx,
fmt.Sprintf("Could not find claim depth %v", depth), fmt.Sprintf("Could not find claim depth %v", depth),
func(claim ContractClaim) bool { func(_ int64, claim ContractClaim) bool {
pos := types.NewPositionFromGIndex(claim.Position) pos := types.NewPositionFromGIndex(claim.Position)
return pos.Depth() == depth return pos.Depth() == depth
}) })
...@@ -237,7 +240,7 @@ func (g *OutputGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered ...@@ -237,7 +240,7 @@ func (g *OutputGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered
g.waitForClaim( g.waitForClaim(
ctx, ctx,
fmt.Sprintf("Could not find claim depth %v with countered=%v", maxDepth, countered), fmt.Sprintf("Could not find claim depth %v with countered=%v", maxDepth, countered),
func(claim ContractClaim) bool { func(_ int64, claim ContractClaim) bool {
pos := types.NewPositionFromGIndex(claim.Position) pos := types.NewPositionFromGIndex(claim.Position)
return int64(pos.Depth()) == maxDepth && claim.Countered == countered return int64(pos.Depth()) == maxDepth && claim.Countered == countered
}) })
...@@ -325,50 +328,42 @@ func (g *OutputGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlo ...@@ -325,50 +328,42 @@ func (g *OutputGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlo
} }
// Mover is a function that either attacks or defends the claim at parentClaimIdx // Mover is a function that either attacks or defends the claim at parentClaimIdx
type Mover func(parentClaimIdx int64) type Mover func(parent *ClaimHelper) *ClaimHelper
// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx // Stepper is a function that attempts to perform a step against the claim at parentClaimIdx
type Stepper func(parentClaimIdx int64) type Stepper func(parentClaimIdx int64)
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim. // DefendClaim uses the supplied Mover to perform moves in an attempt to defend the supplied claim.
// It is assumed that the output root being disputed is valid and that an honest op-challenger is already running. // It is assumed that the specified claim is invalid 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. // 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) DefendClaim(ctx context.Context, claim *ClaimHelper, performMove Mover) {
maxDepth := g.MaxDepth(ctx) g.t.Logf("Defending claim %v at depth %v", claim.index, claim.Depth())
for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; { for !claim.IsMaxDepth(ctx) {
g.LogGameData(ctx) g.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter // Wait for the challenger to counter
g.WaitForClaimCount(ctx, claimCount) claim = claim.WaitForCounterClaim(ctx)
g.LogGameData(ctx) g.LogGameData(ctx)
// Respond with our own move // Respond with our own move
performMove(claimCount - 1) claim = performMove(claim)
claimCount++
g.WaitForClaimCount(ctx, claimCount)
} }
// Wait for the challenger to call step and counter our invalid claim claim.WaitForCountered(ctx)
g.WaitForClaimAtMaxDepth(ctx, true)
} }
// ChallengeRootClaim uses the supplied Mover and Stepper to perform moves and steps in an attempt to challenge the root claim. // ChallengeClaim uses the supplied functions to perform moves and steps in an attempt to challenge the supplied claim.
// It is assumed that the output root being disputed is invalid and that an honest op-challenger is already running. // It is assumed that the claim being disputed is valid 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. // 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. // Since the output root is valid, 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) ChallengeClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, attemptStep Stepper) {
maxDepth := g.MaxDepth(ctx) for !claim.IsMaxDepth(ctx) {
for claimCount := g.getClaimCount(ctx); claimCount < maxDepth; {
g.LogGameData(ctx) g.LogGameData(ctx)
// Perform our move // Perform our move
performMove(claimCount - 1) claim = performMove(claim)
claimCount++
g.WaitForClaimCount(ctx, claimCount)
// Wait for the challenger to counter // Wait for the challenger to counter
claimCount++ g.LogGameData(ctx)
g.WaitForClaimCount(ctx, claimCount) claim = claim.WaitForCounterClaim(ctx)
} }
// Confirm the game has reached max depth and the last claim hasn't been countered // Confirm the game has reached max depth and the last claim hasn't been countered
...@@ -376,7 +371,7 @@ func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove M ...@@ -376,7 +371,7 @@ func (g *OutputGameHelper) ChallengeRootClaim(ctx context.Context, performMove M
g.LogGameData(ctx) g.LogGameData(ctx)
// It's on us to call step if we want to win but shouldn't be possible // It's on us to call step if we want to win but shouldn't be possible
attemptStep(maxDepth) attemptStep(claim.index)
} }
func (g *OutputGameHelper) getClaimCount(ctx context.Context) int64 { func (g *OutputGameHelper) getClaimCount(ctx context.Context) int64 {
...@@ -405,6 +400,7 @@ func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64 ...@@ -405,6 +400,7 @@ func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64
} }
func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) { func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) {
g.t.Logf("Attacking claim %v with value %v", claimIdx, claim)
tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim) tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim)
if err != nil { if err != nil {
g.require.NoErrorf(err, "Attack transaction did not send. Game state: \n%v", g.gameData(ctx)) g.require.NoErrorf(err, "Attack transaction did not send. Game state: \n%v", g.gameData(ctx))
...@@ -416,6 +412,7 @@ func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim com ...@@ -416,6 +412,7 @@ func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim com
} }
func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash) { func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash) {
g.t.Logf("Defending claim %v with value %v", claimIdx, claim)
tx, err := g.game.Defend(g.opts, big.NewInt(claimIdx), claim) tx, err := g.game.Defend(g.opts, big.NewInt(claimIdx), claim)
if err != nil { if err != nil {
g.require.NoErrorf(err, "Defend transaction did not send. Game state: \n%v", g.gameData(ctx)) g.require.NoErrorf(err, "Defend transaction did not send. Game state: \n%v", g.gameData(ctx))
......
...@@ -18,6 +18,16 @@ type OutputHonestHelper struct { ...@@ -18,6 +18,16 @@ type OutputHonestHelper struct {
correctTrace types.TraceAccessor correctTrace types.TraceAccessor
} }
func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper) *ClaimHelper {
h.Attack(ctx, claim.index)
return claim.WaitForCounterClaim(ctx)
}
func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper) *ClaimHelper {
h.Defend(ctx, claim.index)
return claim.WaitForCounterClaim(ctx)
}
func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) { func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) {
// Ensure the claim exists // Ensure the claim exists
h.game.WaitForClaimCount(ctx, claimIdx+1) h.game.WaitForClaimCount(ctx, claimIdx+1)
......
...@@ -123,18 +123,19 @@ func TestOutputCannonDisputeGame(t *testing.T) { ...@@ -123,18 +123,19 @@ func TestOutputCannonDisputeGame(t *testing.T) {
require.NotNil(t, game) require.NotNil(t, game)
game.LogGameData(ctx) game.LogGameData(ctx)
game.DisputeLastBlock(ctx) outputClaim := game.DisputeLastBlock(ctx)
splitDepth := game.SplitDepth(ctx) splitDepth := game.SplitDepth(ctx)
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
game.DefendRootClaim( game.DefendClaim(
ctx, ctx,
func(parentClaimIdx int64) { outputClaim,
if parentClaimIdx+1 == splitDepth+test.defendClaimDepth { func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
game.Defend(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)}) if claim.Depth()+1 == splitDepth+test.defendClaimDepth {
return claim.Defend(ctx, common.Hash{byte(claim.Depth())})
} else { } else {
game.Attack(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)}) return claim.Attack(ctx, common.Hash{byte(claim.Depth())})
} }
}) })
...@@ -157,21 +158,21 @@ func TestOutputCannonDefendStep(t *testing.T) { ...@@ -157,21 +158,21 @@ func TestOutputCannonDefendStep(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa})
require.NotNil(t, game) require.NotNil(t, game)
game.DisputeLastBlock(ctx) outputRootClaim := game.DisputeLastBlock(ctx)
game.LogGameData(ctx) game.LogGameData(ctx)
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory))
splitDepth := game.SplitDepth(ctx) maxDepth := game.MaxDepth(ctx)
game.DefendRootClaim(ctx, func(parentClaimIdx int64) { game.DefendClaim(ctx, outputRootClaim, func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
// 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 parentClaimIdx < splitDepth+27 { if claim.Depth() < maxDepth-3 {
game.Attack(ctx, parentClaimIdx, common.Hash{byte(parentClaimIdx)}) return claim.Attack(ctx, common.Hash{0xaa})
} 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, parentClaimIdx) return correctTrace.AttackClaim(ctx, claim)
} }
}) })
...@@ -198,7 +199,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { ...@@ -198,7 +199,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
// performMove is called to respond to each claim posted by the honest op-challenger. // performMove is called to respond to each claim posted by the honest op-challenger.
// It should either attack or defend the claim at parentClaimIdx // It should either attack or defend the claim at parentClaimIdx
performMove func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) performMove func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, claim *disputegame.ClaimHelper) *disputegame.ClaimHelper
// performStep is called once the maximum game depth is reached. It should perform a step to counter the // 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. // claim at parentClaimIdx. Since the proposed output root is invalid, the step call should always revert.
...@@ -206,38 +207,33 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { ...@@ -206,38 +207,33 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
}{ }{
{ {
name: "AttackWithCorrectTrace", name: "AttackWithCorrectTrace",
performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) { performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
// Attack everything but oddly using the correct hash. // Attack everything but oddly using the correct hash.
// Except the root of the cannon game must have an invalid VM status code. // Except the root of the cannon game must have an invalid VM status code.
splitDepth := game.SplitDepth(ctx) if claim.IsOutputRootLeaf(ctx) {
if splitDepth == parentClaimIdx {
// TODO(client-pod#262): Verify that an attack with a valid status code is rejected // TODO(client-pod#262): Verify that an attack with a valid status code is rejected
game.Attack(ctx, parentClaimIdx, common.Hash{0x01}) return claim.Attack(ctx, common.Hash{0x01})
return
} }
correctTrace.Attack(ctx, parentClaimIdx) return correctTrace.AttackClaim(ctx, claim)
}, },
performStep: honestStepsFail, performStep: honestStepsFail,
}, },
{ {
name: "DefendWithCorrectTrace", name: "DefendWithCorrectTrace",
performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) { performMove: func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
splitDepth := game.SplitDepth(ctx)
// Can only attack the root claim or the first cannon claim // Can only attack the root claim or the first cannon claim
if parentClaimIdx == 0 { if claim.IsRootClaim() {
correctTrace.Attack(ctx, parentClaimIdx) return correctTrace.AttackClaim(ctx, claim)
return
} }
// The root of the cannon game must have an invalid VM status code // 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 // Attacking ensure we're running the cannon trace between two different blocks
// instead of being in the trace extension of the output root bisection // instead of being in the trace extension of the output root bisection
if splitDepth == parentClaimIdx { if claim.IsOutputRootLeaf(ctx) {
// TODO(client-pod#262): Verify that an attack with a valid status code is rejected // TODO(client-pod#262): Verify that an attack with a valid status code is rejected
game.Attack(ctx, parentClaimIdx, common.Hash{0x01}) return claim.Attack(ctx, common.Hash{0x01})
return
} }
// Otherwise, defend everything using the correct hash. // Otherwise, defend everything using the correct hash.
correctTrace.Defend(ctx, parentClaimIdx) return correctTrace.DefendClaim(ctx, claim)
}, },
performStep: honestStepsFail, performStep: honestStepsFail,
}, },
...@@ -259,9 +255,10 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { ...@@ -259,9 +255,10 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// 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, game.ChallengeClaim(ctx,
func(parentClaimIdx int64) { game.RootClaim(ctx),
test.performMove(ctx, game, correctTrace, parentClaimIdx) func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return test.performMove(ctx, game, correctTrace, claim)
}, },
func(parentClaimIdx int64) { func(parentClaimIdx int64) {
test.performStep(ctx, game, correctTrace, parentClaimIdx) test.performStep(ctx, game, correctTrace, parentClaimIdx)
...@@ -291,52 +288,48 @@ func TestOutputCannonPoisonedPostState(t *testing.T) { ...@@ -291,52 +288,48 @@ func TestOutputCannonPoisonedPostState(t *testing.T) {
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Honest first attack at "honest" level // Honest first attack at "honest" level
correctTrace.Attack(ctx, 0) claim := correctTrace.AttackClaim(ctx, game.RootClaim(ctx))
// Honest defense at "dishonest" level // Honest defense at "dishonest" level
correctTrace.Defend(ctx, 1) claim = correctTrace.DefendClaim(ctx, claim)
// Dishonest attack at "honest" level - honest move would be to ignore // Dishonest attack at "honest" level - honest move would be to ignore
game.Attack(ctx, 2, common.Hash{0x03, 0xaa}) claimToIgnore1 := claim.Attack(ctx, common.Hash{0x03, 0xaa})
// Honest attack at "dishonest" level - honest move would be to ignore // Honest attack at "dishonest" level - honest move would be to ignore
correctTrace.Attack(ctx, 3) claimToIgnore2 := correctTrace.AttackClaim(ctx, claimToIgnore1)
game.LogGameData(ctx) game.LogGameData(ctx)
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
// Start dishonest challenger that posts correct claims // Start dishonest challenger that posts correct claims
// It participates in the subgame root the honest claim index 4
claimCount := int64(5)
depth := game.MaxDepth(ctx)
splitDepth := game.SplitDepth(ctx)
for { for {
game.LogGameData(ctx) game.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter // Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount) // Note that we need to ignore claimToIgnore1 which already counters this...
claim = claim.WaitForCounterClaim(ctx, claimToIgnore1)
// Respond with our own move // Respond with our own move
if claimCount == splitDepth+4 { if claim.IsBottomGameRoot(ctx) {
// Root of the cannon game must have the right VM status code (so it can't be honest). // Root of the cannon game must have the right VM status code (so it can't be honest).
// Note this occurs when there are splitDepth + 4 claims because there are multiple forks in this game. // Note this occurs when there are splitDepth + 4 claims because there are multiple forks in this game.
game.Attack(ctx, claimCount-1, common.Hash{0x01}) claim = claim.Attack(ctx, common.Hash{0x01})
} else { } else {
correctTrace.Defend(ctx, claimCount-1) claim = correctTrace.DefendClaim(ctx, claim)
} }
claimCount++
game.WaitForClaimCount(ctx, claimCount)
// Defender moves last. If we're at max depth, then we're done // Defender moves last. If we're at max depth, then we're done
pos := game.GetClaimPosition(ctx, claimCount-1) if claim.IsMaxDepth(ctx) {
if int64(pos.Depth()) == depth {
break break
} }
} }
// Wait for the challenger to drive the subgame at 4 to the leaf node, which should be countered // Wait for the challenger to call step
game.WaitForClaimAtMaxDepth(ctx, true) claim.WaitForCountered(ctx)
// Verify that the challenger didn't challenge our poisoned claims
claimToIgnore1.RequireOnlyCounteredBy(ctx, claimToIgnore2)
claimToIgnore2.RequireOnlyCounteredBy(ctx /* nothing */)
// 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))
......
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