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 (
"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/genesis"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"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/geth"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/l2oo"
......@@ -37,8 +35,6 @@ const (
alphabetGameDepth = 4
)
var lastAlphabetTraceIndex = big.NewInt(1<<alphabetGameDepth - 1)
type Status uint8
const (
......@@ -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")
rollupClient := h.system.RollupClient(l2Node)
......@@ -168,10 +171,6 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
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) {
return h.factory.Create(opts, alphabetGameType, rootClaim, extraData)
})
......
......@@ -3,7 +3,13 @@ package disputegame
import (
"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-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/log"
)
type OutputAlphabetGameHelper struct {
......@@ -28,3 +34,24 @@ func (g *OutputAlphabetGameHelper) StartChallenger(
})
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 {
// 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.
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)
pos := types.NewPositionFromGIndex(big.NewInt(1))
getClaimValue := func(parentClaim *ClaimHelper, claimPos types.Position) common.Hash {
claimBlockNum, err := g.correctOutputProvider.BlockNumber(claimPos)
g.require.NoError(err, "failed to calculate claim block number")
// Use the correct output root for the challenger and incorrect for the defender
if parentClaim.AgreesWithOutputRoot() || claimBlockNum < disputeBlockNum {
if 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)
}
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 {
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) {
gameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
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)
latestClaim2 := game2.DisputeLastBlock(ctx)
......
......@@ -13,13 +13,15 @@ import (
)
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))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
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)
opts := challenger.WithPrivKey(sys.Cfg.Secrets.Alice)
......@@ -59,3 +61,39 @@ func TestOutputAlphabetGame(t *testing.T) {
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
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 {
_gameType: GameTypes.ALPHABET,
_absolutePrestate: 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