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

op-challenger: Add list-games and list-claims subcommands. (#9150)

* op-challenger: Add list-games and list-claims subcommands.

* op-challenger: Hide subcommands as they are unsupported.

* fix(op-challenger): lint for loop variable capture

* rabbit's foot
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(op-challenger): fix nomenclature

---------
Co-authored-by: default avatarrefcell <abigger87@gmail.com>
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
parent e75eb597
package main
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/dial"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/urfave/cli/v2"
)
var (
GameAddressFlag = &cli.StringFlag{
Name: "game-address",
Usage: "Address of the fault game contract.",
EnvVars: opservice.PrefixEnvVar("OP_CHALLENGER", "GAME_FACTORY_ADDRESS"),
}
)
func ListClaims(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
gameAddr, err := opservice.ParseAddress(ctx.String(GameAddressFlag.Name))
if err != nil {
return err
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(gameAddr, caller)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
return listClaims(ctx.Context, contract)
}
func listClaims(ctx context.Context, game *contracts.FaultDisputeGameContract) error {
maxDepth, err := game.GetMaxGameDepth(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve max depth: %w", err)
}
splitDepth, err := game.GetSplitDepth(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve split depth: %w", err)
}
status, err := game.GetStatus(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve status: %w", err)
}
_, l2BlockNum, err := game.GetBlockRange(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve status: %w", err)
}
claims, err := game.GetAllClaims(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve claims: %w", err)
}
info := fmt.Sprintf("Claim count: %v\n", len(claims))
for i, claim := range claims {
pos := claim.Position
info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, Value: %v, Countered: %v, ParentIndex: %v\n",
i, pos.ToGIndex(), pos.Depth(), pos.IndexAtDepth(), pos.TraceIndex(maxDepth), claim.Value.Hex(), claim.CounteredBy, claim.ParentContractIndex)
}
fmt.Printf("Status: %v - L2 Block: %v - Split Depth: %v - Max Depth: %v:\n%v\n",
status, l2BlockNum, splitDepth, maxDepth, info)
return nil
}
var listClaimsFlags = []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
}
func init() {
listClaimsFlags = append(listClaimsFlags, oplog.CLIFlags("OP_CHALLENGER")...)
}
var ListClaimsCommand = &cli.Command{
Name: "list-claims",
Usage: "List the claims in a dispute game",
Description: "Lists the claims in a dispute game",
Action: ListClaims,
Flags: listClaimsFlags,
Hidden: true,
}
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/dial"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
func ListGames(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
factoryAddr, err := opservice.ParseAddress(ctx.String(flags.FactoryAddressFlag.Name))
if err != nil {
return err
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract, err := contracts.NewDisputeGameFactoryContract(factoryAddr, caller)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
head, err := l1Client.HeaderByNumber(ctx.Context, nil)
if err != nil {
return fmt.Errorf("failed to retrieve current head block: %w", err)
}
return listGames(ctx.Context, caller, contract, head.Hash())
}
type gameInfo struct {
types.GameMetadata
claimCount uint64
status types.GameStatus
err error
}
func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contracts.DisputeGameFactoryContract, block common.Hash) error {
games, err := factory.GetAllGames(ctx, block)
if err != nil {
return fmt.Errorf("failed to retrieve games: %w", err)
}
infos := make([]*gameInfo, len(games))
var wg sync.WaitGroup
for idx, game := range games {
gameContract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller)
if err != nil {
return fmt.Errorf("failed to bind game contract at %v: %w", game.Proxy, err)
}
info := gameInfo{GameMetadata: game}
infos[idx] = &info
gameProxy := game.Proxy
wg.Add(1)
go func() {
defer wg.Done()
claimCount, err := gameContract.GetClaimCount(ctx)
if err != nil {
info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err)
return
}
info.claimCount = claimCount
status, err := gameContract.GetStatus(ctx)
if err != nil {
info.err = fmt.Errorf("failed to retrieve status for game %v: %w", gameProxy, err)
return
}
info.status = status
}()
}
wg.Wait()
for idx, game := range infos {
if game.err != nil {
return err
}
fmt.Printf("%v Game: %v Type: %v Created: %v Claims: %v Status: %v\n",
idx, game.Proxy, game.GameType, time.Unix(int64(game.Timestamp), 0), game.claimCount, game.status)
}
return nil
}
var listGamesFlags = []cli.Flag{
flags.L1EthRpcFlag,
flags.FactoryAddressFlag,
}
func init() {
listGamesFlags = append(listGamesFlags, oplog.CLIFlags("OP_CHALLENGER")...)
}
var ListGamesCommand = &cli.Command{
Name: "list-games",
Usage: "List the games created by a dispute game factory",
Description: "Lists the games created by a dispute game factory",
Action: ListGames,
Flags: listGamesFlags,
Hidden: true,
}
......@@ -45,6 +45,10 @@ func run(ctx context.Context, args []string, action ConfiguredLifecycle) error {
app.Name = "op-challenger"
app.Usage = "Challenge outputs"
app.Description = "Ensures that on chain outputs are correct."
app.Commands = []*cli.Command{
ListGamesCommand,
ListClaimsCommand,
}
app.Action = cliapp.LifecycleCmd(func(ctx *cli.Context, close context.CancelCauseFunc) (cliapp.Lifecycle, error) {
logger, err := setupLogging(ctx)
if err != nil {
......
......@@ -57,6 +57,29 @@ func (f *DisputeGameFactoryContract) GetGameImpl(ctx context.Context, gameType u
return result.GetAddress(0), nil
}
func (f *DisputeGameFactoryContract) GetAllGames(ctx context.Context, blockHash common.Hash) ([]types.GameMetadata, error) {
count, err := f.GetGameCount(ctx, blockHash)
if err != nil {
return nil, err
}
calls := make([]*batching.ContractCall, count)
for i := uint64(0); i < count; i++ {
calls[i] = f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(i))
}
results, err := f.multiCaller.Call(ctx, batching.BlockByHash(blockHash), calls...)
if err != nil {
return nil, fmt.Errorf("failed to fetch games: %w", err)
}
var games []types.GameMetadata
for _, result := range results {
games = append(games, f.decodeGame(result))
}
return games, nil
}
func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata {
gameType := result.GetUint8(0)
timestamp := result.GetUint64(1)
......
......@@ -78,6 +78,35 @@ func TestLoadGame(t *testing.T) {
}
}
func TestGetAllGames(t *testing.T) {
blockHash := common.Hash{0xbb, 0xce}
stubRpc, factory := setupDisputeGameFactoryTest(t)
game0 := types.GameMetadata{
GameType: 0,
Timestamp: 1234,
Proxy: common.Address{0xaa},
}
game1 := types.GameMetadata{
GameType: 1,
Timestamp: 5678,
Proxy: common.Address{0xbb},
}
game2 := types.GameMetadata{
GameType: 99,
Timestamp: 9988,
Proxy: common.Address{0xcc},
}
expectedGames := []types.GameMetadata{game0, game1, game2}
stubRpc.SetResponse(factoryAddr, methodGameCount, batching.BlockByHash(blockHash), nil, []interface{}{big.NewInt(int64(len(expectedGames)))})
for idx, expected := range expectedGames {
expectGetGame(stubRpc, idx, blockHash, expected)
}
actualGames, err := factory.GetAllGames(context.Background(), blockHash)
require.NoError(t, err)
require.Equal(t, expectedGames, actualGames)
}
func TestGetGameImpl(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
gameType := uint8(3)
......
......@@ -479,7 +479,7 @@ func (g *OutputGameHelper) gameData(ctx context.Context) string {
extra = fmt.Sprintf("Block num: %v", blockNum)
}
}
info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, Value: %v, Countered: %v, ParentIndex: %v %v\n",
info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, Value: %v, Countered By: %v, ParentIndex: %v %v\n",
i, claim.Position.Int64(), pos.Depth(), pos.IndexAtDepth(), pos.TraceIndex(maxDepth), common.Hash(claim.Claim).Hex(), claim.CounteredBy, claim.ParentIndex, extra)
}
l2BlockNum := g.L2BlockNum(ctx)
......
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