Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion op-challenger/cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1409,7 +1409,7 @@ func requiredArgs(gameType gameTypes.GameType) map[string]string {
addRequiredSuperCannonKonaArgs(args)
case gameTypes.SuperAsteriscKonaGameType:
addRequiredSuperAsteriscKonaArgs(args)
case gameTypes.AlphabetGameType, gameTypes.FastGameType:
case gameTypes.OptimisticZKGameType, gameTypes.AlphabetGameType, gameTypes.FastGameType:
addRequiredOutputRootArgs(args)
}
return args
Expand Down
5 changes: 5 additions & 0 deletions op-challenger/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ func (c Config) Check() error {
return err
}
}
if c.GameTypeEnabled(gameTypes.OptimisticZKGameType) {
if c.RollupRpc == "" {
Comment thread
ajsutton marked this conversation as resolved.
return ErrMissingRollupRpc
}
}
if c.GameTypeEnabled(gameTypes.AlphabetGameType) || c.GameTypeEnabled(gameTypes.FastGameType) {
if c.RollupRpc == "" {
return ErrMissingRollupRpc
Expand Down
7 changes: 7 additions & 0 deletions op-challenger/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func applyValidConfigForSuperAsteriscKona(t *testing.T, cfg *Config) {
applyValidConfigForAsteriscKona(t, cfg)
}

func applyValidConfigForOptimisticZK(cfg *Config) {
cfg.RollupRpc = validRollupRpc
}

func validConfig(t *testing.T, gameType gameTypes.GameType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, gameType)
if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType {
Expand All @@ -173,6 +177,9 @@ func validConfig(t *testing.T, gameType gameTypes.GameType) Config {
if gameType == gameTypes.SuperAsteriscKonaGameType {
applyValidConfigForSuperAsteriscKona(t, &cfg)
}
if gameType == gameTypes.OptimisticZKGameType {
applyValidConfigForOptimisticZK(&cfg)
}
return cfg
}

Expand Down
2 changes: 1 addition & 1 deletion op-challenger/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ func CheckRequired(ctx *cli.Context, types []gameTypes.GameType) error {
if err := CheckSuperAsteriscKonaFlags(ctx); err != nil {
return err
}
case gameTypes.AlphabetGameType, gameTypes.FastGameType:
case gameTypes.OptimisticZKGameType, gameTypes.AlphabetGameType, gameTypes.FastGameType:
if err := checkOutputProviderFlags(ctx); err != nil {
return err
}
Expand Down
87 changes: 87 additions & 0 deletions op-challenger/game/fault/contracts/optimisticzkdisputegame.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,36 @@ import (
"github.com/ethereum/go-ethereum/common"
)

type ProposalStatus uint8

const (
ProposalStatusUnchallenged ProposalStatus = iota
ProposalStatusChallenged
ProposalStatusUnchallengedAndValidProofProvided
ProposalStatusChallengedAndValidProofProvided
ProposalStatusResolved
)

var (
methodChallenge = "challenge"
methodChallengerBond = "challengerBond"
methodClaimData = "claimData"
)

type claimData struct {
ParentIndex uint32
CounteredBy common.Address
Prover common.Address
Claim common.Hash
Status ProposalStatus
Deadline uint64
}

type OptimisticZKDisputeGameContract interface {
DisputeGameContract
CanChallenge(ctx context.Context) (bool, error)
ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error)
GetProposal(ctx context.Context) (common.Hash, uint64, error)
}

type OptimisticZKDisputeGameContractLatest struct {
Expand Down Expand Up @@ -103,6 +131,48 @@ func (g *OptimisticZKDisputeGameContractLatest) GetGameRange(ctx context.Context
return
}

func (g *OptimisticZKDisputeGameContractLatest) CanChallenge(ctx context.Context) (bool, error) {
data, err := g.claimData(ctx)
if err != nil {
return false, err
}
return data.Status == ProposalStatusUnchallenged, nil
Comment thread
ajsutton marked this conversation as resolved.
}

func (g *OptimisticZKDisputeGameContractLatest) claimData(ctx context.Context) (claimData, error) {
defer g.metrics.StartContractRequest("ClaimData")()
result, err := g.multiCaller.SingleCall(ctx, rpcblock.Latest, g.contract.Call(methodClaimData))
if err != nil {
return claimData{}, fmt.Errorf("failed to retrieve claim data: %w", err)
}
return g.decodeClaimData(result), nil
}

func (g *OptimisticZKDisputeGameContractLatest) ChallengeTx(ctx context.Context) (txmgr.TxCandidate, error) {
tx, err := g.contract.Call(methodChallenge).ToTxCandidate()
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to create challenge tx: %w", err)
}

result, err := g.multiCaller.SingleCall(ctx, rpcblock.Latest, g.contract.Call(methodChallengerBond))
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to retrieve challenger bond: %w", err)
}
tx.Value = result.GetBigInt(0)
return tx, nil
}

func (g *OptimisticZKDisputeGameContractLatest) GetProposal(ctx context.Context) (common.Hash, uint64, error) {
results, err := g.multiCaller.Call(ctx, rpcblock.Latest, g.contract.Call(methodRootClaim), g.contract.Call(methodL2SequenceNumber))
if err != nil {
return common.Hash{}, 0, fmt.Errorf("failed to retrieve proposal: %w", err)
}
if len(results) != 2 {
return common.Hash{}, 0, fmt.Errorf("expected 2 results but got %v", len(results))
}
return results[0].GetHash(0), results[1].GetBigInt(0).Uint64(), nil
}

func (g *OptimisticZKDisputeGameContractLatest) GetResolvedAt(ctx context.Context, block rpcblock.Block) (time.Time, error) {
defer g.metrics.StartContractRequest("GetResolvedAt")()
result, err := g.multiCaller.SingleCall(ctx, block, g.contract.Call(methodResolvedAt))
Expand Down Expand Up @@ -132,4 +202,21 @@ func (g *OptimisticZKDisputeGameContractLatest) resolveCall() *batching.Contract
return g.contract.Call(methodResolve)
}

func (g *OptimisticZKDisputeGameContractLatest) decodeClaimData(result *batching.CallResult) claimData {
parentIndex := result.GetUint32(0)
counteredBy := result.GetAddress(1)
prover := result.GetAddress(2)
claim := result.GetHash(3)
status := result.GetUint8(4)
deadline := result.GetUint64(5)
return claimData{
ParentIndex: parentIndex,
CounteredBy: counteredBy,
Prover: prover,
Claim: claim,
Status: ProposalStatus(status),
Deadline: deadline,
}
}

var _ DisputeGameContract = (*OptimisticZKDisputeGameContractLatest)(nil)
124 changes: 112 additions & 12 deletions op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const (
versZKLatest = "0.0.0"
)

var (
zkGameAddr = common.Address{0x45, 0x44, 0x43}
)

var zkVersions = []contractVersion{
{
version: versZKLatest,
Expand Down Expand Up @@ -82,7 +86,7 @@ func TestZKSimpleGetters(t *testing.T) {
t.Skip("Skipping for this version")
}
stubRpc, game := setupZKDisputeGameTest(t, version)
stubRpc.SetResponse(fdgAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result})
stubRpc.SetResponse(zkGameAddr, test.method, rpcblock.Latest, nil, []interface{}{test.result})
status, err := test.call(game)
require.NoError(t, err)
expected := test.expected
Expand All @@ -106,10 +110,10 @@ func TestZKGetMetadata(t *testing.T) {
expectedRootClaim := common.Hash{0x01, 0x02}
expectedStatus := gameTypes.GameStatusChallengerWon
block := rpcblock.ByNumber(889)
stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head})
stubRpc.SetResponse(fdgAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)})
stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim})
stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus})
stubRpc.SetResponse(zkGameAddr, methodL1Head, block, nil, []interface{}{expectedL1Head})
stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)})
stubRpc.SetResponse(zkGameAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim})
stubRpc.SetResponse(zkGameAddr, methodStatus, block, nil, []interface{}{expectedStatus})
actual, err := contract.GetMetadata(context.Background(), block)
expected := GenericGameMetadata{
L1Head: expectedL1Head,
Expand All @@ -130,8 +134,8 @@ func TestZKGetGameRange(t *testing.T) {
stubRpc, contract := setupZKDisputeGameTest(t, version)
expectedStart := uint64(65)
expectedEnd := uint64(102)
stubRpc.SetResponse(fdgAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)})
stubRpc.SetResponse(fdgAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)})
stubRpc.SetResponse(zkGameAddr, methodStartingBlockNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedStart)})
stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(expectedEnd)})
start, end, err := contract.GetGameRange(context.Background())
require.NoError(t, err)
require.Equal(t, expectedStart, start)
Expand All @@ -145,29 +149,125 @@ func TestZKResolveTx(t *testing.T) {
version := version
t.Run(version.String(), func(t *testing.T) {
stubRpc, game := setupZKDisputeGameTest(t, version)
stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil)
stubRpc.SetResponse(zkGameAddr, methodResolve, rpcblock.Latest, nil, nil)
tx, err := game.ResolveTx()
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
}
}

func TestZKCanChallenge(t *testing.T) {
for _, version := range zkVersions {
version := version
t.Run(version.String(), func(t *testing.T) {
parentIndex := uint32(525)
claim := common.Hash{0xbb}
deadline := uint64(42824240)

tests := []struct {
name string
counteredBy common.Address
prover common.Address
status ProposalStatus
expectedResult bool
}{
{
name: "Unchallenged",
status: ProposalStatusUnchallenged,
expectedResult: true,
},
{
name: "Challenged",
counteredBy: common.Address{0xaa},
status: ProposalStatusChallenged,
expectedResult: false,
},
{
name: "UnchallengedAndProven",
prover: common.Address{0xaa},
status: ProposalStatusUnchallengedAndValidProofProvided,
expectedResult: false,
},
{
name: "ChallengedAndProven",
counteredBy: common.Address{0xaa},
prover: common.Address{0xbb},
status: ProposalStatusChallengedAndValidProofProvided,
expectedResult: false,
},
{
name: "Resolved",
status: ProposalStatusResolved,
expectedResult: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
stubRpc, game := setupZKDisputeGameTest(t, version)
stubRpc.SetResponse(zkGameAddr, methodClaimData, rpcblock.Latest, nil, []interface{}{
parentIndex, test.counteredBy, test.prover, claim, test.status, deadline,
})
result, err := game.CanChallenge(context.Background())
require.NoError(t, err)
require.Equal(t, test.expectedResult, result)
})
}
})
}
}

func TestZKChallengeTx(t *testing.T) {
for _, version := range zkVersions {
version := version
t.Run(version.String(), func(t *testing.T) {
bond := big.NewInt(97592472)

stubRpc, game := setupZKDisputeGameTest(t, version)
stubRpc.SetResponse(zkGameAddr, methodChallengerBond, rpcblock.Latest, nil, []interface{}{bond})
stubRpc.SetResponse(zkGameAddr, methodChallenge, rpcblock.Latest, nil, nil)

tx, err := game.ChallengeTx(context.Background())
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
}
}

func TestZKGetProposal(t *testing.T) {
for _, version := range zkVersions {
version := version
t.Run(version.String(), func(t *testing.T) {
rootClaim := common.Hash{0xaa}
l2SequenceNumber := big.NewInt(1236)
stubRpc, game := setupZKDisputeGameTest(t, version)
stubRpc.SetResponse(zkGameAddr, methodRootClaim, rpcblock.Latest, nil, []interface{}{rootClaim})
stubRpc.SetResponse(zkGameAddr, methodL2SequenceNumber, rpcblock.Latest, nil, []interface{}{l2SequenceNumber})

actualClaim, actualSeqNum, err := game.GetProposal(context.Background())
require.NoError(t, err)
require.Equal(t, rootClaim, actualClaim)
require.Equal(t, l2SequenceNumber.Uint64(), actualSeqNum)
})
}
}

func setupZKDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, OptimisticZKDisputeGameContract) {
fdgAbi := version.loadAbi()

vmAbi := snapshots.LoadMIPSABI()
oracleAbi := snapshots.LoadPreimageOracleABI()

stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi)
stubRpc := batchingTest.NewAbiBasedRpc(t, zkGameAddr, fdgAbi)
stubRpc.AddContract(vmAddr, vmAbi)
stubRpc.AddContract(oracleAddr, oracleAbi)
caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)

stubRpc.SetResponse(fdgAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)})
stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version})
stubRpc.SetResponse(zkGameAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)})
stubRpc.SetResponse(zkGameAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version})
stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest})
game, err := NewOptimisticZKDisputeGameContract(contractMetrics.NoopContractMetrics, fdgAddr, caller)
game, err := NewOptimisticZKDisputeGameContract(contractMetrics.NoopContractMetrics, zkGameAddr, caller)
require.NoError(t, err)
return stubRpc, game
}
5 changes: 5 additions & 0 deletions op-challenger/game/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
challengerClient "github.com/ethereum-optimism/optimism/op-challenger/game/client"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher"
"github.com/ethereum-optimism/optimism/op-challenger/game/zk"
"github.com/ethereum-optimism/optimism/op-challenger/sender"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
Expand Down Expand Up @@ -217,6 +218,10 @@ func (s *Service) registerGameTypes(ctx context.Context, cfg *config.Config) err
if err != nil {
return err
}
err = zk.RegisterGameTypes(ctx, s.systemClock, s.l1Clock, s.logger, s.metrics, cfg, gameTypeRegistry, s.txSender, s.clientProvider)
if err != nil {
return err
}
s.registry = gameTypeRegistry
s.oracles = oracles
return nil
Expand Down
3 changes: 2 additions & 1 deletion op-challenger/game/types/game_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
SuperAsteriscKonaGameType GameType = 7
CannonKonaGameType GameType = 8
SuperCannonKonaGameType GameType = 9
OptimisticZKGameType GameType = 10 // Not (yet) supported by op-challenger
OptimisticZKGameType GameType = 10
FastGameType GameType = 254
AlphabetGameType GameType = 255
KailuaGameType GameType = 1337 // Not supported by op-challenger
Expand All @@ -42,6 +42,7 @@ var SupportedGameTypes = []GameType{
SuperCannonKonaGameType,
SuperPermissionedGameType,
SuperAsteriscKonaGameType,
OptimisticZKGameType,
}

// Set implements the Set method required by the [cli.Generic] interface.
Expand Down
Loading