Commit cd59d5e1 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #7200 from ethereum-optimism/aj/action-rules

op-challenger: Define basic validation rules for all honest actions.
parents f8c704db eabf282d
...@@ -10,13 +10,11 @@ import ( ...@@ -10,13 +10,11 @@ import (
type GameSolver struct { type GameSolver struct {
claimSolver *claimSolver claimSolver *claimSolver
gameDepth int
} }
func NewGameSolver(gameDepth int, trace types.TraceProvider) *GameSolver { func NewGameSolver(gameDepth int, trace types.TraceProvider) *GameSolver {
return &GameSolver{ return &GameSolver{
claimSolver: newClaimSolver(gameDepth, trace), claimSolver: newClaimSolver(gameDepth, trace),
gameDepth: gameDepth,
} }
} }
...@@ -26,7 +24,7 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ...@@ -26,7 +24,7 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game)
for _, claim := range game.Claims() { for _, claim := range game.Claims() {
var action *types.Action var action *types.Action
var err error var err error
if claim.Depth() == s.gameDepth { if uint64(claim.Depth()) == game.MaxDepth() {
action, err = s.calculateStep(ctx, game, claim) action, err = s.calculateStep(ctx, game, claim)
} else { } else {
action, err = s.calculateMove(ctx, game, claim) action, err = s.calculateMove(ctx, game, claim)
...@@ -69,7 +67,7 @@ func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim t ...@@ -69,7 +67,7 @@ func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim t
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to calculate next move for claim index %v: %w", claim.ContractIndex, err) return nil, fmt.Errorf("failed to calculate next move for claim index %v: %w", claim.ContractIndex, err)
} }
if move == nil || game.IsDuplicate(*move) { if move == nil || game.IsDuplicate(move.ClaimData) {
return nil, nil return nil, nil
} }
return &types.Action{ return &types.Action{
......
...@@ -102,6 +102,8 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -102,6 +102,8 @@ func TestCalculateNextActions(t *testing.T) {
for i, action := range actions { for i, action := range actions {
t.Logf("Move %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", t.Logf("Move %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v",
i, action.Type, action.ParentIdx, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) i, action.Type, action.ParentIdx, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData))
// Check that every move the solver returns meets the generic validation rules
require.NoError(t, checkRules(game, action), "Attempting to perform invalid action")
} }
for i, action := range builder.ExpectedActions { for i, action := range builder.ExpectedActions {
t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v",
......
package solver
import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
type actionRule func(game types.Game, action types.Action) error
var rules = []actionRule{
parentMustExist,
onlyStepAtMaxDepth,
onlyMoveBeforeMaxDepth,
onlyCounterClaimsAtDisagreeingLevels,
doNotDuplicateExistingMoves,
doNotDefendRootClaim,
}
func checkRules(game types.Game, action types.Action) error {
var errs []error
for _, rule := range rules {
errs = append(errs, rule(game, action))
}
return errors.Join(errs...)
}
func parentMustExist(game types.Game, action types.Action) error {
if len(game.Claims()) <= action.ParentIdx || action.ParentIdx < 0 {
return fmt.Errorf("parent claim %v does not exist in game with %v claims", action.ParentIdx, len(game.Claims()))
}
return nil
}
func onlyStepAtMaxDepth(game types.Game, action types.Action) error {
if action.Type == types.ActionTypeStep {
return nil
}
parentDepth := uint64(game.Claims()[action.ParentIdx].Position.Depth())
if parentDepth >= game.MaxDepth() {
return fmt.Errorf("parent at max depth (%v) but attempting to perform %v action instead of step",
parentDepth, action.Type)
}
return nil
}
func onlyMoveBeforeMaxDepth(game types.Game, action types.Action) error {
if action.Type == types.ActionTypeMove {
return nil
}
parentDepth := uint64(game.Claims()[action.ParentIdx].Position.Depth())
if parentDepth < game.MaxDepth() {
return fmt.Errorf("parent (%v) not at max depth (%v) but attempting to perform %v action instead of move",
parentDepth, game.MaxDepth(), action.Type)
}
return nil
}
func onlyCounterClaimsAtDisagreeingLevels(game types.Game, action types.Action) error {
parentClaim := game.Claims()[action.ParentIdx]
if game.AgreeWithClaimLevel(parentClaim) {
return fmt.Errorf("countering a claim at depth %v that supports our view of the root", parentClaim.Position.Depth())
}
return nil
}
func doNotDuplicateExistingMoves(game types.Game, action types.Action) error {
newClaimData := types.ClaimData{
Value: action.Value,
Position: resultingPosition(game, action),
}
if game.IsDuplicate(newClaimData) {
return fmt.Errorf("creating duplicate claim at %v with value %v", newClaimData.Position.ToGIndex(), newClaimData.Value)
}
return nil
}
func doNotDefendRootClaim(game types.Game, action types.Action) error {
if game.Claims()[action.ParentIdx].IsRootPosition() && !action.IsAttack {
return fmt.Errorf("defending the root claim at idx %v", action.ParentIdx)
}
return nil
}
func resultingPosition(game types.Game, action types.Action) types.Position {
parentPos := game.Claims()[action.ParentIdx].Position
if action.Type == types.ActionTypeStep {
return parentPos
}
if action.IsAttack {
return parentPos.Attack()
}
return parentPos.Defend()
}
...@@ -24,10 +24,12 @@ type Game interface { ...@@ -24,10 +24,12 @@ type Game interface {
Claims() []Claim Claims() []Claim
// IsDuplicate returns true if the provided [Claim] already exists in the game state. // IsDuplicate returns true if the provided [Claim] already exists in the game state.
IsDuplicate(claim Claim) bool IsDuplicate(claim ClaimData) bool
// AgreeWithClaimLevel returns if the game state agrees with the provided claim level. // AgreeWithClaimLevel returns if the game state agrees with the provided claim level.
AgreeWithClaimLevel(claim Claim) bool AgreeWithClaimLevel(claim Claim) bool
MaxDepth() uint64
} }
type extendedClaim struct { type extendedClaim struct {
...@@ -85,7 +87,7 @@ func (g *gameState) PutAll(claims []Claim) error { ...@@ -85,7 +87,7 @@ func (g *gameState) PutAll(claims []Claim) error {
// Put adds a claim into the game state. // Put adds a claim into the game state.
func (g *gameState) Put(claim Claim) error { func (g *gameState) Put(claim Claim) error {
if claim.IsRoot() || g.IsDuplicate(claim) { if claim.IsRoot() || g.IsDuplicate(claim.ClaimData) {
return ErrClaimExists return ErrClaimExists
} }
parent, ok := g.claims[claim.Parent] parent, ok := g.claims[claim.Parent]
...@@ -101,8 +103,8 @@ func (g *gameState) Put(claim Claim) error { ...@@ -101,8 +103,8 @@ func (g *gameState) Put(claim Claim) error {
return nil return nil
} }
func (g *gameState) IsDuplicate(claim Claim) bool { func (g *gameState) IsDuplicate(claim ClaimData) bool {
_, ok := g.claims[claim.ClaimData] _, ok := g.claims[claim]
return ok return ok
} }
...@@ -118,6 +120,10 @@ func (g *gameState) Claims() []Claim { ...@@ -118,6 +120,10 @@ func (g *gameState) Claims() []Claim {
return out return out
} }
func (g *gameState) MaxDepth() uint64 {
return g.depth
}
func (g *gameState) getChildren(c ClaimData) []ClaimData { func (g *gameState) getChildren(c ClaimData) []ClaimData {
return g.claims[c].children return g.claims[c].children
} }
......
...@@ -52,12 +52,12 @@ func TestIsDuplicate(t *testing.T) { ...@@ -52,12 +52,12 @@ func TestIsDuplicate(t *testing.T) {
require.NoError(t, g.Put(top)) require.NoError(t, g.Put(top))
// Root + Top should be duplicates // Root + Top should be duplicates
require.True(t, g.IsDuplicate(root)) require.True(t, g.IsDuplicate(root.ClaimData))
require.True(t, g.IsDuplicate(top)) require.True(t, g.IsDuplicate(top.ClaimData))
// Middle + Bottom should not be a duplicate // Middle + Bottom should not be a duplicate
require.False(t, g.IsDuplicate(middle)) require.False(t, g.IsDuplicate(middle.ClaimData))
require.False(t, g.IsDuplicate(bottom)) require.False(t, g.IsDuplicate(bottom.ClaimData))
} }
// TestGame_Put_RootAlreadyExists tests the [Game.Put] method using a [gameState] // TestGame_Put_RootAlreadyExists tests the [Game.Put] method using a [gameState]
......
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