Commit a6ec5b02 authored by refcell.eth's avatar refcell.eth Committed by GitHub

feat(op-e2e): Large Preimage Test (#9296)

* feat(op-e2e): large preimage e2e output cannon test

* fix: Attempt to wait until leaves are uploaded (finalized) and then time travel past the challenge period

* fix: remove args in callback constructor

* op-e2e: Make batch size configurable via an option instead of always using large batches.

* fix: defend step

* op-e2e: Wait for preimage instead of immediate check and some tidy ups.

* op-e2e: Reduce keccak challenge period in devnet

* op-e2e: Make timings self-consistent.

* Use errors.Is

* feat(op-challenger): simple clock with accessor and mutator

* fix(op-service): make simple clock concurrent

* fix(op-service): simple clock lock free atomics

* op-challenger: Use state matrix matching the prestate leaf instead of poststate when calling squeeze.

* fix(op-service): move simple clock interfaces to their usage

* op-e2e: Skip test until insertion is fixed.

* op-e2e: Fix typo

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent 50ef8f73
......@@ -69,9 +69,9 @@ var (
Usage: "stop at the first preimage request matching this type (must be either 'any', 'local' or 'global')",
Required: false,
}
RunStopAtPreimageKeyFlag = &cli.StringFlag{
Name: "stop-at-preimage-key",
Usage: "stop at the first step that requests the specified preimage key",
RunStopAtPreimageLargerThanFlag = &cli.StringFlag{
Name: "stop-at-preimage-larger-than",
Usage: "stop at the first step that requests a preimage larger than the specified size (in bytes)",
Required: false,
}
RunMetaFlag = &cli.PathFlag{
......@@ -247,7 +247,7 @@ func Run(ctx *cli.Context) error {
if stopAtPreimageType != "" && stopAtPreimageType != "any" && stopAtPreimageType != "local" && stopAtPreimageType != "global" {
return fmt.Errorf("invalid preimage type %q, must be either 'any', 'local' or 'global'", stopAtPreimageType)
}
stopAtPreimageKey := common.HexToHash(ctx.String(RunStopAtPreimageKeyFlag.Name))
stopAtPreimageLargerThan := ctx.Int(RunStopAtPreimageLargerThanFlag.Name)
// split CLI args after first '--'
args := ctx.Args().Slice()
......@@ -392,7 +392,7 @@ func Run(ctx *cli.Context) error {
break
}
}
if (stopAtPreimageKey != common.Hash{}) && state.PreimageKey == stopAtPreimageKey {
if stopAtPreimageLargerThan != 0 && len(us.LastPreimage()) > stopAtPreimageLargerThan {
break
}
}
......@@ -418,7 +418,7 @@ var RunCommand = &cli.Command{
RunSnapshotFmtFlag,
RunStopAtFlag,
RunStopAtPreimageTypeFlag,
RunStopAtPreimageKeyFlag,
RunStopAtPreimageLargerThanFlag,
RunMetaFlag,
RunInfoAtFlag,
RunPProfCPU,
......
......@@ -80,3 +80,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
}
return
}
func (m *InstrumentedState) LastPreimage() []byte {
return m.lastPreimage
}
......@@ -8,7 +8,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-service/clock"
)
// Main is the programmatic entry-point for running op-challenger with a given configuration.
......@@ -16,5 +15,5 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) (cliapp.Li
if err := cfg.Check(); err != nil {
return nil, err
}
return game.NewService(ctx, clock.SystemClock, logger, cfg)
return game.NewService(ctx, logger, cfg)
}
......@@ -10,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
......@@ -109,13 +108,13 @@ func (c *PreimageOracleContract) CallSqueeze(
ctx context.Context,
claimant common.Address,
uuid *big.Int,
stateMatrix *matrix.StateMatrix,
prestateMatrix keccakTypes.StateSnapshot,
preState keccakTypes.Leaf,
preStateProof merkle.Proof,
postState keccakTypes.Leaf,
postStateProof merkle.Proof,
) error {
call := c.contract.Call(methodSqueezeLPP, claimant, uuid, abiEncodeStateMatrix(stateMatrix), toPreimageOracleLeaf(preState), preStateProof, toPreimageOracleLeaf(postState), postStateProof)
call := c.contract.Call(methodSqueezeLPP, claimant, uuid, abiEncodeSnapshot(prestateMatrix), toPreimageOracleLeaf(preState), preStateProof, toPreimageOracleLeaf(postState), postStateProof)
_, err := c.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return fmt.Errorf("failed to call squeeze: %w", err)
......@@ -126,7 +125,7 @@ func (c *PreimageOracleContract) CallSqueeze(
func (c *PreimageOracleContract) Squeeze(
claimant common.Address,
uuid *big.Int,
stateMatrix *matrix.StateMatrix,
prestateMatrix keccakTypes.StateSnapshot,
preState keccakTypes.Leaf,
preStateProof merkle.Proof,
postState keccakTypes.Leaf,
......@@ -136,7 +135,7 @@ func (c *PreimageOracleContract) Squeeze(
methodSqueezeLPP,
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
abiEncodeSnapshot(prestateMatrix),
toPreimageOracleLeaf(preState),
preStateProof,
toPreimageOracleLeaf(postState),
......@@ -145,11 +144,6 @@ func (c *PreimageOracleContract) Squeeze(
return call.ToTxCandidate()
}
// abiEncodeStateMatrix encodes the state matrix for the contract ABI
func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakStateMatrix {
return abiEncodeSnapshot(stateMatrix.StateSnapshot())
}
func abiEncodeSnapshot(packedState keccakTypes.StateSnapshot) bindings.LibKeccakStateMatrix {
return bindings.LibKeccakStateMatrix{State: packedState}
}
......
......@@ -9,7 +9,6 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
......@@ -134,7 +133,7 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) {
claimant := common.Address{0x12}
uuid := big.NewInt(123)
stateMatrix := matrix.NewStateMatrix()
preStateMatrix := keccakTypes.StateSnapshot{0, 1, 2, 3, 4}
preState := keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{0x12},
Index: 123,
......@@ -150,14 +149,14 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) {
stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, batching.BlockLatest, []interface{}{
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
abiEncodeSnapshot(preStateMatrix),
toPreimageOracleLeaf(preState),
preStateProof,
toPreimageOracleLeaf(postState),
postStateProof,
}, nil)
tx, err := oracle.Squeeze(claimant, uuid, stateMatrix, preState, preStateProof, postState, postStateProof)
tx, err := oracle.Squeeze(claimant, uuid, preStateMatrix, preState, preStateProof, postState, postStateProof)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
......
......@@ -10,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
......@@ -44,7 +43,7 @@ type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth type
func NewGamePlayer(
ctx context.Context,
cl clock.Clock,
cl types.ClockReader,
logger log.Logger,
m metrics.Metricer,
dir string,
......
......@@ -13,9 +13,9 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
......@@ -39,22 +39,23 @@ const MaxChunkSize = MaxBlocksPerChunk * keccakTypes.BlockSize
type LargePreimageUploader struct {
log log.Logger
clock clock.Clock
clock types.ClockReader
txSender gameTypes.TxSender
contract PreimageOracleContract
}
func NewLargePreimageUploader(logger log.Logger, cl clock.Clock, txSender gameTypes.TxSender, contract PreimageOracleContract) *LargePreimageUploader {
func NewLargePreimageUploader(logger log.Logger, cl types.ClockReader, txSender gameTypes.TxSender, contract PreimageOracleContract) *LargePreimageUploader {
return &LargePreimageUploader{logger, cl, txSender, contract}
}
func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint64, data *types.PreimageOracleData) error {
p.log.Debug("Upload large preimage", "key", data.OracleKey)
stateMatrix, calls, err := p.splitCalls(data)
if err != nil {
return fmt.Errorf("failed to split preimage into chunks for data with oracle offset %d: %w", data.OracleOffset, err)
}
uuid := p.newUUID(data)
uuid := NewUUID(p.txSender.From(), data)
// Fetch the current metadata for this preimage data, if it exists.
ident := keccakTypes.LargePreimageIdent{Claimant: p.txSender.From(), UUID: uuid}
......@@ -89,10 +90,9 @@ func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint6
return p.Squeeze(ctx, uuid, stateMatrix)
}
// newUUID generates a new unique identifier for the preimage by hashing the
// NewUUID generates a new unique identifier for the preimage by hashing the
// concatenated preimage data, preimage offset, and sender address.
func (p *LargePreimageUploader) newUUID(data *types.PreimageOracleData) *big.Int {
sender := p.txSender.From()
func NewUUID(sender common.Address, data *types.PreimageOracleData) *big.Int {
offset := make([]byte, 4)
binary.LittleEndian.PutUint32(offset, data.OracleOffset)
concatenated := append(data.OracleData, offset...)
......@@ -122,6 +122,7 @@ func (p *LargePreimageUploader) splitCalls(data *types.PreimageOracleData) (*mat
}
func (p *LargePreimageUploader) Squeeze(ctx context.Context, uuid *big.Int, stateMatrix *matrix.StateMatrix) error {
prestateMatrix := stateMatrix.PrestateMatrix()
prestate, prestateProof := stateMatrix.PrestateWithProof()
poststate, poststateProof := stateMatrix.PoststateWithProof()
challengePeriod, err := p.contract.ChallengePeriod(ctx)
......@@ -140,11 +141,12 @@ func (p *LargePreimageUploader) Squeeze(ctx context.Context, uuid *big.Int, stat
if uint64(currentTimestamp) < metadata[0].Timestamp+challengePeriod {
return ErrChallengePeriodNotOver
}
if err := p.contract.CallSqueeze(ctx, p.txSender.From(), uuid, stateMatrix, prestate, prestateProof, poststate, poststateProof); err != nil {
p.log.Debug("expected a successful squeeze call", "metadataTimestamp", metadata[0].Timestamp, "currentTimestamp", currentTimestamp, "err", err)
if err := p.contract.CallSqueeze(ctx, p.txSender.From(), uuid, prestateMatrix, prestate, prestateProof, poststate, poststateProof); err != nil {
p.log.Warn("Expected a successful squeeze call", "metadataTimestamp", metadata[0].Timestamp, "currentTimestamp", currentTimestamp, "err", err)
return fmt.Errorf("failed to call squeeze: %w", err)
}
tx, err := p.contract.Squeeze(p.txSender.From(), uuid, stateMatrix, prestate, prestateProof, poststate, poststateProof)
p.log.Info("Squeezing large preimage", "uuid", uuid)
tx, err := p.contract.Squeeze(p.txSender.From(), uuid, prestateMatrix, prestate, prestateProof, poststate, poststateProof)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
}
......@@ -157,6 +159,7 @@ func (p *LargePreimageUploader) Squeeze(ctx context.Context, uuid *big.Int, stat
// initLargePreimage initializes the large preimage proposal.
// This method *must* be called before adding any leaves.
func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) error {
p.log.Info("Init large preimage upload", "uuid", uuid, "partOffset", partOffset, "size", claimedSize)
candidate, err := p.contract.InitLargePreimage(uuid, partOffset, claimedSize)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
......@@ -181,6 +184,7 @@ func (p *LargePreimageUploader) addLargePreimageData(uuid *big.Int, chunks []kec
blocksProcessed += int64(len(chunk.Input) / keccakTypes.BlockSize)
txs[i] = tx
}
p.log.Info("Adding large preimage leaves", "uuid", uuid, "blocksProcessed", blocksProcessed, "txs", len(txs))
_, err := p.txSender.SendAndWait("add leaf to large preimage", txs...)
return err
}
......@@ -69,7 +69,7 @@ func TestLargePreimageUploader_NewUUID(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
oracle, _, _, _ := newTestLargePreimageUploader(t)
uuid := oracle.newUUID(test.data)
uuid := NewUUID(oracle.txSender.From(), test.data)
require.Equal(t, test.expectedUUID, uuid)
})
}
......@@ -249,7 +249,6 @@ func TestLargePreimageUploader_UploadPreimage_Succeeds(t *testing.T) {
require.Equal(t, poststate, contract.squeezePoststate)
})
}
}
func newTestLargePreimageUploader(t *testing.T) (*LargePreimageUploader, *clock.AdvancingClock, *mockTxSender, *mockPreimageOracleContract) {
......@@ -298,7 +297,7 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ *big.Int, input []b
return txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, prestate keccakTypes.Leaf, _ merkle.Proof, poststate keccakTypes.Leaf, _ merkle.Proof) (txmgr.TxCandidate, error) {
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ keccakTypes.StateSnapshot, prestate keccakTypes.Leaf, _ merkle.Proof, poststate keccakTypes.Leaf, _ merkle.Proof) (txmgr.TxCandidate, error) {
s.squeezeCalls++
s.squeezePrestate = prestate
s.squeezePoststate = poststate
......@@ -340,7 +339,7 @@ func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ ba
s.squeezeCallClaimSize = 1
return []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: idents[0]}}, nil
}
func (s *mockPreimageOracleContract) CallSqueeze(_ context.Context, _ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ merkle.Proof, _ keccakTypes.Leaf, _ merkle.Proof) error {
func (s *mockPreimageOracleContract) CallSqueeze(_ context.Context, _ common.Address, _ *big.Int, _ keccakTypes.StateSnapshot, _ keccakTypes.Leaf, _ merkle.Proof, _ keccakTypes.Leaf, _ merkle.Proof) error {
if s.squeezeCallFails {
return mockSqueezeCallError
}
......
......@@ -6,7 +6,6 @@ import (
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
......@@ -26,8 +25,8 @@ type PreimageUploader interface {
type PreimageOracleContract interface {
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) (txmgr.TxCandidate, error)
CallSqueeze(ctx context.Context, claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) error
Squeeze(claimant common.Address, uuid *big.Int, prestateMatrix keccakTypes.StateSnapshot, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) (txmgr.TxCandidate, error)
CallSqueeze(ctx context.Context, claimant common.Address, uuid *big.Int, prestateMatrix keccakTypes.StateSnapshot, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) error
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
ChallengePeriod(ctx context.Context) (uint64, error)
}
......@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
......@@ -35,7 +34,7 @@ type Registry interface {
func RegisterGameTypes(
registry Registry,
ctx context.Context,
cl clock.Clock,
cl faultTypes.ClockReader,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
......@@ -70,7 +69,7 @@ func RegisterGameTypes(
func registerAlphabet(
registry Registry,
ctx context.Context,
cl clock.Clock,
cl faultTypes.ClockReader,
logger log.Logger,
m metrics.Metricer,
rollupClient outputs.OutputRollupClient,
......@@ -135,7 +134,7 @@ func createOracle(ctx context.Context, gameFactory *contracts.DisputeGameFactory
func registerCannon(
registry Registry,
ctx context.Context,
cl clock.Clock,
cl faultTypes.ClockReader,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
......
......@@ -2,6 +2,7 @@ package responder
import (
"context"
"errors"
"fmt"
"math/big"
......@@ -95,7 +96,7 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action)
// Always upload local preimages
if !preimageExists {
err := r.uploader.UploadPreimage(ctx, uint64(action.ParentIdx), action.OracleData)
if err == preimages.ErrChallengePeriodNotOver {
if errors.Is(err, preimages.ErrChallengePeriodNotOver) {
r.log.Debug("Large Preimage Squeeze failed, challenge period not over")
return nil
} else if err != nil {
......
......@@ -70,13 +70,13 @@ func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs
// GenerateProof executes cannon to generate a proof at the specified trace index.
// The proof is stored at the specified directory.
func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error {
return e.generateProofOrUntilPreimageRead(ctx, dir, i, i, false)
return e.generateProof(ctx, dir, i, i)
}
// generateProofOrUntilPreimageRead executes cannon to generate a proof at the specified trace index,
// or until a non-local preimage read is encountered if untilPreimageRead is true.
// The proof is stored at the specified directory.
func (e *Executor) generateProofOrUntilPreimageRead(ctx context.Context, dir string, begin uint64, end uint64, untilPreimageRead bool) error {
func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraCannonArgs ...string) error {
snapshotDir := filepath.Join(dir, snapsDir)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin)
if err != nil {
......@@ -99,9 +99,7 @@ func (e *Executor) generateProofOrUntilPreimageRead(ctx context.Context, dir str
if end < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10))
}
if untilPreimageRead {
args = append(args, "--stop-at-preimage-type", "global")
}
args = append(args, extraCannonArgs...)
args = append(args,
"--",
e.server, "--server",
......
......@@ -9,6 +9,7 @@ import (
"math"
"os"
"path/filepath"
"strconv"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
......@@ -247,6 +248,22 @@ type CannonTraceProviderForTest struct {
*CannonTraceProvider
}
type preimageOpts []string
type PreimageOpt func() preimageOpts
func FirstGlobalPreimageLoad() PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-type", "global"}
}
}
func PreimageLargerThan(size int) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-larger-than", strconv.Itoa(size)}
}
}
func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest {
p := &CannonTraceProvider{
logger: logger,
......@@ -258,28 +275,28 @@ func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Co
return &CannonTraceProviderForTest{p}
}
func (p *CannonTraceProviderForTest) FindStepReferencingPreimage(ctx context.Context, start uint64) (uint64, error) {
func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage PreimageOpt) (uint64, common.Hash, error) {
// First generate a snapshot of the starting state, so we can snap to it later for the full trace search
prestateProof, err := p.loadProof(ctx, start)
if err != nil {
return 0, err
return 0, common.Hash{}, err
}
start += 1
for {
if err := p.generator.(*Executor).generateProofOrUntilPreimageRead(ctx, p.dir, start, math.MaxUint64, true); err != nil {
return 0, fmt.Errorf("generate cannon trace (until preimage read) with proof at %d: %w", start, err)
if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil {
return 0, common.Hash{}, fmt.Errorf("generate cannon trace (until preimage read) with proof at %d: %w", start, err)
}
state, err := p.finalState()
if err != nil {
return 0, err
return 0, common.Hash{}, err
}
if state.Exited {
break
}
if state.PreimageOffset != 0 && state.PreimageOffset != prestateProof.OracleOffset {
return state.Step - 1, nil
return state.Step - 1, state.PreimageKey, nil
}
start = state.Step
}
return 0, io.EOF
return 0, common.Hash{}, io.EOF
}
......@@ -4,6 +4,7 @@ import (
"context"
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
)
......@@ -16,6 +17,10 @@ var (
NoLocalContext = common.Hash{}
)
type ClockReader interface {
Now() time.Time
}
// PreimageOracleData encapsulates the preimage oracle data
// to load into the onchain oracle.
type PreimageOracleData struct {
......
......@@ -14,6 +14,8 @@ import (
// StateMatrix implements a stateful keccak sponge with the ability to create state commitments after each permutation
type StateMatrix struct {
s *state
//prestateMatrix is the state matrix snapshot after processing prestateLeaf but before processing poststateLeaf
prestateMatrix types.StateSnapshot
// prestateLeaf is the last prestate leaf.
// Used to retrieve the prestate to squeeze.
prestateLeaf types.Leaf
......@@ -163,6 +165,10 @@ func (d *StateMatrix) AbsorbUpTo(in io.Reader, maxLen int) (types.InputData, err
}, nil
}
func (d *StateMatrix) PrestateMatrix() types.StateSnapshot {
return d.prestateMatrix
}
// PrestateWithProof returns the prestate leaf with its merkle proof.
func (d *StateMatrix) PrestateWithProof() (types.Leaf, merkle.Proof) {
proof := d.merkleTree.ProofAtIndex(d.prestateLeaf.Index)
......@@ -198,6 +204,7 @@ func (d *StateMatrix) absorbNextLeafInput(in io.Reader, stateCommitment func() c
// The next call will read no data from the Reader (already at EOF) and so add the final padding as an
// additional block. We can then return EOF to indicate there are no further blocks.
final = final && len(input) < types.BlockSize
d.prestateMatrix = d.StateSnapshot()
d.absorbLeafInput(input, final)
commitment := stateCommitment()
if d.poststateLeaf == (types.Leaf{}) {
......
......@@ -9,7 +9,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum"
......@@ -26,6 +25,11 @@ type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockHash common.Hash) ([]types.GameMetadata, error)
}
type RWClock interface {
SetTime(uint64)
Now() time.Time
}
type gameScheduler interface {
Schedule([]types.GameMetadata, uint64) error
}
......@@ -40,7 +44,7 @@ type claimer interface {
type gameMonitor struct {
logger log.Logger
clock clock.Clock
clock RWClock
source gameSource
scheduler gameScheduler
preimages preimageScheduler
......@@ -67,7 +71,7 @@ func (s *headSource) SubscribeNewHead(ctx context.Context, ch chan<- *ethTypes.H
func newGameMonitor(
logger log.Logger,
cl clock.Clock,
cl RWClock,
source gameSource,
scheduler gameScheduler,
preimages preimageScheduler,
......@@ -140,6 +144,7 @@ func (m *gameMonitor) progressGames(ctx context.Context, blockHash common.Hash,
}
func (m *gameMonitor) onNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
m.clock.SetTime(sig.Time)
if err := m.progressGames(ctx, sig.Hash, sig.Number); err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
......
......@@ -32,16 +32,18 @@ func TestMonitorMinGameTimestamp(t *testing.T) {
t.Run("non-zero game window with zero clock", func(t *testing.T) {
monitor, _, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
monitor.clock = clock.NewDeterministicClock(time.Unix(0, 0))
require.Equal(t, monitor.minGameTimestamp(), uint64(0))
monitor.clock = clock.NewSimpleClock()
monitor.clock.SetTime(0)
require.Equal(t, uint64(0), monitor.minGameTimestamp())
})
t.Run("minimum computed correctly", func(t *testing.T) {
monitor, _, _, _, _ := setupMonitorTest(t, []common.Address{})
monitor.gameWindow = time.Minute
frozen := time.Unix(int64(time.Hour.Seconds()), 0)
monitor.clock = clock.NewDeterministicClock(frozen)
expected := uint64(frozen.Add(-time.Minute).Unix())
monitor.clock = clock.NewSimpleClock()
frozen := uint64(time.Hour.Seconds())
monitor.clock.SetTime(frozen)
expected := uint64(time.Unix(int64(frozen), 0).Add(-time.Minute).Unix())
require.Equal(t, monitor.minGameTimestamp(), expected)
})
}
......@@ -192,7 +194,7 @@ func setupMonitorTest(
mockScheduler := &mockScheduler{}
monitor := newGameMonitor(
logger,
clock.SystemClock,
clock.NewSimpleClock(),
source,
sched,
preimages,
......
......@@ -43,11 +43,11 @@ type Service struct {
preimages *keccak.LargePreimageScheduler
cl clock.Clock
txMgr *txmgr.SimpleTxManager
txSender *sender.TxSender
cl *clock.SimpleClock
loader *loader.GameLoader
claimer *claims.BondClaimScheduler
......@@ -68,9 +68,9 @@ type Service struct {
}
// NewService creates a new Service.
func NewService(ctx context.Context, cl clock.Clock, logger log.Logger, cfg *config.Config) (*Service, error) {
func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Service, error) {
s := &Service{
cl: cl,
cl: clock.NewSimpleClock(),
logger: logger,
metrics: metrics.NewMetrics(),
}
......
......@@ -66,6 +66,8 @@ type DisputeSystem interface {
L1Deployments() *genesis.L1Deployments
RollupCfg() *rollup.Config
L2Genesis() *core.Genesis
AdvanceTime(time.Duration)
}
type FactoryHelper struct {
......
......@@ -5,13 +5,17 @@ import (
"crypto/ecdsa"
"math/big"
"path/filepath"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
......@@ -72,16 +76,55 @@ func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node s
}
}
// ChallengeToFirstGlobalPreimageLoad challenges the supplied execution root claim by inducing a step that requires a preimage to be loaded
type PreimageLoadCheck func(types.TraceProvider, uint64) error
func (g *OutputCannonGameHelper) CreateStepLargePreimageLoadCheck(ctx context.Context, sender common.Address) PreimageLoadCheck {
return func(provider types.TraceProvider, targetTraceIndex uint64) error {
// Fetch the challenge period
challengePeriod := g.ChallengePeriod(ctx)
// Get the preimage data
execDepth := g.ExecDepth(ctx)
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.require.NoError(err)
// Wait until the challenge period has started by checking until the challenge
// period start time is not zero by calling the ChallengePeriodStartTime method
g.WaitForChallengePeriodStart(ctx, sender, preimageData)
challengePeriodStart := g.ChallengePeriodStartTime(ctx, sender, preimageData)
challengePeriodEnd := challengePeriodStart + challengePeriod
// Time travel past the challenge period.
g.system.AdvanceTime(time.Duration(challengePeriod) * time.Second)
g.require.NoError(wait.ForBlockWithTimestamp(ctx, g.system.NodeClient("l1"), challengePeriodEnd))
// Assert that the preimage was indeed loaded by an honest challenger
g.waitForPreimageInOracle(ctx, preimageData)
return nil
}
}
func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context) PreimageLoadCheck {
return func(provider types.TraceProvider, targetTraceIndex uint64) error {
execDepth := g.ExecDepth(ctx)
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.require.NoError(err)
g.waitForPreimageInOracle(ctx, preimageData)
return nil
}
}
// ChallengeToPreimageLoad challenges the supplied execution root claim by inducing a step that requires a preimage to be loaded
// It does this by:
// 1. Identifying the first state transition that loads a global preimage
// 2. Descending the execution game tree to reach the step that loads the preimage
// 3. Asserting that the preimage was indeed loaded by an honest challenger (assuming the preimage is not preloaded)
// This expects an odd execution game depth in order for the honest challenger to step on our leaf claim
func (g *OutputCannonGameHelper) ChallengeToFirstGlobalPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preloadPreimage bool) {
// 1. Identifying the first state transition that loads a global preimage
func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage cannon.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) {
// Identifying the first state transition that loads a global preimage
provider := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey))
targetTraceIndex, err := provider.FindStepReferencingPreimage(ctx, 0)
targetTraceIndex, _, err := provider.FindStep(ctx, 0, preimage)
g.require.NoError(err)
splitDepth := g.SplitDepth(ctx)
......@@ -94,10 +137,10 @@ func (g *OutputCannonGameHelper) ChallengeToFirstGlobalPreimageLoad(ctx context.
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.require.NoError(err)
g.uploadPreimage(ctx, preimageData, challengerKey)
g.require.True(g.preimageExistsInOracle(ctx, preimageData))
g.waitForPreimageInOracle(ctx, preimageData)
}
// 2. Descending the execution game tree to reach the step that loads the preimage
// Descending the execution game tree to reach the step that loads the preimage
bisectTraceIndex := func(claim *ClaimHelper) *ClaimHelper {
execClaimPosition, err := claim.position.RelativeToAncestorAtDepth(splitDepth + 1)
g.require.NoError(err)
......@@ -158,13 +201,14 @@ func (g *OutputCannonGameHelper) ChallengeToFirstGlobalPreimageLoad(ctx context.
g.LogGameData(ctx)
// Initial bisect to put us on defense
claim := bisectTraceIndex(outputRootClaim)
g.DefendClaim(ctx, claim, bisectTraceIndex)
mover := bisectTraceIndex(outputRootClaim)
leafClaim := g.DefendClaim(ctx, mover, bisectTraceIndex, WithoutWaitingForStep())
// 3. Asserts that the preimage was indeed loaded by an honest challenger
_, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex))))
g.require.NoError(err)
g.require.True(g.preimageExistsInOracle(ctx, preimageData))
// Validate that the preimage was loaded correctly
g.require.NoError(preimageCheck(provider, targetTraceIndex))
// Now the preimage is available wait for the step call to succeed.
leafClaim.WaitForCountered(ctx)
}
func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, l2Node string, outputRootClaim *ClaimHelper, options ...challenger.Option) *cannon.CannonTraceProviderForTest {
......@@ -187,34 +231,26 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
prestateProvider := outputs.NewPrestateProvider(ctx, logger, rollupClient, prestateBlock)
outputProvider := outputs.NewTraceProviderFromInputs(logger, prestateProvider, rollupClient, splitDepth, prestateBlock, poststateBlock)
topLeaf := g.getClaim(ctx, int64(outputRootClaim.parentIndex))
topLeafPosition := types.NewPositionFromGIndex(topLeaf.Position)
var pre, post types.Claim
if outputRootClaim.position.TraceIndex(outputRootClaim.Depth()).Cmp(topLeafPosition.TraceIndex(outputRootClaim.Depth())) > 0 {
pre, err = contract.GetClaim(ctx, uint64(outputRootClaim.parentIndex))
g.require.NoError(err, "Failed to construct pre claim")
post, err = contract.GetClaim(ctx, uint64(outputRootClaim.index))
g.require.NoError(err, "Failed to construct post claim")
} else {
post, err = contract.GetClaim(ctx, uint64(outputRootClaim.parentIndex))
postTraceIdx := post.TraceIndex(splitDepth)
if postTraceIdx.Cmp(big.NewInt(0)) == 0 {
pre = types.Claim{}
} else {
g.require.NoError(err, "Failed to construct post claim")
pre, err = contract.GetClaim(ctx, uint64(outputRootClaim.index))
g.require.NoError(err, "Failed to construct pre claim")
}
}
agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post)
g.require.NoError(err, "Failed to fetch proposals")
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, disputed)
g.require.NoError(err, "Failed to fetch local inputs")
localContext := outputs.CreateLocalContext(pre, post)
dir := filepath.Join(cfg.Datadir, "cannon-trace")
subdir := filepath.Join(dir, localContext.Hex())
return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics, cfg, localInputs, subdir, g.MaxDepth(ctx)-splitDepth-1)
selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post)
g.require.NoError(err)
g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber)
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, disputed)
g.require.NoError(err, "Failed to fetch local inputs")
localContext := outputs.CreateLocalContext(pre, post)
dir := filepath.Join(cfg.Datadir, "cannon-trace")
subdir := filepath.Join(dir, localContext.Hex())
return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics, cfg, localInputs, subdir, g.MaxDepth(ctx)-splitDepth-1), nil
})
claims, err := contract.GetAllClaims(ctx)
g.require.NoError(err)
game := types.NewGameState(claims, g.MaxDepth(ctx))
provider, err := selector(ctx, game, game.Claims()[outputRootClaim.parentIndex], outputRootClaim.position)
g.require.NoError(err)
translatingProvider := provider.(*trace.TranslatingProvider)
return translatingProvider.Original().(*cannon.CannonTraceProviderForTest)
}
func (g *OutputCannonGameHelper) defaultChallengerOptions(l2Node string) []challenger.Option {
......
......@@ -10,8 +10,10 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
......@@ -61,10 +63,14 @@ func (g *OutputGameHelper) GenesisBlockNum(ctx context.Context) uint64 {
return blockNum.Uint64()
}
// DisputeLastBlock posts claims from both the honest and dishonest actor to progress the output root part of the game
func (g *OutputGameHelper) DisputeLastBlock(ctx context.Context) *ClaimHelper {
return g.DisputeBlock(ctx, g.L2BlockNum(ctx))
}
// DisputeBlock posts claims from both the honest and dishonest actor to progress the output root part of the game
// 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 {
func (g *OutputGameHelper) DisputeBlock(ctx context.Context, disputeBlockNum uint64) *ClaimHelper {
dishonestValue := g.GetClaimValue(ctx, 0)
correctRootClaim := g.correctOutputRoot(ctx, types.NewPositionFromGIndex(big.NewInt(1)))
rootIsValid := dishonestValue == correctRootClaim
......@@ -73,7 +79,6 @@ func (g *OutputGameHelper) DisputeLastBlock(ctx context.Context) *ClaimHelper {
// 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)
......@@ -355,11 +360,28 @@ type Mover func(parent *ClaimHelper) *ClaimHelper
// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx
type Stepper func(parentClaimIdx int64)
type defendClaimCfg struct {
skipWaitingForStep bool
}
type DefendClaimOpt func(cfg *defendClaimCfg)
func WithoutWaitingForStep() DefendClaimOpt {
return func(cfg *defendClaimCfg) {
cfg.skipWaitingForStep = true
}
}
// DefendClaim uses the supplied Mover to perform moves in an attempt to defend the supplied claim.
// It is assumed that the specified claim is invalid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
func (g *OutputGameHelper) DefendClaim(ctx context.Context, claim *ClaimHelper, performMove Mover) {
// Returns the final leaf claim
func (g *OutputGameHelper) DefendClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, opts ...DefendClaimOpt) *ClaimHelper {
g.t.Logf("Defending claim %v at depth %v", claim.index, claim.Depth())
cfg := &defendClaimCfg{}
for _, opt := range opts {
opt(cfg)
}
for !claim.IsMaxDepth(ctx) {
g.LogGameData(ctx)
// Wait for the challenger to counter
......@@ -370,7 +392,10 @@ func (g *OutputGameHelper) DefendClaim(ctx context.Context, claim *ClaimHelper,
claim = performMove(claim)
}
claim.WaitForCountered(ctx)
if !cfg.skipWaitingForStep {
claim.WaitForCountered(ctx)
}
return claim
}
// ChallengeClaim uses the supplied functions to perform moves and steps in an attempt to challenge the supplied claim.
......@@ -466,11 +491,57 @@ func (g *OutputGameHelper) ResolveClaim(ctx context.Context, claimIdx int64) {
g.require.NoError(err, "ResolveClaim transaction was not OK")
}
func (g *OutputGameHelper) preimageExistsInOracle(ctx context.Context, data *types.PreimageOracleData) bool {
// ChallengePeriod returns the challenge period fetched from the PreimageOracle contract.
// The returned uint64 value is the number of seconds for the challenge period.
func (g *OutputGameHelper) ChallengePeriod(ctx context.Context) uint64 {
oracle := g.oracle(ctx)
exists, err := oracle.GlobalDataExists(ctx, data)
g.require.NoError(err)
return exists
period, err := oracle.ChallengePeriod(ctx)
g.require.NoError(err, "Failed to get challenge period")
return period
}
// WaitForChallengePeriodStart waits for the challenge period to start for a given large preimage claim.
func (g *OutputGameHelper) WaitForChallengePeriodStart(ctx context.Context, sender common.Address, data *types.PreimageOracleData) {
timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
err := wait.For(timedCtx, time.Second, func() (bool, error) {
ctx, cancel := context.WithTimeout(timedCtx, 30*time.Second)
defer cancel()
timestamp := g.ChallengePeriodStartTime(ctx, sender, data)
g.t.Log("Waiting for challenge period start", "timestamp", timestamp, "key", data.OracleKey, "game", g.addr)
return timestamp > 0, nil
})
if err != nil {
g.LogGameData(ctx)
g.require.NoErrorf(err, "Failed to get challenge start period for preimage data %v", data)
}
}
// ChallengePeriodStartTime returns the start time of the challenge period for a given large preimage claim.
// If the returned start time is 0, the challenge period has not started.
func (g *OutputGameHelper) ChallengePeriodStartTime(ctx context.Context, sender common.Address, data *types.PreimageOracleData) uint64 {
oracle := g.oracle(ctx)
uuid := preimages.NewUUID(sender, data)
metadata, err := oracle.GetProposalMetadata(ctx, batching.BlockLatest, keccakTypes.LargePreimageIdent{
Claimant: sender,
UUID: uuid,
})
g.require.NoError(err, "Failed to get proposal metadata")
if len(metadata) == 0 {
return 0
}
return metadata[0].Timestamp
}
func (g *OutputGameHelper) waitForPreimageInOracle(ctx context.Context, data *types.PreimageOracleData) {
timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
oracle := g.oracle(ctx)
err := wait.For(timedCtx, time.Second, func() (bool, error) {
g.t.Logf("Waiting for preimage (%v) to be present in oracle", data.OracleKey)
return oracle.GlobalDataExists(ctx, data)
})
g.require.NoErrorf(err, "Did not find preimage (%v) in oracle", data.OracleKey)
}
func (g *OutputGameHelper) uploadPreimage(ctx context.Context, data *types.PreimageOracleData, privateKey *ecdsa.PrivateKey) {
......
......@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
......@@ -216,6 +217,57 @@ func TestOutputCannonDefendStep(t *testing.T) {
require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx))
}
func TestOutputCannonStepWithLargePreimage(t *testing.T) {
// TODO(client-pod#525): Fix preimage insertion and enable this test
t.Skip("Preimage not being inserted under correct key")
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(outputCannonTestExecutor))
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t, withLargeBatches())
t.Cleanup(sys.Close)
// Send a large l2 transaction and use the receipt block number as the l2 block number for the game
l2Client := sys.NodeClient("sequencer")
// Send a large, difficult to compress L2 transaction. This isn't read by op-program but the batcher has to include
// it in a batch which *is* read.
receipt := op_e2e.SendLargeL2Tx(t, sys.Cfg, l2Client, sys.Cfg.Secrets.Alice, func(opts *op_e2e.TxOpts) {
aliceAddr := sys.Cfg.Secrets.Addresses().Alice
startL2Nonce, err := l2Client.NonceAt(ctx, aliceAddr, nil)
require.NoError(t, err)
opts.Nonce = startL2Nonce
opts.ToAddr = &common.Address{}
// By default, the receipt status must be successful and is checked in the SendL2Tx function
})
l2BlockNumber := receipt.BlockNumber.Uint64()
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// Dispute any block - it will have to read the L1 batches to see if the block is reached
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", l2BlockNumber, common.Hash{0x01, 0xaa})
require.NotNil(t, game)
outputRootClaim := game.DisputeBlock(ctx, l2BlockNumber)
game.LogGameData(ctx)
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Wait for the honest challenger to dispute the outputRootClaim.
// This creates a root of an execution game that we challenge by
// coercing a step at a preimage trace index.
outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx)
game.LogGameData(ctx)
// Now the honest challenger is positioned as the defender of the
// execution game. We then move to challenge it to induce a large preimage load.
sender := sys.Cfg.Secrets.Addresses().Alice
preimageLoadCheck := game.CreateStepLargePreimageLoadCheck(ctx, sender)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, cannon.PreimageLargerThan(18_000), preimageLoadCheck, false)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game.WaitForInactivity(ctx, 10, true)
game.LogGameData(ctx)
require.EqualValues(t, disputegame.StatusChallengerWins, game.Status(ctx))
}
func TestOutputCannonStepWithPreimage(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon, op_e2e.UseExecutor(outputCannonTestExecutor))
testPreimageStep := func(t *testing.T, preloadPreimage bool) {
......@@ -239,7 +291,8 @@ func TestOutputCannonStepWithPreimage(t *testing.T) {
// Now the honest challenger is positioned as the defender of the execution game
// We then move to challenge it to induce a preimage load
game.ChallengeToFirstGlobalPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, preloadPreimage)
preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, cannon.FirstGlobalPreimageLoad(), preimageLoadCheck, preloadPreimage)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
......
......@@ -8,9 +8,22 @@ import (
"github.com/stretchr/testify/require"
)
func startFaultDisputeSystem(t *testing.T) (*op_e2e.System, *ethclient.Client) {
type faultDisputeConfigOpts func(cfg *op_e2e.SystemConfig)
func withLargeBatches() faultDisputeConfigOpts {
return func(cfg *op_e2e.SystemConfig) {
// Allow the batcher to produce really huge calldata transactions.
cfg.BatcherTargetL1TxSizeBytes = 130072 // A bit under the max tx size as per Ethereum spec
cfg.BatcherMaxL1TxSizeBytes = 130072
}
}
func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_e2e.System, *ethclient.Client) {
cfg := op_e2e.DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier")
for _, opt := range opts {
opt(&cfg)
}
cfg.DeployConfig.SequencerWindowSize = 4
cfg.DeployConfig.FinalizationPeriodSeconds = 2
cfg.SupportL1TimeTravel = true
......
......@@ -289,6 +289,14 @@ type System struct {
rollupClients map[string]*sources.RollupClient
}
// AdvanceTime advances the system clock by the given duration.
// If the [System.TimeTravelClock] is nil, this is a no-op.
func (sys *System) AdvanceTime(d time.Duration) {
if sys.TimeTravelClock != nil {
sys.TimeTravelClock.AdvanceTime(d)
}
}
func (sys *System) NodeEndpoint(name string) string {
return selectEndpoint(sys.EthInstances[name])
}
......
......@@ -4,6 +4,7 @@ import (
"context"
"crypto/ecdsa"
"math/big"
"math/rand"
"testing"
"time"
......@@ -11,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
......@@ -77,6 +79,17 @@ func defaultDepositTxOpts(opts *bind.TransactOpts) *DepositTxOpts {
}
}
// SendLargeL2Tx creates and sends a transaction with a large amount of txdata.
func SendLargeL2Tx(t *testing.T, cfg SystemConfig, l2Client *ethclient.Client, privKey *ecdsa.PrivateKey, applyTxOpts TxOptsFn) *types.Receipt {
maxTxDataSize := 131072 // As per the Ethereum spec.
data := testutils.RandomData(rand.New(rand.NewSource(12342)), maxTxDataSize-200) // Leave some buffer
return SendL2Tx(t, cfg, l2Client, privKey, func(opts *TxOpts) {
opts.Data = data
opts.Gas = uint64(2_200_000) // Lots but less than the block limit
applyTxOpts(opts)
})
}
// SendL2Tx creates and sends a transaction.
// The supplied privKey is used to specify the account to send from and the transaction is sent to the supplied l2Client
// Transaction options and expected status can be configured in the applyTxOpts function by modifying the supplied TxOpts
......@@ -92,6 +105,7 @@ func SendL2Tx(t *testing.T, cfg SystemConfig, l2Client *ethclient.Client, privKe
GasTipCap: opts.GasTipCap,
GasFeeCap: opts.GasFeeCap,
Gas: opts.Gas,
Data: opts.Data,
})
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
......
package clock
import (
"sync/atomic"
"time"
)
type SimpleClock struct {
unix atomic.Uint64
}
func NewSimpleClock() *SimpleClock {
return &SimpleClock{}
}
func (c *SimpleClock) SetTime(u uint64) {
c.unix.Store(u)
}
func (c *SimpleClock) Now() time.Time {
return time.Unix(int64(c.unix.Load()), 0)
}
package clock
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestSimpleClock_Now(t *testing.T) {
c := NewSimpleClock()
require.Equal(t, time.Unix(0, 0), c.Now())
expectedTime := uint64(time.Now().Unix())
c.unix = atomic.Uint64{}
c.unix.Store(expectedTime)
require.Equal(t, time.Unix(int64(expectedTime), 0), c.Now())
}
func TestSimpleClock_SetTime(t *testing.T) {
tests := []struct {
name string
expectedTime int64
}{
{
name: "SetZeroTime",
expectedTime: 0,
},
{
name: "SetZeroUnixTime",
expectedTime: time.Unix(0, 0).Unix(),
},
{
name: "SetCurrentTime",
expectedTime: time.Now().Unix(),
},
{
name: "SetFutureTime",
expectedTime: time.Now().Add(time.Hour).Unix(),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
c := NewSimpleClock()
c.SetTime(uint64(test.expectedTime))
require.Equal(t, time.Unix(test.expectedTime, 0), c.Now())
})
}
}
......@@ -51,11 +51,11 @@
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 44,
"faultGameMaxDuration": 1200,
"faultGameMaxDuration": 2400,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameSplitDepth": 14,
"preimageOracleMinProposalSize": 18000,
"preimageOracleChallengePeriod": 86400,
"preimageOracleChallengePeriod": 120,
"preimageOracleCancunActivationTimestamp": 0
}
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