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

Merge pull request #8159 from ethereum-optimism/aj/register-output-game

op-challenger: Start wiring up output cannon trace type
parents c7f6938e b12bf0ea
...@@ -34,6 +34,7 @@ var ( ...@@ -34,6 +34,7 @@ var (
ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrCannonNetworkUnknown = errors.New("unknown cannon network") ErrCannonNetworkUnknown = errors.New("unknown cannon network")
ErrMissingRollupRpc = errors.New("missing rollup rpc url") ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrCannonAndOutputCannonConflict = errors.New("trace types cannon and outputCannon cannot be enabled at the same time")
) )
type TraceType string type TraceType string
...@@ -187,6 +188,9 @@ func (c Config) Check() error { ...@@ -187,6 +188,9 @@ func (c Config) Check() error {
return ErrMissingRollupRpc return ErrMissingRollupRpc
} }
} }
if c.TraceTypeEnabled(TraceTypeCannon) && c.TraceTypeEnabled(TraceTypeOutputCannon) {
return ErrCannonAndOutputCannonConflict
}
if c.TraceTypeEnabled(TraceTypeCannon) || c.TraceTypeEnabled(TraceTypeOutputCannon) { if c.TraceTypeEnabled(TraceTypeCannon) || c.TraceTypeEnabled(TraceTypeOutputCannon) {
if c.CannonBin == "" { if c.CannonBin == "" {
return ErrMissingCannonBin return ErrMissingCannonBin
......
...@@ -135,6 +135,12 @@ func TestRollupRpcRequired(t *testing.T) { ...@@ -135,6 +135,12 @@ func TestRollupRpcRequired(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingRollupRpc) require.ErrorIs(t, config.Check(), ErrMissingRollupRpc)
} }
func TestCannotEnableBothCannonAndOutputCannonTraceTypes(t *testing.T) {
config := validConfig(TraceTypeOutputCannon)
config.TraceTypes = append(config.TraceTypes, TraceTypeCannon)
require.ErrorIs(t, config.Check(), ErrCannonAndOutputCannonConflict)
}
func TestCannonL2Required(t *testing.T) { func TestCannonL2Required(t *testing.T) {
config := validConfig(TraceTypeCannon) config := validConfig(TraceTypeCannon)
config.CannonL2 = "" config.CannonL2 = ""
...@@ -195,19 +201,14 @@ func TestNetworkMustBeValid(t *testing.T) { ...@@ -195,19 +201,14 @@ func TestNetworkMustBeValid(t *testing.T) {
require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown) require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown)
} }
func TestRequireConfigForAllSupportedTraceTypes(t *testing.T) { func TestRequireConfigForMultipleTraceTypes(t *testing.T) {
cfg := validConfig(TraceTypeCannon) cfg := validConfig(TraceTypeCannon)
cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeOutputCannon, TraceTypeAlphabet} cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeAlphabet}
// Set all required options and check its valid // Set all required options and check its valid
cfg.RollupRpc = validRollupRpc cfg.RollupRpc = validRollupRpc
cfg.AlphabetTrace = validAlphabetTrace cfg.AlphabetTrace = validAlphabetTrace
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
// Require output cannon specific args
cfg.RollupRpc = ""
require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc)
cfg.RollupRpc = validRollupRpc
// Require cannon specific args // Require cannon specific args
cfg.CannonL2 = "" cfg.CannonL2 = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2) require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2)
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,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/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
...@@ -20,8 +21,9 @@ import ( ...@@ -20,8 +21,9 @@ import (
) )
var ( var (
cannonGameType = uint8(0) cannonGameType = uint8(0)
alphabetGameType = uint8(255) outputCannonGameType = uint8(0) // TODO(client-pod#43): This should be a unique game type
alphabetGameType = uint8(255)
) )
type Registry interface { type Registry interface {
...@@ -37,35 +39,90 @@ func RegisterGameTypes( ...@@ -37,35 +39,90 @@ func RegisterGameTypes(
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
client *ethclient.Client, client *ethclient.Client,
) { ) {
if cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) {
registerOutputCannon(registry, ctx, logger, m, cfg, txMgr, client)
}
if cfg.TraceTypeEnabled(config.TraceTypeCannon) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) { registerCannon(registry, ctx, logger, m, cfg, txMgr, client)
logger := logger.New("game", addr) }
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, cannon.NoLocalContext, dir, gameDepth) if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
if err != nil { registerAlphabet(registry, ctx, logger, m, cfg, txMgr, client)
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err) }
} }
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract) func registerOutputCannon(
} registry Registry,
return trace.NewSimpleTraceAccessor(provider), validator, nil ctx context.Context,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
// TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks
agreed, disputed, err := contract.GetProposals(ctx)
if err != nil {
return nil, nil, err
}
accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, cfg.RollupRpc, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64())
if err != nil {
return nil, nil, err
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { // TODO(client-pod#44): Validate absolute pre-state for split games
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) noopValidator := func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error {
return nil
} }
registry.RegisterGameType(cannonGameType, playerCreator) return accessor, noopValidator, nil
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) { return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth) }
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error { registry.RegisterGameType(outputCannonGameType, playerCreator)
return ValidateAbsolutePrestate(ctx, provider, contract) }
}
return trace.NewSimpleTraceAccessor(provider), validator, nil func registerCannon(
registry Registry,
ctx context.Context,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, cannon.NoLocalContext, dir, gameDepth)
if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { return trace.NewSimpleTraceAccessor(provider), validator, nil
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
}
registry.RegisterGameType(cannonGameType, playerCreator)
}
func registerAlphabet(
registry Registry,
ctx context.Context,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
} }
registry.RegisterGameType(alphabetGameType, playerCreator) return trace.NewSimpleTraceAccessor(provider), validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
} }
registry.RegisterGameType(alphabetGameType, playerCreator)
} }
...@@ -11,11 +11,15 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { ...@@ -11,11 +11,15 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) { selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) {
return trace, nil return trace, nil
} }
return &Accessor{selector} return NewAccessor(selector)
} }
type ProviderSelector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) type ProviderSelector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error)
func NewAccessor(selector ProviderSelector) *Accessor {
return &Accessor{selector}
}
type Accessor struct { type Accessor struct {
selector ProviderSelector selector ProviderSelector
} }
......
package outputs
import (
"context"
"errors"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/log"
)
func NewOutputCannonTraceAccessor(ctx context.Context, logger log.Logger, rollupRpc string, gameDepth uint64, prestateBlock uint64, poststateBlock uint64) (*trace.Accessor, error) {
topDepth := gameDepth / 2 // TODO(client-pod#43): Load this from the contract
outputProvider, err := NewTraceProvider(ctx, logger, rollupRpc, topDepth, prestateBlock, poststateBlock)
if err != nil {
return nil, err
}
cannonCreator := func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
// TODO(client-pod#43): Actually create the cannon trace provider for the trace between the given claims.
return nil, errors.New("not implemented")
}
selector := newSplitProviderSelector(outputProvider, int(topDepth), cannonCreator)
return trace.NewAccessor(selector), nil
}
...@@ -78,25 +78,48 @@ func WithCannon( ...@@ -78,25 +78,48 @@ func WithCannon(
l2Endpoint string, l2Endpoint string,
) Option { ) Option {
return func(c *config.Config) { return func(c *config.Config) {
require := require.New(t)
c.TraceTypes = append(c.TraceTypes, config.TraceTypeCannon) c.TraceTypes = append(c.TraceTypes, config.TraceTypeCannon)
c.CannonL2 = l2Endpoint applyCannonConfig(c, t, rollupCfg, l2Genesis, l2Endpoint)
c.CannonBin = "../cannon/bin/cannon" }
c.CannonServer = "../op-program/bin/op-program" }
c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
c.CannonSnapshotFreq = 10_000_000 func applyCannonConfig(
c *config.Config,
genesisBytes, err := json.Marshal(l2Genesis) t *testing.T,
require.NoError(err, "marshall l2 genesis config") rollupCfg *rollup.Config,
genesisFile := filepath.Join(c.Datadir, "l2-genesis.json") l2Genesis *core.Genesis,
require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644)) l2Endpoint string,
c.CannonL2GenesisPath = genesisFile ) {
require := require.New(t)
rollupBytes, err := json.Marshal(rollupCfg) c.CannonL2 = l2Endpoint
require.NoError(err, "marshall rollup config") c.CannonBin = "../cannon/bin/cannon"
rollupFile := filepath.Join(c.Datadir, "rollup.json") c.CannonServer = "../op-program/bin/op-program"
require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644)) c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
c.CannonRollupConfigPath = rollupFile c.CannonSnapshotFreq = 10_000_000
genesisBytes, err := json.Marshal(l2Genesis)
require.NoError(err, "marshall l2 genesis config")
genesisFile := filepath.Join(c.Datadir, "l2-genesis.json")
require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644))
c.CannonL2GenesisPath = genesisFile
rollupBytes, err := json.Marshal(rollupCfg)
require.NoError(err, "marshall rollup config")
rollupFile := filepath.Join(c.Datadir, "rollup.json")
require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644))
c.CannonRollupConfigPath = rollupFile
}
func WithOutputCannon(
t *testing.T,
rollupCfg *rollup.Config,
l2Genesis *core.Genesis,
rollupEndpoint string,
l2Endpoint string) Option {
return func(c *config.Config) {
c.TraceTypes = append(c.TraceTypes, config.TraceTypeOutputCannon)
c.RollupRpc = rollupEndpoint
applyCannonConfig(c, t, rollupCfg, l2Genesis, l2Endpoint)
} }
} }
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -33,6 +34,7 @@ import ( ...@@ -33,6 +34,7 @@ import (
const alphabetGameType uint8 = 255 const alphabetGameType uint8 = 255
const cannonGameType uint8 = 0 const cannonGameType uint8 = 0
const outputCannonGameType uint8 = 0 // TODO(client-pod#43): This should be a unique game type
const alphabetGameDepth = 4 const alphabetGameDepth = 4
var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1) var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1)
...@@ -101,8 +103,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1 ...@@ -101,8 +103,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, deployments *genesis.L1
} }
func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *AlphabetGameHelper { func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *AlphabetGameHelper {
l2BlockNumber := h.waitForProposals(ctx) extraData, _, _ := h.createDisputeGameExtraData(ctx)
l1Head := h.checkpointL1Block(ctx)
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
...@@ -111,9 +112,6 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -111,9 +112,6 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
pos := faultTypes.NewPosition(alphabetGameDepth, lastAlphabetTraceIndex) pos := faultTypes.NewPosition(alphabetGameDepth, lastAlphabetTraceIndex)
rootClaim, err := trace.Get(ctx, pos) rootClaim, err := trace.Get(ctx, pos)
h.require.NoError(err, "get root claim") h.require.NoError(err, "get root claim")
extraData := make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber)
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err := transactions.PadGasEstimate(h.opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) { tx, err := transactions.PadGasEstimate(h.opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) {
return h.factory.Create(opts, alphabetGameType, rootClaim, extraData) return h.factory.Create(opts, alphabetGameType, rootClaim, extraData)
}) })
...@@ -141,13 +139,49 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -141,13 +139,49 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
} }
} }
func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, rollupEndpoint string, rootClaim common.Hash) *OutputCannonGameHelper {
extraData, _, _ := h.createDisputeGameExtraData(ctx)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
tx, err := transactions.PadGasEstimate(h.opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) {
return h.factory.Create(opts, outputCannonGameType, rootClaim, extraData)
})
h.require.NoError(err, "create fault dispute game")
rcpt, err := wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err, "wait for create fault dispute game receipt to be OK")
h.require.Len(rcpt.Logs, 1, "should have emitted a single DisputeGameCreated event")
createdEvent, err := h.factory.ParseDisputeGameCreated(*rcpt.Logs[0])
h.require.NoError(err)
game, err := bindings.NewFaultDisputeGame(createdEvent.DisputeProxy, h.client)
h.require.NoError(err)
rollupClient, err := dial.DialRollupClientWithTimeout(ctx, 30*time.Second, testlog.Logger(h.t, log.LvlInfo), rollupEndpoint)
h.require.NoError(err)
return &OutputCannonGameHelper{
FaultGameHelper: FaultGameHelper{
t: h.t,
require: h.require,
client: h.client,
opts: h.opts,
game: game,
factoryAddr: h.factoryAddr,
addr: createdEvent.DisputeProxy,
},
rollupClient: rollupClient,
}
}
func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Hash) *CannonGameHelper { func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Hash) *CannonGameHelper {
l2BlockNumber, l1Head := h.prepareCannonGame(ctx) extraData, _, _ := h.createDisputeGameExtraData(ctx)
return h.createCannonGame(ctx, l2BlockNumber, l1Head, rootClaim) return h.createCannonGame(ctx, rootClaim, extraData)
} }
func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, options ...challenger.Option) (*CannonGameHelper, *HonestHelper) { func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, options ...challenger.Option) (*CannonGameHelper, *HonestHelper) {
l2BlockNumber, l1Head := h.prepareCannonGame(ctx) extraData, l1Head, l2BlockNumber := h.createDisputeGameExtraData(ctx)
challengerOpts := []challenger.Option{ challengerOpts := []challenger.Option{
challenger.WithCannon(h.t, rollupCfg, l2Genesis, l2Endpoint), challenger.WithCannon(h.t, rollupCfg, l2Genesis, l2Endpoint),
challenger.WithFactoryAddress(h.factoryAddr), challenger.WithFactoryAddress(h.factoryAddr),
...@@ -202,7 +236,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll ...@@ -202,7 +236,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll
// Otherwise creating the game will fail // Otherwise creating the game will fail
rootClaim[0] = mipsevm.VMStatusInvalid rootClaim[0] = mipsevm.VMStatusInvalid
game := h.createCannonGame(ctx, l2BlockNumber, l1Head, rootClaim) game := h.createCannonGame(ctx, rootClaim, extraData)
correctMaxDepth := game.MaxDepth(ctx) correctMaxDepth := game.MaxDepth(ctx)
provider.SetMaxDepth(uint64(correctMaxDepth)) provider.SetMaxDepth(uint64(correctMaxDepth))
honestHelper := &HonestHelper{ honestHelper := &HonestHelper{
...@@ -214,13 +248,10 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll ...@@ -214,13 +248,10 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll
return game, honestHelper return game, honestHelper
} }
func (h *FactoryHelper) createCannonGame(ctx context.Context, l2BlockNumber uint64, l1Head *big.Int, rootClaim common.Hash) *CannonGameHelper { func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.Hash, extraData []byte) *CannonGameHelper {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
extraData := make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber)
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
tx, err := transactions.PadGasEstimate(h.opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) { tx, err := transactions.PadGasEstimate(h.opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) {
return h.factory.Create(opts, cannonGameType, rootClaim, extraData) return h.factory.Create(opts, cannonGameType, rootClaim, extraData)
}) })
...@@ -246,6 +277,15 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, l2BlockNumber uint ...@@ -246,6 +277,15 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, l2BlockNumber uint
} }
} }
func (h *FactoryHelper) createDisputeGameExtraData(ctx context.Context) (extraData []byte, l1Head *big.Int, l2BlockNumber uint64) {
l2BlockNumber = h.waitForProposals(ctx)
l1Head = h.checkpointL1Block(ctx)
extraData = make([]byte, 64)
binary.BigEndian.PutUint64(extraData[24:], l2BlockNumber)
binary.BigEndian.PutUint64(extraData[56:], l1Head.Uint64())
return
}
func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, name string, options ...challenger.Option) *challenger.Helper { func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{ opts := []challenger.Option{
challenger.WithFactoryAddress(h.factoryAddr), challenger.WithFactoryAddress(h.factoryAddr),
...@@ -258,12 +298,6 @@ func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string, ...@@ -258,12 +298,6 @@ func (h *FactoryHelper) StartChallenger(ctx context.Context, l1Endpoint string,
return c return c
} }
func (h *FactoryHelper) prepareCannonGame(ctx context.Context) (uint64, *big.Int) {
l2BlockNumber := h.waitForProposals(ctx)
l1Head := h.checkpointL1Block(ctx)
return l2BlockNumber, l1Head
}
// waitForProposals waits until there are at least two proposals in the output oracle // waitForProposals waits until there are at least two proposals in the output oracle
// This is the minimum required for creating a game. // This is the minimum required for creating a game.
// Returns the l2 block number of the latest available proposal // Returns the l2 block number of the latest available proposal
......
package disputegame
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core"
)
type OutputCannonGameHelper struct {
FaultGameHelper
rollupClient *sources.RollupClient
}
func (g *OutputCannonGameHelper) StartChallenger(
ctx context.Context,
rollupCfg *rollup.Config,
l2Genesis *core.Genesis,
rollupEndpoint string,
l1Endpoint string,
l2Endpoint string,
name string,
options ...challenger.Option,
) *challenger.Helper {
opts := []challenger.Option{
challenger.WithOutputCannon(g.t, rollupCfg, l2Genesis, rollupEndpoint, l2Endpoint),
challenger.WithFactoryAddress(g.factoryAddr),
challenger.WithGameAddress(g.addr),
}
opts = append(opts, options...)
c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
g.t.Cleanup(func() {
_ = c.Close()
})
return c
}
func (g *OutputCannonGameHelper) WaitForCorrectOutputRoot(ctx context.Context, claimIdx int64) {
g.WaitForClaimCount(ctx, claimIdx+1)
claim := g.getClaim(ctx, claimIdx)
err, blockNum := g.blockNumForClaim(ctx, claim)
g.require.NoError(err)
output, err := g.rollupClient.OutputAtBlock(ctx, blockNum)
g.require.NoErrorf(err, "Failed to get output at block %v", blockNum)
g.require.EqualValuesf(output.OutputRoot, claim.Claim, "Incorrect output root at claim %v. Expected to be from block %v", claimIdx, blockNum)
}
func (g *OutputCannonGameHelper) blockNumForClaim(ctx context.Context, claim ContractClaim) (error, uint64) {
proposals, err := g.game.Proposals(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "failed to retrieve proposals")
prestateBlockNum := proposals.Starting.L2BlockNumber
disputedBlockNum := proposals.Disputed.L2BlockNumber
gameDepth := g.MaxDepth(ctx)
// TODO(client-pod#43): Load this from the contract
topDepth := gameDepth / 2
traceIdx := types.NewPositionFromGIndex(claim.Position).TraceIndex(int(topDepth))
blockNum := new(big.Int).Add(prestateBlockNum, traceIdx).Uint64() + 1
if blockNum > disputedBlockNum.Uint64() {
blockNum = disputedBlockNum.Uint64()
}
return err, blockNum
}
...@@ -592,6 +592,38 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) { ...@@ -592,6 +592,38 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) {
require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx)) require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx))
} }
func TestOutputCannonGame(t *testing.T) {
InitParallel(t, UsesCannon, UseExecutor(0))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
rollupEndpoint := sys.RollupNodes["sequencer"].HTTPEndpoint()
l1Endpoint := sys.NodeEndpoint("l1")
l2Endpoint := sys.NodeEndpoint("sequencer")
require.NotEqual(t, rollupEndpoint, l2Endpoint)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartOutputCannonGame(ctx, rollupEndpoint, common.Hash{0x01})
game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, rollupEndpoint, l1Endpoint, l2Endpoint, "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.cfg.Secrets.Alice),
)
game.LogGameData(ctx)
maxDepth := game.MaxDepth(ctx)
// Challenger should post an output root to counter claims down to the leaf level of the top game
// TODO(client-pod#43): Load the depth of the top game from the contract instead of deriving it
for i := int64(1); i <= maxDepth/2+1; i += 2 {
game.WaitForCorrectOutputRoot(ctx, i)
game.Attack(ctx, i, common.Hash{0xaa})
game.LogGameData(ctx)
}
}
func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) { func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier") delete(cfg.Nodes, "verifier")
......
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