Commit 700a1dbc authored by refcell's avatar refcell Committed by GitHub

feat(op-challenger): sort the games listing subcommand (#10454)

parent 7e2bb8a9
package main package main
import ( import (
"cmp"
"context" "context"
"fmt" "fmt"
"slices" "slices"
...@@ -14,6 +15,7 @@ import ( ...@@ -14,6 +15,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/dial"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
...@@ -21,6 +23,22 @@ import ( ...@@ -21,6 +23,22 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var ColumnTypes = []string{"time", "claimCount", "l2BlockNum"}
var (
SortByFlag = &cli.StringFlag{
Name: "sort-by",
Usage: "Sort games by column. Valid options: " + openum.EnumString(ColumnTypes),
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "SORT_BY"),
}
SortOrderFlag = &cli.StringFlag{
Name: "sort-order",
Usage: "Sort order for games. Valid options: 'asc' or 'desc'.",
Value: "asc",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "SORT_ORDER"),
}
)
func ListGames(ctx *cli.Context) error { func ListGames(ctx *cli.Context) error {
logger, err := setupLogging(ctx) logger, err := setupLogging(ctx)
if err != nil { if err != nil {
...@@ -34,6 +52,14 @@ func ListGames(ctx *cli.Context) error { ...@@ -34,6 +52,14 @@ func ListGames(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
sortBy := ctx.String(SortByFlag.Name)
if !slices.Contains(ColumnTypes, sortBy) {
return fmt.Errorf("invalid sort-by value: %v", sortBy)
}
sortOrder := ctx.String(SortOrderFlag.Name)
if sortOrder != "" && sortOrder != "asc" && sortOrder != "desc" {
return fmt.Errorf("invalid sort-order value: %v", sortOrder)
}
gameWindow := ctx.Duration(flags.GameWindowFlag.Name) gameWindow := ctx.Duration(flags.GameWindowFlag.Name)
...@@ -49,7 +75,7 @@ func ListGames(ctx *cli.Context) error { ...@@ -49,7 +75,7 @@ func ListGames(ctx *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve current head block: %w", err) return fmt.Errorf("failed to retrieve current head block: %w", err)
} }
return listGames(ctx.Context, caller, contract, head.Hash(), gameWindow) return listGames(ctx.Context, caller, contract, head.Hash(), gameWindow, sortBy, sortOrder)
} }
type gameInfo struct { type gameInfo struct {
...@@ -61,7 +87,7 @@ type gameInfo struct { ...@@ -61,7 +87,7 @@ type gameInfo struct {
err error err error
} }
func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contracts.DisputeGameFactoryContract, block common.Hash, gameWindow time.Duration) error { func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contracts.DisputeGameFactoryContract, block common.Hash, gameWindow time.Duration, sortBy, sortOrder string) error {
earliestTimestamp := clock.MinCheckedTimestamp(clock.SystemClock, gameWindow) earliestTimestamp := clock.MinCheckedTimestamp(clock.SystemClock, gameWindow)
games, err := factory.GetGamesAtOrAfter(ctx, block, earliestTimestamp) games, err := factory.GetGamesAtOrAfter(ctx, block, earliestTimestamp)
if err != nil { if err != nil {
...@@ -69,7 +95,7 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr ...@@ -69,7 +95,7 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
} }
slices.Reverse(games) slices.Reverse(games)
infos := make([]*gameInfo, len(games)) infos := make([]gameInfo, len(games))
var wg sync.WaitGroup var wg sync.WaitGroup
for idx, game := range games { for idx, game := range games {
gameContract, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, game.Proxy, caller) gameContract, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, game.Proxy, caller)
...@@ -77,8 +103,9 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr ...@@ -77,8 +103,9 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
return fmt.Errorf("failed to create dispute game contract: %w", err) return fmt.Errorf("failed to create dispute game contract: %w", err)
} }
info := gameInfo{GameMetadata: game} info := gameInfo{GameMetadata: game}
infos[idx] = &info infos[idx] = info
gameProxy := game.Proxy gameProxy := game.Proxy
currIndex := idx
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
...@@ -87,20 +114,46 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr ...@@ -87,20 +114,46 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
info.err = fmt.Errorf("failed to retrieve metadata for game %v: %w", gameProxy, err) info.err = fmt.Errorf("failed to retrieve metadata for game %v: %w", gameProxy, err)
return return
} }
info.status = status infos[currIndex].status = status
info.l2BlockNum = l2BlockNum infos[currIndex].l2BlockNum = l2BlockNum
info.rootClaim = rootClaim infos[currIndex].rootClaim = rootClaim
claimCount, err := gameContract.GetClaimCount(ctx) claimCount, err := gameContract.GetClaimCount(ctx)
if err != nil { if err != nil {
info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err) info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err)
return return
} }
info.claimCount = claimCount infos[currIndex].claimCount = claimCount
}() }()
} }
wg.Wait() wg.Wait()
lineFormat := "%3v %-42v %4v %-21v %14v %-66v %6v %-14v\n" lineFormat := "%3v %-42v %4v %-21v %14v %-66v %6v %-14v\n"
fmt.Printf(lineFormat, "Idx", "Game", "Type", "Created (Local)", "L2 Block", "Output Root", "Claims", "Status") fmt.Printf(lineFormat, "Idx", "Game", "Type", "Created (Local)", "L2 Block", "Output Root", "Claims", "Status")
// Sort infos by the specified column
switch sortBy {
case "time":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.Timestamp, i.Timestamp)
}
return cmp.Compare(i.Timestamp, j.Timestamp)
})
case "claimCount":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.claimCount, i.claimCount)
}
return cmp.Compare(i.claimCount, j.claimCount)
})
case "l2BlockNum":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.l2BlockNum, i.l2BlockNum)
}
return cmp.Compare(i.l2BlockNum, j.l2BlockNum)
})
}
for _, game := range infos { for _, game := range infos {
if game.err != nil { if game.err != nil {
return game.err return game.err
...@@ -114,6 +167,8 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr ...@@ -114,6 +167,8 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
func listGamesFlags() []cli.Flag { func listGamesFlags() []cli.Flag {
cliFlags := []cli.Flag{ cliFlags := []cli.Flag{
SortByFlag,
SortOrderFlag,
flags.L1EthRpcFlag, flags.L1EthRpcFlag,
flags.FactoryAddressFlag, flags.FactoryAddressFlag,
flags.GameWindowFlag, flags.GameWindowFlag,
......
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