Commit 50558c82 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/cannon-executor

parents e0bb0187 6c5085a4
...@@ -6,9 +6,14 @@ import ( ...@@ -6,9 +6,14 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func alphabetClaim(index uint64, letter string) common.Hash {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter))
}
// TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function. // TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function.
func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) { func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) {
// Create a new alphabet provider. // Create a new alphabet provider.
......
package fault
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
// ClaimBuilder is a test utility to enable creating claims in a wide range of situations
type ClaimBuilder struct {
require *require.Assertions
maxDepth int
correct types.TraceProvider
}
func NewClaimBuilder(t *testing.T, maxDepth int) *ClaimBuilder {
return &ClaimBuilder{
require: require.New(t),
maxDepth: maxDepth,
correct: &alphabetWithProofProvider{NewAlphabetProvider("abcdefghijklmnopqrstuvwxyz", uint64(maxDepth))},
}
}
// CorrectTraceProvider returns a types.TraceProvider that provides the canonical trace.
func (c *ClaimBuilder) CorrectTraceProvider() types.TraceProvider {
return c.correct
}
// CorrectClaim returns the canonical claim at a specified trace index
func (c *ClaimBuilder) CorrectClaim(idx uint64) common.Hash {
value, err := c.correct.Get(idx)
c.require.NoError(err)
return value
}
// CorrectPreState returns the pre-image of the canonical claim at the specified trace index
func (c *ClaimBuilder) CorrectPreState(idx uint64) []byte {
preimage, _, err := c.correct.GetPreimage(idx)
c.require.NoError(err)
return preimage
}
// CorrectProofData returns the proof-data for the canonical claim at the specified trace index
func (c *ClaimBuilder) CorrectProofData(idx uint64) []byte {
_, proof, err := c.correct.GetPreimage(idx)
c.require.NoError(err)
return proof
}
func (c *ClaimBuilder) incorrectClaim(idx uint64) common.Hash {
return common.BigToHash(new(big.Int).SetUint64(idx))
}
func (c *ClaimBuilder) claim(idx uint64, correct bool) common.Hash {
if correct {
return c.CorrectClaim(idx)
} else {
return c.incorrectClaim(idx)
}
}
func (c *ClaimBuilder) CreateRootClaim(correct bool) types.Claim {
value := c.claim((1<<c.maxDepth)-1, correct)
return types.Claim{
ClaimData: types.ClaimData{
Value: value,
Position: types.NewPosition(0, 0),
},
}
}
func (c *ClaimBuilder) CreateLeafClaim(traceIndex uint64, correct bool) types.Claim {
parentPos := types.NewPosition(c.maxDepth-1, 0)
pos := types.NewPosition(c.maxDepth, int(traceIndex))
return types.Claim{
ClaimData: types.ClaimData{
Value: c.claim(pos.TraceIndex(c.maxDepth), correct),
Position: pos,
},
Parent: types.ClaimData{
Value: c.claim(parentPos.TraceIndex(c.maxDepth), !correct),
Position: parentPos,
},
}
}
func (c *ClaimBuilder) AttackClaim(claim types.Claim, correct bool) types.Claim {
pos := claim.Position.Attack()
return types.Claim{
ClaimData: types.ClaimData{
Value: c.claim(pos.TraceIndex(c.maxDepth), correct),
Position: pos,
},
Parent: claim.ClaimData,
}
}
func (c *ClaimBuilder) DefendClaim(claim types.Claim, correct bool) types.Claim {
pos := claim.Position.Defend()
return types.Claim{
ClaimData: types.ClaimData{
Value: c.claim(pos.TraceIndex(c.maxDepth), correct),
Position: pos,
},
Parent: claim.ClaimData,
}
}
type SequenceBuilder struct {
builder *ClaimBuilder
lastClaim types.Claim
}
// Seq starts building a claim by following a sequence of attack and defend moves from the root
// The returned SequenceBuilder can be used to add additional moves. e.g:
// claim := Seq(true).Attack(false).Attack(true).Defend(true).Get()
func (c *ClaimBuilder) Seq(rootCorrect bool) *SequenceBuilder {
claim := c.CreateRootClaim(rootCorrect)
return &SequenceBuilder{
builder: c,
lastClaim: claim,
}
}
func (s *SequenceBuilder) Attack(correct bool) *SequenceBuilder {
claim := s.builder.AttackClaim(s.lastClaim, correct)
return &SequenceBuilder{
builder: s.builder,
lastClaim: claim,
}
}
func (s *SequenceBuilder) Defend(correct bool) *SequenceBuilder {
claim := s.builder.DefendClaim(s.lastClaim, correct)
return &SequenceBuilder{
builder: s.builder,
lastClaim: claim,
}
}
func (s *SequenceBuilder) Get() types.Claim {
return s.lastClaim
}
type alphabetWithProofProvider struct {
*AlphabetProvider
}
func (a *alphabetWithProofProvider) GetPreimage(i uint64) ([]byte, []byte, error) {
preimage, _, err := a.AlphabetProvider.GetPreimage(i)
if err != nil {
return nil, nil, err
}
return preimage, []byte{byte(i)}, nil
}
...@@ -10,11 +10,13 @@ import ( ...@@ -10,11 +10,13 @@ import (
var ( var (
ErrGameDepthReached = errors.New("game depth reached") ErrGameDepthReached = errors.New("game depth reached")
ErrStepNonLeafNode = errors.New("cannot step on non-leaf claims")
ErrStepAgreedClaim = errors.New("cannot step on claims we agree with")
) )
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game. // Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct { type Solver struct {
types.TraceProvider trace types.TraceProvider
gameDepth int gameDepth int
} }
...@@ -80,10 +82,10 @@ type StepData struct { ...@@ -80,10 +82,10 @@ type StepData struct {
// An error will be returned if the claim is not at the max depth. // An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim types.Claim, agreeWithClaimLevel bool) (StepData, error) { func (s *Solver) AttemptStep(claim types.Claim, agreeWithClaimLevel bool) (StepData, error) {
if claim.Depth() != s.gameDepth { if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims") return StepData{}, ErrStepNonLeafNode
} }
if agreeWithClaimLevel { if agreeWithClaimLevel {
return StepData{}, errors.New("cannot step on claims we agree with") return StepData{}, ErrStepAgreedClaim
} }
claimCorrect, err := s.agreeWithClaim(claim.ClaimData) claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil { if err != nil {
...@@ -94,13 +96,13 @@ func (s *Solver) AttemptStep(claim types.Claim, agreeWithClaimLevel bool) (StepD ...@@ -94,13 +96,13 @@ func (s *Solver) AttemptStep(claim types.Claim, agreeWithClaimLevel bool) (StepD
var proofData []byte var proofData []byte
// If we are attacking index 0, we provide the absolute pre-state, not an intermediate state // If we are attacking index 0, we provide the absolute pre-state, not an intermediate state
if index == 0 && !claimCorrect { if index == 0 && !claimCorrect {
preState = s.AbsolutePreState() preState = s.trace.AbsolutePreState()
} else { } else {
// If attacking, get the state just before, other get the state after // If attacking, get the state just before, other get the state after
if !claimCorrect { if !claimCorrect {
index = index - 1 index = index - 1
} }
preState, proofData, err = s.GetPreimage(index) preState, proofData, err = s.trace.GetPreimage(index)
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
} }
...@@ -151,6 +153,6 @@ func (s *Solver) agreeWithClaim(claim types.ClaimData) (bool, error) { ...@@ -151,6 +153,6 @@ func (s *Solver) agreeWithClaim(claim types.ClaimData) (bool, error) {
// traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position]. // traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position].
func (s *Solver) traceAtPosition(p types.Position) (common.Hash, error) { func (s *Solver) traceAtPosition(p types.Position) (common.Hash, error) {
index := p.TraceIndex(s.gameDepth) index := p.TraceIndex(s.gameDepth)
hash, err := s.Get(index) hash, err := s.trace.Get(index)
return hash, err return hash, err
} }
package fault package fault
import ( import (
"fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func alphabetClaim(index uint64, letter string) common.Hash { func TestNextMove(t *testing.T) {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter)) maxDepth := 4
} builder := NewClaimBuilder(t, maxDepth)
tests := []struct {
// TestSolver_NextMove_Opponent tests the [Solver] NextMove function name string
// with an [fault.AlphabetProvider] as the [TraceProvider]. claim types.Claim
func TestSolver_NextMove_Opponent(t *testing.T) { agreeWithLevel bool
// Construct the solver. expectedErr error
maxDepth := 3 expectedMove func(claim types.Claim, correct bool) types.Claim
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 {
claim types.Claim
response types.ClaimData
}{ }{
{ {
types.Claim{ name: "AgreeWithLevel_CorrectRoot",
ClaimData: types.ClaimData{ claim: builder.CreateRootClaim(true),
Value: alphabetClaim(7, "z"), agreeWithLevel: true,
Position: types.NewPosition(0, 0),
},
// Root claim has no parent
},
types.ClaimData{
Value: alphabetClaim(3, "d"),
Position: types.NewPosition(1, 0),
},
},
{
types.Claim{
ClaimData: types.ClaimData{
Value: alphabetClaim(3, "d"),
Position: types.NewPosition(1, 0),
},
Parent: types.ClaimData{
Value: alphabetClaim(7, "h"),
Position: types.NewPosition(0, 0),
},
},
types.ClaimData{
Value: alphabetClaim(5, "f"),
Position: types.NewPosition(2, 2),
},
},
{
types.Claim{
ClaimData: types.ClaimData{
Value: alphabetClaim(5, "x"),
Position: types.NewPosition(2, 2),
},
Parent: types.ClaimData{
Value: alphabetClaim(7, "h"),
Position: types.NewPosition(1, 1),
},
},
types.ClaimData{
Value: alphabetClaim(4, "e"),
Position: types.NewPosition(3, 4),
},
}, },
} {
name: "AgreeWithLevel_IncorrectRoot",
for _, test := range indices { claim: builder.CreateRootClaim(false),
res, err := solver.NextMove(test.claim, false) agreeWithLevel: true,
require.NoError(t, err) },
require.Equal(t, test.response, res.ClaimData) {
} name: "AgreeWithLevel_EvenDepth",
} claim: builder.Seq(false).Attack(false).Get(),
agreeWithLevel: true,
func TestNoMoveAgainstOwnLevel(t *testing.T) { },
maxDepth := 3 {
mallory := NewAlphabetProvider("abcdepqr", uint64(maxDepth)) name: "AgreeWithLevel_OddDepth",
solver := NewSolver(maxDepth, mallory) claim: builder.Seq(false).Attack(false).Defend(false).Get(),
agreeWithLevel: true,
claim := types.Claim{ },
ClaimData: types.ClaimData{ {
Value: alphabetClaim(7, "z"), name: "Root_CorrectValue",
Position: types.NewPosition(0, 0), claim: builder.CreateRootClaim(true),
},
{
name: "Root_IncorrectValue",
claim: builder.CreateRootClaim(false),
expectedMove: builder.AttackClaim,
},
{
name: "NonRoot_AgreeWithParentAndClaim",
claim: builder.Seq(true).Attack(true).Get(),
expectedMove: builder.DefendClaim,
},
{
name: "NonRoot_AgreeWithParentDisagreeWithClaim",
claim: builder.Seq(true).Attack(false).Get(),
expectedMove: builder.AttackClaim,
},
{
name: "NonRoot_DisagreeWithParentAgreeWithClaim",
claim: builder.Seq(false).Attack(true).Get(),
expectedMove: builder.DefendClaim,
},
{
name: "NonRoot_DisagreeWithParentAndClaim",
claim: builder.Seq(false).Attack(false).Get(),
expectedMove: builder.AttackClaim,
},
{
name: "ErrorWhenClaimIsLeaf_Correct",
claim: builder.CreateLeafClaim(4, true),
expectedErr: ErrGameDepthReached,
},
{
name: "ErrorWhenClaimIsLeaf_Incorrect",
claim: builder.CreateLeafClaim(6, false),
expectedErr: ErrGameDepthReached,
}, },
// Root claim has no parent
} }
for _, test := range tests {
move, err := solver.NextMove(claim, true) test := test
require.Nil(t, move) t.Run(test.name, func(t *testing.T) {
require.Nil(t, err) solver := NewSolver(maxDepth, builder.CorrectTraceProvider())
move, err := solver.NextMove(test.claim, test.agreeWithLevel)
if test.expectedErr == nil {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, test.expectedErr)
}
if test.expectedMove == nil {
require.Nil(t, move)
} else {
expected := test.expectedMove(test.claim, true)
require.Equal(t, &expected, move)
}
})
}
} }
func TestAttemptStep(t *testing.T) { func TestAttemptStep(t *testing.T) {
maxDepth := 3 maxDepth := 3
canonicalProvider := &alphabetWithProofProvider{NewAlphabetProvider("abcdefgh", uint64(maxDepth))} builder := NewClaimBuilder(t, maxDepth)
solver := NewSolver(maxDepth, canonicalProvider) solver := NewSolver(maxDepth, builder.CorrectTraceProvider())
_, _, middle, bottom := createTestClaims()
// Last accessible leaf is the second last trace index
zero := types.Claim{ // The root node is used for the last trace index and can only be attacked.
ClaimData: types.ClaimData{ lastLeafTraceIndex := uint64(1<<maxDepth - 2)
// Zero value is a purposely disagree with claim value "a"
Position: types.NewPosition(3, 0), tests := []struct {
name string
claim types.Claim
agreeWithLevel bool
expectedErr error
expectAttack bool
expectPreState []byte
expectProofData []byte
}{
{
name: "AttackFirstTraceIndex",
claim: builder.CreateLeafClaim(0, false),
expectAttack: true,
expectPreState: builder.CorrectTraceProvider().AbsolutePreState(),
expectProofData: nil,
}, },
} {
name: "DefendFirstTraceIndex",
step, err := solver.AttemptStep(bottom, false) claim: builder.CreateLeafClaim(0, true),
require.NoError(t, err) expectAttack: false,
require.Equal(t, bottom, step.LeafClaim) expectPreState: builder.CorrectPreState(0),
require.True(t, step.IsAttack) expectProofData: builder.CorrectProofData(0),
require.Equal(t, step.PreState, BuildAlphabetPreimage(3, "d"))
require.Equal(t, step.ProofData, []byte{3})
_, err = solver.AttemptStep(middle, false)
require.Error(t, err)
step, err = solver.AttemptStep(zero, false)
require.NoError(t, err)
require.Equal(t, zero, step.LeafClaim)
require.True(t, step.IsAttack)
require.Equal(t, canonicalProvider.AbsolutePreState(), step.PreState)
}
func TestAttempStep_AgreeWithClaimLevel_Fails(t *testing.T) {
maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider)
_, _, middle, _ := createTestClaims()
step, err := solver.AttemptStep(middle, true)
require.Error(t, err)
require.Equal(t, StepData{}, step)
}
type alphabetWithProofProvider struct {
*AlphabetProvider
}
func (a *alphabetWithProofProvider) GetPreimage(i uint64) ([]byte, []byte, error) {
preimage, _, err := a.AlphabetProvider.GetPreimage(i)
if err != nil {
return nil, nil, err
}
return preimage, []byte{byte(i)}, nil
}
func createTestClaims() (types.Claim, types.Claim, types.Claim, types.Claim) {
// root & middle are from the trace "abcdexyz"
// top & bottom are from the trace "abcdefgh"
root := types.Claim{
ClaimData: types.ClaimData{
Value: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000077a"),
Position: types.NewPosition(0, 0),
},
// Root claim has no parent
}
top := types.Claim{
ClaimData: types.ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000364"),
Position: types.NewPosition(1, 0),
}, },
Parent: root.ClaimData, {
} name: "AttackMiddleTraceIndex",
middle := types.Claim{ claim: builder.CreateLeafClaim(4, false),
ClaimData: types.ClaimData{ expectAttack: true,
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"), expectPreState: builder.CorrectPreState(3),
Position: types.NewPosition(2, 2), expectProofData: builder.CorrectProofData(3),
}, },
Parent: top.ClaimData, {
} name: "DefendMiddleTraceIndex",
claim: builder.CreateLeafClaim(4, true),
bottom := types.Claim{ expectAttack: false,
ClaimData: types.ClaimData{ expectPreState: builder.CorrectPreState(4),
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"), expectProofData: builder.CorrectProofData(4),
Position: types.NewPosition(3, 4), },
{
name: "AttackLastTraceIndex",
claim: builder.CreateLeafClaim(lastLeafTraceIndex, false),
expectAttack: true,
expectPreState: builder.CorrectPreState(lastLeafTraceIndex - 1),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex - 1),
},
{
name: "DefendLastTraceIndex",
claim: builder.CreateLeafClaim(lastLeafTraceIndex, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(lastLeafTraceIndex),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex),
},
{
name: "CannotStepNonLeaf",
claim: builder.Seq(false).Attack(false).Get(),
expectedErr: ErrStepNonLeafNode,
},
{
name: "CannotStepAgreedNode",
claim: builder.Seq(false).Attack(false).Get(),
agreeWithLevel: true,
expectedErr: ErrStepNonLeafNode,
}, },
Parent: middle.ClaimData,
} }
return root, top, middle, bottom for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
fmt.Printf("%v\n", test.claim.Position.TraceIndex(maxDepth))
step, err := solver.AttemptStep(test.claim, test.agreeWithLevel)
if test.expectedErr == nil {
require.NoError(t, err)
require.Equal(t, test.claim, step.LeafClaim)
require.Equal(t, test.expectAttack, step.IsAttack)
require.Equal(t, test.expectPreState, step.PreState)
require.Equal(t, test.expectProofData, step.ProofData)
} else {
require.ErrorIs(t, err, test.expectedErr)
require.Equal(t, StepData{}, step)
}
})
}
} }
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