Commit 344c266e authored by Adrian Sutton's avatar Adrian Sutton

Add e2e test for starting trace extension one block too late (#268)

parent 9750b3b0
...@@ -47,23 +47,49 @@ func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name strin ...@@ -47,23 +47,49 @@ func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name strin
return c return c
} }
func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...challenger.Option) *OutputHonestHelper { type honestActorConfig struct {
opts := g.defaultChallengerOptions() prestateBlock uint64
opts = append(opts, options...) poststateBlock uint64
cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, opts...) challengerOpts []challenger.Option
}
type HonestActorOpt func(cfg *honestActorConfig)
func WithClaimedL2BlockNumber(num uint64) HonestActorOpt {
return func(cfg *honestActorConfig) {
cfg.poststateBlock = num
}
}
func WithPrivKey(privKey *ecdsa.PrivateKey) HonestActorOpt {
return func(cfg *honestActorConfig) {
cfg.challengerOpts = append(cfg.challengerOpts, challenger.WithPrivKey(privKey))
}
}
func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...HonestActorOpt) *OutputHonestHelper {
logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr) logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr)
l2Client := g.System.NodeClient(l2Node) l2Client := g.System.NodeClient(l2Node)
prestateBlock, poststateBlock, err := g.Game.GetBlockRange(ctx) realPrestateBlock, realPostStateBlock, err := g.Game.GetBlockRange(ctx)
g.Require.NoError(err, "Failed to load block range") g.Require.NoError(err, "Failed to load block range")
dir := filepath.Join(cfg.Datadir, "honest")
splitDepth := g.SplitDepth(ctx) splitDepth := g.SplitDepth(ctx)
rollupClient := g.System.RollupClient(l2Node) rollupClient := g.System.RollupClient(l2Node)
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) actorCfg := &honestActorConfig{
prestateBlock: realPrestateBlock,
poststateBlock: realPostStateBlock,
challengerOpts: g.defaultChallengerOptions(),
}
for _, option := range options {
option(actorCfg)
}
cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, actorCfg.challengerOpts...)
dir := filepath.Join(cfg.Datadir, "honest")
prestateProvider := outputs.NewPrestateProvider(rollupClient, actorCfg.prestateBlock)
l1Head := g.GetL1Head(ctx) l1Head := g.GetL1Head(ctx)
accessor, err := outputs.NewOutputCannonTraceAccessor( accessor, err := outputs.NewOutputCannonTraceAccessor(
logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, actorCfg.prestateBlock, actorCfg.poststateBlock)
g.Require.NoError(err, "Failed to create output cannon trace accessor") g.Require.NoError(err, "Failed to create output cannon trace accessor")
return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor) return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor)
} }
...@@ -128,7 +154,7 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou ...@@ -128,7 +154,7 @@ func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, ou
if preloadPreimage { if preloadPreimage {
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.Require.NoError(err) g.Require.NoError(err)
g.UploadPreimage(ctx, preimageData, challengerKey) g.UploadPreimage(ctx, preimageData)
g.WaitForPreimageInOracle(ctx, preimageData) g.WaitForPreimageInOracle(ctx, preimageData)
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"testing" "testing"
"time" "time"
...@@ -584,7 +585,11 @@ func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAtta ...@@ -584,7 +585,11 @@ func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAtta
_, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail()) _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail())
err = errutil.TryAddRevertReason(err) err = errutil.TryAddRevertReason(err)
g.Require.Error(err, "Transaction should fail") g.Require.Error(err, "Transaction should fail")
g.Require.Contains(err.Error(), "0xfb4e40dd", "Revert reason should be abi encoded ValidStep()") validStepErr := "0xfb4e40dd"
invalidPrestateErr := "0x696550ff"
if !strings.Contains(err.Error(), validStepErr) && !strings.Contains(err.Error(), invalidPrestateErr) {
g.Require.Failf("Revert reason should be abi encoded ValidStep() or InvalidPrestate() but was: %v", err.Error())
}
} }
// ResolveClaim resolves a single subgame // ResolveClaim resolves a single subgame
...@@ -647,7 +652,7 @@ func (g *OutputGameHelper) WaitForPreimageInOracle(ctx context.Context, data *ty ...@@ -647,7 +652,7 @@ func (g *OutputGameHelper) WaitForPreimageInOracle(ctx context.Context, data *ty
g.Require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey)) g.Require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey))
} }
func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData, privateKey *ecdsa.PrivateKey) { func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData) {
oracle := g.oracle(ctx) oracle := g.oracle(ctx)
tx, err := oracle.AddGlobalDataTx(data) tx, err := oracle.AddGlobalDataTx(data)
g.Require.NoError(err, "Failed to create preimage upload tx") g.Require.NoError(err, "Failed to create preimage upload tx")
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -99,8 +100,13 @@ func (h *OutputHonestHelper) StepFails(ctx context.Context, claimIdx int64, isAt ...@@ -99,8 +100,13 @@ func (h *OutputHonestHelper) StepFails(ctx context.Context, claimIdx int64, isAt
// If we're defending, then the step will be from the trace to the next one // If we're defending, then the step will be from the trace to the next one
pos = pos.MoveRight() pos = pos.MoveRight()
} }
prestate, proofData, _, err := h.correctTrace.GetStepData(ctx, game, claim, pos) prestate, proofData, preimage, err := h.correctTrace.GetStepData(ctx, game, claim, pos)
h.require.NoError(err, "Get step data") h.require.NoError(err, "Get step data")
if preimage != nil {
tx, err := h.game.Game.UpdateOracleTx(ctx, uint64(claimIdx), preimage)
h.require.NoError(err)
transactions.RequireSendTx(h.t, ctx, h.game.Client, tx, h.game.PrivKey)
}
h.game.StepFails(ctx, claimIdx, isAttack, prestate, proofData) h.game.StepFails(ctx, claimIdx, isAttack, prestate, proofData)
} }
......
...@@ -194,7 +194,7 @@ func TestOutputCannonDefendStep(t *testing.T) { ...@@ -194,7 +194,7 @@ func TestOutputCannonDefendStep(t *testing.T) {
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
maxDepth := game.MaxDepth(ctx) maxDepth := game.MaxDepth(ctx)
game.DefendClaim(ctx, outputRootClaim, func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper { game.DefendClaim(ctx, outputRootClaim, func(claim *disputegame.ClaimHelper) *disputegame.ClaimHelper {
...@@ -410,7 +410,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { ...@@ -410,7 +410,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1)
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
...@@ -445,7 +445,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) { ...@@ -445,7 +445,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Honest first attack at "honest" level // Honest first attack at "honest" level
claim := correctTrace.AttackClaim(ctx, game.RootClaim(ctx)) claim := correctTrace.AttackClaim(ctx, game.RootClaim(ctx))
...@@ -509,7 +509,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) { ...@@ -509,7 +509,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1)
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -559,7 +559,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) { ...@@ -559,7 +559,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -610,7 +610,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) { ...@@ -610,7 +610,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Root claim is dishonest // Root claim is dishonest
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa}) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 1, common.Hash{0xaa})
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -700,7 +700,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) { ...@@ -700,7 +700,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) {
// Root claim is _dishonest_ because the required data is not available on L1 // Root claim is _dishonest_ because the required data is not available on L1
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal()) game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", blockNum, disputegame.WithUnsafeProposal())
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -762,7 +762,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) { ...@@ -762,7 +762,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) {
// Root claim is _dishonest_ because the required data is not available on L1 // Root claim is _dishonest_ because the required data is not available on L1
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal()) game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", farFutureBlockNum, common.Hash{0xaa}, disputegame.WithFutureProposal())
correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Alice))
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
...@@ -815,3 +815,98 @@ func TestInvalidateCorrectProposalFutureBlock(t *testing.T) { ...@@ -815,3 +815,98 @@ func TestInvalidateCorrectProposalFutureBlock(t *testing.T) {
game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon)
game.LogGameData(ctx) game.LogGameData(ctx)
} }
func TestOutputCannonHonestSafeTraceExtension_ValidRoot(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, l1Client := StartFaultDisputeSystem(t)
t.Cleanup(sys.Close)
// Wait for there to be there are safe L2 blocks past the claimed safe head that have data available on L1 within
// the commitment stored in the dispute game.
safeHeadNum := uint64(3)
require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), safeHeadNum))
// Create a dispute game with an honest claim
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", safeHeadNum-1)
require.NotNil(t, game)
// Create a correct trace actor with an honest trace extending to L2 block #4
correctTrace := game.CreateHonestActor(ctx, "sequencer", disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory))
// Create a correct trace actor with an honest trace extending to L2 block #5
// Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it
// will be committed to within the L1 head of the dispute game.
correctTracePlus1 := game.CreateHonestActor(ctx, "sequencer",
disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory),
disputegame.WithClaimedL2BlockNumber(safeHeadNum))
// Start the honest challenger. They will defend the root claim.
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
claim := game.RootClaim(ctx)
game.ChallengeClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
// Have to disagree with the root claim - we're trying to invalidate a valid output root
if parent.IsRootClaim() {
return parent.Attack(ctx, common.Hash{0xdd})
}
return correctTracePlus1.CounterClaim(ctx, parent)
}, func(parentClaimIdx int64) {
correctTrace.StepFails(ctx, parentClaimIdx, true)
correctTrace.StepFails(ctx, parentClaimIdx, false)
correctTracePlus1.StepFails(ctx, parentClaimIdx, true)
correctTracePlus1.StepFails(ctx, parentClaimIdx, false)
})
game.LogGameData(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, gameTypes.GameStatusDefenderWon, game.Status(ctx))
}
func TestOutputCannonHonestSafeTraceExtension_InvalidRoot(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, l1Client := StartFaultDisputeSystem(t)
t.Cleanup(sys.Close)
// Wait for there to be there are safe L2 blocks past the claimed safe head that have data available on L1 within
// the commitment stored in the dispute game.
safeHeadNum := uint64(2)
require.NoError(t, wait.ForSafeBlock(ctx, sys.RollupClient("sequencer"), safeHeadNum))
// Create a dispute game with a dishonest claim @ L2 block #4
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", safeHeadNum-1, common.Hash{0xCA, 0xFE})
require.NotNil(t, game)
// Create a correct trace actor with an honest trace extending to L2 block #5
// Notably, L2 block #5 is a valid block within the safe chain, and the data required to reproduce it
// will be committed to within the L1 head of the dispute game.
correctTracePlus1 := game.CreateHonestActor(ctx, "sequencer",
disputegame.WithPrivKey(sys.Cfg.Secrets.Mallory),
disputegame.WithClaimedL2BlockNumber(safeHeadNum))
// Start the honest challenger. They will challenge the root claim.
game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
claim := game.RootClaim(ctx)
game.DefendClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper {
return correctTracePlus1.CounterClaim(ctx, parent)
})
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, gameTypes.GameStatusChallengerWon, game.Status(ctx))
}
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