Commit 290f58c0 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Merge pull request #8110 from ethereum-optimism/aj/split-providers

op-challenger: Add a provider selector function for split output games
parents 111f3f3a c2e4696c
...@@ -29,10 +29,14 @@ type GameBuilderSeq struct { ...@@ -29,10 +29,14 @@ type GameBuilderSeq struct {
} }
func (g *GameBuilder) Seq() *GameBuilderSeq { func (g *GameBuilder) Seq() *GameBuilderSeq {
return g.SeqFrom(g.Game.Claims()[0])
}
func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq {
return &GameBuilderSeq{ return &GameBuilderSeq{
gameBuilder: g, gameBuilder: g,
builder: g.builder, builder: g.builder,
lastClaim: g.Game.Claims()[0], lastClaim: claim,
} }
} }
......
...@@ -7,8 +7,6 @@ import ( ...@@ -7,8 +7,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type ProviderCreator func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error)
func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) { selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) {
return trace, nil return trace, nil
...@@ -16,8 +14,10 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { ...@@ -16,8 +14,10 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
return &Accessor{selector} return &Accessor{selector}
} }
type ProviderSelector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error)
type Accessor struct { type Accessor struct {
selector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) selector ProviderSelector
} }
func (t *Accessor) Get(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (common.Hash, error) { func (t *Accessor) Get(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (common.Hash, error) {
......
...@@ -3,6 +3,7 @@ package alphabet ...@@ -3,6 +3,7 @@ package alphabet
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"strings" "strings"
...@@ -43,7 +44,7 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi ...@@ -43,7 +44,7 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
traceIndex = traceIndex.Sub(traceIndex, big.NewInt(1)) traceIndex = traceIndex.Sub(traceIndex, big.NewInt(1))
// The index cannot be larger than the maximum index as computed by the depth. // The index cannot be larger than the maximum index as computed by the depth.
if traceIndex.Cmp(big.NewInt(int64(ap.maxLen))) >= 0 { if traceIndex.Cmp(big.NewInt(int64(ap.maxLen))) >= 0 {
return nil, nil, nil, ErrIndexTooLarge return nil, nil, nil, fmt.Errorf("%w traceIndex: %v max: %v pos: %v", ErrIndexTooLarge, traceIndex, ap.maxLen, i)
} }
// We extend the deepest hash to the maximum depth if the trace is not expansive. // We extend the deepest hash to the maximum depth if the trace is not expansive.
if traceIndex.Cmp(big.NewInt(int64(len(ap.state)))) >= 0 { if traceIndex.Cmp(big.NewInt(int64(len(ap.state)))) >= 0 {
...@@ -54,6 +55,9 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi ...@@ -54,6 +55,9 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
// Get returns the claim value at the given index in the trace. // Get returns the claim value at the given index in the trace.
func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) { func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) {
if uint64(i.Depth()) > ap.depth {
return common.Hash{}, fmt.Errorf("%w depth: %v max: %v", ErrIndexTooLarge, i.Depth(), ap.depth)
}
// Step data returns the pre-state, so add 1 to get the state for index i // Step data returns the pre-state, so add 1 to get the state for index i
ti := i.TraceIndex(int(ap.depth)) ti := i.TraceIndex(int(ap.depth))
postPosition := types.NewPosition(int(ap.depth), new(big.Int).Add(ti, big.NewInt(1))) postPosition := types.NewPosition(int(ap.depth), new(big.Int).Add(ti, big.NewInt(1)))
......
...@@ -92,6 +92,14 @@ func TestGet_IndexTooLarge(t *testing.T) { ...@@ -92,6 +92,14 @@ func TestGet_IndexTooLarge(t *testing.T) {
require.ErrorIs(t, err, ErrIndexTooLarge) require.ErrorIs(t, err, ErrIndexTooLarge)
} }
func TestGet_DepthTooLarge(t *testing.T) {
depth := 2
ap := NewTraceProvider("abc", uint64(depth))
pos := types.NewPosition(depth+1, big.NewInt(0))
_, err := ap.Get(context.Background(), pos)
require.ErrorIs(t, err, ErrIndexTooLarge)
}
// TestGet_Extends tests the Get function with an index that is larger // TestGet_Extends tests the Get function with an index that is larger
// than the trace, but smaller than the maximum depth. // than the trace, but smaller than the maximum depth.
func TestGet_Extends(t *testing.T) { func TestGet_Extends(t *testing.T) {
......
package outputs
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
var (
errRefClaimNotDeepEnough = errors.New("reference claim is not deep enough")
)
type ProviderCreator func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error)
func newSplitProviderSelector(topProvider types.TraceProvider, topDepth int, bottomProviderCreator ProviderCreator) trace.ProviderSelector {
return func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) {
if pos.Depth() <= topDepth {
return topProvider, nil
}
if ref.Position.Depth() < topDepth {
return nil, fmt.Errorf("%w, claim depth: %v, depth required: %v", errRefClaimNotDeepEnough, ref.Position.Depth(), topDepth)
}
// Find the ancestor claim at the leaf level for the top game.
topLeaf, err := findAncestorAtDepth(game, ref, topDepth)
if err != nil {
return nil, err
}
var pre, post types.Claim
// If pos is to the right of the leaf from the top game, we must be defending that output root
// otherwise, we're attacking it.
if pos.TraceIndex(pos.Depth()).Cmp(topLeaf.TraceIndex(pos.Depth())) > 0 {
// Defending the top leaf claim, so use it as the pre-claim and find the post
pre = topLeaf
postTraceIdx := new(big.Int).Add(pre.TraceIndex(topDepth), big.NewInt(1))
post, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, postTraceIdx)
if err != nil {
return nil, fmt.Errorf("failed to find post claim: %w", err)
}
} else {
// Attacking the top leaf claim, so use it as the post-claim and find the pre
post = topLeaf
postTraceIdx := post.TraceIndex(topDepth)
if postTraceIdx.Cmp(big.NewInt(0)) == 0 {
pre = types.Claim{}
} else {
preTraceIdx := new(big.Int).Sub(postTraceIdx, big.NewInt(1))
pre, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, preTraceIdx)
if err != nil {
return nil, fmt.Errorf("failed to find pre claim: %w", err)
}
}
}
provider, err := bottomProviderCreator(ctx, pre, post)
if err != nil {
return nil, err
}
// Translate such that the root of the bottom game is the level below the top game leaf
return trace.Translate(provider, uint64(topDepth)+1), nil
}
}
func findAncestorAtDepth(game types.Game, claim types.Claim, depth int) (types.Claim, error) {
for claim.Depth() > depth {
parent, err := game.GetParent(claim)
if err != nil {
return types.Claim{}, fmt.Errorf("failed to find ancestor at depth %v: %w", depth, err)
}
claim = parent
}
return claim, nil
}
func findAncestorWithTraceIndex(game types.Game, ref types.Claim, depth int, traceIdx *big.Int) (types.Claim, error) {
candidate := ref
for candidate.TraceIndex(depth).Cmp(traceIdx) != 0 {
parent, err := game.GetParent(candidate)
if err != nil {
return types.Claim{}, fmt.Errorf("failed to get parent of claim %v: %w", candidate.ContractIndex, err)
}
candidate = parent
}
return candidate, nil
}
package outputs
import (
"context"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/stretchr/testify/require"
)
const (
topDepth = 3
bottomDepth = 4
)
func TestUseTopProvider(t *testing.T) {
ctx := context.Background()
topProvider, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref := gameBuilder.Game.Claims()[0]
pos := ref.Position
for pos.Depth() <= topDepth {
provider, err := selector(ctx, gameBuilder.Game, ref, ref.Position)
require.NoError(t, err)
require.Same(t, topProvider, provider)
_, err = topProvider.Get(ctx, pos)
require.NoError(t, err, "should be able to use provider for position")
pos = pos.Attack()
}
}
func TestErrorWhenRefAboveTopGameLeafButPositionInBottom(t *testing.T) {
ctx := context.Background()
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
// Generate claims at depths up to but not including the leaf of the top providers
createClaimsToDepth(gameBuilder, topDepth-1)
for _, ref := range gameBuilder.Game.Claims() {
pos := types.NewPosition(topDepth+1, big.NewInt(0))
provider, err := selector(ctx, gameBuilder.Game, ref, pos)
require.ErrorIsf(t, err, errRefClaimNotDeepEnough, "should not get provider with ref claim at depth: %v", ref.Depth())
require.Nil(t, provider)
}
}
func TestTranslatePositionsForBottomProvider(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim)
}{
// There are 4 leaf nodes that can be accessed in the top tree of depth 3: 8, 10, 12, 14
// Then you can attack and defend any of those to challenge all blocks
{"attackTopLeafGIndex8", attackTopLeafGIndex8},
{"defendTopLeafGIndex8", defendTopLeafGIndex8},
{"attackTopLeafGIndex10", attackTopLeafGIndex10},
{"defendTopLeafGIndex10", defendTopLeafGIndex10},
{"attackTopLeafGIndex12", attackTopLeafGIndex12},
{"defendTopLeafGIndex12", defendTopLeafGIndex12},
{"attackTopLeafGIndex14", attackTopLeafGIndex14},
{"attackTopLeafGIndex14", defendTopLeafGIndex14},
}
for _, tCase := range tests {
tCase := tCase
t.Run(tCase.name, func(t *testing.T) {
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref, pos, _, _ := tCase.setup(t, gameBuilder)
provider, err := selector(context.Background(), gameBuilder.Game, ref, pos)
require.NoError(t, err)
claimPos := pos
localClaimPos := types.NewPositionFromGIndex(big.NewInt(1))
requireSameValue(t, provider, claimPos, asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos)
requireSameValue(t, provider, claimPos.Attack(), asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos.Attack())
requireSameValue(t, provider, claimPos.Attack().Defend(), asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos.Attack().Defend())
})
}
}
func requireSameValue(t *testing.T, a types.TraceProvider, aPos types.Position, b types.TraceProvider, bPos types.Position) {
// Check Get returns the same results
aValue, err := a.Get(context.Background(), aPos)
require.NoError(t, err)
bValue, err := b.Get(context.Background(), bPos)
require.NoError(t, err)
require.Equal(t, aValue, bValue)
// Check GetStepData returns the same results
aPrestate, aProofData, aPreimageData, err := a.GetStepData(context.Background(), aPos)
require.NoError(t, err)
bPrestate, bProofData, bPreimageData, err := b.GetStepData(context.Background(), bPos)
require.NoError(t, err)
require.Equal(t, aPrestate, bPrestate)
require.Equal(t, aProofData, bProofData)
require.Equal(t, aPreimageData, bPreimageData)
}
func TestBottomProviderAttackingTopLeaf(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim)
}{
// There are 4 leaf nodes that can be accessed in the top tree of depth 3: 8, 10, 12, 14
// Then you can attack and defend any of those to challenge all blocks
// We can then use these setups to test any other reference claim descending from what these setup since
// that whole subtree should have the same pre and post claim from the top provider.
{"attackTopLeafGIndex8", attackTopLeafGIndex8},
{"defendTopLeafGIndex8", defendTopLeafGIndex8},
{"attackTopLeafGIndex10", attackTopLeafGIndex10},
{"defendTopLeafGIndex10", defendTopLeafGIndex10},
{"attackTopLeafGIndex12", attackTopLeafGIndex12},
{"defendTopLeafGIndex12", defendTopLeafGIndex12},
{"attackTopLeafGIndex14", attackTopLeafGIndex14},
{"attackTopLeafGIndex14", defendTopLeafGIndex14},
}
for _, tCase := range tests {
tCase := tCase
t.Run(tCase.name, func(t *testing.T) {
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref, pos, expectedPre, expectedPost := tCase.setup(t, gameBuilder)
runTest := func(ref types.Claim, pos types.Position) {
t.Run(fmt.Sprintf("Ref-d%vi%v_Pos-d%vi%v", ref.Depth(), ref.IndexAtDepth(), pos.Depth(), pos.IndexAtDepth()), func(t *testing.T) {
provider, err := selector(context.Background(), gameBuilder.Game, ref, pos)
require.NoError(t, err)
requireBottomProviderForClaims(t, provider, expectedPre, expectedPost)
})
}
// Check we get the same pre and post for any reference claim lower in the game
var testDescendantClaims func(ref types.Claim, pos types.Position)
testDescendantClaims = func(ref types.Claim, pos types.Position) {
// For each reference claim, check it works with the claim position, or attacking or defending the claim
runTest(ref, pos)
runTest(ref, pos.Attack())
runTest(ref, pos.Defend())
if pos.Depth() >= topDepth+bottomDepth {
return
}
// If the ref is the leaf of the top claim, ensure we respect whether the test is setup
// to attack or defend the top leaf claim.
if ref.Depth() != topDepth || !pos.RightOf(ref.Position) {
gameBuilder.SeqFrom(ref).AttackCorrect()
attackRef := latestClaim(gameBuilder)
testDescendantClaims(attackRef, attackRef.Position)
}
if ref.Depth() != topDepth || pos.RightOf(ref.Position) {
gameBuilder.SeqFrom(ref).DefendCorrect()
defendRef := latestClaim(gameBuilder)
testDescendantClaims(defendRef, defendRef.Position)
}
}
testDescendantClaims(ref, pos)
})
}
}
func attackTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
// Generate claims down to the top provider's leaf
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
seq.AttackCorrect() // gindex 8, trace 0
expectPost = latestClaim(gameBuilder)
// No pre-claim as the first output root is being challenged.
expectPre = types.Claim{}
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
// Generate claims down to the top provider's leaf
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
expectPost = latestClaim(gameBuilder)
seq.AttackCorrect() // gindex 8, trace 0
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
expectPre = latestClaim(gameBuilder)
seq.DefendCorrect() // gindex 10, trace 2
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
expectPost = latestClaim(gameBuilder)
seq = seq.AttackCorrect() // gindex 4, trace 1
seq.DefendCorrect() // gindex 10, trace 2
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
expectPre = latestClaim(gameBuilder)
seq = seq.DefendCorrect() // gindex 6, trace 5
seq.AttackCorrect() // gindex 12, trace 4
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
expectPost = latestClaim(gameBuilder)
seq.AttackCorrect() // gindex 12, trace 4
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
expectPre = latestClaim(gameBuilder)
seq.DefendCorrect() // gindex 14, trace 6
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
expectPost = latestClaim(gameBuilder)
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
seq.DefendCorrect() // gindex 14, trace 6
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func latestClaim(gameBuilder *test.GameBuilder) types.Claim {
return gameBuilder.Game.Claims()[len(gameBuilder.Game.Claims())-1]
}
func createClaimsToDepth(gameBuilder *test.GameBuilder, depth int) {
seq := gameBuilder.Seq()
for i := 0; i < depth; i++ {
seq = seq.AttackCorrect()
}
}
func requireBottomProviderForClaims(t *testing.T, actual types.TraceProvider, expectedPre types.Claim, expectedPost types.Claim) {
if expectedPre != (types.Claim{}) {
require.Equal(t,
new(big.Int).Add(expectedPre.TraceIndex(topDepth), big.NewInt(1)),
expectedPost.TraceIndex(topDepth),
"should expect adjacent top level trace indices")
}
bottomProvider := asBottomTraceProvider(t, actual)
require.Equal(t, expectedPre, bottomProvider.pre, "Incorrect pre claim")
require.Equal(t, expectedPost, bottomProvider.post, "Incorrect post claim")
}
func asBottomTraceProvider(t *testing.T, actual types.TraceProvider) *bottomTraceProvider {
translatingProvider, ok := actual.(*trace.TranslatingProvider)
require.True(t, ok)
bottomProvider, ok := translatingProvider.Original().(*bottomTraceProvider)
require.True(t, ok)
return bottomProvider
}
func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider, trace.ProviderSelector, *test.GameBuilder) {
top := alphabet.NewTraceProvider("abcdef", topDepth)
bottomCreator := func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
return &bottomTraceProvider{
pre: pre,
post: post,
AlphabetTraceProvider: alphabet.NewTraceProvider(post.Value.Hex(), bottomDepth),
}, nil
}
selector := newSplitProviderSelector(top, topDepth, bottomCreator)
claimBuilder := test.NewAlphabetClaimBuilder(t, topDepth+bottomDepth)
gameBuilder := claimBuilder.GameBuilder(true, true)
return top, selector, gameBuilder
}
type bottomTraceProvider struct {
pre types.Claim
post types.Claim
*alphabet.AlphabetTraceProvider
}
package split
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
var _ types.TraceProvider = (*SplitTraceProvider)(nil)
// SplitTraceProvider is a [types.TraceProvider] implementation that
// routes requests to the correct internal trace provider based on the
// depth of the requested trace.
type SplitTraceProvider struct {
logger log.Logger
topProvider types.TraceProvider
bottomProvider types.TraceProvider
topDepth uint64
}
// NewTraceProvider creates a new [SplitTraceProvider] instance.
// The [topDepth] parameter specifies the depth at which the internal
// [types.TraceProvider] should be switched.
func NewTraceProvider(logger log.Logger, topProvider types.TraceProvider, bottomProvider types.TraceProvider, topDepth uint64) *SplitTraceProvider {
return &SplitTraceProvider{
logger: logger,
topProvider: topProvider,
bottomProvider: bottomProvider,
topDepth: topDepth,
}
}
func (s *SplitTraceProvider) providerForDepth(depth uint64) (uint64, types.TraceProvider) {
if depth <= s.topDepth {
return 0, s.topProvider
}
return s.topDepth, s.bottomProvider
}
// Get routes the Get request to the internal [types.TraceProvider] that
// that serves the trace index at the depth.
func (s *SplitTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
ancestorDepth, provider := s.providerForDepth(uint64(pos.Depth()))
relativePosition, err := pos.RelativeToAncestorAtDepth(ancestorDepth)
if err != nil {
return common.Hash{}, err
}
return provider.Get(ctx, relativePosition)
}
// AbsolutePreStateCommitment returns the absolute prestate from the lowest internal [types.TraceProvider]
func (s *SplitTraceProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
return s.bottomProvider.AbsolutePreStateCommitment(ctx)
}
// GetStepData routes the GetStepData request to the lowest internal [types.TraceProvider].
func (s *SplitTraceProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
ancestorDepth, provider := s.providerForDepth(uint64(pos.Depth()))
relativePosition, err := pos.RelativeToAncestorAtDepth(ancestorDepth)
if err != nil {
return nil, nil, nil, err
}
return provider.GetStepData(ctx, relativePosition)
}
package split
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
mockGetError = errors.New("mock get error")
mockOutput = common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
mockCommitment = common.HexToHash("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
)
func TestGet(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getError: mockGetError}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, nil, 40)
_, err := splitProvider.Get(context.Background(), types.NewPosition(1, common.Big0))
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectOutputFromTopProvider", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getOutput: mockOutput}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, &mockTraceProvider{}, 40)
output, err := splitProvider.Get(context.Background(), types.NewPosition(6, big.NewInt(3)))
require.NoError(t, err)
expectedGIndex := types.NewPosition(6, big.NewInt(3)).ToGIndex()
require.Equal(t, common.BigToHash(expectedGIndex), output)
})
t.Run("ReturnsCorrectOutputWithMultipleProviders", func(t *testing.T) {
bottomProvider := mockTraceProvider{getOutput: mockOutput}
splitProvider := newSplitTraceProvider(t, &mockTraceProvider{}, &bottomProvider, 40)
output, err := splitProvider.Get(context.Background(), types.NewPosition(42, big.NewInt(17)))
require.NoError(t, err)
expectedGIndex := types.NewPosition(2, big.NewInt(1)).ToGIndex()
require.Equal(t, common.BigToHash(expectedGIndex), output)
})
}
func TestAbsolutePreStateCommitment(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{absolutePreStateCommitmentError: mockGetError}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
_, err := splitProvider.AbsolutePreStateCommitment(context.Background())
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectOutput", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{absolutePreStateCommitment: mockCommitment}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
output, err := splitProvider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, mockCommitment, output)
})
}
func TestGetStepData(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getStepDataError: mockGetError}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, nil, 40)
_, _, _, err := splitProvider.GetStepData(context.Background(), types.NewPosition(0, common.Big0))
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectStepData", func(t *testing.T) {
expectedStepData := []byte{1, 2, 3, 4}
mockOutputProvider := mockTraceProvider{stepPrestateData: expectedStepData}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
output, _, _, err := splitProvider.GetStepData(context.Background(), types.NewPosition(41, common.Big0))
require.NoError(t, err)
require.Equal(t, expectedStepData, output)
})
}
type mockTraceProvider struct {
getOutput common.Hash
getError error
absolutePreStateCommitmentError error
absolutePreStateCommitment common.Hash
absolutePreStateError error
preImageData []byte
getStepDataError error
stepPrestateData []byte
}
func newSplitTraceProvider(t *testing.T, tp *mockTraceProvider, bp *mockTraceProvider, topDepth uint64) SplitTraceProvider {
return SplitTraceProvider{
logger: testlog.Logger(t, log.LvlInfo),
topProvider: tp,
bottomProvider: bp,
topDepth: topDepth,
}
}
func (m *mockTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
if m.getError != nil {
return common.Hash{}, m.getError
}
return common.BigToHash(pos.ToGIndex()), nil
}
func (m *mockTraceProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
if m.absolutePreStateCommitmentError != nil {
return common.Hash{}, m.absolutePreStateCommitmentError
}
return m.absolutePreStateCommitment, nil
}
func (m *mockTraceProvider) AbsolutePreState(ctx context.Context) (preimage []byte, err error) {
if m.absolutePreStateError != nil {
return []byte{}, m.absolutePreStateError
}
return m.preImageData, nil
}
func (m *mockTraceProvider) GetStepData(ctx context.Context, pos types.Position) ([]byte, []byte, *types.PreimageOracleData, error) {
if m.getStepDataError != nil {
return nil, nil, nil, m.getStepDataError
}
return m.stepPrestateData, nil, nil, nil
}
...@@ -7,36 +7,43 @@ import ( ...@@ -7,36 +7,43 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type translatingProvider struct { type TranslatingProvider struct {
parentDepth uint64 rootDepth uint64
provider types.TraceProvider provider types.TraceProvider
} }
func Translate(provider types.TraceProvider, parentDepth uint64) types.TraceProvider { // Translate returns a new TraceProvider that translates any requested positions before passing them on to the
return &translatingProvider{ // specified provider.
parentDepth: parentDepth, // The translation is done such that the root node for provider is at rootDepth.
func Translate(provider types.TraceProvider, rootDepth uint64) types.TraceProvider {
return &TranslatingProvider{
rootDepth: rootDepth,
provider: provider, provider: provider,
} }
} }
func (p translatingProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) { func (p *TranslatingProvider) Original() types.TraceProvider {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth) return p.provider
}
func (p *TranslatingProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.rootDepth)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return p.provider.Get(ctx, relativePos) return p.provider.Get(ctx, relativePos)
} }
func (p translatingProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { func (p *TranslatingProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth) relativePos, err := pos.RelativeToAncestorAtDepth(p.rootDepth)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
return p.provider.GetStepData(ctx, relativePos) return p.provider.GetStepData(ctx, relativePos)
} }
func (p translatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) { func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
return p.provider.AbsolutePreStateCommitment(ctx) return p.provider.AbsolutePreStateCommitment(ctx)
} }
var _ types.TraceProvider = (*translatingProvider)(nil) var _ types.TraceProvider = (*TranslatingProvider)(nil)
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
) )
var ( var (
ErrPositionDepthTooSmall = errors.New("Position depth is too small") ErrPositionDepthTooSmall = errors.New("position depth is too small")
) )
// Position is a golang wrapper around the dispute game Position type. // Position is a golang wrapper around the dispute game Position type.
...@@ -32,6 +32,10 @@ func NewPositionFromGIndex(x *big.Int) Position { ...@@ -32,6 +32,10 @@ func NewPositionFromGIndex(x *big.Int) Position {
return NewPosition(depth, indexAtDepth) return NewPosition(depth, indexAtDepth)
} }
func (p Position) String() string {
return fmt.Sprintf("Position(depth: %v, indexAtDepth: %v)", p.depth, p.indexAtDepth)
}
func (p Position) MoveRight() Position { func (p Position) MoveRight() Position {
return Position{ return Position{
depth: p.depth, depth: p.depth,
......
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