Commit bf94ed8d authored by Joshua Gutow's avatar Joshua Gutow

op-challenger: Update Claim types

This updates the claim type to focus on the claim data & to split
out the claim data from the claim. This makes it easier to reference
the parent claim data without pointers. Avoid pointers is helpful
because it limits the chance for memory confusion and enforces
cleaner boundaries by simply passing data around.
parent dc3922cf
...@@ -8,76 +8,72 @@ import ( ...@@ -8,76 +8,72 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/fault" "github.com/ethereum-optimism/optimism/op-challenger/fault"
) )
func PrettyPrintAlphabetClaim(name string, claim fault.Claim) {
value := claim.Value
idx := value[30]
letter := value[31]
if claim.IsRoot() {
fmt.Printf("%s\ttrace %v letter %c\n", name, idx, letter)
} else {
fmt.Printf("%s\ttrace %v letter %c is attack %v\n", name, idx, letter, !claim.DefendsParent())
}
}
// SolverExampleOne uses the [fault.Solver] with a [fault.AlphabetProvider] // SolverExampleOne uses the [fault.Solver] with a [fault.AlphabetProvider]
// to print out fault game traces for the "abcdexyz" counter-state. // to print out fault game traces for the "abcdexyz" counter-state.
func SolverExampleOne() { func SolverExampleOne() {
fmt.Println()
fmt.Println("Solver: Example 1") fmt.Println("Solver: Example 1")
fmt.Println()
// Construct the fault position. // Construct the fault position.
canonical := "abcdefgh" canonical := "abcdefgh"
disputed := "abcdexyz" disputed := "abcdexyz"
maxDepth := 3 maxDepth := 3
parent := fault.Claim{ // Root claim is z at trace index 7 from the disputed provider
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), root := fault.Claim{
Position: fault.NewPosition(0, 0), ClaimData: fault.ClaimData{
Value: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000077a"),
Position: fault.NewPosition(0, 0),
},
}
// Note: We have to create the first counter claim seperately because next move does not know how to counter
// the root claim at this time.
// Counter claim is d at trace index 3 from the canonical provider
counter := fault.Claim{
ClaimData: fault.ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: fault.NewPosition(1, 0),
},
Parent: root.ClaimData,
} }
canonicalProvider := fault.NewAlphabetProvider(canonical, uint64(maxDepth)) canonicalProvider := fault.NewAlphabetProvider(canonical, uint64(maxDepth))
disputedProvider := fault.NewAlphabetProvider(disputed, uint64(maxDepth)) disputedProvider := fault.NewAlphabetProvider(disputed, uint64(maxDepth))
// Create a solver with the canonical provider. // Create a solver with the canonical provider.
solver := fault.NewSolver(maxDepth, canonicalProvider) cannonicalSolver := fault.NewSolver(maxDepth, canonicalProvider)
disputedSolver := fault.NewSolver(maxDepth, disputedProvider)
// Print the initial state. // Print the initial state.
fmt.Println("Canonical state: ", canonical) fmt.Println("Canonical state: ", canonical)
fmt.Println("Disputed state: ", disputed) fmt.Println("Disputed state: ", disputed)
fmt.Println() fmt.Println()
fmt.Println("Proceeding with the following moves:") fmt.Println("Proceeding with the following moves:")
fmt.Println("go left to d, then right to f, then left to e") fmt.Println("go left to d, then right to x (cannonical is f), then left to e")
fmt.Println() fmt.Println()
PrettyPrintAlphabetClaim("Root claim", root)
PrettyPrintAlphabetClaim("Counter claim", counter)
// Get the claim from the disputed provider. claim1, err := disputedSolver.NextMove(counter)
claim, err := disputedProvider.Get(3)
if err != nil { if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err) fmt.Printf("error getting claim from disputed provider: %v", err)
} }
firstDisputedClaim := fault.Claim{ PrettyPrintAlphabetClaim("Disputed moved", *claim1)
Value: claim,
Position: fault.NewPosition(1, 0),
}
res, err := solver.NextMove(firstDisputedClaim, parent)
if err != nil {
fmt.Printf("error getting next move: %v", err)
}
fmt.Printf("Disputed claim: %s\n", claim)
fmt.Printf("Expected claim: %s\n", parent.Value)
fmt.Printf("Response: [Attack: %v, Value: %s]\n", res.Attack, res.Value)
fmt.Println()
// Get the next claim from the disputed provider. claim2, err := cannonicalSolver.NextMove(*claim1)
claim, err = disputedProvider.Get(5)
if err != nil { if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err) fmt.Printf("error getting claim from disputed provider: %v", err)
} }
firstDisputedClaim = fault.Claim{ PrettyPrintAlphabetClaim("Cannonical move", *claim2)
Value: claim,
Position: fault.NewPosition(2, 2),
}
res, err = solver.NextMove(firstDisputedClaim, parent)
if err != nil {
fmt.Printf("error getting next move: %v", err)
}
fmt.Printf("Disputed claim: %s\n", claim)
fmt.Printf("Expected claim: %s\n", parent.Value)
fmt.Printf("Response: [Attack: %v, Value: %s]\n", res.Attack, res.Value)
fmt.Println()
// This marks the end of the game!
if res.Attack {
fmt.Println("Game successfully completed!")
} else {
fmt.Println("Game failed!")
}
fmt.Println()
} }
...@@ -26,6 +26,10 @@ func (p *Position) IndexAtDepth() int { ...@@ -26,6 +26,10 @@ func (p *Position) IndexAtDepth() int {
return p.indexAtDepth return p.indexAtDepth
} }
func (p *Position) IsRootPosition() bool {
return p.depth == 0 && p.indexAtDepth == 0
}
// TraceIndex calculates the what the index of the claim value would be inside the trace. // TraceIndex calculates the what the index of the claim value would be inside the trace.
// It is equivalent to going right until the final depth has been reached. // It is equivalent to going right until the final depth has been reached.
func (p *Position) TraceIndex(maxDepth int) uint64 { func (p *Position) TraceIndex(maxDepth int) uint64 {
......
...@@ -22,12 +22,12 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver { ...@@ -22,12 +22,12 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver {
} }
// NextMove returns the next move to make given the current state of the game. // NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) { func (s *Solver) NextMove(claim Claim) (*Claim, error) {
parentCorrect, err := s.agreeWithClaim(parent) parentCorrect, err := s.agreeWithClaim(claim.Parent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
claimCorrect, err := s.agreeWithClaim(claim) claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -41,7 +41,7 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) { ...@@ -41,7 +41,7 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
return s.attack(claim) return s.attack(claim)
} else if !parentCorrect && claimCorrect { } else if !parentCorrect && claimCorrect {
// Do nothing, we disagree with the parent, but this claim has correctly countered it // Do nothing, we disagree with the parent, but this claim has correctly countered it
return s.doNothing() return nil, nil
} else if !parentCorrect && !claimCorrect { } else if !parentCorrect && !claimCorrect {
// We disagree with the parent so want to counter it (which the claim is doing) // We disagree with the parent so want to counter it (which the claim is doing)
// but we also disagree with the claim so there must be a difference to the left of claim // but we also disagree with the claim so there must be a difference to the left of claim
...@@ -52,30 +52,34 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) { ...@@ -52,30 +52,34 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
return nil, errors.New("no next move") return nil, errors.New("no next move")
} }
func (s *Solver) doNothing() (*Response, error) {
return nil, nil
}
// attack returns a response that attacks the claim. // attack returns a response that attacks the claim.
func (s *Solver) attack(claim Claim) (*Response, error) { func (s *Solver) attack(claim Claim) (*Claim, error) {
value, err := s.traceAtPosition(claim.Attack()) position := claim.Attack()
value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Response{Attack: true, Value: value}, nil return &Claim{
ClaimData: ClaimData{Value: value, Position: position},
Parent: claim.ClaimData,
}, nil
} }
// defend returns a response that defends the claim. // defend returns a response that defends the claim.
func (s *Solver) defend(claim Claim) (*Response, error) { func (s *Solver) defend(claim Claim) (*Claim, error) {
value, err := s.traceAtPosition(claim.Defend()) position := claim.Defend()
value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Response{Attack: false, Value: value}, nil return &Claim{
ClaimData: ClaimData{Value: value, Position: position},
Parent: claim.ClaimData,
}, nil
} }
// agreeWithClaim returns true if the [Claim] is correct according to the internal [TraceProvider]. // agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider].
func (s *Solver) agreeWithClaim(claim Claim) (bool, error) { func (s *Solver) agreeWithClaim(claim ClaimData) (bool, error) {
ourValue, err := s.traceAtPosition(claim.Position) ourValue, err := s.traceAtPosition(claim.Position)
return ourValue == claim.Value, err return ourValue == claim.Value, err
} }
......
...@@ -20,44 +20,47 @@ func TestSolver_NextMove_Opponent(t *testing.T) { ...@@ -20,44 +20,47 @@ func TestSolver_NextMove_Opponent(t *testing.T) {
indices := []struct { indices := []struct {
traceIndex int traceIndex int
claim Claim claim Claim
parent Claim response ClaimData
response *Response
}{ }{
{ {
3, 3,
Claim{ Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"), ClaimData: ClaimData{
Position: NewPosition(1, 0), Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: NewPosition(1, 0),
},
Parent: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(0, 0),
},
}, },
Claim{ ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
Position: NewPosition(0, 0), Position: NewPosition(2, 2),
},
&Response{
Attack: false,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
}, },
}, },
{ {
5, 5,
Claim{ Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"), ClaimData: ClaimData{
Position: NewPosition(2, 2), Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
}, Position: NewPosition(2, 2),
Claim{ },
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"), Parent: ClaimData{
Position: NewPosition(1, 1), Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(1, 1),
},
}, },
&Response{ ClaimData{
Attack: true, Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"), Position: NewPosition(3, 4),
}, },
}, },
} }
for _, test := range indices { for _, test := range indices {
res, err := solver.NextMove(test.claim, test.parent) res, err := solver.NextMove(test.claim)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.response, res) require.Equal(t, test.response, res.ClaimData)
} }
} }
...@@ -19,19 +19,34 @@ type TraceProvider interface { ...@@ -19,19 +19,34 @@ type TraceProvider interface {
Get(i uint64) (common.Hash, error) Get(i uint64) (common.Hash, error)
} }
type Claim struct { // ClaimData is the core of a claim. It must be unique inside a specific game.
type ClaimData struct {
Value common.Hash Value common.Hash
Position Position
} }
type Response struct { // Claim extends ClaimData with information about the relationship between two claims.
Attack bool // note: can we flip this to true == going right / defending?? // It uses ClaimData to break cyclicity without using pointers.
Value common.Hash // If the position of the game is Depth 0, IndexAtDepth 0 it is the root claim
Parent Claim // and the Parent field is empty & meaningless.
type Claim struct {
ClaimData
Parent ClaimData
}
// IsRoot returns true if this claim is the root claim.
func (c *Claim) IsRoot() bool {
return c.Position.IsRootPosition()
}
// DefendsParent returns true if the the claim is a defense (i.e. goes right) of the
// parent. It returns false if the claim is an attack (i.e. goes left) of the parent.
func (c *Claim) DefendsParent() bool {
return (c.IndexAtDepth() >> 1) != c.Parent.IndexAtDepth()
} }
// Responder takes a response action & executes. // Responder takes a response action & executes.
// For full op-challenger this means executing the transaction on chain. // For full op-challenger this means executing the transaction on chain.
type Responder interface { type Responder interface {
Respond(ctx context.Context, response Response) error Respond(ctx context.Context, response Claim) error
} }
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