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 (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"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/stretchr/testify/require"
)
......@@ -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)
}
func (c *ClaimHelper) Attack(ctx context.Context, value common.Hash) *ClaimHelper {
c.game.Attack(ctx, c.index, value)
func (c *ClaimHelper) Attack(ctx context.Context, value common.Hash, opts ...MoveOpt) *ClaimHelper {
c.game.Attack(ctx, c.index, value, opts...)
return c.WaitForCounterClaim(ctx)
}
func (c *ClaimHelper) AttackWithTransactOpts(ctx context.Context, value common.Hash, opts *bind.TransactOpts) *ClaimHelper {
c.game.AttackWithTransactOpts(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)
func (c *ClaimHelper) Defend(ctx context.Context, value common.Hash, opts ...MoveOpt) *ClaimHelper {
c.game.Defend(ctx, c.index, value, opts...)
return c.WaitForCounterClaim(ctx)
}
......
......@@ -9,57 +9,14 @@ import (
"github.com/ethereum/go-ethereum/common"
)
type dishonestClaim struct {
ParentIndex int64
IsAttack bool
Valid bool
}
type DishonestHelper struct {
*OutputGameHelper
*OutputHonestHelper
claims map[dishonestClaim]bool
defender bool
}
func newDishonestHelper(g *OutputGameHelper, correctTrace *OutputHonestHelper, defender bool) *DishonestHelper {
return &DishonestHelper{g, correctTrace, make(map[dishonestClaim]bool), 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)
return &DishonestHelper{g, correctTrace, defender}
}
// 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
d.OutputGameHelper.t.Logf("Dishonest moves against claimIndex %d", claimIndex)
agreeWithLevel := d.defender == (pos.Depth()%2 == 0)
if !agreeWithLevel {
d.AttackCorrect(ctx, claimIndex)
d.OutputHonestHelper.Attack(ctx, claimIndex, WithIgnoreDuplicates())
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 {
d.Defend(ctx, claimIndex)
d.OutputGameHelper.Defend(ctx, claimIndex, common.Hash{byte(claimIndex)}, WithIgnoreDuplicates())
}
}
......
......@@ -38,6 +38,33 @@ type OutputGameHelper struct {
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 {
return g.addr
}
......@@ -449,48 +476,78 @@ func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64
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)
cfg := g.moveCfg(opts...)
claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(claimIdx))
g.require.NoError(err, "Failed to get claim data")
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)
if err != nil {
g.require.NoErrorf(err, "Attack transaction did not send. Game state: \n%v", g.gameData(ctx))
}
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
err = g.sendMove(ctx, func() (*gethtypes.Transaction, error) {
return g.game.Attack(transactOpts, big.NewInt(claimIdx), claim)
})
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) {
g.AttackWithTransactOpts(ctx, claimIdx, claim, g.opts)
}
func (g *OutputGameHelper) DefendWithTransactOpts(ctx context.Context, claimIdx int64, claim common.Hash, opts *bind.TransactOpts) {
func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash, opts ...MoveOpt) {
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))
g.require.NoError(err, "Failed to get claim data")
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 {
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) {
g.DefendWithTransactOpts(ctx, claimIdx, claim, g.opts)
func (g *OutputGameHelper) sendMove(ctx context.Context, send func() (*gethtypes.Transaction, error)) error {
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 {
......
......@@ -7,7 +7,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"
)
......@@ -19,13 +18,8 @@ type OutputHonestHelper struct {
correctTrace types.TraceAccessor
}
func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper) *ClaimHelper {
h.Attack(ctx, claim.index)
return claim.WaitForCounterClaim(ctx)
}
func (h *OutputHonestHelper) AttackClaimWithTransactOpts(ctx context.Context, claim *ClaimHelper, opts *bind.TransactOpts) *ClaimHelper {
h.AttackWithTransactOpts(ctx, claim.index, opts)
func (h *OutputHonestHelper) AttackClaim(ctx context.Context, claim *ClaimHelper, opts ...MoveOpt) *ClaimHelper {
h.Attack(ctx, claim.index, opts...)
return claim.WaitForCounterClaim(ctx)
}
......@@ -34,11 +28,7 @@ func (h *OutputHonestHelper) DefendClaim(ctx context.Context, claim *ClaimHelper
return claim.WaitForCounterClaim(ctx)
}
func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64) {
h.AttackWithTransactOpts(ctx, claimIdx, h.game.opts)
}
func (h *OutputHonestHelper) AttackWithTransactOpts(ctx context.Context, claimIdx int64, opts *bind.TransactOpts) {
func (h *OutputHonestHelper) Attack(ctx context.Context, claimIdx int64, opts ...MoveOpt) {
// Ensure the claim exists
h.game.WaitForClaimCount(ctx, claimIdx+1)
......@@ -51,11 +41,11 @@ func (h *OutputHonestHelper) AttackWithTransactOpts(ctx context.Context, claimId
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.t.Log("Performing attack")
h.game.AttackWithTransactOpts(ctx, claimIdx, value, opts)
h.game.Attack(ctx, claimIdx, value, opts...)
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
h.game.WaitForClaimCount(ctx, claimIdx+1)
......@@ -65,7 +55,7 @@ func (h *OutputHonestHelper) Defend(ctx context.Context, claimIdx int64) {
defendPos := claim.Position.Defend()
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.Defend(ctx, claimIdx, value)
h.game.Defend(ctx, claimIdx, value, opts...)
}
func (h *OutputHonestHelper) StepClaimFails(ctx context.Context, claim *ClaimHelper, isAttack bool) {
......
......@@ -103,8 +103,6 @@ func TestOutputAlphabetGame_ValidOutputRoot(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)
testCase := func(t *testing.T, isRootCorrect bool) {
......@@ -205,17 +203,17 @@ func TestOutputAlphabetGame_FreeloaderEarnsNothing(t *testing.T) {
// dishonest
dishonest := correctTrace.AttackClaim(ctx, claim)
freeloaders = append(freeloaders, correctTrace.AttackClaimWithTransactOpts(ctx, dishonest, freeloaderOpts))
freeloaders = append(freeloaders, dishonest.AttackWithTransactOpts(ctx, common.Hash{0x02}, freeloaderOpts))
freeloaders = append(freeloaders, dishonest.DefendWithTransactOpts(ctx, common.Hash{0x03}, freeloaderOpts))
freeloaders = append(freeloaders, correctTrace.AttackClaim(ctx, dishonest, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.Attack(ctx, common.Hash{0x02}, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.Defend(ctx, common.Hash{0x03}, disputegame.WithTransactOpts(freeloaderOpts)))
// Ensure freeloaders respond before the honest challenger
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
dishonest.WaitForCounterClaim(ctx, freeloaders...)
// Freeloaders after the honest challenger
freeloaders = append(freeloaders, dishonest.AttackWithTransactOpts(ctx, common.Hash{0x04}, freeloaderOpts))
freeloaders = append(freeloaders, dishonest.DefendWithTransactOpts(ctx, common.Hash{0x05}, freeloaderOpts))
freeloaders = append(freeloaders, dishonest.Attack(ctx, common.Hash{0x04}, disputegame.WithTransactOpts(freeloaderOpts)))
freeloaders = append(freeloaders, dishonest.Defend(ctx, common.Hash{0x05}, disputegame.WithTransactOpts(freeloaderOpts)))
for _, freeloader := range freeloaders {
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