-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
op-challenger: Begin implementing super root trace provider (#13777)
* op-challenger: Begin implementing super root trace provider * op-challenger: Remove first attempt at handling unsafe proposals. Will replace with a proper implementation as a follow up * op-challenger: Update for move to eth package
- Loading branch information
Showing
8 changed files
with
411 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package super | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" | ||
interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" | ||
"github.com/ethereum-optimism/optimism/op-service/eth" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ethereum/go-ethereum/log" | ||
) | ||
|
||
var ( | ||
ErrGetStepData = errors.New("GetStepData not supported") | ||
ErrIndexTooBig = errors.New("trace index is greater than max uint64") | ||
|
||
InvalidTransition = []byte("invalid") | ||
InvalidTransitionHash = crypto.Keccak256Hash(InvalidTransition) | ||
) | ||
|
||
const ( | ||
StepsPerTimestamp = 1024 | ||
) | ||
|
||
type RootProvider interface { | ||
SuperRootAtTimestamp(timestamp uint64) (eth.SuperRootResponse, error) | ||
} | ||
|
||
type SuperTraceProvider struct { | ||
types.PrestateProvider | ||
logger log.Logger | ||
rootProvider RootProvider | ||
prestateTimestamp uint64 | ||
poststateTimestamp uint64 | ||
l1Head eth.BlockID | ||
gameDepth types.Depth | ||
} | ||
|
||
func NewSuperTraceProvider(logger log.Logger, prestateProvider types.PrestateProvider, rootProvider RootProvider, l1Head eth.BlockID, gameDepth types.Depth, prestateTimestamp, poststateTimestamp uint64) *SuperTraceProvider { | ||
return &SuperTraceProvider{ | ||
PrestateProvider: prestateProvider, | ||
logger: logger, | ||
rootProvider: rootProvider, | ||
prestateTimestamp: prestateTimestamp, | ||
poststateTimestamp: poststateTimestamp, | ||
l1Head: l1Head, | ||
gameDepth: gameDepth, | ||
} | ||
} | ||
|
||
func (s *SuperTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) { | ||
// Find the timestamp and step at position | ||
timestamp, step, err := s.ComputeStep(pos) | ||
if err != nil { | ||
return common.Hash{}, err | ||
} | ||
s.logger.Info("Getting claim", "pos", pos.ToGIndex(), "timestamp", timestamp, "step", step) | ||
if step == 0 { | ||
root, err := s.rootProvider.SuperRootAtTimestamp(timestamp) | ||
if err != nil { | ||
return common.Hash{}, fmt.Errorf("failed to retrieve super root at timestamp %v: %w", timestamp, err) | ||
} | ||
return common.Hash(root.SuperRoot), nil | ||
} | ||
// Fetch the super root at the next timestamp since we are part way through the transition to it | ||
prevRoot, err := s.rootProvider.SuperRootAtTimestamp(timestamp) | ||
if err != nil { | ||
return common.Hash{}, fmt.Errorf("failed to retrieve super root at timestamp %v: %w", timestamp, err) | ||
} | ||
nextTimestamp := timestamp + 1 | ||
nextRoot, err := s.rootProvider.SuperRootAtTimestamp(nextTimestamp) | ||
if err != nil { | ||
return common.Hash{}, fmt.Errorf("failed to retrieve super root at timestamp %v: %w", nextTimestamp, err) | ||
} | ||
prevChainOutputs := make([]eth.ChainIDAndOutput, 0, len(prevRoot.Chains)) | ||
for _, chain := range prevRoot.Chains { | ||
prevChainOutputs = append(prevChainOutputs, eth.ChainIDAndOutput{ChainID: chain.ChainID.ToBig().Uint64(), Output: chain.Canonical}) | ||
} | ||
expectedState := interopTypes.TransitionState{ | ||
SuperRoot: eth.NewSuperV1(prevRoot.Timestamp, prevChainOutputs...).Marshal(), | ||
PendingProgress: make([]interopTypes.OptimisticBlock, 0, step), | ||
Step: step, | ||
} | ||
for i := uint64(0); i < min(step, uint64(len(prevChainOutputs))); i++ { | ||
rawOutput, err := eth.UnmarshalOutput(nextRoot.Chains[i].Pending) | ||
if err != nil { | ||
return common.Hash{}, fmt.Errorf("failed to unmarshal pending output %v at timestamp %v: %w", i, nextTimestamp, err) | ||
} | ||
output, ok := rawOutput.(*eth.OutputV0) | ||
if !ok { | ||
return common.Hash{}, fmt.Errorf("unsupported output version %v at timestamp %v", output.Version(), nextTimestamp) | ||
} | ||
expectedState.PendingProgress = append(expectedState.PendingProgress, interopTypes.OptimisticBlock{ | ||
BlockHash: output.BlockHash, | ||
OutputRoot: eth.OutputRoot(output), | ||
}) | ||
} | ||
return expectedState.Hash(), nil | ||
} | ||
|
||
func (s *SuperTraceProvider) ComputeStep(pos types.Position) (timestamp uint64, step uint64, err error) { | ||
bigIdx := pos.TraceIndex(s.gameDepth) | ||
if !bigIdx.IsUint64() { | ||
err = fmt.Errorf("%w: %v", ErrIndexTooBig, bigIdx) | ||
return | ||
} | ||
|
||
traceIdx := bigIdx.Uint64() + 1 | ||
timestampIncrements := traceIdx / StepsPerTimestamp | ||
timestamp = s.prestateTimestamp + timestampIncrements | ||
if timestamp >= s.poststateTimestamp { // Apply trace extension once the claimed timestamp is reached | ||
timestamp = s.poststateTimestamp | ||
step = 0 | ||
} else { | ||
step = traceIdx % StepsPerTimestamp | ||
} | ||
return | ||
} | ||
|
||
func (s *SuperTraceProvider) GetStepData(_ context.Context, _ types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { | ||
return nil, nil, nil, ErrGetStepData | ||
} | ||
|
||
func (s *SuperTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { | ||
// Never need to challenge L2 block number for super root games. | ||
return nil, types.ErrL2BlockNumberValid | ||
} | ||
|
||
var _ types.TraceProvider = (*SuperTraceProvider)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
package super | ||
|
||
import ( | ||
"context" | ||
"math/big" | ||
"math/rand" | ||
"testing" | ||
|
||
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" | ||
interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" | ||
"github.com/ethereum-optimism/optimism/op-service/eth" | ||
"github.com/ethereum-optimism/optimism/op-service/testlog" | ||
"github.com/ethereum-optimism/optimism/op-service/testutils" | ||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var ( | ||
gameDepth = types.Depth(30) | ||
prestateTimestamp = uint64(1000) | ||
poststateTimestamp = uint64(5000) | ||
) | ||
|
||
func TestGet(t *testing.T) { | ||
t.Run("AtPostState", func(t *testing.T) { | ||
provider, stubSupervisor := createProvider(t) | ||
superRoot := eth.Bytes32{0xaa} | ||
stubSupervisor.Add(eth.SuperRootResponse{ | ||
Timestamp: poststateTimestamp, | ||
SuperRoot: superRoot, | ||
Chains: []eth.ChainRootInfo{ | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(1), | ||
Canonical: eth.Bytes32{0xbb}, | ||
Pending: []byte{0xcc}, | ||
}, | ||
}, | ||
}) | ||
claim, err := provider.Get(context.Background(), types.RootPosition) | ||
require.NoError(t, err) | ||
require.Equal(t, common.Hash(superRoot), claim) | ||
}) | ||
|
||
t.Run("AtNewTimestamp", func(t *testing.T) { | ||
provider, stubSupervisor := createProvider(t) | ||
superRoot := eth.Bytes32{0xaa} | ||
stubSupervisor.Add(eth.SuperRootResponse{ | ||
Timestamp: prestateTimestamp + 1, | ||
SuperRoot: superRoot, | ||
Chains: []eth.ChainRootInfo{ | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(1), | ||
Canonical: eth.Bytes32{0xbb}, | ||
Pending: []byte{0xcc}, | ||
}, | ||
}, | ||
}) | ||
claim, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(StepsPerTimestamp-1))) | ||
require.NoError(t, err) | ||
require.Equal(t, common.Hash(superRoot), claim) | ||
}) | ||
|
||
t.Run("FirstTimestamp", func(t *testing.T) { | ||
rng := rand.New(rand.NewSource(1)) | ||
provider, stubSupervisor := createProvider(t) | ||
outputA1 := testutils.RandomOutputV0(rng) | ||
outputA2 := testutils.RandomOutputV0(rng) | ||
outputB1 := testutils.RandomOutputV0(rng) | ||
outputB2 := testutils.RandomOutputV0(rng) | ||
superRoot1 := eth.NewSuperV1( | ||
prestateTimestamp, | ||
eth.ChainIDAndOutput{ChainID: 1, Output: eth.OutputRoot(outputA1)}, | ||
eth.ChainIDAndOutput{ChainID: 2, Output: eth.OutputRoot(outputB1)}) | ||
superRoot2 := eth.NewSuperV1(prestateTimestamp+1, | ||
eth.ChainIDAndOutput{ChainID: 1, Output: eth.OutputRoot(outputA2)}, | ||
eth.ChainIDAndOutput{ChainID: 2, Output: eth.OutputRoot(outputB2)}) | ||
stubSupervisor.Add(eth.SuperRootResponse{ | ||
Timestamp: prestateTimestamp, | ||
SuperRoot: eth.SuperRoot(superRoot1), | ||
Chains: []eth.ChainRootInfo{ | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(1), | ||
Canonical: eth.OutputRoot(outputA1), | ||
Pending: outputA1.Marshal(), | ||
}, | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(2), | ||
Canonical: eth.OutputRoot(outputB1), | ||
Pending: outputB1.Marshal(), | ||
}, | ||
}, | ||
}) | ||
stubSupervisor.Add(eth.SuperRootResponse{ | ||
Timestamp: prestateTimestamp + 1, | ||
SuperRoot: eth.SuperRoot(superRoot2), | ||
Chains: []eth.ChainRootInfo{ | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(1), | ||
Canonical: eth.OutputRoot(outputA2), | ||
Pending: outputA2.Marshal(), | ||
}, | ||
{ | ||
ChainID: eth.ChainIDFromUInt64(1), | ||
Canonical: eth.OutputRoot(outputB2), | ||
Pending: outputB2.Marshal(), | ||
}, | ||
}, | ||
}) | ||
|
||
expectedFirstStep := &interopTypes.TransitionState{ | ||
SuperRoot: superRoot1.Marshal(), | ||
PendingProgress: []interopTypes.OptimisticBlock{ | ||
{BlockHash: outputA2.BlockHash, OutputRoot: eth.OutputRoot(outputA2)}, | ||
}, | ||
Step: 1, | ||
} | ||
claim, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(0))) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedFirstStep.Hash(), claim) | ||
|
||
expectedSecondStep := &interopTypes.TransitionState{ | ||
SuperRoot: superRoot1.Marshal(), | ||
PendingProgress: []interopTypes.OptimisticBlock{ | ||
{BlockHash: outputA2.BlockHash, OutputRoot: eth.OutputRoot(outputA2)}, | ||
{BlockHash: outputB2.BlockHash, OutputRoot: eth.OutputRoot(outputB2)}, | ||
}, | ||
Step: 2, | ||
} | ||
claim, err = provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(1))) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedSecondStep.Hash(), claim) | ||
|
||
for step := uint64(3); step < StepsPerTimestamp; step++ { | ||
expectedPaddingStep := &interopTypes.TransitionState{ | ||
SuperRoot: superRoot1.Marshal(), | ||
PendingProgress: []interopTypes.OptimisticBlock{ | ||
{BlockHash: outputA2.BlockHash, OutputRoot: eth.OutputRoot(outputA2)}, | ||
{BlockHash: outputB2.BlockHash, OutputRoot: eth.OutputRoot(outputB2)}, | ||
}, | ||
Step: step, | ||
} | ||
claim, err = provider.Get(context.Background(), types.NewPosition(gameDepth, new(big.Int).SetUint64(step-1))) | ||
require.NoError(t, err) | ||
require.Equalf(t, expectedPaddingStep.Hash(), claim, "incorrect hash at step %v", step) | ||
} | ||
}) | ||
} | ||
|
||
func TestGetStepDataReturnsError(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
_, _, _, err := provider.GetStepData(context.Background(), types.RootPosition) | ||
require.ErrorIs(t, err, ErrGetStepData) | ||
} | ||
|
||
func TestGetL2BlockNumberChallengeReturnsError(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
_, err := provider.GetL2BlockNumberChallenge(context.Background()) | ||
require.ErrorIs(t, err, types.ErrL2BlockNumberValid) | ||
} | ||
|
||
func TestComputeStep(t *testing.T) { | ||
t.Run("ErrorWhenTraceIndexTooBig", func(t *testing.T) { | ||
// Uses a big game depth so the trace index doesn't fit in uint64 | ||
provider := NewSuperTraceProvider(testlog.Logger(t, log.LvlInfo), nil, &stubRootProvider{}, eth.BlockID{}, 65, prestateTimestamp, poststateTimestamp) | ||
// Left-most position in top game | ||
_, _, err := provider.ComputeStep(types.RootPosition) | ||
require.ErrorIs(t, err, ErrIndexTooBig) | ||
}) | ||
|
||
t.Run("FirstTimestampSteps", func(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
for i := int64(0); i < StepsPerTimestamp-1; i++ { | ||
timestamp, step, err := provider.ComputeStep(types.NewPosition(gameDepth, big.NewInt(i))) | ||
require.NoError(t, err) | ||
// The prestate must be a super root and is on the timestamp boundary. | ||
// So the first step has the same timestamp and increments step from 0 to 1. | ||
require.Equalf(t, prestateTimestamp, timestamp, "Incorrect timestamp at trace index %d", i) | ||
require.Equalf(t, uint64(i+1), step, "Incorrect step at trace index %d", i) | ||
} | ||
}) | ||
|
||
t.Run("SecondTimestampSteps", func(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
for i := int64(-1); i < StepsPerTimestamp-1; i++ { | ||
traceIndex := StepsPerTimestamp + i | ||
timestamp, step, err := provider.ComputeStep(types.NewPosition(gameDepth, big.NewInt(traceIndex))) | ||
require.NoError(t, err) | ||
// We should now be iterating through the steps of the second timestamp - 1s after the prestate | ||
require.Equalf(t, prestateTimestamp+1, timestamp, "Incorrect timestamp at trace index %d", traceIndex) | ||
require.Equalf(t, uint64(i+1), step, "Incorrect step at trace index %d", traceIndex) | ||
} | ||
}) | ||
|
||
t.Run("LimitToPoststateTimestamp", func(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
timestamp, step, err := provider.ComputeStep(types.RootPosition) | ||
require.NoError(t, err) | ||
require.Equal(t, poststateTimestamp, timestamp, "Incorrect timestamp at root position") | ||
require.Equal(t, uint64(0), step, "Incorrect step at trace index at root position") | ||
}) | ||
|
||
t.Run("StepShouldLoopBackToZero", func(t *testing.T) { | ||
provider, _ := createProvider(t) | ||
prevTimestamp := prestateTimestamp | ||
prevStep := uint64(0) // Absolute prestate is always on a timestamp boundary, so step 0 | ||
for traceIndex := int64(0); traceIndex < 5*StepsPerTimestamp; traceIndex++ { | ||
timestamp, step, err := provider.ComputeStep(types.NewPosition(gameDepth, big.NewInt(traceIndex))) | ||
require.NoError(t, err) | ||
if timestamp == prevTimestamp { | ||
require.Equal(t, prevStep+1, step, "Incorrect step at trace index %d", traceIndex) | ||
} else { | ||
require.Equal(t, prevTimestamp+1, timestamp, "Incorrect timestamp at trace index %d", traceIndex) | ||
require.Zero(t, step, "Incorrect step at trace index %d", traceIndex) | ||
require.Equal(t, uint64(1023), prevStep, "Should only loop back to step 0 after the consolidation step") | ||
} | ||
prevTimestamp = timestamp | ||
prevStep = step | ||
} | ||
}) | ||
} | ||
|
||
func createProvider(t *testing.T) (*SuperTraceProvider, *stubRootProvider) { | ||
logger := testlog.Logger(t, log.LvlInfo) | ||
stubSupervisor := &stubRootProvider{ | ||
rootsByTimestamp: make(map[uint64]eth.SuperRootResponse), | ||
} | ||
return NewSuperTraceProvider(logger, nil, stubSupervisor, eth.BlockID{}, gameDepth, prestateTimestamp, poststateTimestamp), stubSupervisor | ||
} | ||
|
||
type stubRootProvider struct { | ||
rootsByTimestamp map[uint64]eth.SuperRootResponse | ||
} | ||
|
||
func (s *stubRootProvider) Add(root eth.SuperRootResponse) { | ||
if s.rootsByTimestamp == nil { | ||
s.rootsByTimestamp = make(map[uint64]eth.SuperRootResponse) | ||
} | ||
s.rootsByTimestamp[root.Timestamp] = root | ||
} | ||
|
||
func (s *stubRootProvider) SuperRootAtTimestamp(timestamp uint64) (eth.SuperRootResponse, error) { | ||
root, ok := s.rootsByTimestamp[timestamp] | ||
if !ok { | ||
return eth.SuperRootResponse{}, ethereum.NotFound | ||
} | ||
return root, nil | ||
} |
Oops, something went wrong.