Commit 3bad749a authored by Andreas Bigger's avatar Andreas Bigger

Solver implementation, Solver example, Position examples, and

tests.
parent 7ca852ec
...@@ -8,6 +8,38 @@ import ( ...@@ -8,6 +8,38 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function.
func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) {
// Create a new alphabet provider.
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(3))
// Build a list of traces.
traces := []struct {
traceIndex uint64
expectedHash common.Hash
}{
{
7,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
},
{
3,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
},
{
5,
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
},
}
// Execute each trace and check the alphabet provider returns the expected hash.
for _, trace := range traces {
expectedHash, err := canonicalProvider.Get(trace.traceIndex)
require.NoError(t, err)
require.Equal(t, trace.expectedHash, expectedHash)
}
}
// FuzzIndexToBytes tests the IndexToBytes function. // FuzzIndexToBytes tests the IndexToBytes function.
func FuzzIndexToBytes(f *testing.F) { func FuzzIndexToBytes(f *testing.F) {
f.Fuzz(func(t *testing.T, index uint64) { f.Fuzz(func(t *testing.T, index uint64) {
......
package examples
import (
"github.com/ethereum-optimism/optimism/op-challenger/fault"
)
func PositionExampleOne() {
// Example 1
// abcdefgh
// abcdexyz
// go left to d, then right to f, then left to e
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
p.Attack()
p.Print(3)
}
func PositionExampleTwo() {
// Example 2
// abcdefgh
// abqrstuv
// go left r, then left to b, then right to q
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
}
package examples
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/fault"
)
// 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),
}
canonicalProvider := fault.NewAlphabetProvider(canonical, uint64(maxDepth))
disputedProvider := fault.NewAlphabetProvider(disputed, uint64(maxDepth))
// Create a solver with the canonical provider.
solver := fault.NewSolver(maxDepth, canonicalProvider)
// Print the initial state.
fmt.Println("Canonical state: ", canonical)
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()
// Get the claim from the disputed provider.
claim, err := disputedProvider.Get(3)
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()
// Get the next claim from the disputed provider.
claim, err = disputedProvider.Get(5)
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()
}
package main package main
import ( import (
"github.com/ethereum-optimism/optimism/op-challenger/fault" "github.com/ethereum-optimism/optimism/op-challenger/fault/cmd/examples"
) )
func main() { func main() {
// Example 1 examples.SolverExampleOne()
// abcdefgh
// abcdexyz
// go left to d, then right to f, then left to e
p := fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
p.Attack()
p.Print(3)
// GIN: 1 Trace Position is 0 Trace Depth is: 0 Trace Index is: 8 // examples.PositionExampleOne()
// GIN: 10 Trace Position is 0 Trace Depth is: 1 Trace Index is: 4 // examples.PositionExampleTwo()
// GIN: 110 Trace Position is 10 Trace Depth is: 2 Trace Index is: 6
// GIN: 1100 Trace Position is 100 Trace Depth is: 3 Trace Index is: 5
// Example 2
// abcdefgh
// abqrstuv
// go left r, then left to b, then right to q
p = fault.Position{}
p.Print(3)
p.Attack()
p.Print(3)
p.Attack()
p.Print(3)
p.Defend()
p.Print(3)
// Trace Position is 0000 Trace Depth is: 0 Trace Index is: 8
// Trace Position is 0000 Trace Depth is: 1 Trace Index is: 4
// Trace Position is 0000 Trace Depth is: 2 Trace Index is: 2
// Trace Position is 0010 Trace Depth is: 3 Trace Index is: 3
} }
package fault
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct {
TraceProvider
gameDepth int
}
// NewSolver creates a new [Solver] using the provided [TraceProvider].
func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver {
return &Solver{
traceProvider,
gameDepth,
}
}
// 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)
if err != nil {
return nil, err
}
claimCorrect, err := s.agreeWithClaim(claim)
if err != nil {
return nil, err
}
if parentCorrect && claimCorrect {
// We agree with the parent, but the claim is disagreeing with it.
// Since we agree with the claim, the difference must be to the right of the claim
return s.defend(claim)
} else if parentCorrect && !claimCorrect {
// We agree with the parent, but the claim disagrees with it.
// Since we disagree with the claim, the difference must be to the left of the claim
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()
} 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
// Note that we will create the correct counter-claim for parent when it is evaluated, no need to do it here
return s.attack(claim)
}
// This should not be reached
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) {
claim.Position.Attack()
value, err := s.traceAtPosition(&claim.Position)
if err != nil {
return nil, err
}
return &Response{Attack: true, Value: value}, nil
}
// defend returns a response that defends the claim.
func (s *Solver) defend(claim Claim) (*Response, error) {
claim.Position.Defend()
value, err := s.traceAtPosition(&claim.Position)
if err != nil {
return nil, err
}
return &Response{Attack: false, Value: value}, nil
}
// agreeWithClaim returns true if the [Claim] is correct according to the internal [TraceProvider].
func (s *Solver) agreeWithClaim(claim Claim) (bool, error) {
ourValue, err := s.traceAtPosition(&claim.Position)
return ourValue == claim.Value, err
}
// traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position].
func (s *Solver) traceAtPosition(p *Position) (common.Hash, error) {
// todo: update TraceIndex to return a uint64 to keep types consistent
index := p.TraceIndex(s.gameDepth)
hash, err := s.Get(uint64(index))
return hash, err
}
package fault
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
// TestSolver_NextMove_Opponent tests the [Solver] NextMove function
// with an [fault.AlphabetProvider] as the [TraceProvider].
func TestSolver_NextMove_Opponent(t *testing.T) {
// Construct the solver.
maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider)
// The following claims are created using the state: "abcdexyz".
// The responses are the responses we expect from the solver.
indices := []struct {
traceIndex int
claim Claim
parent Claim
response *Response
}{
{
3,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: NewPosition(1, 0),
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(0, 0),
},
&Response{
Attack: false,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000566"),
},
},
{
5,
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
Position: NewPosition(2, 2),
},
Claim{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000768"),
Position: NewPosition(1, 1),
},
&Response{
Attack: true,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
},
},
}
for _, test := range indices {
res, err := solver.NextMove(test.claim, test.parent)
require.NoError(t, err)
require.Equal(t, test.response, res)
}
}
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