Commit 54be5434 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Migrate multi game e2e tests (#8742)

* op-e2e: Remove TestCannonChallengeWithCorrectRoot

Challenging with the correct root is already covered by TestOutputCannonPoisonedPostState

* op-e2e: Switch multi game test to use output bisection

Remove the multiple cannon games test - we have unit tests checking the games are separate enough that we don't need this additional coverage.

* op-e2e: Remove cannon helpers

* op-e2e: Update multi game test to use claim helpers.
parent 9d13ae01
package disputegame
import (
"context"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/log"
)
type CannonGameHelper struct {
FaultGameHelper
}
func (g *CannonGameHelper) StartChallenger(ctx context.Context, l2Node string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{
challenger.WithCannon(g.t, g.system.RollupCfg(), g.system.L2Genesis(), g.system.NodeEndpoint(l2Node)),
challenger.WithFactoryAddress(g.factoryAddr),
challenger.WithGameAddress(g.addr),
}
opts = append(opts, options...)
c := challenger.NewChallenger(g.t, ctx, g.system.NodeEndpoint("l1"), name, opts...)
g.t.Cleanup(func() {
_ = c.Close()
})
return c
}
func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...challenger.Option) *HonestHelper {
opts := []challenger.Option{
challenger.WithCannon(g.t, g.system.RollupCfg(), g.system.L2Genesis(), g.system.NodeEndpoint(l2Node)),
challenger.WithFactoryAddress(g.factoryAddr),
challenger.WithGameAddress(g.addr),
}
opts = append(opts, options...)
cfg := challenger.NewChallengerConfig(g.t, g.system.NodeEndpoint("l1"), opts...)
logger := testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace")
maxDepth := g.MaxDepth(ctx)
gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(g.system.NodeClient("l1").Client(), batching.DefaultBatchSize))
g.require.NoError(err, "Create game contract bindings")
l2Client := g.system.NodeClient(l2Node)
localInputs, err := cannon.FetchLocalInputs(ctx, gameContract, l2Client)
g.require.NoError(err, "fetch cannon local inputs")
provider := cannon.NewTraceProvider(logger, metrics.NoopMetrics, cfg, types.NoLocalContext, localInputs, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
return &HonestHelper{
t: g.t,
require: g.require,
game: &g.FaultGameHelper,
correctTrace: provider,
}
}
...@@ -88,3 +88,7 @@ func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash) *ClaimHelpe ...@@ -88,3 +88,7 @@ func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash) *ClaimHelpe
c.game.Defend(ctx, c.index, value) c.game.Defend(ctx, c.index, value)
return c.WaitForCounterClaim(ctx) return c.WaitForCounterClaim(ctx)
} }
func (c *ClaimHelper) RequireDifferentClaimValue(other *ClaimHelper) {
c.require.NotEqual(c.claim, other.claim, "should have posted different claims")
}
...@@ -8,15 +8,12 @@ import ( ...@@ -8,15 +8,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"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/outputs" "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/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/l2oo" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/l2oo"
...@@ -36,7 +33,6 @@ import ( ...@@ -36,7 +33,6 @@ import (
const ( const (
alphabetGameType uint8 = 255 alphabetGameType uint8 = 255
cannonGameType uint8 = 0
outputCannonGameType uint8 = 1 outputCannonGameType uint8 = 1
outputAlphabetGameType uint8 = 254 outputAlphabetGameType uint8 = 254
alphabetGameDepth = 4 alphabetGameDepth = 4
...@@ -44,9 +40,6 @@ const ( ...@@ -44,9 +40,6 @@ const (
var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1) var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1)
// rootPosition is the position of the root claim.
var rootPosition = faultTypes.NewPositionFromGIndex(big.NewInt(1))
type Status uint8 type Status uint8
const ( const (
...@@ -261,106 +254,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri ...@@ -261,106 +254,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
} }
} }
func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Hash) *CannonGameHelper {
extraData, _, _ := h.createDisputeGameExtraData(ctx)
return h.createCannonGame(ctx, rootClaim, extraData)
}
func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, l2Node string, options ...challenger.Option) (*CannonGameHelper, *HonestHelper) {
extraData, l1Head, l2BlockNumber := h.createDisputeGameExtraData(ctx)
challengerOpts := []challenger.Option{
challenger.WithCannon(h.t, h.system.RollupCfg(), h.system.L2Genesis(), h.system.NodeEndpoint(l2Node)),
challenger.WithFactoryAddress(h.factoryAddr),
}
challengerOpts = append(challengerOpts, options...)
cfg := challenger.NewChallengerConfig(h.t, h.system.NodeEndpoint("l1"), challengerOpts...)
opts := &bind.CallOpts{Context: ctx}
challengedOutput := h.l2ooHelper.GetL2OutputAfter(ctx, l2BlockNumber)
agreedOutput := h.l2ooHelper.GetL2OutputBefore(ctx, l2BlockNumber)
l1BlockInfo, err := h.blockOracle.Load(opts, l1Head)
h.require.NoError(err, "Fetch L1 block info")
l2Client := h.system.NodeClient(l2Node)
defer l2Client.Close()
agreedHeader, err := l2Client.HeaderByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil {
h.require.NoErrorf(err, "Failed to fetch L2 block header %v", agreedOutput.L2BlockNumber)
}
inputs := cannon.LocalGameInputs{
L1Head: l1BlockInfo.Hash,
L2Head: agreedHeader.Hash(),
L2OutputRoot: agreedOutput.OutputRoot,
L2Claim: challengedOutput.OutputRoot,
L2BlockNumber: challengedOutput.L2BlockNumber,
}
cannonTypeAddr, err := h.factory.GameImpls(opts, cannonGameType)
h.require.NoError(err, "fetch cannon game type impl")
gameImpl, err := bindings.NewFaultDisputeGameCaller(cannonTypeAddr, h.client)
h.require.NoError(err, "bind fault dispute game caller")
maxDepth, err := gameImpl.MAXGAMEDEPTH(opts)
h.require.NoError(err, "fetch max game depth")
provider := cannon.NewTraceProvider(
testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"),
metrics.NoopMetrics,
cfg,
faultTypes.NoLocalContext,
inputs,
cfg.Datadir,
maxDepth.Uint64(),
)
rootClaim, err := provider.Get(ctx, rootPosition)
h.require.NoError(err, "Compute correct root hash")
// Override the VM status to claim the root is invalid
// Otherwise creating the game will fail
rootClaim[0] = mipsevm.VMStatusInvalid
game := h.createCannonGame(ctx, rootClaim, extraData)
correctMaxDepth := game.MaxDepth(ctx)
provider.SetMaxDepth(uint64(correctMaxDepth))
honestHelper := &HonestHelper{
t: h.t,
require: h.require,
game: &game.FaultGameHelper,
correctTrace: provider,
}
return game, honestHelper
}
func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.Hash, extraData []byte) *CannonGameHelper {
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, cannonGameType, 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)
return &CannonGameHelper{
FaultGameHelper: FaultGameHelper{
t: h.t,
require: h.require,
system: h.system,
client: h.client,
opts: h.opts,
game: game,
factoryAddr: h.factoryAddr,
addr: createdEvent.DisputeProxy,
},
}
}
func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte { func (h *FactoryHelper) createBisectionGameExtraData(l2Node string, l2BlockNumber uint64) []byte {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber) h.waitForBlockToBeSafe(l2Node, l2BlockNumber)
h.t.Logf("Creating game with l2 block number: %v", l2BlockNumber) h.t.Logf("Creating game with l2 block number: %v", l2BlockNumber)
......
package faultproofs
import (
"context"
"testing"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/stretchr/testify/require"
)
func TestCannonChallengeWithCorrectRoot(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(0))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game, correctTrace := disputeGameFactory.StartCannonGameWithCorrectRoot(ctx, "sequencer",
challenger.WithPrivKey(sys.Cfg.Secrets.Mallory),
)
require.NotNil(t, game)
game.LogGameData(ctx)
game.StartChallenger(ctx, "sequencer", "Challenger",
challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
)
game.DefendRootClaim(ctx, func(parentClaimIdx int64) {
// Defend everything because we have the same trace as the honest proposer
correctTrace.Defend(ctx, parentClaimIdx)
})
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx))
}
...@@ -2,78 +2,14 @@ package faultproofs ...@@ -2,78 +2,14 @@ package faultproofs
import ( import (
"context" "context"
"math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
) )
func TestMultipleCannonGames(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(0))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Start a challenger with the correct alphabet trace
challenger := gameFactory.StartChallenger(ctx, "TowerDefense",
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
)
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
game2 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xbb})
game1.WaitForClaimCount(ctx, 2)
game2.WaitForClaimCount(ctx, 2)
game1Claim := game1.GetClaimValue(ctx, 1)
game2Claim := game2.GetClaimValue(ctx, 1)
require.NotEqual(t, game1Claim, game2Claim, "games should have different cannon traces")
// Check that the helper finds the game directories correctly
challenger.VerifyGameDataExists(game1, game2)
// Push both games down to the step function
maxDepth := game1.MaxDepth(ctx)
for claimCount := int64(1); claimCount <= maxDepth; {
// Challenger should respond to both games
claimCount++
game1.WaitForClaimCount(ctx, claimCount)
game2.WaitForClaimCount(ctx, claimCount)
// Progress both games
game1.Defend(ctx, claimCount-1, common.Hash{0xaa})
game2.Defend(ctx, claimCount-1, common.Hash{0xaa})
claimCount++
}
game1.WaitForClaimAtMaxDepth(ctx, true)
game2.WaitForClaimAtMaxDepth(ctx, true)
gameDuration := game1.GameDuration(ctx)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game1.WaitForInactivity(ctx, 10, true)
game2.WaitForInactivity(ctx, 10, true)
game1.LogGameData(ctx)
game2.LogGameData(ctx)
require.EqualValues(t, disputegame.StatusChallengerWins, game1.Status(ctx))
require.EqualValues(t, disputegame.StatusChallengerWins, game2.Status(ctx))
// Check that the game directories are removed
challenger.WaitForGameDataDeletion(ctx, game1, game2)
}
func TestMultipleGameTypes(t *testing.T) { func TestMultipleGameTypes(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(0)) op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(0))
...@@ -82,28 +18,24 @@ func TestMultipleGameTypes(t *testing.T) { ...@@ -82,28 +18,24 @@ func TestMultipleGameTypes(t *testing.T) {
t.Cleanup(sys.Close) t.Cleanup(sys.Close)
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys) gameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game1 := gameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa})
game2 := gameFactory.StartOutputAlphabetGame(ctx, "sequencer", 1, "xyzabc")
latestClaim1 := game1.DisputeLastBlock(ctx)
latestClaim2 := game2.DisputeLastBlock(ctx)
// Start a challenger with both cannon and alphabet support // Start a challenger with both cannon and alphabet support
gameFactory.StartChallenger(ctx, "TowerDefense", gameFactory.StartChallenger(ctx, "TowerDefense",
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")), challenger.WithOutputCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.RollupEndpoint("sequencer"), sys.NodeEndpoint("sequencer")),
challenger.WithAlphabet(disputegame.CorrectAlphabet), challenger.WithOutputAlphabet(disputegame.CorrectAlphabet, sys.RollupEndpoint("sequencer")),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
game2 := gameFactory.StartAlphabetGame(ctx, "xyzabc")
// Wait for the challenger to respond to both games // Wait for the challenger to respond to both games
game1.WaitForClaimCount(ctx, 2) counter1 := latestClaim1.WaitForCounterClaim(ctx)
game2.WaitForClaimCount(ctx, 2) counter2 := latestClaim2.WaitForCounterClaim(ctx)
game1Response := game1.GetClaimValue(ctx, 1)
game2Response := game2.GetClaimValue(ctx, 1)
// The alphabet game always posts the same traces, so if they're different they can't both be from the alphabet. // The alphabet game always posts the same traces, so if they're different they can't both be from the alphabet.
require.NotEqual(t, game1Response, game2Response, "should have posted different claims") // We're contesting the same block with different VMs, so if the challenger was just playing two cannon or alphabet
// Now check they aren't both just from different cannon games by confirming the alphabet value. // games the responses would be equal.
correctAlphabet := alphabet.NewTraceProvider(disputegame.CorrectAlphabet, uint64(game2.MaxDepth(ctx))) counter1.RequireDifferentClaimValue(counter2)
expectedClaim, err := correctAlphabet.Get(ctx, types.NewPositionFromGIndex(big.NewInt(1)).Attack())
require.NoError(t, err)
require.Equal(t, expectedClaim, game2Response)
// We don't confirm the cannon value because generating the correct claim is expensive
// Just being different is enough to confirm the challenger isn't just playing two alphabet games incorrectly
} }
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