Commit 18db56a7 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Add test demonstrating incorrect alphabet trace implementation (#8798)

* op-e2e: Add test demonstrating incorrect alphabet trace implementation.

* rebase fixes

* op-e2e: Skip known broken tests.

---------
Co-authored-by: default avatarrefcell <abigger87@gmail.com>
parent 38e4684a
...@@ -11,9 +11,7 @@ import ( ...@@ -11,9 +11,7 @@ import (
"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/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
faultTypes "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-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"
...@@ -37,8 +35,6 @@ const ( ...@@ -37,8 +35,6 @@ const (
alphabetGameDepth = 4 alphabetGameDepth = 4
) )
var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1)
type Status uint8 type Status uint8
const ( const (
...@@ -159,7 +155,14 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string ...@@ -159,7 +155,14 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
} }
} }
func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node string, l2BlockNumber uint64, claimedAlphabet string) *OutputAlphabetGameHelper { func (h *FactoryHelper) StartOutputAlphabetGameWithCorrectRoot(ctx context.Context, l2Node string, l2BlockNumber uint64) *OutputAlphabetGameHelper {
h.waitForBlockToBeSafe(l2Node, l2BlockNumber)
output, err := h.system.RollupClient(l2Node).OutputAtBlock(ctx, l2BlockNumber)
h.require.NoErrorf(err, "Failed to get output at block %v", l2BlockNumber)
return h.StartOutputAlphabetGame(ctx, l2Node, l2BlockNumber, common.Hash(output.OutputRoot))
}
func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash) *OutputAlphabetGameHelper {
logger := testlog.Logger(h.t, log.LvlInfo).New("role", "OutputAlphabetGameHelper") logger := testlog.Logger(h.t, log.LvlInfo).New("role", "OutputAlphabetGameHelper")
rollupClient := h.system.RollupClient(l2Node) rollupClient := h.system.RollupClient(l2Node)
...@@ -168,10 +171,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri ...@@ -168,10 +171,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() defer cancel()
trace := alphabet.NewTraceProvider(claimedAlphabet, alphabetGameDepth)
pos := faultTypes.NewPosition(alphabetGameDepth, lastAlphabetTraceIndex)
rootClaim, err := trace.Get(ctx, pos)
h.require.NoError(err, "get root claim")
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)
}) })
......
...@@ -3,7 +3,13 @@ package disputegame ...@@ -3,7 +3,13 @@ package disputegame
import ( import (
"context" "context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"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-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/log"
) )
type OutputAlphabetGameHelper struct { type OutputAlphabetGameHelper struct {
...@@ -28,3 +34,24 @@ func (g *OutputAlphabetGameHelper) StartChallenger( ...@@ -28,3 +34,24 @@ func (g *OutputAlphabetGameHelper) StartChallenger(
}) })
return c return c
} }
func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node string) *OutputHonestHelper {
logger := testlog.Logger(g.t, log.LvlInfo).New("role", "HonestHelper", "game", g.addr)
caller := batching.NewMultiCaller(g.system.NodeClient("l1").Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(g.addr, caller)
g.require.NoError(err, "Failed to create game contact")
prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx)
g.require.NoError(err, "Get block range")
splitDepth := uint64(g.SplitDepth(ctx))
rollupClient := g.system.RollupClient(l2Node)
prestateProvider := outputs.NewPrestateProvider(ctx, logger, rollupClient, prestateBlock)
correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, splitDepth, prestateBlock, poststateBlock)
g.require.NoError(err, "Create trace accessor")
return &OutputHonestHelper{
t: g.t,
require: g.require,
game: &g.OutputGameHelper,
contract: contract,
correctTrace: correctTrace,
}
}
...@@ -58,17 +58,30 @@ func (g *OutputGameHelper) GenesisBlockNum(ctx context.Context) uint64 { ...@@ -58,17 +58,30 @@ func (g *OutputGameHelper) GenesisBlockNum(ctx context.Context) uint64 {
// through to the split depth and the claims are setup such that the last block in the game range is the block // through to the split depth and the claims are setup such that the last block in the game range is the block
// to execute cannon on. ie the first block the honest and dishonest actors disagree about is the l2 block of the game. // to execute cannon on. ie the first block the honest and dishonest actors disagree about is the l2 block of the game.
func (g *OutputGameHelper) DisputeLastBlock(ctx context.Context) *ClaimHelper { func (g *OutputGameHelper) DisputeLastBlock(ctx context.Context) *ClaimHelper {
rootClaim := g.GetClaimValue(ctx, 0) dishonestValue := g.GetClaimValue(ctx, 0)
correctRootClaim := g.correctOutputRoot(ctx, types.NewPositionFromGIndex(big.NewInt(1)))
rootIsValid := dishonestValue == correctRootClaim
if rootIsValid {
// Ensure that the dishonest actor is actually posting invalid roots.
// Otherwise, the honest challenger will defend our counter and ruin everything.
dishonestValue = common.Hash{0xff, 0xff, 0xff}
}
disputeBlockNum := g.L2BlockNum(ctx) disputeBlockNum := g.L2BlockNum(ctx)
pos := types.NewPositionFromGIndex(big.NewInt(1)) pos := types.NewPositionFromGIndex(big.NewInt(1))
getClaimValue := func(parentClaim *ClaimHelper, claimPos types.Position) common.Hash { getClaimValue := func(parentClaim *ClaimHelper, claimPos types.Position) common.Hash {
claimBlockNum, err := g.correctOutputProvider.BlockNumber(claimPos) claimBlockNum, err := g.correctOutputProvider.BlockNumber(claimPos)
g.require.NoError(err, "failed to calculate claim block number") g.require.NoError(err, "failed to calculate claim block number")
// Use the correct output root for the challenger and incorrect for the defender if claimBlockNum < disputeBlockNum {
if parentClaim.AgreesWithOutputRoot() || claimBlockNum < disputeBlockNum { // Use the correct output root for all claims prior to the dispute block number
// This pushes the game to dispute the last block in the range
return g.correctOutputRoot(ctx, claimPos) return g.correctOutputRoot(ctx, claimPos)
}
if rootIsValid == parentClaim.AgreesWithOutputRoot() {
// We are responding to a parent claim that agrees with a valid root, so we're being dishonest
return dishonestValue
} else { } else {
return rootClaim // Otherwise we must be the honest actor so use the correct root
return g.correctOutputRoot(ctx, claimPos)
} }
} }
......
...@@ -20,7 +20,7 @@ func TestMultipleGameTypes(t *testing.T) { ...@@ -20,7 +20,7 @@ func TestMultipleGameTypes(t *testing.T) {
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys) gameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game1 := gameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa}) game1 := gameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa})
game2 := gameFactory.StartOutputAlphabetGame(ctx, "sequencer", 1, "xyzabc") game2 := gameFactory.StartOutputAlphabetGame(ctx, "sequencer", 1, common.Hash{0xbb})
latestClaim1 := game1.DisputeLastBlock(ctx) latestClaim1 := game1.DisputeLastBlock(ctx)
latestClaim2 := game2.DisputeLastBlock(ctx) latestClaim2 := game2.DisputeLastBlock(ctx)
......
...@@ -13,13 +13,15 @@ import ( ...@@ -13,13 +13,15 @@ import (
) )
func TestOutputAlphabetGame(t *testing.T) { func TestOutputAlphabetGame(t *testing.T) {
// TODO(client-pod#43) Fix alphabet trace provider and re-enable.
t.Skip("client-pod#43: AlphabetTraceProvider not using the new alphabet vm spec")
op_e2e.InitParallel(t, op_e2e.UseExecutor(1)) op_e2e.InitParallel(t, op_e2e.UseExecutor(1))
ctx := context.Background() ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t) sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close) t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputAlphabetGame(ctx, "sequencer", 3, "abcdexyz") game := disputeGameFactory.StartOutputAlphabetGame(ctx, "sequencer", 3, common.Hash{0xff})
game.LogGameData(ctx) game.LogGameData(ctx)
opts := challenger.WithPrivKey(sys.Cfg.Secrets.Alice) opts := challenger.WithPrivKey(sys.Cfg.Secrets.Alice)
...@@ -59,3 +61,39 @@ func TestOutputAlphabetGame(t *testing.T) { ...@@ -59,3 +61,39 @@ func TestOutputAlphabetGame(t *testing.T) {
require.NoError(t, wait.ForNextBlock(ctx, l1Client)) require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins) game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
} }
func TestOutputAlphabetGameWithValidOutputRoot(t *testing.T) {
// TODO(client-pod#43) Fix alphabet trace provider and re-enable.
t.Skip("client-pod#43: AlphabetTraceProvider not using the new alphabet vm spec")
op_e2e.InitParallel(t, op_e2e.UseExecutor(1))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputAlphabetGameWithCorrectRoot(ctx, "sequencer", 2)
correctTrace := game.CreateHonestActor(ctx, "sequencer")
game.LogGameData(ctx)
claim := game.DisputeLastBlock(ctx)
// Invalid root claim of the alphabet game
claim = claim.Attack(ctx, common.Hash{0x01})
opts := challenger.WithPrivKey(sys.Cfg.Secrets.Alice)
game.StartChallenger(ctx, "sequencer", "Challenger", opts)
for !claim.IsMaxDepth(ctx) {
claim = claim.WaitForCounterClaim(ctx)
game.LogGameData(ctx)
// Dishonest actor always attacks with the correct trace
claim = correctTrace.AttackClaim(ctx, claim)
}
game.LogGameData(ctx)
// Challenger should be able to call step and counter the leaf claim.
game.WaitForClaimAtMaxDepth(ctx, true)
game.LogGameData(ctx)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusDefenderWins)
}
...@@ -1017,7 +1017,8 @@ contract Deploy is Deployer { ...@@ -1017,7 +1017,8 @@ contract Deploy is Deployer {
_gameType: GameTypes.ALPHABET, _gameType: GameTypes.ALPHABET,
_absolutePrestate: outputAbsolutePrestate, _absolutePrestate: outputAbsolutePrestate,
_faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate)), _faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate)),
_maxGameDepth: cfg.faultGameMaxDepth() // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive.
_maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1
}); });
} }
......
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