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 (
"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]
// to print out fault game traces for the "abcdexyz" counter-state.
func SolverExampleOne() {
fmt.Println()
fmt.Println("Solver: Example 1")
fmt.Println()
// Construct the fault position.
canonical := "abcdefgh"
disputed := "abcdexyz"
maxDepth := 3
parent := fault.Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: fault.NewPosition(0, 0),
// Root claim is z at trace index 7 from the disputed provider
root := fault.Claim{
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))
disputedProvider := fault.NewAlphabetProvider(disputed, uint64(maxDepth))
// 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.
fmt.Println("Canonical state: ", canonical)
fmt.Println("Disputed state: ", disputed)
fmt.Println("Disputed state: ", disputed)
fmt.Println()
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()
PrettyPrintAlphabetClaim("Root claim", root)
PrettyPrintAlphabetClaim("Counter claim", counter)
// Get the claim from the disputed provider.
claim, err := disputedProvider.Get(3)
claim1, err := disputedSolver.NextMove(counter)
if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err)
}
firstDisputedClaim := fault.Claim{
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()
PrettyPrintAlphabetClaim("Disputed moved", *claim1)
// Get the next claim from the disputed provider.
claim, err = disputedProvider.Get(5)
claim2, err := cannonicalSolver.NextMove(*claim1)
if err != nil {
fmt.Printf("error getting claim from disputed provider: %v", err)
}
firstDisputedClaim = fault.Claim{
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()
PrettyPrintAlphabetClaim("Cannonical move", *claim2)
}
......@@ -26,6 +26,10 @@ func (p *Position) IndexAtDepth() int {
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.
// It is equivalent to going right until the final depth has been reached.
func (p *Position) TraceIndex(maxDepth int) uint64 {
......
......@@ -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.
func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
parentCorrect, err := s.agreeWithClaim(parent)
func (s *Solver) NextMove(claim Claim) (*Claim, error) {
parentCorrect, err := s.agreeWithClaim(claim.Parent)
if err != nil {
return nil, err
}
claimCorrect, err := s.agreeWithClaim(claim)
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return nil, err
}
......@@ -41,7 +41,7 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
return s.attack(claim)
} else if !parentCorrect && claimCorrect {
// Do nothing, we disagree with the parent, but this claim has correctly countered it
return s.doNothing()
return nil, nil
} else if !parentCorrect && !claimCorrect {
// 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
......@@ -52,30 +52,34 @@ func (s *Solver) NextMove(claim Claim, parent Claim) (*Response, error) {
return nil, errors.New("no next move")
}
func (s *Solver) doNothing() (*Response, error) {
return nil, nil
}
// attack returns a response that attacks the claim.
func (s *Solver) attack(claim Claim) (*Response, error) {
value, err := s.traceAtPosition(claim.Attack())
func (s *Solver) attack(claim Claim) (*Claim, error) {
position := claim.Attack()
value, err := s.traceAtPosition(position)
if err != nil {
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.
func (s *Solver) defend(claim Claim) (*Response, error) {
value, err := s.traceAtPosition(claim.Defend())
func (s *Solver) defend(claim Claim) (*Claim, error) {
position := claim.Defend()
value, err := s.traceAtPosition(position)
if err != nil {
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].
func (s *Solver) agreeWithClaim(claim Claim) (bool, error) {
// agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider].
func (s *Solver) agreeWithClaim(claim ClaimData) (bool, error) {
ourValue, err := s.traceAtPosition(claim.Position)
return ourValue == claim.Value, err
}
......
......@@ -20,44 +20,47 @@ func TestSolver_NextMove_Opponent(t *testing.T) {
indices := []struct {
traceIndex int
claim Claim
parent Claim
response *Response
response ClaimData
}{
{
3,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: NewPosition(1, 0),
ClaimData: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: NewPosition(1, 0),
},
Parent: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(0, 0),
},
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(0, 0),
},
&Response{
Attack: false,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
Position: NewPosition(2, 2),
},
},
{
5,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
Position: NewPosition(2, 2),
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(1, 1),
ClaimData: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
Position: NewPosition(2, 2),
},
Parent: ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(1, 1),
},
},
&Response{
Attack: true,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
Position: NewPosition(3, 4),
},
},
}
for _, test := range indices {
res, err := solver.NextMove(test.claim, test.parent)
res, err := solver.NextMove(test.claim)
require.NoError(t, err)
require.Equal(t, test.response, res)
require.Equal(t, test.response, res.ClaimData)
}
}
......@@ -19,19 +19,34 @@ type TraceProvider interface {
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
Position
}
type Response struct {
Attack bool // note: can we flip this to true == going right / defending??
Value common.Hash
Parent Claim
// Claim extends ClaimData with information about the relationship between two claims.
// It uses ClaimData to break cyclicity without using pointers.
// If the position of the game is Depth 0, IndexAtDepth 0 it is the root 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.
// For full op-challenger this means executing the transaction on chain.
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