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 (
"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"
)
func alphabetClaim(index uint64, letter string) common.Hash {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter))
}
// TestAlphabetProvider_Get_ClaimsByTraceIndex tests the [fault.AlphabetProvider] Get function.
func TestAlphabetProvider_Get_ClaimsByTraceIndex(t *testing.T) {
// 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 (
var (
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.
type Solver struct {
types.TraceProvider
trace types.TraceProvider
gameDepth int
}
......@@ -80,10 +82,10 @@ type StepData struct {
// 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) {
if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims")
return StepData{}, ErrStepNonLeafNode
}
if agreeWithClaimLevel {
return StepData{}, errors.New("cannot step on claims we agree with")
return StepData{}, ErrStepAgreedClaim
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
......@@ -94,13 +96,13 @@ func (s *Solver) AttemptStep(claim types.Claim, agreeWithClaimLevel bool) (StepD
var proofData []byte
// If we are attacking index 0, we provide the absolute pre-state, not an intermediate state
if index == 0 && !claimCorrect {
preState = s.AbsolutePreState()
preState = s.trace.AbsolutePreState()
} else {
// If attacking, get the state just before, other get the state after
if !claimCorrect {
index = index - 1
}
preState, proofData, err = s.GetPreimage(index)
preState, proofData, err = s.trace.GetPreimage(index)
if err != nil {
return StepData{}, err
}
......@@ -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].
func (s *Solver) traceAtPosition(p types.Position) (common.Hash, error) {
index := p.TraceIndex(s.gameDepth)
hash, err := s.Get(index)
hash, err := s.trace.Get(index)
return hash, err
}
package fault
import (
"fmt"
"testing"
"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"
)
func alphabetClaim(index uint64, letter string) common.Hash {
return crypto.Keccak256Hash(BuildAlphabetPreimage(index, letter))
}
// 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 {
claim types.Claim
response types.ClaimData
func TestNextMove(t *testing.T) {
maxDepth := 4
builder := NewClaimBuilder(t, maxDepth)
tests := []struct {
name string
claim types.Claim
agreeWithLevel bool
expectedErr error
expectedMove func(claim types.Claim, correct bool) types.Claim
}{
{
types.Claim{
ClaimData: types.ClaimData{
Value: alphabetClaim(7, "z"),
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_CorrectRoot",
claim: builder.CreateRootClaim(true),
agreeWithLevel: true,
},
}
for _, test := range indices {
res, err := solver.NextMove(test.claim, false)
require.NoError(t, err)
require.Equal(t, test.response, res.ClaimData)
}
}
func TestNoMoveAgainstOwnLevel(t *testing.T) {
maxDepth := 3
mallory := NewAlphabetProvider("abcdepqr", uint64(maxDepth))
solver := NewSolver(maxDepth, mallory)
claim := types.Claim{
ClaimData: types.ClaimData{
Value: alphabetClaim(7, "z"),
Position: types.NewPosition(0, 0),
{
name: "AgreeWithLevel_IncorrectRoot",
claim: builder.CreateRootClaim(false),
agreeWithLevel: true,
},
{
name: "AgreeWithLevel_EvenDepth",
claim: builder.Seq(false).Attack(false).Get(),
agreeWithLevel: true,
},
{
name: "AgreeWithLevel_OddDepth",
claim: builder.Seq(false).Attack(false).Defend(false).Get(),
agreeWithLevel: true,
},
{
name: "Root_CorrectValue",
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
}
move, err := solver.NextMove(claim, true)
require.Nil(t, move)
require.Nil(t, err)
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
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) {
maxDepth := 3
canonicalProvider := &alphabetWithProofProvider{NewAlphabetProvider("abcdefgh", uint64(maxDepth))}
solver := NewSolver(maxDepth, canonicalProvider)
_, _, middle, bottom := createTestClaims()
zero := types.Claim{
ClaimData: types.ClaimData{
// Zero value is a purposely disagree with claim value "a"
Position: types.NewPosition(3, 0),
builder := NewClaimBuilder(t, maxDepth)
solver := NewSolver(maxDepth, builder.CorrectTraceProvider())
// Last accessible leaf is the second last trace index
// The root node is used for the last trace index and can only be attacked.
lastLeafTraceIndex := uint64(1<<maxDepth - 2)
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,
},
}
step, err := solver.AttemptStep(bottom, false)
require.NoError(t, err)
require.Equal(t, bottom, step.LeafClaim)
require.True(t, step.IsAttack)
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),
{
name: "DefendFirstTraceIndex",
claim: builder.CreateLeafClaim(0, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(0),
expectProofData: builder.CorrectProofData(0),
},
Parent: root.ClaimData,
}
middle := types.Claim{
ClaimData: types.ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000578"),
Position: types.NewPosition(2, 2),
{
name: "AttackMiddleTraceIndex",
claim: builder.CreateLeafClaim(4, false),
expectAttack: true,
expectPreState: builder.CorrectPreState(3),
expectProofData: builder.CorrectProofData(3),
},
Parent: top.ClaimData,
}
bottom := types.Claim{
ClaimData: types.ClaimData{
Value: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000465"),
Position: types.NewPosition(3, 4),
{
name: "DefendMiddleTraceIndex",
claim: builder.CreateLeafClaim(4, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(4),
expectProofData: builder.CorrectProofData(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