Commit c41bb739 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Integrate challenging L2 block numbers (#10446)

* op-challenger: Make l2-eth-rpc required for all trace types.

Add rollupEndpoint to NewConfig signature since it was already always required.

* op-e2e: Remove rollup and l2-eth-rpc params from WithCannon and WithAlphabet

* op-challenger: Integrate challenging L2 block numbers
parent 97cc1f17
...@@ -30,6 +30,7 @@ type Responder interface { ...@@ -30,6 +30,7 @@ type Responder interface {
type ClaimLoader interface { type ClaimLoader interface {
GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error)
IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error)
} }
type Agent struct { type Agent struct {
...@@ -84,6 +85,14 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -84,6 +85,14 @@ func (a *Agent) Act(ctx context.Context) error {
defer func() { defer func() {
a.metrics.RecordGameActTime(a.systemClock.Since(start).Seconds()) a.metrics.RecordGameActTime(a.systemClock.Since(start).Seconds())
}() }()
if challenged, err := a.loader.IsL2BlockNumberChallenged(ctx, rpcblock.Latest); err != nil {
return fmt.Errorf("failed to check if L2 block number already challenged: %w", err)
} else if challenged {
a.log.Debug("Skipping game with already challenged L2 block number")
return nil
}
game, err := a.newGameFromContracts(ctx) game, err := a.newGameFromContracts(ctx)
if err != nil { if err != nil {
return fmt.Errorf("create game from contracts: %w", err) return fmt.Errorf("create game from contracts: %w", err)
...@@ -105,11 +114,13 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -105,11 +114,13 @@ func (a *Agent) Act(ctx context.Context) error {
func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action types.Action) { func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action types.Action) {
defer wg.Done() defer wg.Done()
actionLog := a.log.New("action", action.Type, "is_attack", action.IsAttack, "parent", action.ParentIdx) actionLog := a.log.New("action", action.Type)
if action.Type == types.ActionTypeStep { if action.Type == types.ActionTypeStep {
containsOracleData := action.OracleData != nil containsOracleData := action.OracleData != nil
isLocal := containsOracleData && action.OracleData.IsLocal isLocal := containsOracleData && action.OracleData.IsLocal
actionLog = actionLog.New( actionLog = actionLog.New(
"is_attack", action.IsAttack,
"parent", action.ParentIdx,
"prestate", common.Bytes2Hex(action.PreState), "prestate", common.Bytes2Hex(action.PreState),
"proof", common.Bytes2Hex(action.ProofData), "proof", common.Bytes2Hex(action.ProofData),
"containsOracleData", containsOracleData, "containsOracleData", containsOracleData,
...@@ -118,8 +129,8 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty ...@@ -118,8 +129,8 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty
if action.OracleData != nil { if action.OracleData != nil {
actionLog = actionLog.New("oracleKey", common.Bytes2Hex(action.OracleData.OracleKey)) actionLog = actionLog.New("oracleKey", common.Bytes2Hex(action.OracleData.OracleKey))
} }
} else { } else if action.Type == types.ActionTypeMove {
actionLog = actionLog.New("value", action.Value) actionLog = actionLog.New("is_attack", action.IsAttack, "parent", action.ParentIdx, "value", action.Value)
} }
switch action.Type { switch action.Type {
...@@ -127,6 +138,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty ...@@ -127,6 +138,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty
a.metrics.RecordGameMove() a.metrics.RecordGameMove()
case types.ActionTypeStep: case types.ActionTypeStep:
a.metrics.RecordGameStep() a.metrics.RecordGameStep()
// TODO(client-pod#852): Add metrics for L2 block number challenges
} }
actionLog.Info("Performing action") actionLog.Info("Performing action")
err := a.responder.PerformAction(ctx, action) err := a.responder.PerformAction(ctx, action)
......
...@@ -57,6 +57,18 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) { ...@@ -57,6 +57,18 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) {
} }
} }
func TestDoNotMakeMovesWhenL2BlockNumberChallenged(t *testing.T) {
ctx := context.Background()
agent, claimLoader, responder := setupTestAgent(t)
claimLoader.blockNumChallenged = true
require.NoError(t, agent.Act(ctx))
require.Equal(t, 1, responder.callResolveCount, "should check if game is resolvable")
require.Equal(t, 1, claimLoader.callCount, "should fetch claims only once for resolveClaim")
}
func createClaimsWithClaimants(t *testing.T, d types.Depth) []types.Claim { func createClaimsWithClaimants(t *testing.T, d types.Depth) []types.Claim {
claimBuilder := test.NewClaimBuilder(t, d, alphabet.NewTraceProvider(big.NewInt(0), d)) claimBuilder := test.NewClaimBuilder(t, d, alphabet.NewTraceProvider(big.NewInt(0), d))
rootClaim := claimBuilder.CreateRootClaim() rootClaim := claimBuilder.CreateRootClaim()
...@@ -180,9 +192,14 @@ func setupTestAgent(t *testing.T) (*Agent, *stubClaimLoader, *stubResponder) { ...@@ -180,9 +192,14 @@ func setupTestAgent(t *testing.T) (*Agent, *stubClaimLoader, *stubResponder) {
} }
type stubClaimLoader struct { type stubClaimLoader struct {
callCount int callCount int
maxLoads int maxLoads int
claims []types.Claim claims []types.Claim
blockNumChallenged bool
}
func (s *stubClaimLoader) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) {
return s.blockNumChallenged, nil
} }
func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]types.Claim, error) { func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]types.Claim, error) {
......
...@@ -52,7 +52,10 @@ var ( ...@@ -52,7 +52,10 @@ var (
methodWETH = "weth" methodWETH = "weth"
) )
var ErrSimulationFailed = errors.New("tx simulation failed") var (
ErrSimulationFailed = errors.New("tx simulation failed")
ErrChallengeL2BlockNotSupported = errors.New("contract version does not support challenging L2 block number")
)
type FaultDisputeGameContractLatest struct { type FaultDisputeGameContractLatest struct {
metrics metrics.ContractMetricer metrics metrics.ContractMetricer
...@@ -410,6 +413,14 @@ func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, e ...@@ -410,6 +413,14 @@ func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, e
return NewVMContract(vmAddr, f.multiCaller), nil return NewVMContract(vmAddr, f.multiCaller), nil
} }
func (f *FaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) {
return false, nil
}
func (f *FaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported
}
func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot) call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate() return call.ToTxCandidate()
...@@ -523,6 +534,8 @@ type FaultDisputeGameContract interface { ...@@ -523,6 +534,8 @@ type FaultDisputeGameContract interface {
GetClaim(ctx context.Context, idx uint64) (types.Claim, error) GetClaim(ctx context.Context, idx uint64) (types.Claim, error)
GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error)
IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error)
IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error)
ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error)
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
......
...@@ -652,6 +652,30 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) { ...@@ -652,6 +652,30 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) {
} }
} }
func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) {
for _, version := range versions {
version := version
t.Run(version.version, func(t *testing.T) {
_, game := setupFaultDisputeGameTest(t, version)
challenged, err := game.IsL2BlockNumberChallenged(context.Background(), rpcblock.Latest)
require.NoError(t, err)
require.False(t, challenged)
})
}
}
func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) {
for _, version := range versions {
version := version
t.Run(version.version, func(t *testing.T) {
_, game := setupFaultDisputeGameTest(t, version)
tx, err := game.ChallengeL2BlockNumberTx(&faultTypes.InvalidL2BlockNumberChallenge{})
require.ErrorIs(t, err, ErrChallengeL2BlockNotSupported)
require.Equal(t, txmgr.TxCandidate{}, tx)
})
}
}
func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, FaultDisputeGameContract) { func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, FaultDisputeGameContract) {
fdgAbi := version.loadAbi() fdgAbi := version.loadAbi()
......
...@@ -67,15 +67,9 @@ func RegisterGameTypes( ...@@ -67,15 +67,9 @@ func RegisterGameTypes(
selective bool, selective bool,
claimants []common.Address, claimants []common.Address,
) (CloseFunc, error) { ) (CloseFunc, error) {
var closer CloseFunc l2Client, err := ethclient.DialContext(ctx, cfg.L2Rpc)
var l2Client *ethclient.Client if err != nil {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) || cfg.TraceTypeEnabled(config.TraceTypeAsterisc) { return nil, fmt.Errorf("dial l2 client %v: %w", cfg.L2Rpc, err)
l2, err := ethclient.DialContext(ctx, cfg.L2Rpc)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.L2Rpc, err)
}
l2Client = l2
closer = l2Client.Close
} }
syncValidator := newSyncStatusValidator(rollupClient) syncValidator := newSyncStatusValidator(rollupClient)
...@@ -95,11 +89,11 @@ func RegisterGameTypes( ...@@ -95,11 +89,11 @@ func RegisterGameTypes(
} }
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil { if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, l2Client, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register alphabet game type: %w", err) return nil, fmt.Errorf("failed to register alphabet game type: %w", err)
} }
} }
return closer, nil return l2Client.Close, nil
} }
func registerAlphabet( func registerAlphabet(
...@@ -112,6 +106,7 @@ func registerAlphabet( ...@@ -112,6 +106,7 @@ func registerAlphabet(
m metrics.Metricer, m metrics.Metricer,
syncValidator SyncValidator, syncValidator SyncValidator,
rollupClient RollupClient, rollupClient RollupClient,
l2Client utils.L2HeaderSource,
txSender TxSender, txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract, gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
...@@ -143,7 +138,7 @@ func registerAlphabet( ...@@ -143,7 +138,7 @@ func registerAlphabet(
} }
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock)
creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) { creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) {
accessor, err := outputs.NewOutputAlphabetTraceAccessor(logger, m, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) accessor, err := outputs.NewOutputAlphabetTraceAccessor(logger, m, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -23,6 +22,7 @@ type GameContract interface { ...@@ -23,6 +22,7 @@ type GameContract interface {
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error)
GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error)
} }
...@@ -130,6 +130,8 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) ...@@ -130,6 +130,8 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action)
candidate.Value = bondValue candidate.Value = bondValue
case types.ActionTypeStep: case types.ActionTypeStep:
candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData) candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
case types.ActionTypeChallengeL2BlockNumber:
candidate, err = r.contract.ChallengeL2BlockNumberTx(action.InvalidL2BlockNumberChallenge)
} }
if err != nil { if err != nil {
return err return err
......
...@@ -284,6 +284,19 @@ func TestPerformAction(t *testing.T) { ...@@ -284,6 +284,19 @@ func TestPerformAction(t *testing.T) {
require.Equal(t, 0, uploader.updates) require.Equal(t, 0, uploader.updates)
require.Equal(t, 1, oracle.existCalls) require.Equal(t, 1, oracle.existCalls)
}) })
t.Run("challengeL2Block", func(t *testing.T) {
responder, mockTxMgr, contract, _, _ := newTestFaultResponder(t)
challenge := &types.InvalidL2BlockNumberChallenge{}
action := types.Action{
Type: types.ActionTypeChallengeL2BlockNumber,
InvalidL2BlockNumberChallenge: challenge,
}
err := responder.PerformAction(context.Background(), action)
require.NoError(t, err)
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, []interface{}{challenge}, contract.challengeArgs)
})
} }
func newTestFaultResponder(t *testing.T) (*FaultResponder, *mockTxManager, *mockContract, *mockPreimageUploader, *mockOracle) { func newTestFaultResponder(t *testing.T) (*FaultResponder, *mockTxManager, *mockContract, *mockPreimageUploader, *mockOracle) {
...@@ -359,6 +372,7 @@ type mockContract struct { ...@@ -359,6 +372,7 @@ type mockContract struct {
attackArgs []interface{} attackArgs []interface{}
defendArgs []interface{} defendArgs []interface{}
stepArgs []interface{} stepArgs []interface{}
challengeArgs []interface{}
updateOracleClaimIdx uint64 updateOracleClaimIdx uint64
updateOracleArgs *types.PreimageOracleData updateOracleArgs *types.PreimageOracleData
} }
...@@ -387,6 +401,11 @@ func (m *mockContract) ResolveClaimTx(_ uint64) (txmgr.TxCandidate, error) { ...@@ -387,6 +401,11 @@ func (m *mockContract) ResolveClaimTx(_ uint64) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
func (m *mockContract) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) {
m.challengeArgs = []interface{}{challenge}
return txmgr.TxCandidate{TxData: ([]byte)("challenge")}, nil
}
func (m *mockContract) AttackTx(parentClaimId uint64, claim common.Hash) (txmgr.TxCandidate, error) { func (m *mockContract) AttackTx(parentClaimId uint64, claim common.Hash) (txmgr.TxCandidate, error) {
m.attackArgs = []interface{}{parentClaimId, claim} m.attackArgs = []interface{}{parentClaimId, claim}
return txmgr.TxCandidate{TxData: ([]byte)("attack")}, nil return txmgr.TxCandidate{TxData: ([]byte)("attack")}, nil
......
...@@ -2,6 +2,7 @@ package solver ...@@ -2,6 +2,7 @@ package solver
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
...@@ -27,6 +28,25 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ...@@ -27,6 +28,25 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to determine if root claim is correct: %w", err) return nil, fmt.Errorf("failed to determine if root claim is correct: %w", err)
} }
// Challenging the L2 block number will only work if we have the same output root as the claim
// Otherwise our output root preimage won't match. We can just proceed and invalidate the output root by disputing claims instead.
if agreeWithRootClaim {
if challenge, err := s.claimSolver.trace.GetL2BlockNumberChallenge(ctx, game); errors.Is(err, types.ErrL2BlockNumberValid) {
// We agree with the L2 block number, proceed to processing claims
} else if err != nil {
// Failed to check L2 block validity
return nil, fmt.Errorf("failed to determine L2 block validity: %w", err)
} else {
return []types.Action{
{
Type: types.ActionTypeChallengeL2BlockNumber,
InvalidL2BlockNumberChallenge: challenge,
},
}, nil
}
}
var actions []types.Action var actions []types.Action
agreedClaims := newHonestClaimTracker() agreedClaims := newHonestClaimTracker()
if agreeWithRootClaim { if agreeWithRootClaim {
......
...@@ -10,10 +10,36 @@ import ( ...@@ -10,10 +10,36 @@ import (
faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" faulttest "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"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) {
startingBlock := big.NewInt(5)
maxDepth := types.Depth(6)
challenge := &types.InvalidL2BlockNumberChallenge{
Output: &eth.OutputResponse{OutputRoot: eth.Bytes32{0xbb}},
}
claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingBlock, maxDepth)
traceProvider := faulttest.NewAlphabetWithProofProvider(t, startingBlock, maxDepth, nil)
solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(traceProvider))
// Do not challenge when provider returns error indicating l2 block is valid
actions, err := solver.CalculateNextActions(context.Background(), claimBuilder.GameBuilder().Game)
require.NoError(t, err)
require.Len(t, actions, 0)
// Do challenge when the provider returns a challenge
traceProvider.L2BlockChallenge = challenge
actions, err = solver.CalculateNextActions(context.Background(), claimBuilder.GameBuilder().Game)
require.NoError(t, err)
require.Len(t, actions, 1)
action := actions[0]
require.Equal(t, types.ActionTypeChallengeL2BlockNumber, action.Type)
require.Equal(t, challenge, action.InvalidL2BlockNumberChallenge)
}
func TestCalculateNextActions(t *testing.T) { func TestCalculateNextActions(t *testing.T) {
maxDepth := types.Depth(6) maxDepth := types.Depth(6)
startingL2BlockNumber := big.NewInt(0) startingL2BlockNumber := big.NewInt(0)
...@@ -31,8 +57,6 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -31,8 +57,6 @@ func TestCalculateNextActions(t *testing.T) {
}, },
}, },
{ {
// Note: The fault dispute game contract should prevent a correct root claim from actually being posted
// But for completeness, test we ignore it so we don't get sucked into playing an unwinnable game.
name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot",
rootClaimCorrect: true, rootClaimCorrect: true,
setupGame: func(builder *faulttest.GameBuilder) {}, setupGame: func(builder *faulttest.GameBuilder) {},
......
...@@ -9,11 +9,12 @@ import ( ...@@ -9,11 +9,12 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
) )
func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error) *alphabetWithProofProvider { func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error) *AlphabetWithProofProvider {
return &alphabetWithProofProvider{ return &AlphabetWithProofProvider{
alphabet.NewTraceProvider(startingL2BlockNumber, maxDepth), alphabet.NewTraceProvider(startingL2BlockNumber, maxDepth),
maxDepth, maxDepth,
oracleError, oracleError,
nil,
} }
} }
...@@ -22,13 +23,14 @@ func NewAlphabetClaimBuilder(t *testing.T, startingL2BlockNumber *big.Int, maxDe ...@@ -22,13 +23,14 @@ func NewAlphabetClaimBuilder(t *testing.T, startingL2BlockNumber *big.Int, maxDe
return NewClaimBuilder(t, maxDepth, alphabetProvider) return NewClaimBuilder(t, maxDepth, alphabetProvider)
} }
type alphabetWithProofProvider struct { type AlphabetWithProofProvider struct {
*alphabet.AlphabetTraceProvider *alphabet.AlphabetTraceProvider
depth types.Depth depth types.Depth
OracleError error OracleError error
L2BlockChallenge *types.InvalidL2BlockNumberChallenge
} }
func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i types.Position) ([]byte, []byte, *types.PreimageOracleData, error) { func (a *AlphabetWithProofProvider) GetStepData(ctx context.Context, i types.Position) ([]byte, []byte, *types.PreimageOracleData, error) {
preimage, _, _, err := a.AlphabetTraceProvider.GetStepData(ctx, i) preimage, _, _, err := a.AlphabetTraceProvider.GetStepData(ctx, i)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
...@@ -37,3 +39,11 @@ func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i types.Pos ...@@ -37,3 +39,11 @@ func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i types.Pos
data := types.NewPreimageOracleData([]byte{byte(traceIndex)}, []byte{byte(traceIndex - 1)}, uint32(traceIndex-1)) data := types.NewPreimageOracleData([]byte{byte(traceIndex)}, []byte{byte(traceIndex - 1)}, uint32(traceIndex-1))
return preimage, []byte{byte(traceIndex - 1)}, data, nil return preimage, []byte{byte(traceIndex - 1)}, data, nil
} }
func (c *AlphabetWithProofProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
if c.L2BlockChallenge != nil {
return c.L2BlockChallenge, nil
} else {
return nil, types.ErrL2BlockNumberValid
}
}
...@@ -40,4 +40,12 @@ func (t *Accessor) GetStepData(ctx context.Context, game types.Game, ref types.C ...@@ -40,4 +40,12 @@ func (t *Accessor) GetStepData(ctx context.Context, game types.Game, ref types.C
return provider.GetStepData(ctx, pos) return provider.GetStepData(ctx, pos)
} }
func (t *Accessor) GetL2BlockNumberChallenge(ctx context.Context, game types.Game) (*types.InvalidL2BlockNumberChallenge, error) {
provider, err := t.selector(ctx, game, game.Claims()[0], types.RootPosition)
if err != nil {
return nil, err
}
return provider.GetL2BlockNumberChallenge(ctx)
}
var _ types.TraceAccessor = (*Accessor)(nil) var _ types.TraceAccessor = (*Accessor)(nil)
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -67,4 +68,32 @@ func TestAccessor_UsesSelector(t *testing.T) { ...@@ -67,4 +68,32 @@ func TestAccessor_UsesSelector(t *testing.T) {
require.Equal(t, expectedProofData, actualProofData) require.Equal(t, expectedProofData, actualProofData)
require.Equal(t, expectedPreimageData, actualPreimageData) require.Equal(t, expectedPreimageData, actualPreimageData)
}) })
t.Run("GetL2BlockNumberChallenge", func(t *testing.T) {
provider := &ChallengeTraceProvider{
TraceProvider: provider1,
}
accessor := &Accessor{
selector: func(ctx context.Context, actualGame types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) {
require.Equal(t, game, actualGame)
require.Equal(t, game.Claims()[0], ref)
require.Equal(t, types.RootPosition, pos)
return provider, nil
},
}
challenge, err := accessor.GetL2BlockNumberChallenge(ctx, game)
require.NoError(t, err)
require.NotNil(t, challenge)
require.Equal(t, eth.Bytes32{0xaa, 0xbb}, challenge.Output.OutputRoot)
})
}
type ChallengeTraceProvider struct {
types.TraceProvider
}
func (c *ChallengeTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
return &types.InvalidL2BlockNumberChallenge{
Output: &eth.OutputResponse{OutputRoot: eth.Bytes32{0xaa, 0xbb}},
}, nil
} }
...@@ -75,6 +75,10 @@ func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (com ...@@ -75,6 +75,10 @@ func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (com
return alphabetStateHash(claimBytes), nil return alphabetStateHash(claimBytes), nil
} }
func (ap *AlphabetTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
return nil, types.ErrL2BlockNumberValid
}
// BuildAlphabetPreimage constructs the claim bytes for the index and claim. // BuildAlphabetPreimage constructs the claim bytes for the index and claim.
func BuildAlphabetPreimage(traceIndex *big.Int, claim *big.Int) []byte { func BuildAlphabetPreimage(traceIndex *big.Int, claim *big.Int) []byte {
return append(traceIndex.FillBytes(make([]byte, 32)), claim.FillBytes(make([]byte, 32))...) return append(traceIndex.FillBytes(make([]byte, 32)), claim.FillBytes(make([]byte, 32))...)
......
...@@ -96,6 +96,10 @@ func (p *AsteriscTraceProvider) GetStepData(ctx context.Context, pos types.Posit ...@@ -96,6 +96,10 @@ func (p *AsteriscTraceProvider) GetStepData(ctx context.Context, pos types.Posit
return value, data, oracleData, nil return value, data, oracleData, nil
} }
func (p *AsteriscTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
return nil, types.ErrL2BlockNumberValid
}
// loadProof will attempt to load or generate the proof data at the specified index // loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace it is extended with no-op instructions. // If the requested index is beyond the end of the actual trace it is extended with no-op instructions.
func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
......
...@@ -94,6 +94,10 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio ...@@ -94,6 +94,10 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio
return value, data, oracleData, nil return value, data, oracleData, nil
} }
func (p *CannonTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
return nil, types.ErrL2BlockNumberValid
}
// loadProof will attempt to load or generate the proof data at the specified index // loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace it is extended with no-op instructions. // If the requested index is beyond the end of the actual trace it is extended with no-op instructions.
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "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/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
...@@ -19,12 +20,13 @@ func NewOutputAlphabetTraceAccessor( ...@@ -19,12 +20,13 @@ func NewOutputAlphabetTraceAccessor(
m metrics.Metricer, m metrics.Metricer,
prestateProvider types.PrestateProvider, prestateProvider types.PrestateProvider,
rollupClient OutputRollupClient, rollupClient OutputRollupClient,
l2Client utils.L2HeaderSource,
l1Head eth.BlockID, l1Head eth.BlockID,
splitDepth types.Depth, splitDepth types.Depth,
prestateBlock uint64, prestateBlock uint64,
poststateBlock uint64, poststateBlock uint64,
) (*trace.Accessor, error) { ) (*trace.Accessor, error) {
outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
alphabetCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { alphabetCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
provider := alphabet.NewTraceProvider(agreed.L2BlockNumber, depth) provider := alphabet.NewTraceProvider(agreed.L2BlockNumber, depth)
return provider, nil return provider, nil
......
...@@ -31,7 +31,7 @@ func NewOutputAsteriscTraceAccessor( ...@@ -31,7 +31,7 @@ func NewOutputAsteriscTraceAccessor(
prestateBlock uint64, prestateBlock uint64,
poststateBlock uint64, poststateBlock uint64,
) (*trace.Accessor, error) { ) (*trace.Accessor, error) {
outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
asteriscCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { asteriscCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext) logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex()) subdir := filepath.Join(dir, localContext.Hex())
......
...@@ -31,7 +31,7 @@ func NewOutputCannonTraceAccessor( ...@@ -31,7 +31,7 @@ func NewOutputCannonTraceAccessor(
prestateBlock uint64, prestateBlock uint64,
poststateBlock uint64, poststateBlock uint64,
) (*trace.Accessor, error) { ) (*trace.Accessor, error) {
outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
cannonCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { cannonCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext) logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex()) subdir := filepath.Join(dir, localContext.Hex())
......
...@@ -4,7 +4,9 @@ import ( ...@@ -4,7 +4,9 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -29,17 +31,19 @@ type OutputTraceProvider struct { ...@@ -29,17 +31,19 @@ type OutputTraceProvider struct {
types.PrestateProvider types.PrestateProvider
logger log.Logger logger log.Logger
rollupProvider OutputRollupClient rollupProvider OutputRollupClient
l2Client utils.L2HeaderSource
prestateBlock uint64 prestateBlock uint64
poststateBlock uint64 poststateBlock uint64
l1Head eth.BlockID l1Head eth.BlockID
gameDepth types.Depth gameDepth types.Depth
} }
func NewTraceProvider(logger log.Logger, prestateProvider types.PrestateProvider, rollupProvider OutputRollupClient, l1Head eth.BlockID, gameDepth types.Depth, prestateBlock, poststateBlock uint64) *OutputTraceProvider { func NewTraceProvider(logger log.Logger, prestateProvider types.PrestateProvider, rollupProvider OutputRollupClient, l2Client utils.L2HeaderSource, l1Head eth.BlockID, gameDepth types.Depth, prestateBlock, poststateBlock uint64) *OutputTraceProvider {
return &OutputTraceProvider{ return &OutputTraceProvider{
PrestateProvider: prestateProvider, PrestateProvider: prestateProvider,
logger: logger, logger: logger,
rollupProvider: rollupProvider, rollupProvider: rollupProvider,
l2Client: l2Client,
prestateBlock: prestateBlock, prestateBlock: prestateBlock,
poststateBlock: poststateBlock, poststateBlock: poststateBlock,
l1Head: l1Head, l1Head: l1Head,
...@@ -94,6 +98,29 @@ func (o *OutputTraceProvider) GetStepData(_ context.Context, _ types.Position) ( ...@@ -94,6 +98,29 @@ func (o *OutputTraceProvider) GetStepData(_ context.Context, _ types.Position) (
return nil, nil, nil, ErrGetStepData return nil, nil, nil, ErrGetStepData
} }
func (o *OutputTraceProvider) GetL2BlockNumberChallenge(ctx context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
outputBlock, err := o.HonestBlockNumber(ctx, types.RootPosition)
if err != nil {
return nil, err
}
claimedBlock, err := o.ClaimedBlockNumber(types.RootPosition)
if err != nil {
return nil, err
}
if claimedBlock == outputBlock {
return nil, types.ErrL2BlockNumberValid
}
output, err := o.rollupProvider.OutputAtBlock(ctx, outputBlock)
if err != nil {
return nil, err
}
header, err := o.l2Client.HeaderByNumber(ctx, new(big.Int).SetUint64(outputBlock))
if err != nil {
return nil, fmt.Errorf("failed to retrieve L2 block header %v: %w", outputBlock, err)
}
return types.NewInvalidL2BlockNumberProof(output, header), nil
}
func (o *OutputTraceProvider) outputAtBlock(ctx context.Context, block uint64) (common.Hash, error) { func (o *OutputTraceProvider) outputAtBlock(ctx context.Context, block uint64) (common.Hash, error) {
output, err := o.rollupProvider.OutputAtBlock(ctx, block) output, err := o.rollupProvider.OutputAtBlock(ctx, block)
if err != nil { if err != nil {
......
...@@ -11,7 +11,9 @@ import ( ...@@ -11,7 +11,9 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -29,34 +31,34 @@ var ( ...@@ -29,34 +31,34 @@ var (
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) {
deepGame := types.Depth(164) deepGame := types.Depth(164)
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame)
pos := types.NewPosition(0, big.NewInt(0)) pos := types.NewPosition(0, big.NewInt(0))
_, err := provider.Get(context.Background(), pos) _, err := provider.Get(context.Background(), pos)
require.ErrorIs(t, err, ErrIndexTooBig) require.ErrorIs(t, err, ErrIndexTooBig)
}) })
t.Run("FirstBlockAfterPrestate", func(t *testing.T) { t.Run("FirstBlockAfterPrestate", func(t *testing.T) {
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock)
value, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(0))) value, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(0)))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, firstOutputRoot, value) require.Equal(t, firstOutputRoot, value)
}) })
t.Run("MissingOutputAtBlock", func(t *testing.T) { t.Run("MissingOutputAtBlock", func(t *testing.T) {
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock)
_, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(1))) _, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(1)))
require.ErrorIs(t, err, errNoOutputAtBlock) require.ErrorIs(t, err, errNoOutputAtBlock)
}) })
t.Run("PostStateBlock", func(t *testing.T) { t.Run("PostStateBlock", func(t *testing.T) {
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock)
value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(228))) value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(228)))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, value, poststateOutputRoot) require.Equal(t, value, poststateOutputRoot)
}) })
t.Run("AfterPostStateBlock", func(t *testing.T) { t.Run("AfterPostStateBlock", func(t *testing.T) {
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock)
value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(229))) value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(229)))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, value, poststateOutputRoot) require.Equal(t, value, poststateOutputRoot)
...@@ -87,7 +89,7 @@ func TestHonestBlockNumber(t *testing.T) { ...@@ -87,7 +89,7 @@ func TestHonestBlockNumber(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
provider, stubRollupClient := setupWithTestData(t, prestateBlock, poststateBlock) provider, stubRollupClient, _ := setupWithTestData(t, prestateBlock, poststateBlock)
stubRollupClient.maxSafeHead = test.maxSafeHead stubRollupClient.maxSafeHead = test.maxSafeHead
actual, err := provider.HonestBlockNumber(context.Background(), test.pos) actual, err := provider.HonestBlockNumber(context.Background(), test.pos)
require.NoError(t, err) require.NoError(t, err)
...@@ -97,13 +99,55 @@ func TestHonestBlockNumber(t *testing.T) { ...@@ -97,13 +99,55 @@ func TestHonestBlockNumber(t *testing.T) {
t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) {
deepGame := types.Depth(164) deepGame := types.Depth(164)
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame)
pos := types.NewPosition(0, big.NewInt(0)) pos := types.NewPosition(0, big.NewInt(0))
_, err := provider.HonestBlockNumber(context.Background(), pos) _, err := provider.HonestBlockNumber(context.Background(), pos)
require.ErrorIs(t, err, ErrIndexTooBig) require.ErrorIs(t, err, ErrIndexTooBig)
}) })
} }
func TestGetL2BlockNumberChallenge(t *testing.T) {
tests := []struct {
name string
maxSafeHead uint64
expectChallenge bool
}{
{"NoChallengeWhenMaxHeadNotLimited", math.MaxUint64, false},
{"NoChallengeWhenBeforeMaxHead", poststateBlock + 1, false},
{"NoChallengeWhenAtMaxHead", poststateBlock, false},
{"ChallengeWhenBeforeMaxHead", poststateBlock - 1, true},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
provider, stubRollupClient, stubL2Client := setupWithTestData(t, prestateBlock, poststateBlock)
stubRollupClient.maxSafeHead = test.maxSafeHead
if test.expectChallenge {
stubRollupClient.outputs[test.maxSafeHead] = &eth.OutputResponse{
OutputRoot: eth.Bytes32{0xaa},
BlockRef: eth.L2BlockRef{
Number: test.maxSafeHead,
},
}
stubL2Client.headers[test.maxSafeHead] = &ethTypes.Header{
Number: new(big.Int).SetUint64(test.maxSafeHead),
Root: common.Hash{0xcc},
}
}
actual, err := provider.GetL2BlockNumberChallenge(context.Background())
if test.expectChallenge {
require.NoError(t, err)
require.Equal(t, &types.InvalidL2BlockNumberChallenge{
Output: stubRollupClient.outputs[test.maxSafeHead],
Header: stubL2Client.headers[test.maxSafeHead],
}, actual)
} else {
require.ErrorIs(t, err, types.ErrL2BlockNumberValid)
}
})
}
}
func TestClaimedBlockNumber(t *testing.T) { func TestClaimedBlockNumber(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
...@@ -128,7 +172,7 @@ func TestClaimedBlockNumber(t *testing.T) { ...@@ -128,7 +172,7 @@ func TestClaimedBlockNumber(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
provider, stubRollupClient := setupWithTestData(t, prestateBlock, poststateBlock) provider, stubRollupClient, _ := setupWithTestData(t, prestateBlock, poststateBlock)
stubRollupClient.maxSafeHead = test.maxSafeHead stubRollupClient.maxSafeHead = test.maxSafeHead
actual, err := provider.ClaimedBlockNumber(test.pos) actual, err := provider.ClaimedBlockNumber(test.pos)
require.NoError(t, err) require.NoError(t, err)
...@@ -138,7 +182,7 @@ func TestClaimedBlockNumber(t *testing.T) { ...@@ -138,7 +182,7 @@ func TestClaimedBlockNumber(t *testing.T) {
t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) {
deepGame := types.Depth(164) deepGame := types.Depth(164)
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame)
pos := types.NewPosition(0, big.NewInt(0)) pos := types.NewPosition(0, big.NewInt(0))
_, err := provider.ClaimedBlockNumber(pos) _, err := provider.ClaimedBlockNumber(pos)
require.ErrorIs(t, err, ErrIndexTooBig) require.ErrorIs(t, err, ErrIndexTooBig)
...@@ -146,12 +190,12 @@ func TestClaimedBlockNumber(t *testing.T) { ...@@ -146,12 +190,12 @@ func TestClaimedBlockNumber(t *testing.T) {
} }
func TestGetStepData(t *testing.T) { func TestGetStepData(t *testing.T) {
provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock)
_, _, _, err := provider.GetStepData(context.Background(), types.NewPosition(1, common.Big0)) _, _, _, err := provider.GetStepData(context.Background(), types.NewPosition(1, common.Big0))
require.ErrorIs(t, err, ErrGetStepData) require.ErrorIs(t, err, ErrGetStepData)
} }
func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, customGameDepth ...types.Depth) (*OutputTraceProvider, *stubRollupClient) { func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, customGameDepth ...types.Depth) (*OutputTraceProvider, *stubRollupClient, *stubL2HeaderSource) {
rollupClient := &stubRollupClient{ rollupClient := &stubRollupClient{
outputs: map[uint64]*eth.OutputResponse{ outputs: map[uint64]*eth.OutputResponse{
prestateBlock: { prestateBlock: {
...@@ -166,6 +210,9 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo ...@@ -166,6 +210,9 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo
}, },
maxSafeHead: math.MaxUint64, maxSafeHead: math.MaxUint64,
} }
l2Client := &stubL2HeaderSource{
headers: make(map[uint64]*ethTypes.Header),
}
inputGameDepth := gameDepth inputGameDepth := gameDepth
if len(customGameDepth) > 0 { if len(customGameDepth) > 0 {
inputGameDepth = customGameDepth[0] inputGameDepth = customGameDepth[0]
...@@ -173,10 +220,11 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo ...@@ -173,10 +220,11 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo
return &OutputTraceProvider{ return &OutputTraceProvider{
logger: testlog.Logger(t, log.LevelInfo), logger: testlog.Logger(t, log.LevelInfo),
rollupProvider: rollupClient, rollupProvider: rollupClient,
l2Client: l2Client,
prestateBlock: prestateBlock, prestateBlock: prestateBlock,
poststateBlock: poststateBlock, poststateBlock: poststateBlock,
gameDepth: inputGameDepth, gameDepth: inputGameDepth,
}, rollupClient }, rollupClient, l2Client
} }
type stubRollupClient struct { type stubRollupClient struct {
...@@ -201,3 +249,15 @@ func (s *stubRollupClient) SafeHeadAtL1Block(_ context.Context, l1BlockNum uint6 ...@@ -201,3 +249,15 @@ func (s *stubRollupClient) SafeHeadAtL1Block(_ context.Context, l1BlockNum uint6
}, },
}, nil }, nil
} }
type stubL2HeaderSource struct {
headers map[uint64]*ethTypes.Header
}
func (s *stubL2HeaderSource) HeaderByNumber(_ context.Context, num *big.Int) (*ethTypes.Header, error) {
header, ok := s.headers[num.Uint64()]
if !ok {
return nil, ethereum.NotFound
}
return header, nil
}
...@@ -139,7 +139,7 @@ func setupAdapterTest(t *testing.T, topDepth types.Depth) (split.ProviderCreator ...@@ -139,7 +139,7 @@ func setupAdapterTest(t *testing.T, topDepth types.Depth) (split.ProviderCreator
prestateProvider := &stubPrestateProvider{ prestateProvider := &stubPrestateProvider{
absolutePrestate: prestateOutputRoot, absolutePrestate: prestateOutputRoot,
} }
topProvider := NewTraceProvider(testlog.Logger(t, log.LevelInfo), prestateProvider, rollupClient, l1Head, topDepth, prestateBlock, poststateBlock) topProvider := NewTraceProvider(testlog.Logger(t, log.LevelInfo), prestateProvider, rollupClient, nil, l1Head, topDepth, prestateBlock, poststateBlock)
adapter := OutputRootSplitAdapter(topProvider, creator.Create) adapter := OutputRootSplitAdapter(topProvider, creator.Create)
return adapter, creator return adapter, creator
} }
......
...@@ -46,4 +46,8 @@ func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (h ...@@ -46,4 +46,8 @@ func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (h
return p.provider.AbsolutePreStateCommitment(ctx) return p.provider.AbsolutePreStateCommitment(ctx)
} }
func (p *TranslatingProvider) GetL2BlockNumberChallenge(ctx context.Context) (*types.InvalidL2BlockNumberChallenge, error) {
return p.provider.GetL2BlockNumberChallenge(ctx)
}
var _ types.TraceProvider = (*TranslatingProvider)(nil) var _ types.TraceProvider = (*TranslatingProvider)(nil)
...@@ -9,12 +9,15 @@ func (a ActionType) String() string { ...@@ -9,12 +9,15 @@ func (a ActionType) String() string {
} }
const ( const (
ActionTypeMove ActionType = "move" ActionTypeMove ActionType = "move"
ActionTypeStep ActionType = "step" ActionTypeStep ActionType = "step"
ActionTypeChallengeL2BlockNumber ActionType = "challenge-l2-block-number"
) )
type Action struct { type Action struct {
Type ActionType Type ActionType
// Moves and Steps
ParentIdx int ParentIdx int
ParentPosition Position ParentPosition Position
IsAttack bool IsAttack bool
...@@ -26,4 +29,7 @@ type Action struct { ...@@ -26,4 +29,7 @@ type Action struct {
PreState []byte PreState []byte
ProofData []byte ProofData []byte
OracleData *PreimageOracleData OracleData *PreimageOracleData
// Challenge L2 Block Number
InvalidL2BlockNumberChallenge *InvalidL2BlockNumberChallenge
} }
...@@ -10,6 +10,8 @@ import ( ...@@ -10,6 +10,8 @@ import (
var ( var (
ErrPositionDepthTooSmall = errors.New("position depth is too small") ErrPositionDepthTooSmall = errors.New("position depth is too small")
RootPosition = NewPositionFromGIndex(big.NewInt(1))
) )
// Depth is the depth of a position in a game tree where the root level has // Depth is the depth of a position in a game tree where the root level has
......
...@@ -13,6 +13,10 @@ func bi(i int) *big.Int { ...@@ -13,6 +13,10 @@ func bi(i int) *big.Int {
return big.NewInt(int64(i)) return big.NewInt(int64(i))
} }
func TestRootPosition(t *testing.T) {
require.True(t, RootPosition.IsRootPosition())
}
func TestBigMSB(t *testing.T) { func TestBigMSB(t *testing.T) {
large, ok := new(big.Int).SetString("18446744073709551615", 10) large, ok := new(big.Int).SetString("18446744073709551615", 10)
require.True(t, ok) require.True(t, ok)
......
...@@ -7,12 +7,15 @@ import ( ...@@ -7,12 +7,15 @@ import (
"time" "time"
preimage "github.com/ethereum-optimism/optimism/op-preimage" preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
var ( var (
ErrGameDepthReached = errors.New("game depth reached") ErrGameDepthReached = errors.New("game depth reached")
ErrL2BlockNumberValid = errors.New("l2 block number is valid")
) )
const ( const (
...@@ -103,6 +106,10 @@ type TraceAccessor interface { ...@@ -103,6 +106,10 @@ type TraceAccessor interface {
// GetStepData returns the data required to execute the step at the specified position, // GetStepData returns the data required to execute the step at the specified position,
// evaluated in the context of the specified claim (ref). // evaluated in the context of the specified claim (ref).
GetStepData(ctx context.Context, game Game, ref Claim, pos Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error) GetStepData(ctx context.Context, game Game, ref Claim, pos Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error)
// GetL2BlockNumberChallenge returns the data required to prove the correct L2 block number of the root claim.
// Returns ErrL2BlockNumberValid if the root claim is known to come from the same block as the claimed L2 block.
GetL2BlockNumberChallenge(ctx context.Context, game Game) (*InvalidL2BlockNumberChallenge, error)
} }
// PrestateProvider defines an interface to request the absolute prestate. // PrestateProvider defines an interface to request the absolute prestate.
...@@ -124,6 +131,10 @@ type TraceProvider interface { ...@@ -124,6 +131,10 @@ type TraceProvider interface {
// and any pre-image data that needs to be loaded into the oracle prior to execution (may be nil) // and any pre-image data that needs to be loaded into the oracle prior to execution (may be nil)
// The prestate returned from GetStepData for trace 10 should be the pre-image of the claim from trace 9 // The prestate returned from GetStepData for trace 10 should be the pre-image of the claim from trace 9
GetStepData(ctx context.Context, i Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error) GetStepData(ctx context.Context, i Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error)
// GetL2BlockNumberChallenge returns the data required to prove the correct L2 block number of the root claim.
// Returns ErrL2BlockNumberValid if the root claim is known to come from the same block as the claimed L2 block.
GetL2BlockNumberChallenge(ctx context.Context) (*InvalidL2BlockNumberChallenge, error)
} }
// ClaimData is the core of a claim. It must be unique inside a specific game. // ClaimData is the core of a claim. It must be unique inside a specific game.
...@@ -201,3 +212,15 @@ func NewClock(duration time.Duration, timestamp time.Time) Clock { ...@@ -201,3 +212,15 @@ func NewClock(duration time.Duration, timestamp time.Time) Clock {
Timestamp: timestamp, Timestamp: timestamp,
} }
} }
type InvalidL2BlockNumberChallenge struct {
Output *eth.OutputResponse
Header *ethTypes.Header
}
func NewInvalidL2BlockNumberProof(output *eth.OutputResponse, header *ethTypes.Header) *InvalidL2BlockNumberChallenge {
return &InvalidL2BlockNumberChallenge{
Output: output,
Header: header,
}
}
...@@ -172,6 +172,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string ...@@ -172,6 +172,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
cfg := NewGameCfg(opts...) cfg := NewGameCfg(opts...)
logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputCannonGameHelper") logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputCannonGameHelper")
rollupClient := h.System.RollupClient(l2Node) rollupClient := h.System.RollupClient(l2Node)
l2Client := h.System.NodeClient(l2Node)
extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg) extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg)
...@@ -200,7 +201,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string ...@@ -200,7 +201,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
l1Head := h.GetL1Head(ctx, game) l1Head := h.GetL1Head(ctx, game)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64()) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64())
provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64())
return &OutputCannonGameHelper{ return &OutputCannonGameHelper{
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System), OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
...@@ -228,6 +229,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri ...@@ -228,6 +229,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
cfg := NewGameCfg(opts...) cfg := NewGameCfg(opts...)
logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputAlphabetGameHelper") logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputAlphabetGameHelper")
rollupClient := h.System.RollupClient(l2Node) rollupClient := h.System.RollupClient(l2Node)
l2Client := h.System.NodeClient(l2Node)
extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg) extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg)
...@@ -256,7 +258,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri ...@@ -256,7 +258,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
l1Head := h.GetL1Head(ctx, game) l1Head := h.GetL1Head(ctx, game)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64()) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64())
provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64())
return &OutputAlphabetGameHelper{ return &OutputAlphabetGameHelper{
OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System), OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System),
......
...@@ -46,8 +46,9 @@ func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node ...@@ -46,8 +46,9 @@ func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node
splitDepth := g.SplitDepth(ctx) splitDepth := g.SplitDepth(ctx)
l1Head := g.GetL1Head(ctx) l1Head := g.GetL1Head(ctx)
rollupClient := g.System.RollupClient(l2Node) rollupClient := g.System.RollupClient(l2Node)
l2Client := g.System.NodeClient(l2Node)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock)
correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
g.Require.NoError(err, "Create trace accessor") g.Require.NoError(err, "Create trace accessor")
return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, contract, correctTrace) return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, contract, correctTrace)
} }
......
...@@ -290,7 +290,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, ...@@ -290,7 +290,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
rollupClient := g.System.RollupClient(l2Node) rollupClient := g.System.RollupClient(l2Node)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock)
l1Head := g.GetL1Head(ctx) l1Head := g.GetL1Head(ctx)
outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock)
var localContext common.Hash var localContext common.Hash
selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) { selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
......
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