Skip to content

Commit

Permalink
op-e2e: Add tests with two op-challenger instances completing the ent…
Browse files Browse the repository at this point in the history
…ire dispute game. (ethereum-optimism#6391)

The alphabet is defended.
  • Loading branch information
ajsutton authored Jul 21, 2023
1 parent 2182501 commit 982a086
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 27 deletions.
4 changes: 1 addition & 3 deletions op-challenger/fault/alphabet_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ func (ap *AlphabetProvider) Get(i uint64) (common.Hash, error) {
}

func (ap *AlphabetProvider) AbsolutePreState() []byte {
out := make([]byte, 32)
out[31] = 96 // ascii character 96 is "`"
return out
return common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")
}

// BuildAlphabetPreimage constructs the claim bytes for the index and state item.
Expand Down
4 changes: 2 additions & 2 deletions op-e2e/e2eutils/disputegame/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ func deployDisputeGameContracts(require *require.Assertions, ctx context.Context

// Now setup the fault dispute game type
// Start by deploying the AlphabetVM
_, tx, _, err = bindings.DeployAlphabetVM(opts, client, alphabetVMAbsolutePrestate)
_, tx, _, err = bindings.DeployAlphabetVM(opts, client, alphabetVMAbsolutePrestateClaim)
require.NoError(err)
alphaVMAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)

// Deploy the fault dispute game implementation
_, tx, _, err = bindings.DeployFaultDisputeGame(opts, client, alphabetVMAbsolutePrestate, big.NewInt(alphabetGameDepth), gameDuration, alphaVMAddr)
_, tx, _, err = bindings.DeployFaultDisputeGame(opts, client, alphabetVMAbsolutePrestateClaim, big.NewInt(alphabetGameDepth), gameDuration, alphaVMAddr)
require.NoError(err)
faultDisputeGameAddr, err := bind.WaitDeployed(ctx, client, tx)
require.NoError(err)
Expand Down
62 changes: 54 additions & 8 deletions op-e2e/e2eutils/disputegame/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package disputegame
import (
"context"
"fmt"
"math"
"math/big"
"testing"
"time"
Expand All @@ -16,13 +15,14 @@ import (
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
)

const faultGameType uint8 = 0
const alphabetGameDepth = 4
const lastAlphabetTraceIndex = 1<<alphabetGameDepth - 1

type Status uint8

Expand All @@ -33,7 +33,9 @@ const (
)

var alphaExtraData = common.Hex2Bytes("1000000000000000000000000000000000000000000000000000000000000000")
var alphabetVMAbsolutePrestate = uint256.NewInt(96).Bytes32()
var alphabetVMAbsolutePrestate = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")
var alphabetVMAbsolutePrestateClaim = crypto.Keccak256Hash(alphabetVMAbsolutePrestate)
var CorrectAlphabet = "abcdefghijklmnop"

type FactoryHelper struct {
t *testing.T
Expand Down Expand Up @@ -62,10 +64,10 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, client *ethclient.Clien
}

func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet string) *FaultGameHelper {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
trace := fault.NewAlphabetProvider(claimedAlphabet, 4)
rootClaim, err := trace.Get(uint64(math.Pow(2, alphabetGameDepth)) - 1)
rootClaim, err := trace.Get(lastAlphabetTraceIndex)
h.require.NoError(err)
tx, err := h.factory.Create(h.opts, faultGameType, rootClaim, alphaExtraData)
h.require.NoError(err)
Expand All @@ -82,6 +84,7 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
client: h.client,
opts: h.opts,
game: game,
maxDepth: alphabetGameDepth,
addr: createdEvent.DisputeProxy,
claimedAlphabet: claimedAlphabet,
}
Expand All @@ -93,6 +96,7 @@ type FaultGameHelper struct {
client *ethclient.Client
opts *bind.TransactOpts
game *bindings.FaultDisputeGame
maxDepth int
addr common.Address
claimedAlphabet string
}
Expand All @@ -109,11 +113,15 @@ func (g *FaultGameHelper) StartChallenger(ctx context.Context, l1Endpoint string
},
}
opts = append(opts, options...)
return challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
g.t.Cleanup(func() {
_ = c.Close()
})
return c
}

func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
Expand All @@ -126,8 +134,46 @@ func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
g.require.NoError(err)
}

type ContractClaim struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}

func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim ContractClaim) bool) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, 1*time.Second, func() (bool, error) {
count, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return false, fmt.Errorf("retrieve number of claims: %w", err)
}
// Search backwards because the new claims are at the end and more likely the ones we want.
for i := count.Int64() - 1; i >= 0; i-- {
claimData, err := g.game.ClaimData(&bind.CallOpts{Context: ctx}, big.NewInt(i))
if err != nil {
return false, fmt.Errorf("retrieve claim %v: %w", i, err)
}
if predicate(claimData) {
return true, nil
}
}
return false, nil
})
g.require.NoError(err)
}

func (g *FaultGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) {
g.WaitForClaim(ctx, func(claim ContractClaim) bool {
pos := fault.NewPositionFromGIndex(claim.Position.Uint64())
return pos.Depth() == g.maxDepth && claim.Countered == countered
})
}

func (g *FaultGameHelper) Resolve(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
tx, err := g.game.Resolve(g.opts)
g.require.NoError(err)
Expand Down
4 changes: 4 additions & 0 deletions op-e2e/e2eutils/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func EncodePrivKey(priv *ecdsa.PrivateKey) hexutil.Bytes {
return privkey
}

func EncodePrivKeyToString(priv *ecdsa.PrivateKey) string {
return hexutil.Encode(EncodePrivKey(priv))
}

// Addresses computes the ethereum address of each account,
// which can then be kept around for fast precomputed address access.
func (s *Secrets) Addresses() *Addresses {
Expand Down
132 changes: 118 additions & 14 deletions op-e2e/faultproof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,29 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-service/client/utils"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)

func TestResolveDisputeGame(t *testing.T) {
InitParallel(t)

ctx := context.Background()
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1BlockTime = 1
delete(cfg.Nodes, "verifier")
delete(cfg.Nodes, "sequencer")
cfg.SupportL1TimeTravel = true
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
sys, l1Client := startL1OnlySystem(t)
t.Cleanup(sys.Close)

l1Client := sys.Clients["l1"]
gameDuration := 24 * time.Hour
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, l1Client, uint64(gameDuration.Seconds()))
game := disputeGameFactory.StartAlphabetGame(ctx, "zyxwvut")
require.NotNil(t, game)

game.WaitForGameStatus(ctx, disputegame.StatusInProgress)

honest := game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "honestAlice", func(c *config.Config) {
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "HonestAlice", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
c.AlphabetTrace = "abcdefg"
c.TxMgrConfig.PrivateKey = hexutil.Encode(e2eutils.EncodePrivKey(cfg.Secrets.Alice))
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
defer honest.Close()

game.WaitForClaimCount(ctx, 2)

Expand All @@ -48,5 +40,117 @@ func TestResolveDisputeGame(t *testing.T) {

// Challenger should resolve the game now that the clocks have expired.
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
require.NoError(t, honest.Close())
}

func TestChallengerCompleteDisputeGame(t *testing.T) {
InitParallel(t)

tests := []struct {
name string
rootClaimAlphabet string
otherAlphabet string
expectedResult disputegame.Status
expectStep bool
}{
{
name: "ChallengerWins_DefenseStep",
rootClaimAlphabet: "abcdexyz",
otherAlphabet: disputegame.CorrectAlphabet,
expectedResult: disputegame.StatusChallengerWins,
expectStep: true,
},
{
name: "DefenderWins_DefenseStep",
rootClaimAlphabet: disputegame.CorrectAlphabet,
otherAlphabet: "abcdexyz",
expectedResult: disputegame.StatusDefenderWins,
expectStep: false,
},
{
name: "ChallengerWins_AttackStep",
rootClaimAlphabet: "abcdefghzyx",
otherAlphabet: disputegame.CorrectAlphabet,
expectedResult: disputegame.StatusChallengerWins,
expectStep: true,
},
{
name: "DefenderWins_AttackStep",
rootClaimAlphabet: disputegame.CorrectAlphabet,
otherAlphabet: "abcdexyz",
expectedResult: disputegame.StatusDefenderWins,
expectStep: false,
},
{
name: "DefenderIncorrectAtTraceZero",
rootClaimAlphabet: "zyxwvut",
otherAlphabet: disputegame.CorrectAlphabet,
expectedResult: disputegame.StatusChallengerWins,
expectStep: true,
},
{
name: "ChallengerIncorrectAtTraceZero",
rootClaimAlphabet: disputegame.CorrectAlphabet,
otherAlphabet: "zyxwvut",
expectedResult: disputegame.StatusDefenderWins,
expectStep: false,
},
{
name: "DefenderIncorrectAtLastTraceIndex",
rootClaimAlphabet: "abcdefghijklmnoz",
otherAlphabet: disputegame.CorrectAlphabet,
expectedResult: disputegame.StatusChallengerWins,
expectStep: true,
},
{
name: "ChallengerIncorrectAtLastTraceIndex",
rootClaimAlphabet: disputegame.CorrectAlphabet,
otherAlphabet: "abcdefghijklmnoz",
expectedResult: disputegame.StatusDefenderWins,
expectStep: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
InitParallel(t)

ctx := context.Background()
sys, l1Client := startL1OnlySystem(t)
t.Cleanup(sys.Close)

gameDuration := 24 * time.Hour
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, l1Client, uint64(gameDuration.Seconds()))
game := disputeGameFactory.StartAlphabetGame(ctx, test.rootClaimAlphabet)
require.NotNil(t, game)

game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender", func(c *config.Config) {
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Mallory)
})

game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
c.AlphabetTrace = test.otherAlphabet
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})

// Wait for a claim at the maximum depth that has been countered to indicate we're ready to resolve the game
game.WaitForClaimAtMaxDepth(ctx, test.expectStep)

sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))

game.WaitForGameStatus(ctx, test.expectedResult)
})
}
}

func startL1OnlySystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1BlockTime = 1
delete(cfg.Nodes, "verifier")
delete(cfg.Nodes, "sequencer")
cfg.SupportL1TimeTravel = true
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
return sys, sys.Clients["l1"]
}

0 comments on commit 982a086

Please sign in to comment.