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

op-e2e: Update the exhaustive alphabet e2e test to handle duplicates (#9655)

The new challenger algorithm may post some claims that the exhaustive test was posting.
parent d5436b53
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"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/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -99,23 +98,13 @@ func (c *ClaimHelper) RequireCorrectOutputRoot(ctx context.Context) { ...@@ -99,23 +98,13 @@ func (c *ClaimHelper) RequireCorrectOutputRoot(ctx context.Context) {
c.require.Equalf(expected, c.claim, "Should have correct output root in claim %v and position %v", c.index, c.position) c.require.Equalf(expected, c.claim, "Should have correct output root in claim %v and position %v", c.index, c.position)
} }
func (c *ClaimHelper) Attack(ctx context.Context, value common.Hash) *ClaimHelper { func (c *ClaimHelper) Attack(ctx context.Context, value common.Hash, opts ...MoveOpt) *ClaimHelper {
c.game.Attack(ctx, c.index, value) c.game.Attack(ctx, c.index, value, opts...)
return c.WaitForCounterClaim(ctx) return c.WaitForCounterClaim(ctx)
} }
func (c *ClaimHelper) AttackWithTransactOpts(ctx context.Context, value common.Hash, opts *bind.TransactOpts) *ClaimHelper { func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash, opts ...MoveOpt) *ClaimHelper {
c.game.AttackWithTransactOpts(ctx, c.index, value, opts) c.game.Defend(ctx, c.index, value, opts...)
return c.WaitForCounterClaim(ctx)
}
func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash) *ClaimHelper {
c.game.Defend(ctx, c.index, value)
return c.WaitForCounterClaim(ctx)
}
func (c *ClaimHelper) DefendWithTransactOpts(ctx context.Context, value common.Hash, opts *bind.TransactOpts) *ClaimHelper {
c.game.DefendWithTransactOpts(ctx, c.index, value, opts)
return c.WaitForCounterClaim(ctx) return c.WaitForCounterClaim(ctx)
} }
......
...@@ -9,57 +9,14 @@ import ( ...@@ -9,57 +9,14 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type dishonestClaim struct {
ParentIndex int64
IsAttack bool
Valid bool
}
type DishonestHelper struct { type DishonestHelper struct {
*OutputGameHelper *OutputGameHelper
*OutputHonestHelper *OutputHonestHelper
claims map[dishonestClaim]bool
defender bool defender bool
} }
func newDishonestHelper(g *OutputGameHelper, correctTrace *OutputHonestHelper, defender bool) *DishonestHelper { func newDishonestHelper(g *OutputGameHelper, correctTrace *OutputHonestHelper, defender bool) *DishonestHelper {
return &DishonestHelper{g, correctTrace, make(map[dishonestClaim]bool), defender} return &DishonestHelper{g, correctTrace, defender}
}
func (t *DishonestHelper) Attack(ctx context.Context, claimIndex int64) {
c := dishonestClaim{claimIndex, true, false}
if t.claims[c] {
return
}
t.claims[c] = true
t.OutputGameHelper.Attack(ctx, claimIndex, common.Hash{byte(claimIndex)})
}
func (t *DishonestHelper) Defend(ctx context.Context, claimIndex int64) {
c := dishonestClaim{claimIndex, false, false}
if t.claims[c] {
return
}
t.claims[c] = true
t.OutputGameHelper.Defend(ctx, claimIndex, common.Hash{byte(claimIndex)})
}
func (t *DishonestHelper) AttackCorrect(ctx context.Context, claimIndex int64) {
c := dishonestClaim{claimIndex, true, true}
if t.claims[c] {
return
}
t.claims[c] = true
t.OutputHonestHelper.Attack(ctx, claimIndex)
}
func (t *DishonestHelper) DefendCorrect(ctx context.Context, claimIndex int64) {
c := dishonestClaim{claimIndex, false, true}
if t.claims[c] {
return
}
t.claims[c] = true
t.OutputHonestHelper.Defend(ctx, claimIndex)
} }
// ExhaustDishonestClaims makes all possible significant moves (mod honest challenger's) in a game. // ExhaustDishonestClaims makes all possible significant moves (mod honest challenger's) in a game.
...@@ -85,14 +42,14 @@ func (d *DishonestHelper) ExhaustDishonestClaims(ctx context.Context, rootClaim ...@@ -85,14 +42,14 @@ func (d *DishonestHelper) ExhaustDishonestClaims(ctx context.Context, rootClaim
d.OutputGameHelper.t.Logf("Dishonest moves against claimIndex %d", claimIndex) d.OutputGameHelper.t.Logf("Dishonest moves against claimIndex %d", claimIndex)
agreeWithLevel := d.defender == (pos.Depth()%2 == 0) agreeWithLevel := d.defender == (pos.Depth()%2 == 0)
if !agreeWithLevel { if !agreeWithLevel {
d.AttackCorrect(ctx, claimIndex) d.OutputHonestHelper.Attack(ctx, claimIndex, WithIgnoreDuplicates())
if claimIndex != 0 && pos.Depth() != splitDepth+1 { if claimIndex != 0 && pos.Depth() != splitDepth+1 {
d.DefendCorrect(ctx, claimIndex) d.OutputHonestHelper.Defend(ctx, claimIndex, WithIgnoreDuplicates())
} }
} }
d.Attack(ctx, claimIndex) d.OutputGameHelper.Attack(ctx, claimIndex, common.Hash{byte(claimIndex)}, WithIgnoreDuplicates())
if claimIndex != 0 && pos.Depth() != splitDepth+1 { if claimIndex != 0 && pos.Depth() != splitDepth+1 {
d.Defend(ctx, claimIndex) d.OutputGameHelper.Defend(ctx, claimIndex, common.Hash{byte(claimIndex)}, WithIgnoreDuplicates())
} }
} }
......
...@@ -38,6 +38,33 @@ type OutputGameHelper struct { ...@@ -38,6 +38,33 @@ type OutputGameHelper struct {
system DisputeSystem system DisputeSystem
} }
type moveCfg struct {
opts *bind.TransactOpts
ignoreDupes bool
}
type MoveOpt interface {
Apply(cfg *moveCfg)
}
type moveOptFn func(c *moveCfg)
func (f moveOptFn) Apply(c *moveCfg) {
f(c)
}
func WithTransactOpts(opts *bind.TransactOpts) MoveOpt {
return moveOptFn(func(c *moveCfg) {
c.opts = opts
})
}
func WithIgnoreDuplicates() MoveOpt {
return moveOptFn(func(c *moveCfg) {
c.ignoreDupes = true
})
}
func (g *OutputGameHelper) Addr() common.Address { func (g *OutputGameHelper) Addr() common.Address {
return g.addr return g.addr
} }
...@@ -449,48 +476,78 @@ func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64 ...@@ -449,48 +476,78 @@ func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64
return newClaimLen, err return newClaimLen, err
} }
func (g *OutputGameHelper) AttackWithTransactOpts(ctx context.Context, claimIdx int64, claim common.Hash, opts *bind.TransactOpts) { func (g *OutputGameHelper) moveCfg(opts ...MoveOpt) *moveCfg {
cfg := &moveCfg{
opts: g.opts,
}
for _, opt := range opts {
opt.Apply(cfg)
}
return cfg
}
func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash, opts ...MoveOpt) {
g.t.Logf("Attacking claim %v with value %v", claimIdx, claim) g.t.Logf("Attacking claim %v with value %v", claimIdx, claim)
cfg := g.moveCfg(opts...)
claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(claimIdx)) claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(claimIdx))
g.require.NoError(err, "Failed to get claim data") g.require.NoError(err, "Failed to get claim data")
pos := types.NewPositionFromGIndex(claimData.Position) pos := types.NewPositionFromGIndex(claimData.Position)
opts = g.makeBondedTransactOpts(ctx, pos.Attack().ToGIndex(), opts) attackPos := pos.Attack()
transactOpts := g.makeBondedTransactOpts(ctx, pos.Attack().ToGIndex(), cfg.opts)
tx, err := g.game.Attack(opts, big.NewInt(claimIdx), claim) err = g.sendMove(ctx, func() (*gethtypes.Transaction, error) {
if err != nil { return g.game.Attack(transactOpts, big.NewInt(claimIdx), claim)
g.require.NoErrorf(err, "Attack transaction did not send. Game state: \n%v", g.gameData(ctx)) })
}
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
if err != nil { if err != nil {
g.require.NoErrorf(err, "Attack transaction was not OK. Game state: \n%v", g.gameData(ctx)) if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, attackPos, claim) {
return
}
g.require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.gameData(ctx))
} }
} }
func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) { func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash, opts ...MoveOpt) {
g.AttackWithTransactOpts(ctx, claimIdx, claim, g.opts)
}
func (g *OutputGameHelper) DefendWithTransactOpts(ctx context.Context, claimIdx int64, claim common.Hash, opts *bind.TransactOpts) {
g.t.Logf("Defending claim %v with value %v", claimIdx, claim) g.t.Logf("Defending claim %v with value %v", claimIdx, claim)
cfg := g.moveCfg(opts...)
claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(claimIdx)) claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(claimIdx))
g.require.NoError(err, "Failed to get claim data") g.require.NoError(err, "Failed to get claim data")
pos := types.NewPositionFromGIndex(claimData.Position) pos := types.NewPositionFromGIndex(claimData.Position)
opts = g.makeBondedTransactOpts(ctx, pos.Defend().ToGIndex(), opts) defendPos := pos.Defend()
transactOpts := g.makeBondedTransactOpts(ctx, defendPos.ToGIndex(), cfg.opts)
tx, err := g.game.Defend(opts, big.NewInt(claimIdx), claim) err = g.sendMove(ctx, func() (*gethtypes.Transaction, error) {
return g.game.Defend(transactOpts, big.NewInt(claimIdx), claim)
})
if err != nil { if err != nil {
g.require.NoErrorf(err, "Defend transaction did not send. Game state: \n%v", g.gameData(ctx)) if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, defendPos, claim) {
return
}
g.require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.gameData(ctx))
} }
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash()) }
if err != nil {
g.require.NoErrorf(err, "Defend transaction was not OK. Game state: \n%v", g.gameData(ctx)) func (g *OutputGameHelper) hasClaim(ctx context.Context, parentIdx int64, pos types.Position, value common.Hash) bool {
claims := g.getAllClaims(ctx)
for _, claim := range claims {
if int64(claim.ParentIndex) == parentIdx && claim.Position.Cmp(pos.ToGIndex()) == 0 && claim.Claim == value {
return true
}
} }
return false
} }
func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash) { func (g *OutputGameHelper) sendMove(ctx context.Context, send func() (*gethtypes.Transaction, error)) error {
g.DefendWithTransactOpts(ctx, claimIdx, claim, g.opts) tx, err := send()
if err != nil {
return fmt.Errorf("transaction did not send: %w", err)
}
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
if err != nil {
return fmt.Errorf("transaction was not ok: %w", err)
}
return nil
} }
func (g *OutputGameHelper) makeBondedTransactOpts(ctx context.Context, pos *big.Int, opts *bind.TransactOpts) *bind.TransactOpts { func (g *OutputGameHelper) makeBondedTransactOpts(ctx context.Context, pos *big.Int, opts *bind.TransactOpts) *bind.TransactOpts {
......
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ 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/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -19,13 +18,8 @@ type OutputHonestHelper struct { ...@@ -19,13 +18,8 @@ type OutputHonestHelper struct {
correctTrace types.TraceAccessor correctTrace types.TraceAccessor
} }
func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper) *ClaimHelper { func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper {
h.Attack(ctx, claim.index) h.Attack(ctx, claim.index, opts...)
return claim.WaitForCounterClaim(ctx)
}
func (h *OutputHonestHelper) AttackClaimWithTransactOpts(ctx context.Context, claim *ClaimHelper, opts *bind.TransactOpts) *ClaimHelper {
h.AttackWithTransactOpts(ctx, claim.index, opts)
return claim.WaitForCounterClaim(ctx) return claim.WaitForCounterClaim(ctx)
} }
...@@ -34,11 +28,7 @@ func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper ...@@ -34,11 +28,7 @@ func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper
return claim.WaitForCounterClaim(ctx) return claim.WaitForCounterClaim(ctx)
} }
func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) { func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64, opts ...MoveOpt) {
h.AttackWithTransactOpts(ctx, claimIdx, h.game.opts)
}
func (h *OutputHonestHelper) AttackWithTransactOpts(ctx context.Context, claimIdx int64, opts *bind.TransactOpts) {
// Ensure the claim exists // Ensure the claim exists
h.game.WaitForClaimCount(ctx, claimIdx+1) h.game.WaitForClaimCount(ctx, claimIdx+1)
...@@ -51,11 +41,11 @@ func (h *OutputHonestHelper) AttackWithTransactOpts(ctx context.Context, claimId ...@@ -51,11 +41,11 @@ func (h *OutputHonestHelper) AttackWithTransactOpts(ctx context.Context, claimId
value, err := h.correctTrace.Get(ctx, game, claim, attackPos) value, err := h.correctTrace.Get(ctx, game, claim, attackPos)
h.require.NoErrorf(err, "Get correct claim at position %v with g index %v", attackPos, attackPos.ToGIndex()) h.require.NoErrorf(err, "Get correct claim at position %v with g index %v", attackPos, attackPos.ToGIndex())
h.t.Log("Performing attack") h.t.Log("Performing attack")
h.game.AttackWithTransactOpts(ctx, claimIdx, value, opts) h.game.Attack(ctx, claimIdx, value, opts...)
h.t.Log("Attack complete") h.t.Log("Attack complete")
} }
func (h *OutputHonestHelper) Defend(ctx context.Context, claimIdx int64) { func (h *OutputHonestHelper) Defend(ctx context.Context, claimIdx int64, opts ...MoveOpt) {
// Ensure the claim exists // Ensure the claim exists
h.game.WaitForClaimCount(ctx, claimIdx+1) h.game.WaitForClaimCount(ctx, claimIdx+1)
...@@ -65,7 +55,7 @@ func (h *OutputHonestHelper) Defend(ctx context.Context, claimIdx int64) { ...@@ -65,7 +55,7 @@ func (h *OutputHonestHelper) Defend(ctx context.Context, claimIdx int64) {
defendPos := claim.Position.Defend() defendPos := claim.Position.Defend()
value, err := h.correctTrace.Get(ctx, game, claim, defendPos) value, err := h.correctTrace.Get(ctx, game, claim, defendPos)
h.game.require.NoErrorf(err, "Get correct claim at position %v with g index %v", defendPos, defendPos.ToGIndex()) h.game.require.NoErrorf(err, "Get correct claim at position %v with g index %v", defendPos, defendPos.ToGIndex())
h.game.Defend(ctx, claimIdx, value) h.game.Defend(ctx, claimIdx, value, opts...)
} }
func (h *OutputHonestHelper) StepClaimFails(ctx context.Context, claim *ClaimHelper, isAttack bool) { func (h *OutputHonestHelper) StepClaimFails(ctx context.Context, claim *ClaimHelper, isAttack bool) {
......
...@@ -103,8 +103,6 @@ func TestOutputAlphabetGame_ValidOutputRoot(t *testing.T) { ...@@ -103,8 +103,6 @@ func TestOutputAlphabetGame_ValidOutputRoot(t *testing.T) {
} }
func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) { func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) {
// TODO(client-pod#103): Update ExhaustDishonestClaims to not fail if claim it tried to post exists
t.Skip("Challenger performs many more moves now creating conflicts")
op_e2e.InitParallel(t) op_e2e.InitParallel(t)
testCase := func(t *testing.T, isRootCorrect bool) { testCase := func(t *testing.T, isRootCorrect bool) {
...@@ -205,17 +203,17 @@ func TestOutputAlphabetGame_FreeloaderEarnsNothing(t *testing.T) { ...@@ -205,17 +203,17 @@ func TestOutputAlphabetGame_FreeloaderEarnsNothing(t *testing.T) {
// dishonest // dishonest
dishonest := correctTrace.AttackClaim(ctx, claim) dishonest := correctTrace.AttackClaim(ctx, claim)
freeloaders = append(freeloaders, correctTrace.AttackClaimWithTransactOpts(ctx, dishonest, freeloaderOpts)) freeloaders = append(freeloaders, correctTrace.AttackClaim(ctx, dishonest, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.AttackWithTransactOpts(ctx, common.Hash{0x02}, freeloaderOpts)) freeloaders = append(freeloaders, dishonest.Attack(ctx, common.Hash{0x02}, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.DefendWithTransactOpts(ctx, common.Hash{0x03}, freeloaderOpts)) freeloaders = append(freeloaders, dishonest.Defend(ctx, common.Hash{0x03}, disputegame.WithTransactOpts(freeloaderOpts)))
// Ensure freeloaders respond before the honest challenger // Ensure freeloaders respond before the honest challenger
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
dishonest.WaitForCounterClaim(ctx, freeloaders...) dishonest.WaitForCounterClaim(ctx, freeloaders...)
// Freeloaders after the honest challenger // Freeloaders after the honest challenger
freeloaders = append(freeloaders, dishonest.AttackWithTransactOpts(ctx, common.Hash{0x04}, freeloaderOpts)) freeloaders = append(freeloaders, dishonest.Attack(ctx, common.Hash{0x04}, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.DefendWithTransactOpts(ctx, common.Hash{0x05}, freeloaderOpts)) freeloaders = append(freeloaders, dishonest.Defend(ctx, common.Hash{0x05}, disputegame.WithTransactOpts(freeloaderOpts)))
for _, freeloader := range freeloaders { for _, freeloader := range freeloaders {
if freeloader.IsMaxDepth(ctx) { if freeloader.IsMaxDepth(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