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
9 changes: 9 additions & 0 deletions op-challenger/challenger/challenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ func (c *Challenger) NewOracleSubscription() (*Subscription, error) {
return NewSubscription(query, c.Client(), c.log), nil
}

// NewFactorySubscription creates a new [Subscription] listening to the DisputeGameFactory contract.
func (c *Challenger) NewFactorySubscription() (*Subscription, error) {
query, err := BuildDisputeGameLogFilter(c.dgfABI)
if err != nil {
return nil, err
}
return NewSubscription(query, c.Client(), c.log), nil
}

// NewChallenger creates a new Challenger
func NewChallenger(cfg config.Config, l log.Logger, m metrics.Metricer) (*Challenger, error) {
ctx, cancel := context.WithCancel(context.Background())
Expand Down
33 changes: 33 additions & 0 deletions op-challenger/challenger/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package challenger

import (
"errors"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)

var ErrMissingFactoryEvent = errors.New("missing factory event")

// BuildDisputeGameLogFilter creates a filter query for the DisputeGameFactory contract.
//
// The `DisputeGameCreated` event is encoded as:
// 0: address indexed disputeProxy,
// 1: GameType indexed gameType,
// 2: Claim indexed rootClaim,
func BuildDisputeGameLogFilter(contract *abi.ABI) (ethereum.FilterQuery, error) {
event := contract.Events["DisputeGameCreated"]

if event.ID == (common.Hash{}) {
return ethereum.FilterQuery{}, ErrMissingFactoryEvent
}

query := ethereum.FilterQuery{
Topics: [][]common.Hash{
{event.ID},
},
}

return query, nil
}
46 changes: 46 additions & 0 deletions op-challenger/challenger/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package challenger

import (
"testing"

"github.com/stretchr/testify/require"

eth "github.com/ethereum/go-ethereum"
abi "github.com/ethereum/go-ethereum/accounts/abi"
common "github.com/ethereum/go-ethereum/common"
)

// TestBuildDisputeGameLogFilter_Succeeds tests that the DisputeGame
// Log Filter is built correctly.
func TestBuildDisputeGameLogFilter_Succeeds(t *testing.T) {
event := abi.Event{
ID: [32]byte{0x01},
}

filterQuery := eth.FilterQuery{
Topics: [][]common.Hash{
{event.ID},
},
}

dgfABI := abi.ABI{
Events: map[string]abi.Event{
"DisputeGameCreated": event,
},
}

query, err := BuildDisputeGameLogFilter(&dgfABI)
require.Equal(t, filterQuery, query)
require.NoError(t, err)
}

// TestBuildDisputeGameLogFilter_Fails tests that the DisputeGame
// Log Filter fails when the event definition is missing.
func TestBuildDisputeGameLogFilter_Fails(t *testing.T) {
dgfABI := abi.ABI{
Events: map[string]abi.Event{},
}

_, err := BuildDisputeGameLogFilter(&dgfABI)
require.ErrorIs(t, ErrMissingFactoryEvent, err)
}
18 changes: 18 additions & 0 deletions op-challenger/cmd/watch/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,22 @@ var Subcommands = cli.Commands{
return Oracle(logger, cfg)
},
},
{
Name: "factory",
Usage: "Watches the DisputeGameFactory for new dispute games",
Action: func(ctx *cli.Context) error {
logger, err := config.LoggerFromCLI(ctx)
if err != nil {
return err
}
logger.Info("Listening for new dispute games")

cfg, err := config.NewConfigFromCLI(ctx)
if err != nil {
return err
}

return Factory(logger, cfg)
},
},
}
62 changes: 62 additions & 0 deletions op-challenger/cmd/watch/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package watch

import (
"fmt"
"os"
"os/signal"
"syscall"

"github.com/ethereum/go-ethereum/log"

"github.com/ethereum-optimism/optimism/op-challenger/challenger"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
)

// Factory listens to the DisputeGameFactory for newly created dispute games.
func Factory(logger log.Logger, cfg *config.Config) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config: %w", err)
}

m := metrics.NewMetrics("default")

service, err := challenger.NewChallenger(*cfg, logger, m)
if err != nil {
logger.Error("Unable to create the Challenger", "error", err)
return err
}

logger.Info("Listening for DisputeGameCreated events from the DisputeGameFactory contract", "dgf", cfg.DGFAddress.String())

subscription, err := service.NewFactorySubscription()
if err != nil {
logger.Error("Unable to create the subscription", "error", err)
return err
}

err = subscription.Subscribe()
if err != nil {
logger.Error("Unable to subscribe to the DisputeGameFactory contract", "error", err)
return err
}

defer subscription.Quit()

interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)

for {
select {
case log := <-subscription.Logs():
logger.Info("Received log", "log", log)
case <-interruptChannel:
logger.Info("Received interrupt signal, exiting...")
}
}
}