From 5c6bac01aaf093e449904e40f620ca5762be905d Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Thu, 23 Jul 2026 16:45:07 -0400 Subject: [PATCH 1/4] remove caff node --- espresso/cli.go | 48 --- .../10_soft_confirmation_integrity_test.go | 147 ++----- .../12_enforce_majority_rule_test.go | 20 +- espresso/environment/13_dispute_game_test.go | 12 - .../environment/14_batcher_fallback_test.go | 70 ++-- .../environment/1_espresso_benchmark_test.go | 153 ------- .../environment/2_espresso_liveness_test.go | 350 ---------------- .../3_1_espresso_caff_node_test.go | 125 ------ .../3_2_espresso_deterministic_state_test.go | 116 ++---- .../3_3_fast_derivation_and_caff_node_test.go | 127 ------ .../environment/7_stateless_batcher_test.go | 16 +- espresso/environment/8_reorg_test.go | 110 +---- espresso/environment/benchmark/benchmarker.go | 378 ------------------ espresso/environment/benchmark/channel.go | 21 - espresso/environment/benchmark/context.go | 49 --- espresso/environment/benchmark/metrics.go | 318 --------------- espresso/environment/benchmark/workers.go | 258 ------------ espresso/environment/espresso_caff_node.go | 164 -------- .../environment/espresso_dev_net_launcher.go | 4 + .../optitmism_espresso_test_helpers.go | 14 +- espresso/ethclient.go | 32 -- espresso/interface.go | 77 ---- op-batcher/batcher/driver.go | 5 +- op-batcher/batcher/espresso.go | 7 +- op-batcher/batcher/espresso_driver.go | 12 +- op-batcher/batcher/espresso_service.go | 6 +- op-e2e/e2eutils/opnode/opnode.go | 9 - op-node/node/node.go | 6 - op-node/rollup/derive/attributes_queue.go | 19 +- op-node/rollup/derive/espresso_batch.go | 138 ------- op-node/rollup/derive/espresso_batch_test.go | 237 ----------- .../rollup/derive/espresso_caff_streamer.go | 151 ------- .../derive/espresso_caff_streamer_stub.go | 24 -- op-node/rollup/derive/pipeline.go | 2 +- .../rollup/derive/test/transaction_test.go | 63 --- op-node/rollup/driver/interfaces.go | 2 - op-node/rollup/espresso_config.go | 42 -- op-node/rollup/espresso_types.go | 38 +- op-node/rollup/types.go | 6 +- op-node/service.go | 14 - 40 files changed, 151 insertions(+), 3239 deletions(-) delete mode 100644 espresso/environment/1_espresso_benchmark_test.go delete mode 100644 espresso/environment/3_1_espresso_caff_node_test.go delete mode 100644 espresso/environment/3_3_fast_derivation_and_caff_node_test.go delete mode 100644 espresso/environment/benchmark/benchmarker.go delete mode 100644 espresso/environment/benchmark/channel.go delete mode 100644 espresso/environment/benchmark/context.go delete mode 100644 espresso/environment/benchmark/metrics.go delete mode 100644 espresso/environment/benchmark/workers.go delete mode 100644 espresso/environment/espresso_caff_node.go delete mode 100644 espresso/interface.go delete mode 100644 op-node/rollup/derive/espresso_batch.go delete mode 100644 op-node/rollup/derive/espresso_batch_test.go delete mode 100644 op-node/rollup/derive/espresso_caff_streamer.go delete mode 100644 op-node/rollup/derive/espresso_caff_streamer_stub.go delete mode 100644 op-node/rollup/derive/test/transaction_test.go diff --git a/espresso/cli.go b/espresso/cli.go index 56db8291199..d7980c0f24e 100644 --- a/espresso/cli.go +++ b/espresso/cli.go @@ -6,16 +6,10 @@ import ( "strings" "time" - op "github.com/EspressoSystems/espresso-streamers/op" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" - - espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" - espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" ) // espressoFlags returns the flag names for espresso @@ -302,45 +296,3 @@ func ReadCLIConfig(c *cli.Context) CLIConfig { return config } - -func BatchStreamerFromCLIConfig[B op.Batch]( - cfg CLIConfig, - log log.Logger, - unmarshalBatch func([]byte) (*B, error), -) (*op.BatchStreamer[B], error) { - if !cfg.Enabled { - return nil, fmt.Errorf("espresso is not enabled") - } - - l1Client, err := ethclient.Dial(cfg.L1URL) - if err != nil { - return nil, fmt.Errorf("failed to dial L1 RPC at %s: %w", cfg.L1URL, err) - } - - RollupL1Client, err := ethclient.Dial(cfg.RollupL1URL) - if err != nil { - return nil, fmt.Errorf("failed to dial Rollup L1 RPC at %s: %w", cfg.RollupL1URL, err) - } - - urlZero := cfg.QueryServiceURLs[0] - espressoClient := espressoClient.NewClient(urlZero) - - espressoLightClient, err := espressoLightClient.NewLightclientCaller(cfg.LightClientAddr, l1Client) - if err != nil { - return nil, fmt.Errorf("failed to create Espresso light client") - } - - return op.NewEspressoStreamer( - cfg.Namespace, - NewAdaptL1BlockRefClient(l1Client), - NewAdaptL1BlockRefClient(RollupL1Client), - espressoClient, - espressoLightClient, - log, - unmarshalBatch, - cfg.CaffeinationHeightEspresso, - cfg.CaffeinationHeightL2, - cfg.BatchAuthenticatorAddr, - false, - ) -} diff --git a/espresso/environment/10_soft_confirmation_integrity_test.go b/espresso/environment/10_soft_confirmation_integrity_test.go index 7a1f2db0d94..4fb3f0960d7 100644 --- a/espresso/environment/10_soft_confirmation_integrity_test.go +++ b/espresso/environment/10_soft_confirmation_integrity_test.go @@ -4,7 +4,7 @@ // Rollup when compared against the confirmations being provided by // Espresso/HotShot. The derivation from the L2 / L1 should not be compromised // or result in different results than the derivation provided by the -// Caff Node. +// sequencer. // // Assumption: The rollup sequencer is correct, online, and honest. It // produces a valid sequence of rollup blocks every few seconds or faster, @@ -25,17 +25,16 @@ import ( crypto_rand "crypto/rand" "encoding/hex" "math/big" - "net" - "net/url" "testing" "time" espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types/common" + "github.com/EspressoSystems/espresso-streamers/op/derivation" env "github.com/ethereum-optimism/optimism/espresso/environment" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" op_crypto "github.com/ethereum-optimism/optimism/op-service/crypto" op_signer "github.com/ethereum-optimism/optimism/op-service/signer" ethereum "github.com/ethereum/go-ethereum" @@ -118,11 +117,10 @@ func setupHeaderStreamSubscription(ctx context.Context, t *testing.T, cli *ethcl } // setupHeaderStreamSubscriptions sets up subscriptions to the new head -// event on the given Ethereum clients (sequencer, verifier, and caff). -func setupHeaderStreamSubscriptions(ctx context.Context, t *testing.T, l2Seq, l2Verif, caff *ethclient.Client) ( +// event on the given Ethereum clients (sequencer and verifier). +func setupHeaderStreamSubscriptions(ctx context.Context, t *testing.T, l2Seq, l2Verif *ethclient.Client) ( seqStream timestampedHeaderStream, verifStream timestampedHeaderStream, - caffStream timestampedHeaderStream, ) { seqStream, err := setupHeaderStreamSubscription(ctx, t, l2Seq) @@ -135,18 +133,13 @@ func setupHeaderStreamSubscriptions(ctx context.Context, t *testing.T, l2Seq, l2 t.Fatalf("Failed to subscribe to verifier new head:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) } - caffStream, err = setupHeaderStreamSubscription(ctx, t, caff) - if have, want := err, error(nil); have != want { - t.Fatalf("Failed to subscribe to caff new head:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - return seqStream, verifStream, caffStream + return seqStream, verifStream } // nextStreamEntries is a helper function that retrieves the next entries -// from the sequencer, verifier, and caff streams. -func nextStreamEntries[T any](ctx context.Context, seqCh, verifCh, caffCh <-chan messageWithTimestamp[T]) ( - seqHeader, verifHeader, caffHeader messageWithTimestamp[T], +// from the sequencer and verifier streams. +func nextStreamEntries[T any](ctx context.Context, seqCh, verifCh <-chan messageWithTimestamp[T]) ( + seqHeader, verifHeader messageWithTimestamp[T], ) { select { case <-ctx.Done(): @@ -161,13 +154,7 @@ func nextStreamEntries[T any](ctx context.Context, seqCh, verifCh, caffCh <-chan case verifHeader = <-verifCh: } - select { - case <-ctx.Done(): - return - case caffHeader = <-caffCh: - } - - return seqHeader, verifHeader, caffHeader + return seqHeader, verifHeader } // advanceStreamToHeight is a helper function that advances the @@ -195,29 +182,26 @@ func advanceStreamToHeight( } // EnsureStreamsAreSynced is a helper function that ensures that the -// sequencer, verifier, and caff streams are all at the same block height. +// sequencer and verifier streams are all at the same block height. // It does this by advancing each stream to the largest block number -// among the three streams. +// among the two streams. // // Advancing the streams to the same block height is necessary as it ensures -// that we are comparing the same block across all three streams. +// that we are comparing the same block across both streams. // // Advancing in this way does skip over existing blocks, so there is a // potential for missing blocks in this way. func ensureStreamsAreSynced( ctx context.Context, - seqStream, verifStream, caffStream timestampedHeaderStream, + seqStream, verifStream timestampedHeaderStream, ) { - seqHeader, verifHeader, caffHeader := nextStreamEntries(ctx, seqStream.ch, verifStream.ch, caffStream.ch) + seqHeader, verifHeader := nextStreamEntries(ctx, seqStream.ch, verifStream.ch) - // Determine the largest block from the three streams + // Determine the largest block from the two streams var largestNumber = seqHeader.entry.Number if verifHeader.entry.Number.Cmp(largestNumber) > 0 { largestNumber = verifHeader.entry.Number } - if caffHeader.entry.Number.Cmp(largestNumber) > 0 { - largestNumber = caffHeader.entry.Number - } // Now advance all of these streams so that the last entry consumed // all point to the same block number. @@ -225,18 +209,17 @@ func ensureStreamsAreSynced( // Advance the Sequencer Stream advanceStreamToHeight(ctx, seqStream, seqHeader, largestNumber) advanceStreamToHeight(ctx, verifStream, verifHeader, largestNumber) - advanceStreamToHeight(ctx, caffStream, caffHeader, largestNumber) } // verifyStreamSequenceForNextN is a helper function that verifies -// the sequence of blocks being produced by the sequencer, verifier, and caff +// the sequence of blocks being produced by the sequencer and verifier // streams all match for the next N blocks. // // It does this by waiting for the next entry from each stream and // comparing their header values. // -// The sequence being consumed should be ordered, and the same across all -// three streams. +// The sequence being consumed should be ordered, and the same across both +// streams. // // The streams are assumed to be synced before this function is called. // This means that they should be at the same block height before this @@ -245,16 +228,16 @@ func ensureStreamsAreSynced( func verifyStreamSequenceForNextN( ctx context.Context, t *testing.T, - seqStream, verifStream, caffStream timestampedHeaderStream, + seqStream, verifStream timestampedHeaderStream, count int, ) { for i := 0; i < count; i++ { // The easiest way to verify this is to just wait for each of these // streams entries in turn, then compare their header hashes. - seqHeader, verifHeader, caffHeader := nextStreamEntries(ctx, seqStream.ch, verifStream.ch, caffStream.ch) + seqHeader, verifHeader := nextStreamEntries(ctx, seqStream.ch, verifStream.ch) - // Alright, we should have all three next headers now. + // Alright, we should have both next headers now. // Let's compare them to make sure they are the same. select { case <-ctx.Done(): @@ -267,17 +250,6 @@ func verifyStreamSequenceForNextN( t.Fatalf("Sequencer and Verifier headers do not match:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) return } - - if have, want := seqHeader.entry.Hash(), caffHeader.entry.Hash(); have.Cmp(want) != 0 { - t.Fatalf("Sequencer and Caff headers do not match:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - return - } - - // This check should be redundant. - if have, want := verifHeader.entry.Hash(), caffHeader.entry.Hash(); have.Cmp(want) != 0 { - t.Fatalf("Verifier and Caff headers do not match:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - return - } } } @@ -317,8 +289,8 @@ func submitRandomDataToSequencerNamespace(ctx context.Context, espCli espressoCl // constructing a block with a deposit transaction. It uses the latest // block from the sequencer to create a new block with a deposit // transaction. The block is then converted to an Espresso batch using -// the derive.BlockToEspressoBatch function. -func createMaliciousEspressoBatch(ctx context.Context, cli *ethclient.Client, rollupCfg *rollup.Config) (*derive.EspressoBatch, error) { +// the derivation.BlockToEspressoBatch function. +func createMaliciousEspressoBatch(ctx context.Context, cli *ethclient.Client, rollupCfg *rollup.Config) (*derivation.EspressoBatch, error) { // / Determine what the latest block in the sequencer is, so we can // hope to create a valid transaction, to get something out of it. latestBlock, err := cli.BlockByNumber(ctx, nil) @@ -353,7 +325,7 @@ func createMaliciousEspressoBatch(ctx context.Context, cli *ethclient.Client, ro } block := geth_types.NewBlockWithHeader(header).WithBody(body) - return derive.BlockToEspressoBatch(rollupCfg, block) + return derivation.BlockToEspressoBatch(rollupCfg, block) } // SUBMIT_VALID_DATA_WITH_WRONG_SIGNATURE_INTERVAlL is the interval / frequency @@ -474,15 +446,14 @@ func submitValidDataWithRandomSignature( } // TestSequencerFeedConsistency is a test that ensures that the sequence of -// blocks being produced by the feeds from the Sequencer, the Caff Node, and -// another L2 Verifier are consistent with each other. +// blocks being produced by the feeds from the Sequencer and another L2 +// Verifier are consistent with each other. // // The criteria / goal of this test are outlined by the following requirement: // -// Run the rollup and subscribe to the sequencer feed, a feed which derives -// from Espresso, and a feed which derives the finalized block sequence from -// L1. All of these should yield the same blocks in the same order (but at -// different times). +// Run the rollup and subscribe to the sequencer feed and a feed which derives +// the finalized block sequence from L1. All of these should yield the same +// blocks in the same order (but at different times). func TestSequencerFeedConsistency(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -498,47 +469,36 @@ func TestSequencerFeedConsistency(t *testing.T) { defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - l2Seq := system.NodeClient(e2esys.RoleSeq) l2Verif := system.NodeClient(e2esys.RoleVerif) - caff := system.NodeClient(env.RoleCaffNode) - seqStream, verifStream, caffStream := setupHeaderStreamSubscriptions(ctx, t, l2Seq, l2Verif, caff) + seqStream, verifStream := setupHeaderStreamSubscriptions(ctx, t, l2Seq, l2Verif) defer seqStream.sub.Unsubscribe() defer verifStream.sub.Unsubscribe() - defer caffStream.sub.Unsubscribe() // We need to sync these streams up. We created them at different points // in their life times. so we need to wait for them all to be at the same // block height before we start comparing them. // // It is most likely going to be the case that the sequencer is ahead of - // the verifier and the caff node. We would expect the caff node to be - // ahead of the verifier, but we will play it safe, and just make no - // assumptions by grabbing the largest block - ensureStreamsAreSynced(ctx, seqStream, verifStream, caffStream) + // the verifier. We will play it safe, and just make no assumptions by + // grabbing the largest block + ensureStreamsAreSynced(ctx, seqStream, verifStream) // Let's verify that these streams are producing the same blocks // in the same order. We will do this by waiting for a few blocks to - verifyStreamSequenceForNextN(ctx, t, seqStream, verifStream, caffStream, 100) + verifyStreamSequenceForNextN(ctx, t, seqStream, verifStream, 100) } // TestSequencerFeedConsistencyWithAttackOnEspresso is a test that expands // upon the previous test by introducing attacks against Espresso with the // specific goal of arriving at a state where the Espresso feed is producing -// different blocks than the sequencer and the caff node, for a variety of -// different potential reasons. +// different blocks than the sequencer, for a variety of different potential +// reasons. // // These attacks are designed to cover some different use cases, and may // reflect attempts of third parties to attack or manipulate the data being -// consumed by the Caff Node for individual gain, or disruption. +// consumed for individual gain, or disruption. // // The criteria / goal of this test are outlined by the following requirement: // Consider rollup-specific adversarial behavior which could break sequencer @@ -560,28 +520,9 @@ func TestSequencerFeedConsistencyWithAttackOnEspresso(t *testing.T) { defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - _, port, err := net.SplitHostPort(espressoDevNode.SequencerPort()) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to parse sequencer port URL:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - espressoSequencerURL := url.URL{ - Scheme: "http", - Host: net.JoinHostPort("localhost", port), - Path: "/", - } - l2Seq := system.NodeClient(e2esys.RoleSeq) - espCli := espressoClient.NewClient(espressoSequencerURL.String()) - namespace := system.RollupConfig.L2ChainID.Uint64() + espCli := espressoDevNode.Client() + namespace := bigs.Uint64Strict(system.RollupConfig.L2ChainID) // Attack Espresso Integrity by Submitting Garbage Data to the Same // namespace as the Sequencer's namespace. @@ -597,16 +538,14 @@ func TestSequencerFeedConsistencyWithAttackOnEspresso(t *testing.T) { go submitValidDataWithRandomSignature(ctx, system.RollupConfig, l2Seq, espCli, namespace) l2Verif := system.NodeClient(e2esys.RoleVerif) - caff := system.NodeClient(env.RoleCaffNode) - seqStream, verifStream, caffStream := setupHeaderStreamSubscriptions(ctx, t, l2Seq, l2Verif, caff) + seqStream, verifStream := setupHeaderStreamSubscriptions(ctx, t, l2Seq, l2Verif) defer seqStream.sub.Unsubscribe() defer verifStream.sub.Unsubscribe() - defer caffStream.sub.Unsubscribe() // Sync the Streams to the same block height - ensureStreamsAreSynced(ctx, seqStream, verifStream, caffStream) + ensureStreamsAreSynced(ctx, seqStream, verifStream) // Verify the sequence of blocks being produced. - verifyStreamSequenceForNextN(ctx, t, seqStream, verifStream, caffStream, 100) + verifyStreamSequenceForNextN(ctx, t, seqStream, verifStream, 100) } diff --git a/espresso/environment/12_enforce_majority_rule_test.go b/espresso/environment/12_enforce_majority_rule_test.go index 61a3410b037..70910afd03d 100644 --- a/espresso/environment/12_enforce_majority_rule_test.go +++ b/espresso/environment/12_enforce_majority_rule_test.go @@ -53,30 +53,14 @@ func runWithMultiClient(t *testing.T, numGoodUrls int, numBadUrls int, expectedE t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) } - caffNode, err := env.LaunchCaffNode(t, system, devNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } + defer env.Stop(t, system) + defer env.Stop(t, devNode) l2Verif := system.NodeClient(e2esys.RoleVerif) - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - caffClient := system.NodeClient(e2esys.RoleVerif) - // Wait for batcher to start advancing L2 head blockNumber := int64(2) - // Check the caff node can/cannot make progress - _, err = geth.WaitForBlockToBeSafe(big.NewInt(blockNumber), caffClient, 60*time.Second) - - if expectedError { - require.Error(t, err, "The L2 should not be progressing") - } else { - require.NoError(t, err, "The L2 should be progressing") - } - // Check the l2Verif node can/cannot make progress _, err = geth.WaitForBlockToBeSafe(big.NewInt(blockNumber), l2Verif, 60*time.Second) if expectedError { diff --git a/espresso/environment/13_dispute_game_test.go b/espresso/environment/13_dispute_game_test.go index 449a629da91..ae3e2cecfb4 100644 --- a/espresso/environment/13_dispute_game_test.go +++ b/espresso/environment/13_dispute_game_test.go @@ -60,18 +60,6 @@ func TestOutputAlphabetGameWithEspresso_ChallengerWins(t *testing.T) { } }() - // Launch a Caff Node and check it can still make progress - caffNode, err := env.LaunchCaffNode(t, sys, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - caffClient := sys.NodeClient(env.RoleCaffNode) - // Make sure Caff Node still make progress - require.NoError(t, wait.ForNextBlock(ctx, caffClient)) - // All the following testing code is pasted from `TestOutputAlphabetGame_ChallengerWins` in `op-e2e/faultproofs/output_alphabet_test.go` disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) game := disputeGameFactory.StartOutputAlphabetGame(ctx, "sequencer", 3, common.Hash{0xff}) diff --git a/espresso/environment/14_batcher_fallback_test.go b/espresso/environment/14_batcher_fallback_test.go index 916d4794061..8e2f775845e 100644 --- a/espresso/environment/14_batcher_fallback_test.go +++ b/espresso/environment/14_batcher_fallback_test.go @@ -6,21 +6,19 @@ import ( "errors" "fmt" "io" - "math/big" "sync" "testing" "time" - espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" "github.com/ethereum-optimism/optimism/espresso/bindings" env "github.com/ethereum-optimism/optimism/espresso/environment" "github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-node/config" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive/params" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -63,10 +61,8 @@ func waitForRollupToMovePastL1Block(ctx context.Context, rollupCli *sources.Roll // sends switch action to the Batch Authenticator contract and switches to the // fallback batcher, verifies transactions continue to go through. Next, it switches // back to the Espresso batcher by restarting it with proper caffeination heights -// (both Espresso and L2 heights set to ensure correct sync points). Finally, it -// launches a Caff node with the same caffeination heights and verifies it -// derives the same chain state as the verifier by comparing block hashes at the -// same height. +// (both Espresso and L2 heights set to ensure correct sync points) and verifies +// that transactions continue to go through. func TestBatcherSwitching(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -77,15 +73,26 @@ func TestBatcherSwitching(t *testing.T) { // with parameters tweaked. batcherConfig := &batcher.CLIConfig{} // L1FinalizedDistance(0) to avoid long delays after batcher switch. + // The batcher-config options run before GetBatcherConfig so the snapshot it + // takes into batcherConfig reflects them. Small frames + a long channel + // duration force multi-frame channels split across L1 blocks. system, espressoDevNode, err := launcher.StartE2eDevnet(ctx, t, env.WithL1FinalizedDistance(0), env.WithSequencerUseFinalized(true), + env.WithBatcherTargetNumFrames(10), + env.WithBatcherMaxL1TxSize(250), + env.WithBatcherMaxChannelDuration(1000), + // Unbounded pending L1 txs so the Espresso auth+batch pairs (routed + // through the ordered txmgr queue) publish concurrently instead of + // one-per-L1-block; otherwise L1 data availability lags far behind the + // sequencer and the verifier cannot derive recent blocks within the + // test's confirmation windows. + env.WithBatcherMaxPendingTransactions(0), env.GetBatcherConfig(batcherConfig)) require.NoError(t, err) l1Client := system.NodeClient(e2esys.RoleL1) - verifClient := system.NodeClient(e2esys.RoleVerif) - espClient := espressoClient.NewClient(espressoDevNode.EspressoUrls()[0]) + espClient := espressoDevNode.Client() deployerTransactor, err := bind.NewKeyedTransactorWithChainID(system.Config().Secrets.Deployer, system.Cfg.L1ChainIDBig()) require.NoError(t, err) @@ -106,7 +113,7 @@ func TestBatcherSwitching(t *testing.T) { err = system.BatchSubmitter.TestDriver().StopBatchSubmitting(ctx) require.NoError(t, err) - // Switch active batcher to the fallback batcher + // Switch active batcher to the fallback (non-Espresso) path tx, err := batchAuthenticator.SetActiveIsEspresso(deployerTransactor, false) require.NoError(t, err) _, err = wait.ForReceiptOK(ctx, l1Client, tx.Hash()) @@ -123,14 +130,14 @@ func TestBatcherSwitching(t *testing.T) { err = system.FallbackBatchSubmitter.TestDriver().StopBatchSubmitting(ctx) require.NoError(t, err) - // Switch batcher back to the "TEE" batcher + // Switch batcher back to the "TEE" (Espresso) batcher tx, err = batchAuthenticator.SetActiveIsEspresso(deployerTransactor, true) require.NoError(t, err) switchReceipt, err := wait.ForReceiptOK(ctx, l1Client, tx.Hash()) require.NoError(t, err) // Give things time to settle - l2Height, err := waitForRollupToMovePastL1Block(ctx, system.RollupClient(e2esys.RoleVerif), switchReceipt.BlockNumber.Uint64()) + l2Height, err := waitForRollupToMovePastL1Block(ctx, system.RollupClient(e2esys.RoleVerif), bigs.Uint64Strict(switchReceipt.BlockNumber)) require.NoError(t, err) espHeight, err := espClient.FetchLatestBlockHeight(ctx) @@ -142,10 +149,16 @@ func TestBatcherSwitching(t *testing.T) { batcherConfig.MaxChannelDuration = 10 batcherConfig.TargetNumFrames = 1 batcherConfig.MaxL1TxSize = 120_000 - batcherConfig.Espresso.CaffeinationHeightEspresso = espHeight + // Caffeinate at espHeight-1 (last already-sealed block) so the streamer reads from + // espHeight inclusive and picks up the batches this batcher re-submits there. + batcherConfig.Espresso.CaffeinationHeightEspresso = espHeight - 1 batcherConfig.Espresso.CaffeinationHeightL2 = l2Height batcherCtx, cancelBatcher := context.WithCancelCause(ctx) defer cancelBatcher(nil) + // Unlike the upstream mock-client harness, this repo runs against a real + // Espresso dev node: the replacement batcher builds its own client from + // batcherConfig's QueryServiceURLs (snapshotted from the running system), + // so no client override is needed. newBatcher, err := batcher.BatcherServiceFromCLIConfig(batcherCtx, cancelBatcher, "0.0.1", batcherConfig, system.BatchSubmitter.Log) require.NoError(t, err) err = newBatcher.Start(batcherCtx) @@ -153,28 +166,6 @@ func TestBatcherSwitching(t *testing.T) { // Everything should still work (use longer timeout after batcher switch) env.RunSimpleL2BurnWithTimeout(ctx, t, system, 5*time.Minute) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode, func(c *config.Config) { - c.Rollup.CaffNodeConfig.CaffeinationHeightEspresso = espHeight - c.Rollup.CaffNodeConfig.CaffeinationHeightL2 = l2Height - }) - require.NoError(t, err) - defer env.Stop(t, caffNode) - - caffClient := system.NodeClient(env.RoleCaffNode) - - verifHeight, err := verifClient.BlockNumber(ctx) - require.NoError(t, err) - verifBlock, err := verifClient.BlockByNumber(ctx, new(big.Int).SetUint64(verifHeight)) - require.NoError(t, err) - - err = wait.ForBlock(ctx, caffClient, verifHeight) - require.NoError(t, err) - - caffBlock, err := caffClient.BlockByNumber(ctx, new(big.Int).SetUint64(verifHeight)) - require.NoError(t, err) - - require.Equal(t, verifBlock.Hash(), caffBlock.Hash()) } // TxManagerIntercept is a txmgr.TxManager that wraps another txmgr.TxManager @@ -483,6 +474,13 @@ func TestFallbackMechanismIntegrationTestChannelNotClosed(t *testing.T) { // Setting this to 0 explicitly disables the feature, and as a result // it will only send the data when the previous conditions are met. env.WithBatcherMaxChannelDuration(0), + + // Unbounded pending L1 txs so the Espresso auth+batch pairs (routed + // through the ordered txmgr queue) publish concurrently instead of + // one-per-L1-block; otherwise L1 data availability lags far behind the + // sequencer and the verifier cannot derive recent blocks within the + // test's confirmation windows. + env.WithBatcherMaxPendingTransactions(0), ) require.NoError(t, err) @@ -562,7 +560,7 @@ func TestFallbackMechanismIntegrationTestChannelNotClosed(t *testing.T) { err = system.BatchSubmitter.TestDriver().StopBatchSubmitting(ctx) require.NoError(t, err) - // Switch active batcher to the fallback batcher + // Switch active batcher options, err := bind.NewKeyedTransactorWithChainID(system.Config().Secrets.Deployer, system.Cfg.L1ChainIDBig()) require.NoError(t, err) diff --git a/espresso/environment/1_espresso_benchmark_test.go b/espresso/environment/1_espresso_benchmark_test.go deleted file mode 100644 index 6683717f9cb..00000000000 --- a/espresso/environment/1_espresso_benchmark_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package environment_test - -import ( - "context" - "math/big" - "testing" - "time" - - env "github.com/ethereum-optimism/optimism/espresso/environment" - "github.com/ethereum-optimism/optimism/espresso/environment/benchmark" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - geth_types "github.com/ethereum/go-ethereum/core/types" -) - -// TestE2eDevnetWithEspressoFastConfirmationStability is a test that tests -// the benchmarking setup of the Espresso Caff Node's performance versus the -// L2 Verifier derived from the L1. -// -// This test is designed to evaluate Espresso's impact while under load on -// the Optimism stack. The point of this test is to ensure that, even under -// heavy load, the Espresso Caff Node can maintain its performance and -// not introduce significant delays in the confirmation process. -// -// This test spins up the E2E dev net with the espresso-dev-node and the -// Caff Node. It then runs a benchmarks that submits transactions to the -// L2 Sequencer and observes the time it takes to reach each stage of the -// confirmation process. The test will run for a couple of minutes and -// then check the statistics to ensure that the performance is within -// acceptable limits. -// -// The acceptance criteria of this test is stated to be that the time -// taken between each stage of this process should not change significantly -// over time. -// -// It is difficult to meet this criteria as it is stated with vague terms -// and with the intention of a much longer runtime duration than what we'd -// want when evaluating this consistency. -// -// Instead this test will Run for 2 minutes, with Block Times set to the -// values of the typical L1 and L2 block times. It will place a load -// upon it, and it will check the standard deviation of the time taken -// between each stage of the confirmation process in order to make sure -// that they do not exceed a "reasonable" value. -// -// For the purposes of this test the "reasonable" value is defined to -// be 2 seconds. -func TestE2eDevnetWithEspressoFastConfirmationStability(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - launcher := new(env.EspressoDevNodeLauncherDocker) - system, espressoDevNode, err := launcher.StartE2eDevnet( - ctx, - t, - env.WithSequencerUseFinalized(true), - env.WithL1BlockTime(12*time.Second), - env.WithL2BlockTime(2*time.Second), - ) - - // Signal the testnet to shut down - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - defer env.Stop(t, system) - defer env.Stop(t, espressoDevNode) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - keys := system.Cfg.Secrets - addresses := keys.Addresses() - - signer := geth_types.LatestSignerForChainID(system.Cfg.L2ChainIDBig()) - - // Submit Transactions to the Sequencer - bencher := benchmark.CreateBenchmarker( - ctx, - benchmark.WithSeqClient(system.NodeClient(e2esys.RoleSeq)), - benchmark.WithCaffClient(system.NodeClient(env.RoleCaffNode)), - benchmark.WithVerifyClient(system.NodeClient(e2esys.RoleVerif)), - benchmark.AddSubmitter(benchmark.BenchmarkSubmitterConfig{ - Interval: 100 * time.Millisecond, - To: &addresses.Bob, - Value: big.NewInt(1), - Signer: signer, - ChainID: system.RollupConfig.L2ChainID, - Key: keys.Alice, - }), - benchmark.AddSubmitter(benchmark.BenchmarkSubmitterConfig{ - Interval: 100 * time.Millisecond, - To: &addresses.Mallory, - Value: big.NewInt(1), - Signer: signer, - ChainID: system.RollupConfig.L2ChainID, - Key: keys.Bob, - }), - benchmark.AddSubmitter(benchmark.BenchmarkSubmitterConfig{ - Interval: 100 * time.Millisecond, - To: &addresses.Alice, - Value: big.NewInt(1), - Signer: signer, - ChainID: system.RollupConfig.L2ChainID, - Key: keys.Mallory, - }), - ) - - // Alright, let's run the benchmark for a couple of minutes - - { - ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) - defer cancel() - - stats, err := bencher.RunWithContext(ctx) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to run benchmark:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Let's check the statistics - metrics := benchmark.ComputeRawMetricsStatistics(stats) - - if have, want := metrics.SubmittedToReceipt.Count, 0; have <= want { - t.Errorf("expected to have a positive count for receipts received:\nhave:\n\t\"%v\"\nwant:\n\t> \"%v\"\n", have, want) - } - - if have, want := metrics.ReceiptToCaff.Count, 0; have <= want { - t.Errorf("expected to have a positive count for caff headers:\nhave:\n\t\"%v\"\nwant:\n\t> \"%v\"\n", have, want) - } - - if have, want := metrics.ReceiptToVerify.Count, 0; have <= want { - t.Errorf("expected to have a positive count for verify headers:\nhave:\n\t\"%v\"\nwant:\n\t> \"%v\"\n", have, want) - } - - // We do not expect a signification amount of variance or std deviation - if have, want := metrics.SubmittedToReceipt.StdDev, 3*time.Second; have > want { - t.Errorf("expected a small amount of variance in the submitted to receipt time:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := metrics.ReceiptToCaff.StdDev, 3*time.Second; have > want { - t.Errorf("expected a small amount of variance in the receipt to caff time:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := metrics.ReceiptToVerify.StdDev, 3*time.Second; have > want { - t.Errorf("expected a small amount of variance in the receipt to L1 time:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - } - -} diff --git a/espresso/environment/2_espresso_liveness_test.go b/espresso/environment/2_espresso_liveness_test.go index 4b83fdad662..bb845afb4a8 100644 --- a/espresso/environment/2_espresso_liveness_test.go +++ b/espresso/environment/2_espresso_liveness_test.go @@ -2,29 +2,15 @@ package environment_test import ( "context" - "log/slog" "math/big" "math/rand" - "sync" "testing" - "time" - espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" - espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" - op "github.com/EspressoSystems/espresso-streamers/op" - "github.com/ethereum-optimism/optimism/espresso" env "github.com/ethereum-optimism/optimism/espresso/environment" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" geth_types "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" ) // TestE2eDevnetWithEspressoDegradedLiveness is a test that checks that @@ -138,339 +124,3 @@ func TestE2eDevnetWithEspressoDegradedLiveness(t *testing.T) { } } } - -// TestE2eDevnetWithEspressoDegradedLivenessViaCaffNode is a test that -// checks that Espresso will return fast confirmations even when in a -// degraded state. -// -// The criteria for this test is as follows: -// -// Requirement: Liveness: -// The rollup should continue to run, [to] post Espresso confirmations -// within 17 seconds of each rollup block produced by the sequencer. -// -// As a result, this test will submit a number of transactions to the sequencer, -// while also consuming the Espresso stream of blocks utilizing the Espresso -// streamer. We **SHOULD** be able to match up the transactions submitted to -// the blocks being produced by the Espresso Streamer, and the time it takes -// from transaction submission to receiving the Block that contains that same -// transaction should be less than 17 seconds. -// -// More importantly, this **SHOULD** also continue to be the state even when -// Espresso is in a degraded state. -// -// The Batches that are submitted to Espresso are derived from the Blocks -// coming from the L2 Sequencer directly. We are also able to reverse this -// process reconstructing the Block from the Batch. This means, that given -// a Transaction, we should be able to find the receipt on the L2, and then -// we can use that Block information to track the arrival of the Transaction -// / Block coming from Espresso. -func TestE2eDevnetWithEspressoDegradedLivenessViaCaffNode(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - launcher := new(env.EspressoDevNodeLauncherDocker) - - // Start a Server to proxy requests to Espresso, with a decider that will - // simulate degraded liveness failures by reporting false successful - // submissions 10% of the time, and 503 errors 10% of the time, with - // actual proxied requests 80% of the time. - _, server, option := env.SetupQueryServiceIntercept( - env.SetDecider(env.NewRandomRollFakeSubmitTransactionSuccess( - 10, - 0, - 1, - rand.New(rand.NewSource(0)), - )), - ) - - defer env.Stop(t, server) - system, espressoDevNode, err := launcher.StartE2eDevnet( - ctx, - t, - option, - env.WithL1FinalizedDistance(0), - env.WithSequencerUseFinalized(true), - ) - - // Signal the testnet to shut down - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - defer env.Stop(t, system) - defer env.Stop(t, espressoDevNode) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - addressAlice := system.Cfg.Secrets.Addresses().Alice - - l1Client := system.NodeClient(e2esys.RoleL1) - l2Seq := system.NodeClient(e2esys.RoleSeq) - caffVerif := system.NodeClient(env.RoleCaffNode) - - balanceAliceInitial, err := caffVerif.BalanceAt(ctx, addressAlice, nil) - if have, want := err, error(nil); have != want { - t.Fatalf("Failed to fetch Alice's balance:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - type espressoReceived struct { - batch *derive.EspressoBatch - block *geth_types.Block - received time.Time - } - - espressoReceipts := map[common.Hash]espressoReceived{} - - // The number of transaction we want to submit to the L2. - // This will also correspond to the number of batches we expect to receive - // from the Espresso streamer. - const N = 10 - transactions := make([]*geth_types.Transaction, 0, N) - for i := 0; i < N; i++ { - // Create the transaction - tx := geth_types.MustSignNewTx( - system.Cfg.Secrets.Bob, - geth_types.LatestSignerForChainID(system.Cfg.L2ChainIDBig()), - &geth_types.DynamicFeeTx{ - ChainID: system.Cfg.L2ChainIDBig(), - Nonce: uint64(i), - To: &addressAlice, - Value: big.NewInt(1), - GasTipCap: big.NewInt(10), - GasFeeCap: big.NewInt(200), - Gas: 21_000, - }, - ) - - transactions = append(transactions, tx) - } - - streamBlocksCtx, streamBlocksCancel := context.WithCancel(ctx) - var wg sync.WaitGroup - defer streamBlocksCancel() - { - // Streamer Setup and Configuration - l := log.NewLogger(slog.Default().Handler()) - lightClient, err := espressoLightClient.NewLightclientCaller(env.EspressoLightClientAddr(), l1Client) - require.NoError(t, err, "light client creation failed") - streamer, err := op.NewEspressoStreamer( - system.RollupConfig.L2ChainID.Uint64(), - espresso.NewAdaptL1BlockRefClient(l1Client), - espresso.NewAdaptL1BlockRefClient(l1Client), - espressoClient.NewClient(server.URL), - lightClient, - l, - derive.CreateEspressoBatchUnmarshaler(), - 0, - 1, - system.RollupConfig.BatchAuthenticatorAddress, - false, - ) - require.NoError(t, err, "failed to create Espresso streamer") - - l1RPC, err := client.NewRPC(streamBlocksCtx, l, system.NodeEndpoint(e2esys.RoleL1).RPC()) - require.NoError(t, err, "failed to create L1 RPC client") - l2SeqRPC, err := client.NewRPC(streamBlocksCtx, l, system.NodeEndpoint(e2esys.RoleSeq).RPC()) - require.NoError(t, err, "failed to create L2 sequencer RPC client") - - l1RefClient, err := sources.NewL1Client(l1RPC, l, nil, sources.L1ClientDefaultConfig(system.RollupConfig, true, sources.RPCKindStandard)) - require.NoError(t, err, "failed to create L1 Ref client") - l2RefClient, err := sources.NewL2Client(l2SeqRPC, l, nil, sources.L2ClientDefaultConfig(system.RollupConfig, true)) - require.NoError(t, err, "failed to create L2 Ref client") - l2BlockRef, err := l2RefClient.L2BlockRefByLabel(streamBlocksCtx, eth.Safe) - require.NoError(t, err, "failed to get safe L2 block ref") - finalizedL1BlockRef, err := l1RefClient.L1BlockRefByLabel(streamBlocksCtx, eth.Finalized) - require.NoError(t, err, "failed to get finalized L1 block ref") - err = streamer.Refresh(streamBlocksCtx, finalizedL1BlockRef, l2BlockRef.Number, l2BlockRef.L1Origin) - require.NoError(t, err, "failed to refresh streamer") - lastTransaction := transactions[N-1] - - // Start consuming Batches from the Streamer - // We cannot guarantee that we will receive only the batches that - // correspond to the transactions we submitted, so we will need to - // keep track of the batches we receive and match them up with the - // transactions we submitted. - // - // Luckily, it seems that the Block contained within the batch will - // maintain the same block hash, and the transaction hashes will match - // for the transactions beyond the first in the block. - wg.Add(1) - go (func(ctx context.Context, wg *sync.WaitGroup, streamer espresso.EspressoStreamer[derive.EspressoBatch]) { - cfg := system.RollupConfig - defer wg.Done() - for { - select { - default: - case <-ctx.Done(): - // We are being told to exit, so we exit - return - } - - finalizedL1, finalizedL1Err := l1RefClient.BlockRefByLabel(ctx, eth.Finalized) - safeL2, safeL2Error := l2RefClient.L2BlockRefByLabel(ctx, eth.Safe) - if finalizedL1Err == nil && safeL2Error == nil { - // Refresh the Streamer with the latest finalized L1 and safe L2 - err := streamer.Refresh(ctx, finalizedL1, safeL2.Number, safeL2.L1Origin) - if have, want := err, error(nil); have != want { - // NOTE: we are in a go-routine here, so we are unable - // to fail fatally here. Instead, we'll Fail and and - // return. - t.Errorf("Failed to refresh streamer:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - return - } - } - - if !streamer.HasNext(ctx) { - if err := streamer.Update(ctx); err != nil { - // Try again after a short delay if we we fail to - // update the streamer. - time.Sleep(50 * time.Millisecond) - continue - } - } - - // consume all of the available batches - for batch := streamer.Next(ctx); batch != nil; batch = streamer.Next(ctx) { - block, err := batch.ToBlock(cfg) - if have, want := err, error(nil); have != want { - return - } - - espressoReceipts[block.Hash()] = espressoReceived{ - batch: batch, - block: block, - received: time.Now(), - } - - txns := block.Transactions() - for _, tx := range txns { - if tx.Hash() == lastTransaction.Hash() { - // We've encountered the last transaction we - // were looking for, and now we can stop - // consuming batches. - return - } - } - } - } - })(streamBlocksCtx, &wg, streamer) - } - - type submission struct { - receipt *geth_types.Receipt - created time.Time - submitted time.Time - received time.Time - } - var submissions []submission - - { - for _, tx := range transactions { - created := time.Now() - - // Send the transaction - err := l2Seq.SendTransaction(ctx, tx) - if have, want := err, error(nil); have != want { - t.Fatalf("Sending L2 tx:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // We have submitted the transaction to the L2, successfully. - submitted := time.Now() - - // Wait for the receive - receipt, err := wait.ForReceiptOK(ctx, l2Seq, tx.Hash()) - if have, want := err, error(nil); have != want { - t.Fatalf("Waiting for L2 tx:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // We now have a receipt from the L2 Sequencer, indicating that - // the transaction was successfully included in a block. - received := time.Now() - - submissions = append(submissions, submission{ - receipt: receipt, - created: created, - submitted: submitted, - received: received, - }) - } - - // Let's verify that all of our transactions came through successfully, - // using our Caff Node as the verification client. - for i, submission := range submissions { - receipt, err := wait.ForReceiptOK(ctx, caffVerif, submission.receipt.TxHash) - if have, want := err, error(nil); have != want { - t.Fatalf("Waiting for L2 tx on verification client:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Transaction Hash should match - if have, want := receipt.TxHash, submission.receipt.TxHash; have != want { - t.Errorf("Receipt tx hash mismatch for submission %d:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", i, have, want) - } - - // Block Hash should match - if have, want := receipt.BlockHash, submission.receipt.BlockHash; have != want { - t.Errorf("Receipt block hash mismatch for submission %d:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", i, have, want) - } - } - - // Alice's balance should have increased by N - balanceAliceFinal, err := caffVerif.BalanceAt(ctx, addressAlice, nil) - if have, want := err, error(nil); have != want { - t.Fatalf("Failed to fetch Alice's balance:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - expectedBalance := new(big.Int).Add(balanceAliceInitial, big.NewInt(int64(N))) - if balanceAliceFinal.Cmp(expectedBalance) != 0 { - t.Errorf("Alice's balance did not increase as expected:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", balanceAliceFinal, expectedBalance) - } - } - - // Wait for the Streamer to get all of the batches it was waiting for - wg.Wait() - // Tell the Streamer to stop streaming. - streamBlocksCancel() - - if have, want := len(espressoReceipts), N; have < want { - t.Fatalf("Expected to received at least many batches as submissions:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // We'll check that our timings meet or exceed the requirements of the test. - var totalDiff time.Duration - var totalDenom time.Duration - for i, submission := range submissions { - espressoReceived, ok := espressoReceipts[submission.receipt.BlockHash] - if have, want := ok, true; have != want { - t.Errorf("Failed to find batch for submission %d:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", i, have, want) - continue - } - - diff := espressoReceived.received.Sub(submission.received) - totalDiff += diff - totalDenom++ - - if have, want := diff, 17*time.Second; have > want { - t.Errorf("Submission %d was not confirmed in an espresso block within 17 seconds of submission:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", i, diff, want) - } - } - - if have, want := int(totalDenom), N; have != want { - t.Errorf("Expected to have a total of %d submissions:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", want, have, want) - } - - if totalDenom > 0 { - // We cast the len(espressoReceipts) to a time.Duration so we can divide - // the totalDiff to get the average duration, to appease the type system. - averageDuration := totalDiff / totalDenom - if have, want := averageDuration, 17*time.Second; have >= want { - t.Errorf("Average time to confirm transactions in espresso blocks exceeded 17 seconds:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", averageDuration, want) - } - } -} diff --git a/espresso/environment/3_1_espresso_caff_node_test.go b/espresso/environment/3_1_espresso_caff_node_test.go deleted file mode 100644 index fdb7152ca28..00000000000 --- a/espresso/environment/3_1_espresso_caff_node_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package environment_test - -import ( - "context" - "math/big" - "testing" - - env "github.com/ethereum-optimism/optimism/espresso/environment" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" - "github.com/ethereum/go-ethereum/accounts/abi/bind" -) - -// TestE2eDevnetWithEspressoWithCaffNodeDeterministicDerivation is a test that -// attempts to make sure that the caff node can derive the same state as the -// original op-node (non caffeinated). -// -// This tests is designed to evaluate Test 3.1 as outlined within the -// Espresso Celo Integration plan. It has stated task definition as follows: -// -// Arrange: -// Running Sequencer, Batcher in Espresso mode, Caff node, and OP node. -// Balance of Alice is 0. -// Check that this is the case querying both Caff and OP nodes -// Act: -// Send a single transaction that transfers 1 coin to Alice -// Assert: -// Query the Caff node to check that Alice balance has been increased by 1 -// Query the OP node to check that Alice balance has been increased by 1 -// -// The actual tests is unable to make Alice's initial balance zero, and will -// instead just check Alice's starting balance against the rest of the cases. -func TestE2eDevnetWithEspressoWithCaffNodeDeterministicDerivation(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - launcher := new(env.EspressoDevNodeLauncherDocker) - - system, espressoDevNode, err := launcher.StartE2eDevnet(ctx, t) - // Signal the testnet to shut down - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - defer env.Stop(t, system) - defer env.Stop(t, espressoDevNode) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - // We want to setup our test - addressAlice := system.Cfg.Secrets.Addresses().Alice - var balanceAliceInitial *big.Int - - l1Client := system.NodeClient(e2esys.RoleL1) - l2Verif := system.NodeClient(e2esys.RoleVerif) - caffVerif := system.NodeClient(env.RoleCaffNode) - - // Retrieve Alice's starting Balance, and verify that they match between - // the Verification Node, and the Caff Node - { - verifBalance, err := l2Verif.BalanceAt(ctx, addressAlice, nil) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to get alice's balance from verification node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - caffBalance, err := caffVerif.BalanceAt(ctx, addressAlice, nil) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to get alice's balance from caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - if have, want := verifBalance, caffBalance; have.Cmp(want) != 0 { - t.Fatalf("alice's balance does not match between verification node and caff node:\nhave:\n\t\"%s\"\nwant:\n\t\"%s\"\n", have, want) - } - - balanceAliceInitial = verifBalance - } - - // Next We want to Increase Alice's balance by 1, and verify that the balance - // matches between the verification node and the caff node - { - privateKey := system.Cfg.Secrets.Bob - bobOptions, err := bind.NewKeyedTransactorWithChainID(privateKey, system.Cfg.L1ChainIDBig()) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to create transaction options for bob:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - mintAmount := new(big.Int).SetUint64(1) - bobOptions.Value = mintAmount - _ = helpers.SendDepositTx(t, system.Cfg, l1Client, l2Verif, bobOptions, func(l2Opts *helpers.DepositTxOpts) { - // Send from Bob to Alice - l2Opts.ToAddr = addressAlice - }) - - verifBalanceNew, err := wait.ForBalanceChange(ctx, l2Verif, addressAlice, balanceAliceInitial) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to get alice's new balance from verification node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - caffBalanceNew, err := wait.ForBalanceChange(ctx, caffVerif, addressAlice, balanceAliceInitial) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to get alice's new balance from caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := verifBalanceNew, caffBalanceNew; have.Cmp(want) != 0 { - t.Fatalf("alice's new balance does not match between verification node and caff node:\nhave:\n\t\"%s\"\nwant:\n\t\"%s\"\n", have, want) - } - - // We have a new balance, and it matches between the verification node - // and the Caff Node. - - // Let's check to make sure that Alice's balance has increased by - // exactly 1. - - diff := new(big.Int).Sub(verifBalanceNew, balanceAliceInitial) - if have, want := diff, mintAmount; have.Cmp(want) != 0 { - t.Fatalf("alice's balance did not increase by 1:\nhave:\n\t\"%s\"\nwant:\n\t\"%s\"\n", have, want) - } - - } - -} diff --git a/espresso/environment/3_2_espresso_deterministic_state_test.go b/espresso/environment/3_2_espresso_deterministic_state_test.go index 69af95b97bd..898d8f9cea5 100644 --- a/espresso/environment/3_2_espresso_deterministic_state_test.go +++ b/espresso/environment/3_2_espresso_deterministic_state_test.go @@ -9,17 +9,15 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" - espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types" env "github.com/ethereum-optimism/optimism/espresso/environment" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common/hexutil" geth_types "github.com/ethereum/go-ethereum/core/types" @@ -29,19 +27,19 @@ import ( ) // TestDeterministicDerivationExecutionStateWithInvalidTransaction is a test that -// attempts to make sure that the caff node can derive the same state as the -// original op-node (non caffeinated). +// attempts to make sure that the op-node continues to derive and finalize a +// valid chain even when malicious transactions are submitted. // // This test is designed to evaluate Test 3.2 as outlined within the // Espresso Celo Integration plan. It has stated task definition as follows: // // Arrange: -// Running Sequencer, Batcher in Espresso mode, Caff node, and OP node. +// Running Sequencer, Batcher in Espresso mode, and OP node. // Act: // Send some transactions from Bob to Alice and some regular L2 transactions. // While sending regular L2 transactions to the sequencer also send transactions to Espresso using an invalid batcher address, and transactions directly to L1 (e.g. transactions that were not previously posted to Espresso). // Assert: -// Once a state of op-node is finalized on L1, it should match the state that was earlier reported by the caff-node for the same block. +// The op-node continues to finalize valid blocks on L1, ignoring the malicious transactions. func TestDeterministicDerivationExecutionStateWithInvalidTransaction(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -59,23 +57,9 @@ func TestDeterministicDerivationExecutionStateWithInvalidTransaction(t *testing. defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - // Get caffNodeL2Client from caff node's engine state - caffNodeL2Client := caffNode.OpNode.EngineState() - // We want to setup our test addressAlice := system.Cfg.Secrets.Addresses().Alice - espressoClient, err := espressoClient.NewMultipleNodesClient(espressoDevNode.EspressoUrls()) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to create Espresso client:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } + espressoClient := espressoDevNode.Client() l1Client := system.NodeClient(e2esys.RoleL1) l2Verif := system.NodeClient(e2esys.RoleVerif) l2Seq := system.NodeClient(e2esys.RoleSeq) @@ -178,24 +162,14 @@ func TestDeterministicDerivationExecutionStateWithInvalidTransaction(t *testing. } } - // Get latest safe blocks from op node first as op node usually lags behind. + // Get latest finalized block from op node, ensuring that the chain + // continues to finalize despite the malicious transactions submitted + // directly to Espresso and L1, which the derivation pipeline ignores. // We use BlockByNumber to get the states as the engine state will be reflected in the block. - opBlock, err := l2Verif.BlockByNumber(ctx, big.NewInt(rpc.FinalizedBlockNumber.Int64())) + _, err = l2Verif.BlockByNumber(ctx, big.NewInt(rpc.FinalizedBlockNumber.Int64())) if err != nil { t.Fatalf("failed to get block from opBlock: %v", err) } - - // Get the corresponding safe blocks from caff node - // We use L2BlockRefByLabel to get the states as the engine state will be reflected in the block. - caffBlock, err := caffNodeL2Client.L2BlockRefByNumber(ctx, opBlock.Number().Uint64()) - if err != nil { - t.Fatalf("failed to get block from caff node: %v", err) - } - - // Compare block states - if have, want := caffBlock.Hash, opBlock.Hash(); have != want { - t.Errorf("block hash mismatch between sequencer and caff node at block %v\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", opBlock.Number(), have, want) - } } } @@ -209,7 +183,7 @@ func realBatcherPrivateKey(system *e2esys.System) (*ecdsa.PrivateKey, error) { return system.Cfg.Secrets.Batcher, nil } -const TEST_ESPRESSO_TRANSACTION = "0xf90388f9023da00d68b82fa254b7d23a8584bcaa67be241a269c86aac05a2a6fc805a672bb910ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a0d6cc9c002bc6a8d1c8501c57301b6b2f037494e1e0f61e417411e17f4e80b5afa028881bc4fc4c5fa67f26462837f88937961b6667ae4af043218a0c1b72a5f53ca0d8056577b8ef8e580c0ebc96def906b3699ddc8d91e15abf9c7a7e7bb4f85c96b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080018401c9c380830272ca84681d98b780a0000000000000000000000000000000000000000000000000000000000000000088000000000000000001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000f849a00d68b82fa254b7d23a8584bcaa67be241a269c86aac05a2a6fc805a672bb910e80a0d7d069186bed40982ca7e7747d61c78718d0dda165d74e62164c1bba165001f784681d98b7c0b8fb7ef8f8a07a2aa57f213dfe5e61ceaebcd45c61252157b4e3c1e82e1ec0dca455b1173ad894deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000f424000000000000000000000000100000000681d98b60000000000000000000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001d7d069186bed40982ca7e7747d61c78718d0dda165d74e62164c1bba165001f70000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc" +const TEST_ESPRESSO_TRANSACTION = "0xf9039df9023da00d68b82fa254b7d23a8584bcaa67be241a269c86aac05a2a6fc805a672bb910ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a0d6cc9c002bc6a8d1c8501c57301b6b2f037494e1e0f61e417411e17f4e80b5afa028881bc4fc4c5fa67f26462837f88937961b6667ae4af043218a0c1b72a5f53ca0d8056577b8ef8e580c0ebc96def906b3699ddc8d91e15abf9c7a7e7bb4f85c96b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080018401c9c380830272ca84681d98b780a0000000000000000000000000000000000000000000000000000000000000000088000000000000000001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000f849a00d68b82fa254b7d23a8584bcaa67be241a269c86aac05a2a6fc805a672bb910e80a0d7d069186bed40982ca7e7747d61c78718d0dda165d74e62164c1bba165001f784681d98b7c0b8fb7ef8f8a07a2aa57f213dfe5e61ceaebcd45c61252157b4e3c1e82e1ec0dca455b1173ad894deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000f424000000000000000000000000100000000681d98b60000000000000000000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001d7d069186bed40982ca7e7747d61c78718d0dda165d74e62164c1bba165001f70000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc940000000000000000000000000000000000000000" // createEspressoTransaction creates a Espresso transaction with a FAKE or REAL batcher private key func createEspressoTransaction(transactionString string, chainID *big.Int, batcherKey *ecdsa.PrivateKey) (*espressoCommon.Transaction, error) { @@ -232,35 +206,15 @@ func createEspressoTransaction(transactionString string, chainID *big.Int, batch // Create and return Espresso Transaction return &espressoCommon.Transaction{ - Namespace: chainID.Uint64(), + Namespace: bigs.Uint64Strict(chainID), Payload: payload, }, nil } -// espressoTransactionDataSkippingUnmarshal extract the L1 info deposit from Espresso transaction without checking whether the unmarshal could work -func espressoTransactionDataSkippingUnmarshal(transactionString string) (*geth_types.Transaction, error) { - bufData, err := hexutil.Decode(transactionString) - if err != nil { - return nil, fmt.Errorf("failed to decode Espresso transaction in the test: %w", err) - } - buf := bytes.NewBuffer(bufData) - - batchData := buf.Bytes() - - var batch derive.EspressoBatch - if err := rlp.DecodeBytes(batchData, &batch); err != nil { - return nil, fmt.Errorf("failed to decode Espresso batch: %w", err) - } - - return batch.L1InfoDeposit, nil -} - // TestValidEspressoTransactionCreation is a test that // make sure we have correct way to create a Espresso transaction. // This test is a unit test to serve the correctness of TestDeterministicDerivationExecutionStateWithInvalidTransaction. func TestValidEspressoTransactionCreation(t *testing.T) { - // Ignore it by default as it takes a long time to run - t.Skip("skipping test") ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -276,21 +230,8 @@ func TestValidEspressoTransactionCreation(t *testing.T) { defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - // We want to setup our test - espressoClient, err := espressoClient.NewMultipleNodesClient(espressoDevNode.EspressoUrls()) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to create Espresso client:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - l2Verif := system.NodeClient(e2esys.RoleVerif) - caffVerif := system.NodeClient(env.RoleCaffNode) + espressoClient := espressoDevNode.Client() // create a real Espresso transaction and make sure it can go through { // Create a real Espresso transaction @@ -337,32 +278,23 @@ func TestValidEspressoTransactionCreation(t *testing.T) { } } - // Make sure the transaction will go through to caff node by checking the unmarshal works - // The check can directly reflect whether the transaction is valid or not - caffStreamer := caffNode.OpNode.EspressoStreamer() - _, err = caffStreamer.UnmarshalBatch(realEspressoTransaction.Payload) - if have, want := err, error(nil); have != want { - t.Fatalf("Failed to unmarshal batch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Make sure the transaction will go through to op node by checking it will go through batch submitter's streamer + // The batcher's streamer must be able to unmarshal the transaction the + // same way it would a batcher-produced one, recovering the batcher + // address from the prepended signature. batchSubmitter := system.BatchSubmitter - _, err = batchSubmitter.EspressoStreamer().UnmarshalBatch(realEspressoTransaction.Payload) + batch, err := batchSubmitter.EspressoStreamer().UnmarshalBatch(realEspressoTransaction.Payload) if have, want := err, error(nil); have != want { t.Fatalf("Failed to unmarshal batch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) } - // Extract L1 info deposit transaction from Espresso transaction - l1InfoDeposit, err := espressoTransactionDataSkippingUnmarshal(TEST_ESPRESSO_TRANSACTION) - if err != nil { - t.Fatalf("Failed to get L1 info deposit:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", err, nil) - } + // The signer recovered from the signature must be the real batcher, since + // realEspressoTransaction was signed with the real batcher key. + realBatcherAddress := crypto.PubkeyToAddress(realBatcherPrivateKey.PublicKey) + require.Equal(t, realBatcherAddress, batch.Signer(), + "recovered signer should be the real batcher address") - // Make sure the transaction will really go through to verifier by waiting for its hash - _, err = wait.ForReceiptOK(ctx, caffVerif, l1InfoDeposit.Hash()) - require.NoError(t, err, "deposit didn't arrive on Caff node") - _, err = wait.ForReceiptOK(ctx, l2Verif, l1InfoDeposit.Hash()) - require.NoError(t, err, "deposit didn't arrive on Decaf node") + // The embedded L1-info deposit must be extractable from the batch. + require.NotNil(t, batch.L1InfoDeposit, "batch should carry an L1 info deposit") } } diff --git a/espresso/environment/3_3_fast_derivation_and_caff_node_test.go b/espresso/environment/3_3_fast_derivation_and_caff_node_test.go deleted file mode 100644 index 1444f8b7534..00000000000 --- a/espresso/environment/3_3_fast_derivation_and_caff_node_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package environment_test - -import ( - "context" - "fmt" - "math/big" - "testing" - "time" - - env "github.com/ethereum-optimism/optimism/espresso/environment" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -// checkNewBlocks is a helper function for TestFastDerivationAndCaffNode to check for new blocks by comparing the hash of new block and previous block -func checkNewBlocks(ctx context.Context, client *ethclient.Client, previousBlock *types.Block, nodeName string, tickerDuration time.Duration) (*types.Block, error) { - newBlock, err := client.BlockByNumber(ctx, nil) - if err != nil { - return nil, fmt.Errorf("Failed to get new %s block: %w", nodeName, err) - } - - // Make sure newBlock comes after previousBlock - if have, want := newBlock.Number(), previousBlock.Number(); have.Cmp(want) <= 0 { - return nil, fmt.Errorf("No new block for %s after %s\nhave:\n\t\"%v\"\nwant:\n\t> \"%v\"\n", nodeName, tickerDuration, have, want) - } - return newBlock, nil -} - -// TestFastDerivationAndCaffNode is a test that -// checks the derivation pipeline is fast and the Caff node is working properly with the happy path. -// -// The criteria for this test is as follows: -// -// Requirement: -// Make sure the node's RPC can be queried with update every 2-4 seconds. -// -// Arrange: -// -// Running Sequencer, Batcher in Espresso mode, and Caff node with happy path. -// -// Act: -// -// Submit a number of transactions (or no transaction?) to the sequencer -// -// Assert: -// -// We should be able to query caff node with update every 2-4 seconds. We use ticker to query the node every 4 seconds. -// -// checkNewBlocks checks for new blocks and verifies their timestamps -func TestFastDerivationAndCaffNode(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - launcher := new(env.EspressoDevNodeLauncherDocker) - - system, espressoDevNode, err := launcher.StartE2eDevnet(ctx, t, env.WithL1FinalizedDistance(0), env.WithSequencerUseFinalized(true)) - - // Signal the testnet to shut down - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - defer env.Stop(t, system) - defer env.Stop(t, espressoDevNode) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - - addressAlice := system.Cfg.Secrets.Addresses().Alice - l1Client := system.NodeClient(e2esys.RoleL1) - l2Verif := system.NodeClient(e2esys.RoleVerif) - caffVerif := system.NodeClient(env.RoleCaffNode) - - // We want to send some transactions from Bob to Alice - { - privateKey := system.Cfg.Secrets.Bob - bobOptions, err := bind.NewKeyedTransactorWithChainID(privateKey, system.Cfg.L1ChainIDBig()) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to create transaction options for bob:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - mintAmount := new(big.Int).SetUint64(1) - bobOptions.Value = mintAmount - _ = helpers.SendDepositTx(t, system.Cfg, l1Client, l2Verif, bobOptions, func(l2Opts *helpers.DepositTxOpts) { - // Send from Bob to Alice - l2Opts.ToAddr = addressAlice - }) - } - - // Initialize ticker to fire every 4 seconds - tickerDuration := 4 * time.Second - ticker := time.NewTicker(tickerDuration) - defer ticker.Stop() - - finishTicker := time.NewTicker(30 * time.Second) - defer finishTicker.Stop() - - lastCaffHead, err := caffVerif.BlockByNumber(ctx, nil) - if err != nil { - t.Fatalf("Failed to get initial caffVerif block: %v", err) - } - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - // Check for new block of caff-node - newCaff, err := checkNewBlocks(ctx, caffVerif, lastCaffHead, "caff-node", tickerDuration) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to get new caff-node block:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - lastCaffHead = newCaff - case <-finishTicker.C: - return - } - } - -} diff --git a/espresso/environment/7_stateless_batcher_test.go b/espresso/environment/7_stateless_batcher_test.go index 266b34484ef..788ecc98da0 100644 --- a/espresso/environment/7_stateless_batcher_test.go +++ b/espresso/environment/7_stateless_batcher_test.go @@ -26,13 +26,12 @@ import ( // We don't need to clear persistent storage because the original Optimism code isn't and our integration work shouldn't use any. // More specifically the test is defined as follows // Arrange: -// Running Sequencer, Batcher in Espresso mode, Caff node OP node. +// Running Sequencer, Batcher in Espresso mode, OP node. // Act: // Loop over n iterations // Randomly pick one iteration to stop the batcher and another to start the batcher // For all the other iterations send one coin to Alice. // Assert: -// Query the Caff node to check that Alice balance has been increased by n-2 // Query the OP node to check that Alice balance has been increased by n-2 func TestStatelessBatcher(t *testing.T) { @@ -50,19 +49,10 @@ func TestStatelessBatcher(t *testing.T) { defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) - addressAlice := system.Cfg.Secrets.Addresses().Alice rollupClient := system.RollupClient(e2esys.RoleVerif) l2Seq := system.NodeClient(e2esys.RoleSeq) l2Verif := system.NodeClient(e2esys.RoleVerif) - caffVerif := system.NodeClient(env.RoleCaffNode) // Fund Alice env.RunSimpleL1TransferAndVerifier(ctx, t, system) @@ -83,8 +73,6 @@ func TestStatelessBatcher(t *testing.T) { numTransfers := 0 bobOptions.Value = amount - var caffBalanceNew *big.Int - driver := system.BatchSubmitter.TestDriver() safeBlockInclusionDuration := time.Duration(6*system.Cfg.DeployConfig.L1BlockTime) * time.Second @@ -169,9 +157,7 @@ func TestStatelessBatcher(t *testing.T) { require.NoError(t, err) } - caffBalanceNew, _ = caffVerif.BalanceAt(ctx, addressAlice, nil) l2BalanceNew, _ := l2Verif.BalanceAt(ctx, addressAlice, nil) assert.Equal(t, expectedAmount, l2BalanceNew) - assert.Equal(t, expectedAmount, caffBalanceNew) } diff --git a/espresso/environment/8_reorg_test.go b/espresso/environment/8_reorg_test.go index 1d5c91022b9..5309ffc0cf6 100644 --- a/espresso/environment/8_reorg_test.go +++ b/espresso/environment/8_reorg_test.go @@ -10,12 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/dial" - "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - rpc "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -47,12 +42,6 @@ func TestBatcherWaitForFinality(t *testing.T) { defer env.Stop(t, system) defer env.Stop(t, espressoDevNode) - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - defer env.Stop(t, caffNode) - rollupClient := system.RollupClient(e2esys.RoleVerif) initialStatus, err := rollupClient.SyncStatus(context.Background()) @@ -83,86 +72,9 @@ func TestBatcherWaitForFinality(t *testing.T) { } } -// TestCaffNodeWaitForFinality is a test that attempts to make sure that the Caff node waits for -// the derived L1 block to be finalized before advancing its safe head. -// -// This tests is designed to evaluate Test 8.2.1 as outlined within the Espresso Celo Integration -// plan. It has stated task definition as follows: -// -// Arrange: -// Run the sequencer and the Caff node in Espresso mode. -// Act: -// Wait until the Caff node's safe L2 head advances. -// Assert: -// The Caff node's safe L2 head always has a finalized L1 origin. -func TestCaffNodeWaitForFinality(t *testing.T) { - // Basic test setup. - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancel() - launcher := new(env.EspressoDevNodeLauncherDocker) - - // Set L1FinalizedDistance to nonzero, NonFinalizedProposals to true, and SequencerUseFinalized - // to false, to make sure we are testing how the Caff node handles the finality. - system, espressoDevNode, err := launcher.StartE2eDevnet(ctx, t, env.WithL1FinalizedDistance(4), env.WithNonFinalizedProposals(true), env.WithSequencerUseFinalized(false)) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - defer env.Stop(t, system) - defer env.Stop(t, espressoDevNode) - - caffNode, err := env.LaunchCaffNode(t, system, espressoDevNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - defer env.Stop(t, caffNode) - - l1Client := system.NodeClient(e2esys.RoleL1) - - // Create a RollupClient for the caff node - caffRpcClient, err := dial.DialRPCClientWithTimeout(ctx, log.New(), caffNode.OpNode.UserRPC().RPC()) - require.NoError(t, err) - caffRollupClient := sources.NewRollupClient(client.NewBaseRPCClient(caffRpcClient)) - - initialStatus, err := caffRollupClient.SyncStatus(ctx) - require.NoError(t, err) - initialSafeL2 := initialStatus.SafeL2.Number - - // Wait for the Caff node's safe L2 head to advance, and verify that - // its L1 origin is always finalized. - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - require.FailNow(t, "Timeout: Caff node safe L2 head did not advance") - case <-ticker.C: - status, err := caffRollupClient.SyncStatus(ctx) - require.NoError(t, err) - - // Check that the safe L2 head's L1 origin is finalized - safeL2Origin := status.SafeL2.L1Origin - finalizedL1, err := l1Client.BlockByNumber(ctx, big.NewInt(rpc.FinalizedBlockNumber.Int64())) - require.NoError(t, err) - - require.LessOrEqual(t, safeL2Origin.Number, finalizedL1.NumberU64(), - "Caff node safe L2 head has non-finalized L1 origin: origin=%d, finalized=%d", - safeL2Origin.Number, finalizedL1.NumberU64()) - - // Test passes once safe L2 head has advanced - if status.SafeL2.Number > initialSafeL2 { - t.Logf("Caff node safe L2 head advanced from %d to %d with finalized L1 origin %d", - initialSafeL2, status.SafeL2.Number, safeL2Origin.Number) - return - } - } - } -} - func runL1Reorg(ctx context.Context, t *testing.T, system *e2esys.System) { l2Seq := system.NodeClient(e2esys.RoleSeq) l1Client := system.NodeClient(e2esys.RoleL1) - caffClient := system.NodeClient(env.RoleCaffNode) // Wait for batcher to start advancing L2 head _, err := geth.WaitForBlockToBeSafe(big.NewInt(2), l2Seq, 2*time.Minute) @@ -207,22 +119,17 @@ func runL1Reorg(ctx context.Context, t *testing.T, system *e2esys.System) { newL2Head, err := l2Seq.BlockByNumber(ctx, new(big.Int).SetUint64(unsafeL2Height)) require.NoError(t, err) require.NotEqual(t, newL2Head.Hash(), l2Head.Hash()) - - // Check that Caff node came to the same conclusion - caffL2Head, err := caffClient.BlockByNumber(ctx, new(big.Int).SetUint64(unsafeL2Height)) - require.NoError(t, err) - require.Equal(t, caffL2Head.Hash(), newL2Head.Hash()) } -// TestE2eDevnetWithL1Reorg tests how the batcher and Caff node handle an L1 reorg. +// TestE2eDevnetWithL1Reorg tests how the batcher handles an L1 reorg. // Specifically, it focuses on cases where unsafe L2 chain contains blocks that // reference unfinalized L1 blocks as their origin. // -// This tests is designed to evaluate Test 8.1.2 and 8.2.2 as outlined within the Espresso Celo +// This tests is designed to evaluate Test 8.1.2 as outlined within the Espresso Celo // Integration plan. The test is defined as follows: // Arrange: // -// Running Sequencer, Batcher in Espresso mode, Caff node & OP node. +// Running Sequencer, Batcher in Espresso mode & OP node. // // Act: // @@ -232,7 +139,7 @@ func runL1Reorg(ctx context.Context, t *testing.T, system *e2esys.System) { // Assert: // // Assert that derivation pipeline still progresses -// Assert that Caff and OP node report a new block at the target L2 height +// Assert that the OP node reports a new block at the target L2 height func TestE2eDevnetWithL1Reorg(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -244,13 +151,8 @@ func TestE2eDevnetWithL1Reorg(t *testing.T) { t.Fatalf("failed to start dev environment with espresso dev node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) } - caffNode, err := env.LaunchCaffNode(t, system, devNode) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to start caff node:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Shut down the Caff Node - defer env.Stop(t, caffNode) + defer env.Stop(t, system) + defer env.Stop(t, devNode) runL1Reorg(ctx, t, system) } diff --git a/espresso/environment/benchmark/benchmarker.go b/espresso/environment/benchmark/benchmarker.go deleted file mode 100644 index 6b3cafed141..00000000000 --- a/espresso/environment/benchmark/benchmarker.go +++ /dev/null @@ -1,378 +0,0 @@ -package benchmark - -import ( - "context" - "crypto/ecdsa" - "math/big" - "runtime" - "time" - - env "github.com/ethereum-optimism/optimism/espresso/environment" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - geth "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - geth_types "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -// TimestampedValue is a generic struct that holds a value of type T and -// a timestamp. It is used to record the time at which a value was assessed -type TimestampedValue[T any] struct { - Value T - Timestamp time.Time -} - -// WithTimestamp is a function that takes a value of type T and returns a -// TimestampedValue[T]. It is used to create a new TimestampedValue with the -// current time as the timestamp. -func WithTimestamp[T any](value T) TimestampedValue[T] { - return TimestampedValue[T]{ - Value: value, - Timestamp: time.Now(), - } -} - -// NOTE: While all of these maps are using a Hash value as the key, they -// -// are not all a hash of the same thing. -// -// Hash Values: -// - Created: Hash of the Transaction -// - Submitted: Hash of the Transaction -// - Receipts: Hash of the Transaction -// - CaffReceipts: Hash of the Block Header -// - VerifyReceipts: Hash of the Block Header -type BenchmarkStats struct { - Created map[common.Hash]TimestampedValue[*geth_types.Transaction] - Submitted map[common.Hash]TimestampedValue[common.Hash] - Receipts map[common.Hash]TimestampedValue[*geth_types.Receipt] - SeqReceipts map[common.Hash]TimestampedValue[common.Hash] - CaffReceipts map[common.Hash]TimestampedValue[common.Hash] - VerifyReceipts map[common.Hash]TimestampedValue[common.Hash] -} - -// BenchmarkSubmissionsConfig is a configuration for a single account -// submission to the L2 Sequencer. This helps to govern the load that -// we attempt to place on the system via a single account. -type BenchmarkSubmitterConfig struct { - Interval time.Duration - To *common.Address - Value *big.Int - Signer geth_types.Signer - ChainID *big.Int - Key *ecdsa.PrivateKey -} - -// BenchmarkConfig is a struct that holds the configuration for the -// benchmarking process. -type BenchmarkConfig struct { - Submitters []BenchmarkSubmitterConfig - NumSubmitTransactionWorkers int - NumReceiptWorkers int - - SeqClient *ethclient.Client - CaffClient *ethclient.Client - VerifyClient *ethclient.Client -} - -// BenchmarkOption is a a configuration option for the StartBenchmarking -// function. It is used to configure the benchmark configuration before -// the benchmark process has started. -type BenchmarkOption func(*BenchmarkConfig) - -// AddSubmitter is a a BenchmarkOption that adds a submitter to the list of -// submitters. -// -// NOTE: Since the nonce **MUST** be unique for transaction submissions on a -// given wallet address, and is expected to be sequential, you absolutely -// **SHOULD NOT** use the same wallet address / private key for multiple -// submitters. -func AddSubmitter( - submitter BenchmarkSubmitterConfig, -) BenchmarkOption { - return func(config *BenchmarkConfig) { - config.Submitters = append(config.Submitters, submitter) - } -} - -// WithSeqClient is a BenchmarkOption that sets the L2 Sequencer client -// to be used for the benchmark. This is the client that will be used -// to submit the transactions to the L2 Sequencer. -// -// NOTE: if you want to submit transactions to the L2 Sequencer with the -// AddSubmitter option, you will need to set the L2 Sequencer Client as well. -func WithSeqClient(client *ethclient.Client) BenchmarkOption { - return func(config *BenchmarkConfig) { - config.SeqClient = client - } -} - -// WithCaffClient is a BenchmarkOption that sets the Caff client to be -// used for the benchmark. This is the client that will be used to -// subscribe to the Caff node for block headers. -// -// NOTE: This option is required if you want to track the Caff Node Receipt -// time. -func WithCaffClient(client *ethclient.Client) BenchmarkOption { - return func(config *BenchmarkConfig) { - config.CaffClient = client - } -} - -// WithVerifyClient is a BenchmarkOption that sets the L2 Verifier client -// to be used for the benchmark. This is the client that will be used -// to subscribe to the L2 Verifier node for block headers. -// -// NOTE: This option is required if you want to track the L2 Verifier -// Receipt time. -func WithVerifyClient(client *ethclient.Client) BenchmarkOption { - return func(config *BenchmarkConfig) { - config.VerifyClient = client - } -} - -// Benchmarker is an interface that defines the functionality of running -// a benchmark and retrieving their statistics. It is a useful abstraction -// so that the user need not worry about all of the details about how the -// benchmark is being run by itself. -type Benchmarker interface { - // RunWithContext will run the benchmarker with the given context. The - // Context itself determines when the benchmarker will stop. Once the - // benchmarker has completed it's run, it will return the statistics for - // the benchmark. - RunWithContext(ctx context.Context) (BenchmarkStats, error) -} - -// benchmarkState is a struct that holds the state of the benchmark. -// It includes the configuration for the benchmark, the channels for -// communication between the workers, and the statistics for the -// benchmark. -type benchmarkState struct { - running bool - cfg BenchmarkConfig - - // WaitGroups and Contexts to synchronize the cancellation of the goroutines - signers waitGroupContext - submitter waitGroupContext - receipt waitGroupContext - subscriptions waitGroupContext - metricRecorder waitGroupContext - - // Communication Channels for the Workers - txSignedChanSrc chan TimestampedValue[*geth_types.Transaction] - txSubmitterChanSrc chan TimestampedValue[common.Hash] - annotatedBlockChanSrc chan TimestampedValue[AnnotatedBlockHash] - - // Client Subscriptions - seqSubscription geth.Subscription - caffSubscription geth.Subscription - verifySubscription geth.Subscription - - // The collected statistics for the benchmark - stats *BenchmarkStats -} - -// start will start spawn the workers and spin up the benchmark criteria. -// After starting, the user will need to call stop in order to stop the -// benchmark. -func (b *benchmarkState) start(ctx context.Context) { - if b.running { - // We are already running, doing so again would be a problem. - return - } - b.running = true - config := b.cfg - - l2SeqClient := config.SeqClient - l2Caff := config.CaffClient - l2Verif := config.VerifyClient - - // Create the Channels for the Workers - b.txSignedChanSrc = make(chan TimestampedValue[*geth_types.Transaction], 10) - txSignedChanDst1, txSignedChanDst2 := TeeChan(b.txSignedChanSrc) - b.txSubmitterChanSrc = make(chan TimestampedValue[common.Hash], 10) - txSubmitterChanDst1, txSubmitterChanDst2 := TeeChan(b.txSubmitterChanSrc) - - txReceiptChan := make(chan TimestampedValue[*geth_types.Receipt], 10) - caffBlockChan := make(chan *geth_types.Header, 1024) - verifyBlockChan := make(chan *geth_types.Header, 1024) - seqBlockChain := make(chan *geth_types.Header, 1024) - b.annotatedBlockChanSrc = make(chan TimestampedValue[AnnotatedBlockHash], 10) - - b.stats = &BenchmarkStats{ - Created: make(map[common.Hash]TimestampedValue[*geth_types.Transaction], 1024), - Submitted: make(map[common.Hash]TimestampedValue[common.Hash], 1024), - Receipts: make(map[common.Hash]TimestampedValue[*geth_types.Receipt], 1024), - SeqReceipts: make(map[common.Hash]TimestampedValue[common.Hash], 1024), - CaffReceipts: make(map[common.Hash]TimestampedValue[common.Hash], 1024), - VerifyReceipts: make(map[common.Hash]TimestampedValue[common.Hash], 1024), - } - - b.signers = NewCancelContext(ctx) - b.submitter = NewCancelContext(ctx) - b.receipt = NewCancelContext(ctx) - b.subscriptions = NewCancelContext(ctx) - b.metricRecorder = NewCancelContext(ctx) - - b.metricRecorder.wg.Add(1) - go WorkerRecordTimestampedEvents(b.metricRecorder.ctx, &b.metricRecorder.wg, txSignedChanDst2, txSubmitterChanDst2, txReceiptChan, b.annotatedBlockChanSrc, b.stats) - - if l2Caff != nil { - b.subscriptions.wg.Add(1) - go WorkerConsumeBlockHeaders(b.subscriptions.ctx, &b.subscriptions.wg, env.RoleCaffNode, caffBlockChan, b.annotatedBlockChanSrc) - cafSub, err := l2Caff.SubscribeNewHead(ctx, caffBlockChan) - if err != nil { - panic(err) - } - b.caffSubscription = cafSub - } - - if l2Verif != nil { - b.subscriptions.wg.Add(1) - go WorkerConsumeBlockHeaders(b.subscriptions.ctx, &b.subscriptions.wg, e2esys.RoleVerif, verifyBlockChan, b.annotatedBlockChanSrc) - verifSub, err := l2Verif.SubscribeNewHead(ctx, verifyBlockChan) - if err != nil { - panic(err) - } - b.verifySubscription = verifSub - } - - if l2SeqClient != nil { - b.subscriptions.wg.Add(1) - go WorkerConsumeBlockHeaders(b.subscriptions.ctx, &b.subscriptions.wg, e2esys.RoleSeq, seqBlockChain, b.annotatedBlockChanSrc) - seqSub, err := l2SeqClient.SubscribeNewHead(ctx, seqBlockChain) - if err != nil { - panic(err) - } - b.seqSubscription = seqSub - for i := 0; i < config.NumSubmitTransactionWorkers; i++ { - b.submitter.wg.Add(1) - go WorkerSubmitSignedTransaction(b.submitter.ctx, &b.submitter.wg, txSignedChanDst1, b.txSubmitterChanSrc, l2SeqClient) - } - - for i := 0; i < config.NumReceiptWorkers; i++ { - b.receipt.wg.Add(1) - go WorkerProcessL2Receipt(b.receipt.ctx, &b.receipt.wg, txSubmitterChanDst1, txReceiptChan, l2SeqClient) - } - - for _, submitter := range config.Submitters { - b.signers.wg.Add(1) - go WorkerSignTransaction(b.signers.ctx, &b.signers.wg, submitter.Interval, b.txSignedChanSrc, submitter.Key, submitter.Signer, submitter.ChainID, submitter.To, submitter.Value) - } - } -} - -// stop will stop the workers spawned by the benchmarks. It does this -// by signalizing to the contexts that govern them that they should stop. -// The method will then wait for all of the workers to exit before returning. -// It will also ensure that it closes all of the channels opened to -// facilitate the communication between the workers. -func (b *benchmarkState) stop() { - if !b.running { - // We are not running, so we have nothing to stop. - return - } - - b.metricRecorder.cancel() - b.subscriptions.cancel() - b.submitter.cancel() - b.signers.cancel() - b.receipt.cancel() - - // We want to stop everything, but we want to do so in a manner that allows - // for us to clean up effectively. - // We also want to do so in a way that minimizes the data we are going to - // lose. That means that we still want to process the data that was already - // generated in-flight. All the worker queues should be drained effectively - // before we we return. - // - // In order to do this, we need to cancel the contexts for the workers in - // order. This should happen in a down stream fashion. - - // Wait for the signer workers to finish. - b.signers.wg.Wait() - close(b.txSignedChanSrc) - - // Now the submitters can be stopped. - // They should automatically stop once they see that the channel they - // are reading from is closed. - b.submitter.wg.Wait() - close(b.txSubmitterChanSrc) - - // Now the receipt workers can be stopped. - // They should automatically stop once they see that the channel they - // are reading from is closed. - b.receipt.wg.Wait() - - // Cancel the subscriptions to the new heads, and - // wait for the subscription workers to finish. - b.verifySubscription.Unsubscribe() - b.caffSubscription.Unsubscribe() - b.subscriptions.wg.Wait() - close(b.annotatedBlockChanSrc) - - // Wait for the metric recorder to finish. - b.metricRecorder.wg.Wait() - - // At this point all goroutines and workers should be stopped and we - // should be synchronized. - b.running = false -} - -// RunWithContext is a function that runs the benchmark with the given -// context. It will start the benchmark and then wait for the context to -// be done. Once the context is done, it will stop the benchmark and -// return the statistics for the benchmark. -func (b *benchmarkState) RunWithContext(oCtx context.Context) (BenchmarkStats, error) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - b.start(ctx) - - // Wait for the context to be done - <-oCtx.Done() - - // We are done, so we can stop the benchmark. - b.stop() - - // Return the stats - return *b.stats, nil -} - -// CreateBenchmarker is a function that creates a new benchmarker with the -// given options. It will create a new benchmarker with the default -// configuration and then apply the given options to the benchmarker. -// It will return the benchmarker. -// -// NOTE: The Benchmarker returned is not started by default. You must call -// the RunWithContext function in order to start the benchmarker. -// Additionally, the Benchmarker will run until the context passed to the -// RunContext function is done. -// -// NOTE: This Benchmarker is designed to supply a load to the L2 Sequencer and -// to track and to track those transactions through their receipt on the -// L2 Sequencer. From there we can track the transaction via the block -// hashes being received from the block headers streamed from the Caff -// node, the L2 Verifier node, and the L2 Sequencer. -// -// NOTE: The benchmarker is flexible and allows for a subset of Client Nodes -// to be used. If you want to get the full set of metrics, you should supply -// the L2 Sequencer, the Caff Node, and the L2 Verifier Nodes. Anything less -// and the full picture cannot be obtained, or even reasoned about. -func CreateBenchmarker( - ctx context.Context, - options ...BenchmarkOption, -) Benchmarker { - config := &BenchmarkConfig{ - NumSubmitTransactionWorkers: runtime.NumCPU(), - NumReceiptWorkers: runtime.NumCPU(), - } - - for _, opt := range options { - opt(config) - } - - return &benchmarkState{ - cfg: *config, - } -} diff --git a/espresso/environment/benchmark/channel.go b/espresso/environment/benchmark/channel.go deleted file mode 100644 index f92f1e1ef2b..00000000000 --- a/espresso/environment/benchmark/channel.go +++ /dev/null @@ -1,21 +0,0 @@ -package benchmark - -// TeeChan is a helper function that takes a channel that is expected to be -// send to, (the source channel) and returns two channels that should be -// submitted to. -func TeeChan[T any](src <-chan T) (<-chan T, <-chan T) { - dst1 := make(chan T, cap(src)) - dst2 := make(chan T, cap(src)) - - go func() { - for v := range src { - dst1 <- v - dst2 <- v - } - - close(dst1) - close(dst2) - }() - - return dst1, dst2 -} diff --git a/espresso/environment/benchmark/context.go b/espresso/environment/benchmark/context.go deleted file mode 100644 index cfb7b461151..00000000000 --- a/espresso/environment/benchmark/context.go +++ /dev/null @@ -1,49 +0,0 @@ -package benchmark - -import ( - "context" - "sync" - "time" -) - -// waitGroupContext is a helpful struct that facilitates the -// pieces of a worker that are important to keep track of in order -// to facilitate their cancellation, shutdown, and synchronization. -type waitGroupContext struct { - wg sync.WaitGroup - ctx context.Context - cancel context.CancelFunc -} - -// Deadline implements context.Context. -func (w *waitGroupContext) Deadline() (deadline time.Time, ok bool) { - return w.ctx.Deadline() -} - -// Done implements context.Context. -func (w *waitGroupContext) Done() <-chan struct{} { - return w.ctx.Done() -} - -// Err implements context.Context. -func (w *waitGroupContext) Err() error { - return w.ctx.Err() -} - -// Value implements context.Context. -func (w *waitGroupContext) Value(key any) any { - return w.ctx.Value(key) -} - -var _ context.Context = (*waitGroupContext)(nil) - -// NewCancelContext creates a new context that is cancellable and -// has a wait group associated with it. -func NewCancelContext(ctx context.Context) waitGroupContext { - ctx, cancel := context.WithCancel(ctx) - return waitGroupContext{ - wg: sync.WaitGroup{}, - ctx: ctx, - cancel: cancel, - } -} diff --git a/espresso/environment/benchmark/metrics.go b/espresso/environment/benchmark/metrics.go deleted file mode 100644 index 20b047b93a6..00000000000 --- a/espresso/environment/benchmark/metrics.go +++ /dev/null @@ -1,318 +0,0 @@ -package benchmark - -import ( - "math" - "math/big" - "slices" - "time" - - geth_types "github.com/ethereum/go-ethereum/core/types" -) - -// SingleL2TransactionMetric is a struct that holds metric information for a -// single transaction submitted to the L2 Sequencer. -// It is meant to hold information about the transaction's lifecycle, and -// progression through the system and it's various milestones. -// -// This information can then be aggregated and used to calculate and compare -// the performance of the system under various confirmations and load -// conditions. -type SingleL2TransactionMetric struct { - SignedTransaction *geth_types.Transaction - Receipt *geth_types.Receipt - - LocalCreated time.Time - LocalSubmitted time.Time - LocalReceipt time.Time - SeqReceipt time.Time - CaffReceipt time.Time - VerifyReceipt time.Time -} - -// Convert to Tracked Transactions -func (s *BenchmarkStats) IndividualTransactionMetrics() []SingleL2TransactionMetric { - transactions := make([]SingleL2TransactionMetric, 0, len(s.Created)) - - // Grab all of the transactions that were created, and track them - for txHash, tx := range s.Created { - value := SingleL2TransactionMetric{ - SignedTransaction: tx.Value, - LocalCreated: tx.Timestamp, - } - - if submitted, ok := s.Submitted[txHash]; ok { - value.LocalSubmitted = submitted.Timestamp - } - - if receipt, ok := s.Receipts[txHash]; ok { - value.Receipt = receipt.Value - value.LocalReceipt = receipt.Timestamp - } - - receipt := value.Receipt - if receipt == nil { - transactions = append(transactions, value) - continue - } - - if seqReceipt, ok := s.SeqReceipts[receipt.BlockHash]; ok { - value.SeqReceipt = seqReceipt.Timestamp - } - - if caffReceipt, ok := s.CaffReceipts[receipt.BlockHash]; ok { - value.CaffReceipt = caffReceipt.Timestamp - } - - if verifyReceipt, ok := s.VerifyReceipts[receipt.BlockHash]; ok { - value.VerifyReceipt = verifyReceipt.Timestamp - } - - transactions = append(transactions, value) - } - - return transactions -} - -// SplitIndividualTransactionMetrics is a function that takes a slice of -// SingleL2TransactionMetric and splits it into two slices: one for -// complete transactions and one for incomplete transactions. A -// transaction is considered complete if it has a receipt and a -// caff receipt. Otherwise, it is considered incomplete. -func SplitIndividualTransactionMetrics( - transactions []SingleL2TransactionMetric, -) (complete, incomplete []SingleL2TransactionMetric) { - complete = make([]SingleL2TransactionMetric, 0, len(transactions)) - incomplete = make([]SingleL2TransactionMetric, 0, len(transactions)) - var zeroTime time.Time - - for _, tx := range transactions { - if tx.Receipt != nil && !tx.CaffReceipt.Equal(zeroTime) && !tx.VerifyReceipt.Equal(zeroTime) { - complete = append(complete, tx) - } else { - incomplete = append(incomplete, tx) - } - } - - return complete, incomplete -} - -// integer represents all of the integer types in Go. -type integer interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 -} - -// SampleSummary is a struct that holds the summary statistics of the given -// samples. -// It includes various measurements that are useful for understanding the -// distribution of the samples, and several key values. -type SampleSummary[T integer] struct { - Count int - Min T - Max T - Mean T - Median T - P99 T - P90 T - P75 T - P50 T - P25 T - P10 T - P01 T - StdDev T -} - -// SummarySamples is a function that takes a slice of sample values and then -// returns a SampleSummary struct that contains the summary statistics. -func SummarizeSamples[T integer](samples []T) SampleSummary[T] { - if len(samples) <= 0 { - return SampleSummary[T]{} - } - - // Sort the samples - slices.Sort(samples) - - l := len(samples) - p1Index := l * 1 / 100 - p10Index := l * 10 / 100 - p25Index := l * 25 / 100 - p50Index := l * 50 / 100 - p75Index := l * 75 / 100 - p90Index := l * 90 / 100 - p99Index := l * 99 / 100 - - metric := SampleSummary[T]{ - Count: len(samples), - Min: samples[0], - Max: samples[l-1], - Median: samples[p50Index], - P99: samples[p99Index], - P90: samples[p90Index], - P75: samples[p75Index], - P50: samples[p50Index], - P25: samples[p25Index], - P10: samples[p10Index], - P01: samples[p1Index], - } - - total := new(big.Int) - for _, sample := range samples { - total.Add(total, big.NewInt(int64(sample))) - } - mean := new(big.Int).Div(total, big.NewInt(int64(len(samples)))) - metric.Mean = T(mean.Int64()) - - // Calculate the standard deviation - variance := new(big.Int) - for _, duration := range samples { - v := duration - metric.Mean - variance.Add(variance, big.NewInt(int64(v*v))) - } - variance = variance.Div(variance, big.NewInt(int64(len(samples)))) - metric.StdDev = T(math.Sqrt(float64(variance.Int64()))) - - return metric -} - -// TimingMetrics is a struct that holds the timing metrics for various stages -// of the transaction lifecycle. -// It is currently populated with only a few timing references, but can be -// expanded to include more as necessary. -type TimingMetrics struct { - CreatedToSubmitted SampleSummary[time.Duration] - SubmittedToLocalReceipt SampleSummary[time.Duration] - SubmittedToReceipt SampleSummary[time.Duration] - ReceiptToCaff SampleSummary[time.Duration] - ReceiptToVerify SampleSummary[time.Duration] -} - -// ComputeCompletedTransactionStatistics is a function that takes a -// slice of SingleL2TransactionMetric and computes the statistics for -// the completed transactions. It returns a TimingMetrics struct that -// contains the summary statistics for the completed transactions. -// -// NOTE: This is tracking statistics on a transaction level. So it will -// exclude blocks that don't have any of the submitted transactions within -// them. -func ComputeCompletedTransactionStatistics(completed []SingleL2TransactionMetric) TimingMetrics { - var zeroTime time.Time - var createdToSubmittedSamples []time.Duration - var submittedToReceiptSamples []time.Duration - var submittedToLocalReceiptSamples []time.Duration - var receiptToCaffSamples []time.Duration - var receiptToVerifySamples []time.Duration - - for _, tx := range completed { - if tx.LocalCreated.Equal(zeroTime) { - continue - } - - if tx.LocalSubmitted.Equal(zeroTime) { - continue - } - - createdToSubmittedSample := tx.LocalReceipt.Sub(tx.LocalSubmitted) - createdToSubmittedSamples = append(createdToSubmittedSamples, createdToSubmittedSample) - - if tx.LocalReceipt.Equal(zeroTime) { - continue - } - - submittedToReceiptSample := tx.LocalReceipt.Sub(tx.LocalSubmitted) - submittedToReceiptSamples = append(submittedToReceiptSamples, submittedToReceiptSample) - - if !tx.SeqReceipt.Equal(zeroTime) { - submittedToLocalReceiptSample := tx.SeqReceipt.Sub(tx.LocalSubmitted) - submittedToLocalReceiptSamples = append(submittedToLocalReceiptSamples, submittedToLocalReceiptSample) - } - - if !tx.CaffReceipt.Equal(zeroTime) { - receiptToCaffSample := tx.CaffReceipt.Sub(tx.SeqReceipt) - receiptToCaffSamples = append(receiptToCaffSamples, receiptToCaffSample) - } - - if !tx.VerifyReceipt.Equal(zeroTime) { - receiptToVerifySample := tx.VerifyReceipt.Sub(tx.SeqReceipt) - receiptToVerifySamples = append(receiptToVerifySamples, receiptToVerifySample) - } - } - - return TimingMetrics{ - CreatedToSubmitted: SummarizeSamples(createdToSubmittedSamples), - SubmittedToReceipt: SummarizeSamples(submittedToReceiptSamples), - SubmittedToLocalReceipt: SummarizeSamples(submittedToLocalReceiptSamples), - ReceiptToCaff: SummarizeSamples(receiptToCaffSamples), - ReceiptToVerify: SummarizeSamples(receiptToVerifySamples), - } -} - -// ComputeRawMetricsStatistics is a function that takes a BenchmarkStats -// struct and computes the statistics for the transactions. It returns a -// TimingMetrics struct that contains the summary statistics for the -// transactions, and the blocks. -// -// NOTE: This attempts to track all references. Any transaction based metric -// will be a necessarily be on a transaction level. However, the sequencer and -// verifier metrics are on the block level. This should allow for the tracking -// of blocks that do not have any of the submitted transactions within them. -// But it also means that the there will be fewer samples that are weight based -// on the number of transactions within the block. -func ComputeRawMetricsStatistics(stats BenchmarkStats) TimingMetrics { - var createdToSubmittedSamples []time.Duration - var submittedToReceiptSamples []time.Duration - var submittedToLocalReceiptSamples []time.Duration - var receiptToCaffSamples []time.Duration - var receiptToVerifySamples []time.Duration - - // Inspect the transactions - for txHash, created := range stats.Created { - submitted, ok := stats.Submitted[txHash] - if !ok { - continue - } - - createdToSubmittedSamples = append(createdToSubmittedSamples, submitted.Timestamp.Sub(created.Timestamp)) - } - - // This is specific Transaction receipts - for txHash, localReceipt := range stats.Receipts { - submitted, ok := stats.Submitted[txHash] - if !ok { - continue - } - - submittedToLocalReceiptSamples = append(submittedToReceiptSamples, localReceipt.Timestamp.Sub(submitted.Timestamp)) - seqReceipt, ok := stats.SeqReceipts[localReceipt.Value.BlockHash] - if !ok { - continue - } - - submittedToReceiptSamples = append(submittedToReceiptSamples, seqReceipt.Timestamp.Sub(submitted.Timestamp)) - } - - for blockHash, caffReceipt := range stats.CaffReceipts { - seqReceipt, ok := stats.SeqReceipts[blockHash] - if !ok { - continue - } - - receiptToCaffSamples = append(receiptToCaffSamples, caffReceipt.Timestamp.Sub(seqReceipt.Timestamp)) - } - - for blockHash, verifyReceipt := range stats.VerifyReceipts { - seqReceipt, ok := stats.SeqReceipts[blockHash] - if !ok { - continue - } - - receiptToVerifySamples = append(receiptToVerifySamples, verifyReceipt.Timestamp.Sub(seqReceipt.Timestamp)) - } - - return TimingMetrics{ - CreatedToSubmitted: SummarizeSamples(createdToSubmittedSamples), - SubmittedToLocalReceipt: SummarizeSamples(submittedToLocalReceiptSamples), - SubmittedToReceipt: SummarizeSamples(submittedToReceiptSamples), - ReceiptToCaff: SummarizeSamples(receiptToCaffSamples), - ReceiptToVerify: SummarizeSamples(receiptToVerifySamples), - } -} diff --git a/espresso/environment/benchmark/workers.go b/espresso/environment/benchmark/workers.go deleted file mode 100644 index 1cb0fa67810..00000000000 --- a/espresso/environment/benchmark/workers.go +++ /dev/null @@ -1,258 +0,0 @@ -package benchmark - -import ( - "context" - "crypto/ecdsa" - "math/big" - "sync" - "time" - - env "github.com/ethereum-optimism/optimism/espresso/environment" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum/go-ethereum/common" - geth_types "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -// WorkerSignTransaction is a function that is meant to be run as a goroutine. -// It will continually sign transactions with increasing nonces with the given -// details, until the given context is done. -func WorkerSignTransaction( - ctx context.Context, - wg *sync.WaitGroup, - interval time.Duration, - txChan chan<- TimestampedValue[*geth_types.Transaction], - key *ecdsa.PrivateKey, - signer geth_types.Signer, - chainID *big.Int, - to *common.Address, - value *big.Int, -) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - defer wg.Done() - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for nonce := uint64(0); true; nonce++ { - // Check to see if we should stop - select { - case <-ctx.Done(): - return - - case <-ticker.C: - // Time to submit a new transaction. - } - - tx := geth_types.MustSignNewTx(key, signer, &geth_types.DynamicFeeTx{ - ChainID: chainID, - Nonce: nonce, - To: to, - Value: big.NewInt(1), - GasTipCap: big.NewInt(10), - GasFeeCap: big.NewInt(200), - Gas: 21_000, - }) - - // Submit the Signed Transaction to the given channel - select { - case txChan <- WithTimestamp(tx): - case <-ctx.Done(): - return - } - } -} - -// WorkerSubmitSignedTransaction is a function that is meant to be run as a -// goroutine. It will continually request new signed transactions to submit, -// and will record the submission time of the transaction. -func WorkerSubmitSignedTransaction( - ctx context.Context, - wg *sync.WaitGroup, - signedTxChannel <-chan TimestampedValue[*geth_types.Transaction], - txSubmittedChan chan<- TimestampedValue[common.Hash], - client *ethclient.Client, -) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - defer wg.Done() - - for { - var tx TimestampedValue[*geth_types.Transaction] - var ok bool - - // Wait for work - select { - case <-ctx.Done(): - return - - case tx, ok = <-signedTxChannel: - if !ok { - // The channel was closed, so we have nothing more - // to process. - return - } - } - - // Submit the Signed Transaction to the given channel - err := client.SendTransaction(ctx, tx.Value) - if err != nil { - continue - } - - select { - case txSubmittedChan <- WithTimestamp(tx.Value.Hash()): - case <-ctx.Done(): - return - } - } -} - -// WorkerProcessL2Receipt is a function that is meant to be run as a -// goroutine. When a submitted transaction is received, it will wait for the -// receipt of the transaction to be available, and then send the receipt to -// the given channel. It will also record the time at which the receipt was -// received. -func WorkerProcessL2Receipt( - ctx context.Context, - wg *sync.WaitGroup, - txSubmittedChan <-chan TimestampedValue[common.Hash], - receiptChan chan<- TimestampedValue[*geth_types.Receipt], - client *ethclient.Client, -) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - defer wg.Done() - - for { - var txHash TimestampedValue[common.Hash] - var ok bool - - // Wait for work - select { - case <-ctx.Done(): - return - - case txHash, ok = <-txSubmittedChan: - if !ok { - // The channel was closed, so we have nothing more - // to process. - return - } - } - - receipt, err := wait.ForReceiptOK(ctx, client, txHash.Value) - if err != nil { - log.Error("Failed to get receipt", "err", err) - continue - } - - select { - case receiptChan <- WithTimestamp(receipt): - case <-ctx.Done(): - return - } - } -} - -// AnnotatedBlockHash is a struct that holds a block hash and a label. -// It is used to annotate block hashes with a label for easier -// identification in logs and metrics. -type AnnotatedBlockHash struct { - Label string - BlockHash common.Hash -} - -// WorkerConsumeBlockHeaders is a function that is meant to be run as a -// goroutine. It will continually receive block headers from the given -// channel, and will send the block hash to the given channel with a -// timestamp. -func WorkerConsumeBlockHeaders( - ctx context.Context, - wg *sync.WaitGroup, - label string, - headerChan <-chan *geth_types.Header, - receivedChain chan<- TimestampedValue[AnnotatedBlockHash], -) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - defer wg.Done() - for { - var header *geth_types.Header - var ok bool - select { - case <-ctx.Done(): - return - case header, ok = <-headerChan: - if !ok { - // The channel was closed, so we have nothing more - // to process. - return - } - } - - select { - case receivedChain <- WithTimestamp(AnnotatedBlockHash{ - BlockHash: header.Hash(), - Label: label, - }): - case <-ctx.Done(): - return - } - } -} - -// WorkerRecordTimestampedEvents is a function that is meant to be run as a -// goroutine. It will continually receive events from the given channels -// and will record the events in the given stats struct. -func WorkerRecordTimestampedEvents( - ctx context.Context, - wg *sync.WaitGroup, - txCreated <-chan TimestampedValue[*geth_types.Transaction], - seqSubmissions <-chan TimestampedValue[common.Hash], - seqReceipts <-chan TimestampedValue[*geth_types.Receipt], - annotatedBlockHeaders <-chan TimestampedValue[AnnotatedBlockHash], - stats *BenchmarkStats, -) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - defer wg.Done() - for { - select { - case <-ctx.Done(): - return - case tx, ok := <-txCreated: - if !ok { - continue - } - stats.Created[tx.Value.Hash()] = tx - case tx, ok := <-seqSubmissions: - if !ok { - continue - } - stats.Submitted[tx.Value] = tx - case receipt, ok := <-seqReceipts: - if !ok { - continue - } - stats.Receipts[receipt.Value.TxHash] = receipt - case annotatedBlock, ok := <-annotatedBlockHeaders: - if !ok { - continue - } - - switch annotatedBlock.Value.Label { - default: - // Not sure what to do for this case - case env.RoleCaffNode: - stats.CaffReceipts[annotatedBlock.Value.BlockHash] = TimestampedValue[common.Hash]{Value: annotatedBlock.Value.BlockHash, Timestamp: annotatedBlock.Timestamp} - case e2esys.RoleSeq: - stats.SeqReceipts[annotatedBlock.Value.BlockHash] = TimestampedValue[common.Hash]{Value: annotatedBlock.Value.BlockHash, Timestamp: annotatedBlock.Timestamp} - case e2esys.RoleVerif: - stats.VerifyReceipts[annotatedBlock.Value.BlockHash] = TimestampedValue[common.Hash]{Value: annotatedBlock.Value.BlockHash, Timestamp: annotatedBlock.Timestamp} - } - } - } -} diff --git a/espresso/environment/espresso_caff_node.go b/espresso/environment/espresso_caff_node.go deleted file mode 100644 index d9d278cef6f..00000000000 --- a/espresso/environment/espresso_caff_node.go +++ /dev/null @@ -1,164 +0,0 @@ -package environment - -import ( - "context" - "errors" - "fmt" - "log/slog" - "net" - "net/url" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/espresso" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/opnode" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-node/chaincfg" - "github.com/ethereum-optimism/optimism/op-node/config" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/clock" - "github.com/ethereum-optimism/optimism/op-service/testlog" -) - -const ( - RoleCaffNode = "caff-node" -) - -// ErrorFailedToParseSequencerPort is returned when the sequencer port -// cannot be parsed from the espresso dev node. -type ErrorFailedToParseSequencerPort struct { - Have string -} - -// Error implements error -func (e ErrorFailedToParseSequencerPort) Error() string { - return fmt.Sprintf("failed to parse sequencer port URL: \"%s\"", e.Have) -} - -// ErrorFailedToStartCaffNodeGeth is returned when the caff node geth -// instance fails to start. -type ErrorFailedToStartCaffNodeGeth struct { - Cause error -} - -// Error implements error -func (e ErrorFailedToStartCaffNodeGeth) Error() string { - return fmt.Sprintf("failed to start caff node geth instance: %v", e.Cause) -} - -// Unwrap allows for the root cause of the error to be extracted. -func (e ErrorFailedToStartCaffNodeGeth) Unwrap() error { - return e.Cause -} - -// ErrorFailedToStartCaffNodeOpNode is returned when the caff node op -// node instance fails to start. -type ErrorFailedToStartCaffNodeOpNode struct { - Cause error -} - -// Error implements error -func (e ErrorFailedToStartCaffNodeOpNode) Error() string { - return fmt.Sprintf("failed to start caff node op node instance: %v", e.Cause) -} - -// Unwrap allows for the root cause of the error to be extracted. -func (e ErrorFailedToStartCaffNodeOpNode) Unwrap() error { - return e.Cause -} - -// CaffNodeInstance is a wrapper around the caff node geth instance and op node -// instance, for the Caff Node. It is used to interact with the caff node. -type CaffNodeInstance struct { - OpNode *opnode.Opnode - Geth *geth.GethInstance -} - -type ConfigOption func(*config.Config) - -// Close closes the caff node geth instance and op node instance. -func (c *CaffNodeInstance) Close(ctx context.Context) error { - return errors.Join(c.OpNode.Stop(ctx), c.Geth.Close()) -} - -// LaunchCaffNode launches a caff node in the given system. It will -// configure the caff node to connect to the given espresso dev node. -func LaunchCaffNode(t *testing.T, system *e2esys.System, espressoDevNode EspressoDevNode, opts ...ConfigOption) (*CaffNodeInstance, error) { - sequencerHostAndPort := espressoDevNode.SequencerPort() - _, sequencerPort, err := net.SplitHostPort(sequencerHostAndPort) - if have, want := err, error(nil); have != want { - return nil, ErrorFailedToParseSequencerPort{Have: sequencerHostAndPort} - } - - u := url.URL{ - Scheme: "http", - Host: net.JoinHostPort("localhost", sequencerPort), - Path: "/", - } - - // Let's start the Caff Node now. - // Configure our caff-node geth instance - caffNodeGeth, err := geth.InitL2(RoleCaffNode, system.L2GenesisCfg, system.Cfg.JWTFilePath) - if have, want := err, error(nil); have != want { - return nil, ErrorFailedToStartCaffNodeGeth{Cause: have} - } - - // start our caff-node geth instance - if have, want := caffNodeGeth.Node.Start(), error(nil); have != want { - return nil, ErrorFailedToStartCaffNodeGeth{Cause: have} - } - - system.EthInstances[RoleCaffNode] = caffNodeGeth - system.Cfg.Loggers[RoleCaffNode] = testlog.Logger(t, slog.LevelInfo).New("role", RoleCaffNode) - - // Make a copy - - caffNodeConfig := *system.Cfg.Nodes[e2esys.RoleVerif] - caffNodeConfig.L1ChainConfig = system.L1GenesisCfg.Config - caffNodeConfig.Rollup = *system.RollupConfig - caffNodeConfig.Rollup.CaffNodeConfig = rollup.CaffNodeConfigFromCLIConfig(espresso.CLIConfig{ - Enabled: true, - PollInterval: 30 * time.Millisecond, - // To create a valid multiple nodes client, we need to provide at least 2 URLs. - QueryServiceURLs: []string{u.String(), u.String()}, - L1URL: system.L1.UserRPC().RPC(), - RollupL1URL: system.L1.UserRPC().RPC(), - LightClientAddr: EspressoLightClientAddr(), - BatchAuthenticatorAddr: system.RollupConfig.BatchAuthenticatorAddress, - }) - - for _, opt := range opts { - opt(&caffNodeConfig) - } - - // Configure - e2esys.ConfigureL1(&caffNodeConfig, system.EthInstances[e2esys.RoleL1], system.L1BeaconEndpoint()) - e2esys.ConfigureL2(&caffNodeConfig, caffNodeGeth, system.Cfg.JWTSecret) - - // Create the Op Node Now - caffNodeConfig.Rollup.LogDescription(system.Cfg.Loggers[RoleCaffNode], chaincfg.L2ChainIDToNetworkDisplayName) - l := system.Cfg.Loggers[RoleCaffNode] - - var opNodeError error - caffNode, err := opnode.NewOpnode(l, &caffNodeConfig, clock.SystemClock, func(e error) { - opNodeError = e - }) - if have, want := err, error(nil); have != want { - // Clean up the Caff Node Geth instance - caffNodeGeth.Close() - return nil, ErrorFailedToStartCaffNodeOpNode{Cause: have} - } - - if have, want := opNodeError, error(nil); have != want { - caffNodeGeth.Close() - return nil, ErrorFailedToStartCaffNodeOpNode{Cause: have} - } - - // Alright, we should have our Caff Node Launched now. - - return &CaffNodeInstance{ - OpNode: caffNode, - Geth: caffNodeGeth, - }, nil -} diff --git a/espresso/environment/espresso_dev_net_launcher.go b/espresso/environment/espresso_dev_net_launcher.go index bf6322ebd0f..acfd03e0500 100644 --- a/espresso/environment/espresso_dev_net_launcher.go +++ b/espresso/environment/espresso_dev_net_launcher.go @@ -4,6 +4,7 @@ import ( "context" "testing" + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" ) @@ -94,6 +95,9 @@ type EspressoDevNode interface { // EspressoUrls returns the URLs of the Espresso node EspressoUrls() []string + // Client returns an Espresso query-service client for this dev node. + Client() espressoClient.EspressoClient + // Shut Down the Espresso Dev Node Stop() error } diff --git a/espresso/environment/optitmism_espresso_test_helpers.go b/espresso/environment/optitmism_espresso_test_helpers.go index c9356161561..d03105687e0 100644 --- a/espresso/environment/optitmism_espresso_test_helpers.go +++ b/espresso/environment/optitmism_espresso_test_helpers.go @@ -211,6 +211,11 @@ func (e *EspressoDevNodeContainerInfo) EspressoUrls() []string { return e.espressoUrls } +// Client returns an Espresso query-service client for this dev node. +func (e *EspressoDevNodeContainerInfo) Client() espressoClient.EspressoClient { + return espressoClient.NewClient(e.EspressoUrls()[0]) +} + var _ EspressoDevNode = (*EspressoDevNodeContainerInfo)(nil) // getPort is a helper function that takes the original port and returns @@ -452,6 +457,11 @@ func (e *EspressoDevNodeDockerContainerInfo) EspressoUrls() []string { return e.espressoUrls } +// Client returns an Espresso query-service client for this dev node. +func (e *EspressoDevNodeDockerContainerInfo) Client() espressoClient.EspressoClient { + return espressoClient.NewClient(e.EspressoUrls()[0]) +} + var _ EspressoDevNode = (*EspressoDevNodeDockerContainerInfo)(nil) // SequencerPort implements EspressoDevNode @@ -977,7 +987,7 @@ func Stop(t *testing.T, toStop any, options ...StopOption) { } // Waits for an Espresso transaction to be confirmed using its hash. -func WaitForEspressoTx(ctx context.Context, txHash *espressoCommon.TaggedBase64, espressoClient *espressoClient.MultipleNodesClient) error { +func WaitForEspressoTx(ctx context.Context, txHash *espressoCommon.TaggedBase64, client espressoClient.EspressoClient) error { const transactionFetchTimeout = 4 * time.Second const transactionFetchInterval = 100 * time.Millisecond @@ -991,7 +1001,7 @@ func WaitForEspressoTx(ctx context.Context, txHash *espressoCommon.TaggedBase64, for { select { case <-ticker.C: - _, err := espressoClient.FetchTransactionByHash(ctx, txHash) + _, err := client.FetchTransactionByHash(ctx, txHash) if err == nil { return nil } diff --git a/espresso/ethclient.go b/espresso/ethclient.go index 38328ce88f8..6be07c9cad9 100644 --- a/espresso/ethclient.go +++ b/espresso/ethclient.go @@ -3,9 +3,7 @@ package espresso import ( "context" "fmt" - "math/big" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -13,36 +11,6 @@ import ( "github.com/ethereum-optimism/optimism/espresso/bindings" ) -// AdaptL1BlockRefClient is a wrapper around eth.L1BlockRef that implements the espresso.L1Client interface -type AdaptL1BlockRefClient struct { - L1Client *ethclient.Client -} - -// NewAdaptL1BlockRefClient creates a new L1BlockRefClient -func NewAdaptL1BlockRefClient(L1Client *ethclient.Client) *AdaptL1BlockRefClient { - return &AdaptL1BlockRefClient{ - L1Client: L1Client, - } -} - -// HeaderHashByNumber implements the espresso.L1Client interface -func (c *AdaptL1BlockRefClient) HeaderHashByNumber(ctx context.Context, number *big.Int) (common.Hash, error) { - expectedL1BlockRef, err := c.L1Client.HeaderByNumber(ctx, number) - if err != nil { - return common.Hash{}, err - } - - return expectedL1BlockRef.Hash(), nil -} - -func (c *AdaptL1BlockRefClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { - return c.L1Client.CodeAt(ctx, contract, blockNumber) -} - -func (c *AdaptL1BlockRefClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - return c.L1Client.CallContract(ctx, call, blockNumber) -} - // FetchEspressoBatcherAddress reads the Espresso batcher address from the BatchAuthenticator // contract on L1. This is used by the caff node to determine which address signed // Espresso batches, since the Espresso batcher may use a different key than the diff --git a/espresso/interface.go b/espresso/interface.go deleted file mode 100644 index 8445baf08c6..00000000000 --- a/espresso/interface.go +++ /dev/null @@ -1,77 +0,0 @@ -package espresso - -import ( - "context" - - op "github.com/EspressoSystems/espresso-streamers/op" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/common" -) - -// EspressoStreamer defines the interface for the Espresso streamer. -type EspressoStreamer[B op.Batch] interface { - // Update will update the `EspressoStreamer“ by attempting to ensure that - // the next call to the `Next` method will return a `Batch`. - // - // It attempts to ensure the existence of a next batch, provided no errors - // occur when communicating with HotShot, by processing Blocks retrieved - // from `HotShot` in discreet batches. If each processing of a batch of - // blocks will not yield a new `Batch`, then it will continue to process - // the next batch of blocks from HotShot until it runs out of blocks to - // process. - // - // NOTE: this method is best effort. It is unable to guarantee that the - // next call to `Next` will return a batch. However, the only things - // that will prevent the next call to `Next` from returning a batch is if - // there are no more HotShot blocks to process currently, or if an error - // occurs when communicating with HotShot. - Update(ctx context.Context) error - - // Refresh updates the local references of the EspressoStreamer to the - // specified values. - // - // These values can be used to help determine whether the Streamer needs - // to be reset or not. - // - // NOTE: This will only automatically reset the Streamer if the - // `safeBatchNumber` moves backwards. - Refresh(ctx context.Context, finalizedL1 eth.L1BlockRef, safeBatchNumber uint64, safeL1Origin eth.BlockID) error - - // RefreshSafeL1Origin updates the safe L1 origin for the streamer. This is - // used to help the streamer determine if it needs to be reset or not based - // on the safe L1 origin moving backwards. - // - // NOTE: This will only automatically reset the Streamer if the - // `safeL1Origin` moves backwards. - RefreshSafeL1Origin(safeL1Origin eth.BlockID) - - // Reset will reset the Streamer to the last known good safe state. - // This generally means resetting to the last know good safe batch - // position, but in the case of consuming blocks from Espresso, it will - // also reset the starting Espresso block position to the last known - // good safe block position there as well. - Reset() - - // UnmarshalBatch is a convenience method that allows the caller to - // attempt to unmarshal a batch from the provided byte slice. - UnmarshalBatch(b []byte) (*B, error) - - // HasNext checks to see if there are any batches left to read in the - // streamer. - HasNext(ctx context.Context) bool - - // Next attempts to return the next batch from the streamer. If there - // are no batches left to read, at the moment of the call, it will return - // nil. - Next(ctx context.Context) *B - - // Peek attempts to return the next batch from the streamer without advancing the streamer's position. - // If there are no batches left to read, at the moment of the call, it will return nil. - Peek(ctx context.Context) *B - - // SetProperHead drains stale/wrong-fork entries from the buffer front, - // positioning it at the correct fork for the next Peek call. Should be called - // when Peek returns a batch whose parentHash doesn't match the current chain tip. - // No-ops if headBatch's block number doesn't match the expected next batch position. - SetProperHead(parentHash common.Hash) -} diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index dc1154d176a..46c227e8ea0 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -21,7 +21,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum-optimism/optimism/espresso" + op "github.com/EspressoSystems/espresso-streamers/op" + "github.com/EspressoSystems/espresso-streamers/op/derivation" "github.com/ethereum-optimism/optimism/espresso/logmodule" altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/batcher/throttler" @@ -136,7 +137,7 @@ type BatchSubmitter struct { publishSignal chan pubInfo espressoSubmitter *espressoTransactionSubmitter - espressoStreamer espresso.EspressoStreamer[derive.EspressoBatch] + espressoStreamer op.EspressoStreamer[derivation.EspressoBatch] // Group to limit number of concurrent batches waiting for approval // from BatchAuthenticator contract, only relevant when running with Espresso enabled teeAuthGroup errgroup.Group diff --git a/op-batcher/batcher/espresso.go b/op-batcher/batcher/espresso.go index 985a1ca7eed..fbd1eb043b2 100644 --- a/op-batcher/batcher/espresso.go +++ b/op-batcher/batcher/espresso.go @@ -18,6 +18,7 @@ import ( espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" tagged_base64 "github.com/EspressoSystems/espresso-network/sdks/go/tagged-base64" espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types" + "github.com/EspressoSystems/espresso-streamers/op/derivation" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -810,7 +811,7 @@ func (s *espressoTransactionSubmitter) Start() { // Returns error only if batch conversion fails, otherwise it is infallible, as the goroutine // will retry publishing until successful. func (l *BatchSubmitter) queueBlockToEspresso(ctx context.Context, block *types.Block) error { - espressoBatch, err := derive.BlockToEspressoBatch(l.RollupConfig, block) + espressoBatch, err := derivation.BlockToEspressoBatch(l.RollupConfig, block) if err != nil { l.Log.Warn(logmodule.FailedToDeriveBatchFromBlock, "err", err) return fmt.Errorf("failed to derive batch from block: %w", err) @@ -865,7 +866,7 @@ func (l *BatchSubmitter) espressoSyncAndRefresh(ctx context.Context, newSyncStat // The expected parent is tip when tip is set. When tip is zero (channel manager was // just cleared), we fall back to safeL2.Hash if the batch is at exactly safeL2+1 — // the one position where we can set tip to the known safe head. Otherwise we accept the batch as-is. -func (l *BatchSubmitter) peekNextBatch(ctx context.Context, syncStatus *eth.SyncStatus) *derive.EspressoBatch { +func (l *BatchSubmitter) peekNextBatch(ctx context.Context, syncStatus *eth.SyncStatus) *derivation.EspressoBatch { l.channelMgrMutex.Lock() tip := l.channelMgr.tip l.channelMgrMutex.Unlock() @@ -932,7 +933,7 @@ func (l *BatchSubmitter) espressoBatchLoadingLoop(ctx context.Context, wg *sync. err = l.EspressoStreamer().Update(ctx) - var batch *derive.EspressoBatch + var batch *derivation.EspressoBatch for { diff --git a/op-batcher/batcher/espresso_driver.go b/op-batcher/batcher/espresso_driver.go index c8fe5bc4048..f997ae59c03 100644 --- a/op-batcher/batcher/espresso_driver.go +++ b/op-batcher/batcher/espresso_driver.go @@ -9,13 +9,11 @@ import ( espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" op "github.com/EspressoSystems/espresso-streamers/op" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum-optimism/optimism/espresso" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/EspressoSystems/espresso-streamers/op/derivation" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" ) // EspressoDriverSetup groups all Espresso-specific runtime state plumbed from @@ -56,7 +54,7 @@ func (a *batcherL1Adapter) CallContract(ctx context.Context, call ethereum.CallM } // EspressoStreamer returns the Espresso batch streamer for use by the service and tests. -func (l *BatchSubmitter) EspressoStreamer() espresso.EspressoStreamer[derive.EspressoBatch] { +func (l *BatchSubmitter) EspressoStreamer() op.EspressoStreamer[derivation.EspressoBatch] { return l.espressoStreamer } @@ -82,7 +80,7 @@ func (l *BatchSubmitter) setupEspressoStreamer() { l.Espresso.Client, lightClientIface, l.Log, - derive.CreateEspressoBatchUnmarshaler(), + derivation.CreateEspressoBatchUnmarshaler(), l.Config.Espresso.CaffeinationHeightEspresso, l.Config.Espresso.CaffeinationHeightL2, l.RollupConfig.BatchAuthenticatorAddress, diff --git a/op-batcher/batcher/espresso_service.go b/op-batcher/batcher/espresso_service.go index 2bbe528b6f9..3d6ccbb25e6 100644 --- a/op-batcher/batcher/espresso_service.go +++ b/op-batcher/batcher/espresso_service.go @@ -7,14 +7,14 @@ import ( espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" + op "github.com/EspressoSystems/espresso-streamers/op" + "github.com/EspressoSystems/espresso-streamers/op/derivation" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/hf/nitrite" - "github.com/ethereum-optimism/optimism/espresso" "github.com/ethereum-optimism/optimism/op-batcher/enclave" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" ) @@ -62,7 +62,7 @@ type EspressoBatcherConfig struct { } // EspressoStreamer returns the Espresso batch streamer driven by this batcher. -func (bs *BatcherService) EspressoStreamer() espresso.EspressoStreamer[derive.EspressoBatch] { +func (bs *BatcherService) EspressoStreamer() op.EspressoStreamer[derivation.EspressoBatch] { return bs.driver.espressoStreamer } diff --git a/op-e2e/e2eutils/opnode/opnode.go b/op-e2e/e2eutils/opnode/opnode.go index 7120d6230de..06ede0028ad 100644 --- a/op-e2e/e2eutils/opnode/opnode.go +++ b/op-e2e/e2eutils/opnode/opnode.go @@ -5,7 +5,6 @@ import ( "github.com/ethereum/go-ethereum/log" - op "github.com/EspressoSystems/espresso-streamers/op" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/services" "github.com/ethereum-optimism/optimism/op-node/config" "github.com/ethereum-optimism/optimism/op-node/metrics" @@ -23,14 +22,6 @@ type Opnode struct { node *rollupNode.OpNode } -// Get the Espresso streamer. -// -// Note: This function should be used carefully to avoid a stall, since it is a getter and does not -// create a new instance, which means the caller may deprive the node of the batches. -func (o *Opnode) EspressoStreamer() *op.BatchStreamer[derive.EspressoBatch] { - return o.node.EspressoStreamer() -} - func (o *Opnode) InteropRPC() (endpoint string, jwtSecret eth.Bytes32) { return o.node.InteropRPC() } diff --git a/op-node/node/node.go b/op-node/node/node.go index 4bdbcadb642..ee2316b994a 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -10,8 +10,6 @@ import ( "sync/atomic" "time" - op "github.com/EspressoSystems/espresso-streamers/op" - "github.com/hashicorp/go-multierror" "github.com/ethereum/go-ethereum" @@ -769,10 +767,6 @@ func initP2PSigner(ctx context.Context, cfg *config.Config, node *OpNode) (p2p.S return p2pSigner, err } -func (n *OpNode) EspressoStreamer() *op.BatchStreamer[derive.EspressoBatch] { - return n.l2Driver.SyncDeriver.Derivation.EspressoStreamer() -} - func (n *OpNode) Start(ctx context.Context) error { // If n.cfg.Driver.SequencerUseFinalized is true, the sequencer uses only finalized L1 blocks // for the L1 origin blocks. This is handled by finalized.finalized block fetcher which only diff --git a/op-node/rollup/derive/attributes_queue.go b/op-node/rollup/derive/attributes_queue.go index 94186151a10..358eb0d8574 100644 --- a/op-node/rollup/derive/attributes_queue.go +++ b/op-node/rollup/derive/attributes_queue.go @@ -59,8 +59,6 @@ type AttributesQueue struct { batch *SingularBatch concluding bool lastAttribs *AttributesWithParent - - espresso espressoAttributesQueue } type SingularBatchProvider interface { @@ -72,11 +70,10 @@ type SingularBatchProvider interface { func NewAttributesQueue(log log.Logger, cfg *rollup.Config, builder AttributesBuilder, prev SingularBatchProvider) *AttributesQueue { return &AttributesQueue{ - log: log, - config: cfg, - builder: builder, - prev: prev, - espresso: newEspressoAttributesQueue(log, cfg), + log: log, + config: cfg, + builder: builder, + prev: prev, } } @@ -84,14 +81,10 @@ func (aq *AttributesQueue) Origin() eth.L1BlockRef { return aq.prev.Origin() } -func (aq *AttributesQueue) NextAttributes(ctx context.Context, parent eth.L2BlockRef, l1Fetcher L1Fetcher) (*AttributesWithParent, error) { +func (aq *AttributesQueue) NextAttributes(ctx context.Context, parent eth.L2BlockRef) (*AttributesWithParent, error) { // Get a batch if we need it if aq.batch == nil { - var batch *SingularBatch - var concluding bool - var err error - batch, concluding, err = aq.espresso.nextBatch(ctx, parent, aq.config.BlockTime, l1Fetcher, aq.prev, aq.log) - + batch, concluding, err := aq.prev.NextBatch(ctx, parent) if err != nil { return nil, err } diff --git a/op-node/rollup/derive/espresso_batch.go b/op-node/rollup/derive/espresso_batch.go deleted file mode 100644 index fc22780bfd5..00000000000 --- a/op-node/rollup/derive/espresso_batch.go +++ /dev/null @@ -1,138 +0,0 @@ -package derive - -import ( - "bytes" - "context" - "fmt" - - espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types" - "github.com/ethereum-optimism/optimism/op-node/rollup" - opCrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" -) - -// A SingularBatch with block number attached to restore ordering -// when fetching from Espresso -type EspressoBatch struct { - BatchHeader *types.Header - Batch SingularBatch - L1InfoDeposit *types.Transaction - SignerAddress common.Address -} - -func (b EspressoBatch) Number() uint64 { - return b.BatchHeader.Number.Uint64() -} - -func (b EspressoBatch) L1Origin() eth.BlockID { - return b.Batch.Epoch() -} - -func (b EspressoBatch) Header() *types.Header { - return b.BatchHeader -} - -func (b EspressoBatch) Hash() common.Hash { - hash := crypto.Keccak256Hash(b.BatchHeader.Hash().Bytes(), b.L1InfoDeposit.Hash().Bytes()) - return hash -} - -func (b EspressoBatch) Signer() common.Address { - return b.SignerAddress -} - -func (b *EspressoBatch) ToEspressoTransaction(ctx context.Context, namespace uint64, signer opCrypto.ChainSigner) (*espressoCommon.Transaction, error) { - buf := new(bytes.Buffer) - err := rlp.Encode(buf, *b) - if err != nil { - return nil, fmt.Errorf("failed to encode batch: %w", err) - } - - batcherSignature, err := signer.Sign(ctx, crypto.Keccak256(buf.Bytes())) - - if err != nil { - return nil, fmt.Errorf("failed to create batcher signature: %w", err) - } - - payload := append(batcherSignature, buf.Bytes()...) - - return &espressoCommon.Transaction{Namespace: namespace, Payload: payload}, nil - -} - -func BlockToEspressoBatch(rollupCfg *rollup.Config, block *types.Block) (*EspressoBatch, error) { - if len(block.Transactions()) == 0 { - return nil, fmt.Errorf("Block doesn't contain any transactions") - } - - l1InfoDeposit := block.Transactions()[0] - if !l1InfoDeposit.IsDepositTx() { - return nil, fmt.Errorf("First transaction is not L1 info deposit") - } - - batch, _, err := BlockToSingularBatch(rollupCfg, block) - if err != nil { - return nil, err - } - - return &EspressoBatch{ - BatchHeader: block.Header(), - Batch: *batch, - L1InfoDeposit: l1InfoDeposit, - }, nil -} - -// CreateEspressoBatchUnmarshaler returns a function that can be used to -// unmarshal an Espresso transaction into an EspressoBatch. -// The signer address is recovered from the signature and stored on the batch -// for later verification in CheckBatch (two-phase verification). -func CreateEspressoBatchUnmarshaler() func(data []byte) (*EspressoBatch, error) { - return func(data []byte) (*EspressoBatch, error) { - return UnmarshalEspressoTransaction(data) - } -} - -func UnmarshalEspressoTransaction(data []byte) (*EspressoBatch, error) { - if len(data) < crypto.SignatureLength { - return nil, fmt.Errorf("transaction data too short: %d bytes, need at least %d", len(data), crypto.SignatureLength) - } - signatureData, batchData := data[:crypto.SignatureLength], data[crypto.SignatureLength:] - batchHash := crypto.Keccak256(batchData) - - signerKey, err := crypto.SigToPub(batchHash, signatureData) - if err != nil { - return nil, err - } - signer := crypto.PubkeyToAddress(*signerKey) - - var batch EspressoBatch - if err := rlp.DecodeBytes(batchData, &batch); err != nil { - return nil, err - } - batch.SignerAddress = signer - - return &batch, nil -} - -// NOTE: This function MUST guarantee no transient errors. It is allowed to fail only on -// invalid batches or in case of misconfiguration of the batcher, in which case it should fail -// for all batches. -func (b *EspressoBatch) ToBlock(rollupCfg *rollup.Config) (*types.Block, error) { - // Re-insert the deposit transaction - txs := []*types.Transaction{b.L1InfoDeposit} - for i, opaqueTx := range b.Batch.Transactions { - var tx types.Transaction - err := tx.UnmarshalBinary(opaqueTx) - if err != nil { - return nil, fmt.Errorf("could not decode tx %d: %w", i, err) - } - txs = append(txs, &tx) - } - return types.NewBlockWithHeader(b.BatchHeader).WithBody(types.Body{ - Transactions: txs, - }), nil -} diff --git a/op-node/rollup/derive/espresso_batch_test.go b/op-node/rollup/derive/espresso_batch_test.go deleted file mode 100644 index 05ef9c2f9d3..00000000000 --- a/op-node/rollup/derive/espresso_batch_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package derive_test - -import ( - "bytes" - "math/big" - "math/rand" - "slices" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-node/rollup" - derive "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - dtest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" -) - -var defaultTestRollUpConfig = &rollup.Config{ - Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}}, - L2ChainID: big.NewInt(1234), -} - -// compareHash is a helper function that compares two hashes. -func compareHash(a, b common.Hash) int { - if c := bytes.Compare(a[:], b[:]); c != 0 { - return c - } - return 0 -} - -// compareTransaction is a helper function that compares two transactions -// by only inspecting their hashes. -func compareTransaction(a, b *gethTypes.Transaction) int { - return compareHash(a.Hash(), b.Hash()) -} - -// compareHeader is a helper function that compares two headers -// by only inspecting their hashes. -func compareHeader(a, b *gethTypes.Header) int { - return compareHash(a.Hash(), b.Hash()) -} - -// compareWithdrawl is a helper function that compares two withdrawals -// by checking that their slice members compare equivalently. -func compareWithdrawl(a, b *gethTypes.Withdrawal) int { - if c := a.Index - b.Index; c != 0 { - return int(c) - } - - if c := a.Validator - b.Validator; c != 0 { - return int(c) - } - - if c := a.Address.Cmp(b.Address); c != 0 { - return c - } - - if c := a.Amount - b.Amount; c != 0 { - return int(c) - } - - return 0 -} - -// compareBody is a helper function that compares two bodies -// by checking that their slice members compare equivalently. -func compareBody(a, b *gethTypes.Body) int { - if c := slices.CompareFunc(a.Transactions, b.Transactions, compareTransaction); c != 0 { - return c - } - - if c := slices.CompareFunc(a.Uncles, b.Uncles, compareHeader); c != 0 { - return c - } - - if c := slices.CompareFunc(a.Withdrawals, b.Withdrawals, compareWithdrawl); c != 0 { - return c - } - - return 0 -} - -// TestUnmarshalEspressoTransactionTooShort verifies that UnmarshalEspressoTransaction -// returns an error (rather than panicking) when the input is shorter than a signature. -func TestUnmarshalEspressoTransactionTooShort(t *testing.T) { - cases := [][]byte{ - nil, - {}, - make([]byte, crypto.SignatureLength-1), - } - for _, data := range cases { - _, err := derive.UnmarshalEspressoTransaction(data) - require.Error(t, err, "expected error for %d-byte input", len(data)) - } -} - -// TestEspressoBatchConversion tests the conversion of a block to an Espresso -// Batch, and ensures that the recovery of the original Block is possible with -// the contents of the Espresso Batch. -func TestEspressoBatchConversion(t *testing.T) { - rng := rand.New(rand.NewSource(4982432)) - ti := time.Now() - - originalBlock := dtest.RandomL2BlockWithChainIdAndTime(rng, rng.Intn(32), defaultTestRollUpConfig.L2ChainID, ti) - - espressoBatch, err := derive.BlockToEspressoBatch(defaultTestRollUpConfig, originalBlock) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to convert block to batch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - decodedBlock, err := espressoBatch.ToBlock(defaultTestRollUpConfig) - if have, want := err, error(nil); have != want { - t.Fatalf("failed to decode batch back to block:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - // Let's perform a sanity check on the decoded block to ensure that all of - // the fields match the original block. - - if have, want := decodedBlock.BaseFee(), originalBlock.BaseFee(); have.Cmp(want) != 0 { - t.Errorf("decoded block base fee mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.BeaconRoot(), originalBlock.BeaconRoot(); have != want { - t.Errorf("decoded block beacon root mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.BlobGasUsed(), originalBlock.BlobGasUsed(); have != want { - t.Errorf("decoded block blob gas used mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Bloom(), originalBlock.Bloom(); have != want { - t.Errorf("decoded block bloom mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Body(), originalBlock.Body(); compareBody(have, want) != 0 { - t.Errorf("decoded block body mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Coinbase(), originalBlock.Coinbase(); have != want { - t.Errorf("decoded block coinbase mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Difficulty(), originalBlock.Difficulty(); have.Cmp(want) != 0 { - t.Errorf("decoded block difficulty mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.ExcessBlobGas(), originalBlock.ExcessBlobGas(); have != want { - t.Errorf("decoded block excess blob gas mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.ExecutionWitness(), originalBlock.ExecutionWitness(); have != want { - t.Errorf("decoded block execution witness mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Extra(), originalBlock.Extra(); !bytes.Equal(have, want) { - t.Errorf("decoded block extra mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.GasLimit(), originalBlock.GasLimit(); have != want { - t.Errorf("decoded block gas limit mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.GasUsed(), originalBlock.GasUsed(); have != want { - t.Errorf("decoded block gas used mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Hash(), originalBlock.Hash(); have != want { - t.Errorf("decoded block hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Header(), originalBlock.Header(); compareHeader(have, want) != 0 { - t.Errorf("decoded block header mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.MixDigest(), originalBlock.MixDigest(); have != want { - t.Errorf("decoded block mix digest mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Nonce(), originalBlock.Nonce(); have != want { - t.Errorf("decoded block nonce mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Number(), originalBlock.Number(); have.Cmp(want) != 0 { - t.Errorf("decoded block number mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.NumberU64(), originalBlock.NumberU64(); have != want { - t.Errorf("decoded block number u64 mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.ParentHash(), originalBlock.ParentHash(); have != want { - t.Errorf("decoded block parent hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.ReceiptHash(), originalBlock.ReceiptHash(); have != want { - t.Errorf("decoded block receipt hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.RequestsHash(), originalBlock.RequestsHash(); have != want { - t.Errorf("decoded block requests hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Root(), originalBlock.Root(); have != want { - t.Errorf("decoded block root mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Size(), originalBlock.Size(); have != want { - t.Errorf("decoded block size mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Time(), originalBlock.Time(); have != want { - t.Errorf("decoded block time mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Transactions(), originalBlock.Transactions(); slices.CompareFunc(have, want, compareTransaction) != 0 { - t.Errorf("decoded block transactions mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.TxHash(), originalBlock.TxHash(); have != want { - t.Errorf("decoded block tx hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.UncleHash(), originalBlock.UncleHash(); have != want { - t.Errorf("decoded block uncle hash mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.Withdrawals(), originalBlock.Withdrawals(); slices.CompareFunc(have, want, compareWithdrawl) != 0 { - t.Errorf("decoded block withdrawals mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } - - if have, want := decodedBlock.WithdrawalsRoot(), originalBlock.WithdrawalsRoot(); have != want { - t.Errorf("decoded block withdrawals root mismatch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) - } -} diff --git a/op-node/rollup/derive/espresso_caff_streamer.go b/op-node/rollup/derive/espresso_caff_streamer.go deleted file mode 100644 index 98b86883bc8..00000000000 --- a/op-node/rollup/derive/espresso_caff_streamer.go +++ /dev/null @@ -1,151 +0,0 @@ -//go:build !mips64 - -package derive - -import ( - "context" - "fmt" - - op "github.com/EspressoSystems/espresso-streamers/op" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/espresso" - "github.com/ethereum-optimism/optimism/espresso/logmodule" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type espressoAttributesQueue struct { - cfg *rollup.Config - isCaffNode bool - caffeinationHeightL2 uint64 - espressoStreamer *op.BatchStreamer[EspressoBatch] -} - -func newEspressoAttributesQueue(logger log.Logger, cfg *rollup.Config) espressoAttributesQueue { - return espressoAttributesQueue{ - cfg: cfg, - isCaffNode: cfg.CaffNodeConfig.Enabled, - caffeinationHeightL2: cfg.CaffNodeConfig.CaffeinationHeightL2, - espressoStreamer: initEspressoStreamer(logger, cfg), - } -} - -// nextBatch returns the next batch to be processed by the AttributesQueue. -// -// Pre-EspressoEnforcement, this stage is a passthrough to the upstream BatchQueue -// (`prev.NextBatch`), preserving full upstream Optimism derivation semantics. -// -// Post-EspressoEnforcement, when this op-node is configured as a Caff node -// (CaffNodeConfig.Enabled) AND the parent L2 block has reached the Caff node's -// configured caffeination height, the batch is fetched directly from the Espresso -// (HotShot) streamer instead of L1. Below that height, the Caff node falls back to -// the upstream L1-based derivation path so a freshly-launched Caff node can catch up -// to the live L2 head before switching to HotShot. Non-Caff nodes always use the -// standard L1-based derivation path. -func (e *espressoAttributesQueue) nextBatch(ctx context.Context, parent eth.L2BlockRef, blockTime uint64, l1Fetcher L1Fetcher, prev SingularBatchProvider, logger log.Logger) (*SingularBatch, bool, error) { - l2BlockTime := parent.Time + blockTime - if e.isCaffNode && e.cfg.IsEspressoEnforcement(l2BlockTime) && parent.Number >= e.caffeinationHeightL2 { - if e.espressoStreamer == nil { - logger.Error("Espresso streamer not initialized as expected when isCaffNode is ON") - return nil, false, ErrCritical - } - return CaffNextBatch(e.espressoStreamer, ctx, parent, blockTime, l1Fetcher) - } - return prev.NextBatch(ctx, parent) -} - -func initEspressoStreamer(log log.Logger, cfg *rollup.Config) *op.BatchStreamer[EspressoBatch] { - if !cfg.CaffNodeConfig.Enabled { - log.Info("Espresso streamer not initialized: Caff node is not enabled") - return nil - } - - if cfg.CaffNodeConfig.Namespace == 0 { - log.Info("Using L2 chain ID as namespace by default") - cfg.CaffNodeConfig.Namespace = cfg.L2ChainID.Uint64() - } - if cfg.CaffNodeConfig.BatchAuthenticatorAddr == (common.Address{}) { - cfg.CaffNodeConfig.BatchAuthenticatorAddr = cfg.BatchAuthenticatorAddress - } - - cliCfg := cfg.CaffNodeConfig.ToCLIConfig() - - streamer, err := espresso.BatchStreamerFromCLIConfig(cliCfg, log, func(data []byte) (*EspressoBatch, error) { - return UnmarshalEspressoTransaction(data) - }) - if err != nil { - log.Error("Failed to initialize Espresso streamer", "err", err) - return nil - } - - log.Info("Espresso streamer initialized", "namespace", streamer.Namespace, "hotshot polling interval", cfg.CaffNodeConfig.PollInterval, "hotshot urls", cfg.CaffNodeConfig.QueryServiceURLs) - return streamer -} - -// CaffNextBatch fetches the next batch from the Espresso streamer for the caff node. -// -// It follows the flow: Refresh() -> Update() -> Next(). -// -// This is similar to the batcher's flow: espressoBatchLoadingLoop -> getSyncStatus -> refresh -> Update -> Next, -// but with a few key differences: -// - It only calls Update() when needed and everytime only calls Next() once. While the batcher calls Next() in a loop. -// - It performs additional checks, such as validating the timestamp and parent hash, which does not apply to the batcher. -func CaffNextBatch(s *op.BatchStreamer[EspressoBatch], ctx context.Context, parent eth.L2BlockRef, blockTime uint64, l1Fetcher L1Fetcher) (*SingularBatch, bool, error) { - // Get the L1 finalized block - finalizedL1Block, err := l1Fetcher.L1BlockRefByLabel(ctx, eth.Finalized) - if err != nil { - return nil, false, fmt.Errorf("failed to get the L1 finalized block: %w", err) - } - // Refresh the sync status - if err := s.Refresh(ctx, finalizedL1Block, parent.Number, parent.L1Origin); err != nil { - return nil, false, fmt.Errorf("failed to refresh Espresso streamer: %w", err) - } - - // Update the streamer if needed - if !s.HasNext(ctx) { - err := s.Update(ctx) - if err != nil { - s.Log.Error("failed to update Espresso streamer", "err", err) - } - } - - // Get the next batch - var espressoBatch = s.Next(ctx) - - if espressoBatch == nil { - return nil, true, NotEnoughData - } - - batch := &espressoBatch.Batch - s.Log.Info("espressoBatch", "batch", espressoBatch.Batch) - - // These batch checks are retained because they add minimal latency (O(1) per batch). - // They're primarily a safeguard for cases where the streamer fails to emit batches correctly, - // which should only happen if there's a bug. - { - // check the batch is valid regarding given parent - nextTimestamp := parent.Time + blockTime - - if batch.Timestamp != nextTimestamp { - s.Log.Error(logmodule.DroppingBatch, "batch", espressoBatch.Number(), "timestamp", batch.Timestamp, "expected", nextTimestamp) - return nil, false, ErrTemporary - } - - // dependent on above timestamp check. If the timestamp is correct, then it must build on top of the safe head. - if batch.ParentHash != parent.Hash { - s.Log.Error("ignoring batch with mismatching parent hash", "current_safe_head", parent.Hash) - return nil, false, ErrTemporary - } - } - // For caff node, when we get a batch, we assign concluding to true to drive progress - concluding := true - return batch, concluding, nil -} - -// EspressoStreamer returns the underlying Espresso batch streamer. -// Used by op-node to expose the streamer for metrics and service-level access. -func (dp *DerivationPipeline) EspressoStreamer() *op.BatchStreamer[EspressoBatch] { - return dp.attrib.espresso.espressoStreamer -} diff --git a/op-node/rollup/derive/espresso_caff_streamer_stub.go b/op-node/rollup/derive/espresso_caff_streamer_stub.go deleted file mode 100644 index 34b36541e7f..00000000000 --- a/op-node/rollup/derive/espresso_caff_streamer_stub.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build mips64 - -package derive - -import ( - "context" - - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/log" -) - -// espressoAttributesQueue is the MIPS64/fault-proof stub. -// Espresso derivation is not available in the fault-proof program. -type espressoAttributesQueue struct{} - -func newEspressoAttributesQueue(_ log.Logger, _ *rollup.Config) espressoAttributesQueue { - return espressoAttributesQueue{} -} - -// nextBatch always falls through to the regular L1-based derivation path. -func (e *espressoAttributesQueue) nextBatch(ctx context.Context, parent eth.L2BlockRef, _ uint64, _ L1Fetcher, prev SingularBatchProvider, _ log.Logger) (*SingularBatch, bool, error) { - return prev.NextBatch(ctx, parent) -} diff --git a/op-node/rollup/derive/pipeline.go b/op-node/rollup/derive/pipeline.go index acdc1d4686d..0725f1df6b0 100644 --- a/op-node/rollup/derive/pipeline.go +++ b/op-node/rollup/derive/pipeline.go @@ -215,7 +215,7 @@ func (dp *DerivationPipeline) Step(ctx context.Context, pendingSafeHead eth.L2Bl dp.origin = newOrigin } - if attrib, err := dp.attrib.NextAttributes(ctx, pendingSafeHead, dp.l1Fetcher); err == nil { + if attrib, err := dp.attrib.NextAttributes(ctx, pendingSafeHead); err == nil { return attrib, nil } else if err == io.EOF { // If every stage has returned io.EOF, try to advance the L1 Origin diff --git a/op-node/rollup/derive/test/transaction_test.go b/op-node/rollup/derive/test/transaction_test.go deleted file mode 100644 index 4d13c25fab3..00000000000 --- a/op-node/rollup/derive/test/transaction_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package test - -import ( - "context" - "math/big" - "math/rand" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-node/rollup" - espresso_batch "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/signer" - "github.com/ethereum/go-ethereum/log" -) - -var rollupCfgTest = &rollup.Config{ - Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}}, - L2ChainID: big.NewInt(42), -} - -const ( - mnemonic = "test test test test test test test test test test test junk" - hdPath = "m/44'/60'/0'/0/1" -) - -func TestBatchRoundtrip(t *testing.T) { - rng := rand.New(rand.NewSource(1)) - - block, _ := RandomL2Block(rng, 10, time.Now()) - - batch, err := espresso_batch.BlockToEspressoBatch(rollupCfgTest, block) - if err != nil { - t.Fatal(err) - } - - signerFactory, batcherAddress, err := crypto.ChainSignerFactoryFromConfig( - log.New(context.Background()), - "", - mnemonic, - hdPath, - signer.NewCLIConfig(), - ) - if err != nil { - t.Fatal(err) - } - signer := signerFactory(rollupCfgTest.L2ChainID, batcherAddress) - - transaction, err := batch.ToEspressoTransaction( - context.Background(), - rollupCfgTest.L2ChainID.Uint64(), - signer, - ) - if err != nil { - t.Fatal(err) - } - - _, err = espresso_batch.UnmarshalEspressoTransaction(transaction.Payload) - if err != nil { - t.Fatal(err) - } -} diff --git a/op-node/rollup/driver/interfaces.go b/op-node/rollup/driver/interfaces.go index 31d59ac292c..fed4c828f7b 100644 --- a/op-node/rollup/driver/interfaces.go +++ b/op-node/rollup/driver/interfaces.go @@ -3,7 +3,6 @@ package driver import ( "context" - op "github.com/EspressoSystems/espresso-streamers/op" altda "github.com/ethereum-optimism/optimism/op-alt-da" opnodemetrics "github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics/metered" @@ -60,7 +59,6 @@ type DerivationPipeline interface { Origin() eth.L1BlockRef DerivationReady() bool ConfirmEngineReset() - EspressoStreamer() *op.BatchStreamer[derive.EspressoBatch] } type AttributesHandler interface { diff --git a/op-node/rollup/espresso_config.go b/op-node/rollup/espresso_config.go index a95496af6cc..5ad1e030123 100644 --- a/op-node/rollup/espresso_config.go +++ b/op-node/rollup/espresso_config.go @@ -4,27 +4,6 @@ package rollup import "github.com/ethereum-optimism/optimism/espresso" -// ToCLIConfig converts the local CaffNodeConfig to espresso.CLIConfig for use -// by the Espresso streamer and other Espresso-specific code paths. -func (c CaffNodeConfig) ToCLIConfig() espresso.CLIConfig { - return espresso.CLIConfig{ - Enabled: c.Enabled, - PollInterval: c.PollInterval, - QueryServiceURLs: c.QueryServiceURLs, - LightClientAddr: c.LightClientAddr, - BatchAuthenticatorAddr: c.BatchAuthenticatorAddr, - L1URL: c.L1URL, - RollupL1URL: c.RollupL1URL, - Namespace: c.Namespace, - CaffeinationHeightEspresso: c.CaffeinationHeightEspresso, - CaffeinationHeightL2: c.CaffeinationHeightL2, - EspressoAttestationService: c.EspressoAttestationService, - VerifyReceiptMaxBlocks: c.VerifyReceiptMaxBlocks, - VerifyReceiptSafetyTimeout: c.VerifyReceiptSafetyTimeout, - VerifyReceiptRetryDelay: c.VerifyReceiptRetryDelay, - } -} - // BatchAuthLookbackWindowOrDefault returns the configured lookback window, // or espresso.DefaultBatchAuthLookbackWindow (100) when unset. func (cfg *Config) BatchAuthLookbackWindowOrDefault() uint64 { @@ -33,24 +12,3 @@ func (cfg *Config) BatchAuthLookbackWindowOrDefault() uint64 { } return cfg.BatchAuthLookbackWindow } - -// CaffNodeConfigFromCLIConfig converts an espresso.CLIConfig to a CaffNodeConfig -// for embedding in rollup.Config. -func CaffNodeConfigFromCLIConfig(c espresso.CLIConfig) CaffNodeConfig { - return CaffNodeConfig{ - Enabled: c.Enabled, - PollInterval: c.PollInterval, - QueryServiceURLs: c.QueryServiceURLs, - LightClientAddr: c.LightClientAddr, - BatchAuthenticatorAddr: c.BatchAuthenticatorAddr, - L1URL: c.L1URL, - RollupL1URL: c.RollupL1URL, - Namespace: c.Namespace, - CaffeinationHeightEspresso: c.CaffeinationHeightEspresso, - CaffeinationHeightL2: c.CaffeinationHeightL2, - EspressoAttestationService: c.EspressoAttestationService, - VerifyReceiptMaxBlocks: c.VerifyReceiptMaxBlocks, - VerifyReceiptSafetyTimeout: c.VerifyReceiptSafetyTimeout, - VerifyReceiptRetryDelay: c.VerifyReceiptRetryDelay, - } -} diff --git a/op-node/rollup/espresso_types.go b/op-node/rollup/espresso_types.go index 9ab3039d6fe..a8b6dbe0f19 100644 --- a/op-node/rollup/espresso_types.go +++ b/op-node/rollup/espresso_types.go @@ -1,43 +1,9 @@ package rollup -import ( - "time" - - "github.com/ethereum/go-ethereum/common" -) - -// CaffNodeConfig holds Espresso (Caff Node) configuration embedded in rollup.Config. -// Fields mirror espresso.CLIConfig without importing the espresso package, keeping -// the fault-proof program (op-program) import graph free of Espresso network dependencies. -// -// CaffeinationHeightL2 is an operational parameter (the L2 batch position at which the -// Caff streamer should start emitting batches) and is independent of the -// EspressoEnforcementTime hardfork on the surrounding rollup.Config: the fork timestamp -// gates derivation semantics consensus-wide, while CaffeinationHeightL2 controls where -// a specific Caff node deployment begins streaming. When zero, callers fall back to -// Config.EspressoOriginBatchPos() so that fresh deployments at genesis still work -// without explicit configuration. -type CaffNodeConfig struct { - Enabled bool - PollInterval time.Duration - QueryServiceURLs []string - LightClientAddr common.Address - BatchAuthenticatorAddr common.Address - L1URL string - RollupL1URL string - Namespace uint64 - CaffeinationHeightEspresso uint64 - CaffeinationHeightL2 uint64 - EspressoAttestationService string - VerifyReceiptMaxBlocks uint64 - VerifyReceiptSafetyTimeout time.Duration - VerifyReceiptRetryDelay time.Duration -} - // IsEspressoEnforcement returns true if the Espresso enforcement upgrade is active at or past // the given L2 block timestamp. When active, the derivation pipeline runs all Espresso-specific -// semantics (event-based batch authentication, Caff node HotShot derivation). When inactive, the -// pipeline behaves exactly as upstream Optimism. +// semantics (event-based batch authentication). When inactive, the pipeline behaves exactly as +// upstream Optimism. func (c *Config) IsEspressoEnforcement(timestamp uint64) bool { return c.EspressoEnforcementTime != nil && timestamp >= *c.EspressoEnforcementTime } diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index cada060bfd6..a11aebaaade 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -174,14 +174,10 @@ type Config struct { // Pre-fork, the derivation pipeline behaves exactly as upstream Optimism: batches are // accepted based on the L1 transaction sender matching the SystemConfig batcher address. // Post-fork, all Espresso semantics are active: BatchInfoAuthenticated events emitted by - // the BatchAuthenticator contract are required for batch acceptance, and Caff nodes derive - // from the Espresso (HotShot) sequencer instead of L1. + // the BatchAuthenticator contract are required for batch acceptance. // Active if EspressoEnforcementTime != nil && L2 block timestamp >= *EspressoEnforcementTime. EspressoEnforcementTime *uint64 `json:"espresso_enforcement_time,omitempty"` - // Caff Node config - CaffNodeConfig CaffNodeConfig `json:"caff_node_config,omitempty"` - BatchAuthenticatorAddress common.Address `json:"batch_authenticator_address,omitempty,omitzero"` // BatchAuthLookbackWindow is the number of L1 blocks to scan for BatchInfoAuthenticated events. diff --git a/op-node/service.go b/op-node/service.go index 5e317560850..20bd2cd51b5 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum-optimism/optimism/espresso" altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/config" @@ -34,7 +33,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" - "github.com/urfave/cli/v2" ) // NewConfig creates a Config from the provided flags or environment variables. @@ -79,14 +77,6 @@ func NewConfig(ctx cliiface.Context, log log.Logger) (*config.Config, error) { l1Endpoint := NewL1EndpointConfig(ctx) - if rollupConfig.CaffNodeConfig.RollupL1URL == "" { - rollupConfig.CaffNodeConfig.RollupL1URL = l1Endpoint.L1NodeAddr - } - - if l1Endpoint.L1NodeAddr != rollupConfig.CaffNodeConfig.RollupL1URL { - log.Warn("Espresso streamer rollup L1 URL does not match L1 node address of caff node", "rollupL1URL", rollupConfig.CaffNodeConfig.RollupL1URL, "l1NodeAddr", l1Endpoint.L1NodeAddr) - } - l2Endpoint, err := NewL2EndpointConfig(ctx, log) if err != nil { return nil, fmt.Errorf("failed to load l2 endpoints info: %w", err) @@ -261,10 +251,6 @@ func NewRollupConfigFromCLI(log log.Logger, ctx cliiface.Context) (*rollup.Confi } applyOverrides(ctx, rollupConfig) - if cliCtx, ok := ctx.(*cli.Context); ok { - rollupConfig.CaffNodeConfig = rollup.CaffNodeConfigFromCLIConfig(espresso.ReadCLIConfig(cliCtx)) - } - return rollupConfig, nil } From 43aa1e120e565a182a82a936d6dd35597061c0b2 Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Fri, 24 Jul 2026 09:19:49 -0400 Subject: [PATCH 2/4] update streamer --- espresso/environment/3_2_espresso_deterministic_state_test.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/espresso/environment/3_2_espresso_deterministic_state_test.go b/espresso/environment/3_2_espresso_deterministic_state_test.go index 898d8f9cea5..115259b736c 100644 --- a/espresso/environment/3_2_espresso_deterministic_state_test.go +++ b/espresso/environment/3_2_espresso_deterministic_state_test.go @@ -282,7 +282,7 @@ func TestValidEspressoTransactionCreation(t *testing.T) { // same way it would a batcher-produced one, recovering the batcher // address from the prepended signature. batchSubmitter := system.BatchSubmitter - batch, err := batchSubmitter.EspressoStreamer().UnmarshalBatch(realEspressoTransaction.Payload) + batch, err := batchSubmitter.EspressoStreamer().UnmarshalBatch(realEspressoTransaction.Payload, 100) if have, want := err, error(nil); have != want { t.Fatalf("Failed to unmarshal batch:\nhave:\n\t\"%v\"\nwant:\n\t\"%v\"\n", have, want) } diff --git a/go.mod b/go.mod index 3da5a298bcd..33d16a2c683 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( ) require ( - github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260720200345-ea918620e28b + github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/joho/godotenv v1.5.1 ) diff --git a/go.sum b/go.sum index b8456c1a11e..6ce9cfd01d4 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e h1:ZIWapoIRN1VqT8GR github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513 h1:4KqlmnDg8pPPj1hCtiLnN0Fh9ltp+PPKOft9I06zQOA= github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513/go.mod h1:kaxR08mJb5Mijy7a2RhWCIWOevFI4PcXwDkzoEbsVTk= -github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260720200345-ea918620e28b h1:AxNSgMiqEGDEC/7LwnCSi5vZwMd0h34yQcfWAGui7IY= -github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260720200345-ea918620e28b/go.mod h1:Op3SNwQnZ3bqwrUXMAORnL2/pNiFzpfOED4ltYs5o/U= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb h1:Q0bYjxAWNBgc2qz6jubFa3TzqUaxbgSxrQ+qsmRoWm8= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb/go.mod h1:Op3SNwQnZ3bqwrUXMAORnL2/pNiFzpfOED4ltYs5o/U= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= From 12050a46f1da07b6f4bd44009b3bde0afb8fbe0f Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Fri, 24 Jul 2026 10:48:12 -0400 Subject: [PATCH 3/4] try fix ci --- espresso/scripts/prepare-allocs.sh | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/espresso/scripts/prepare-allocs.sh b/espresso/scripts/prepare-allocs.sh index c72cc5dd950..08f0a715a7c 100755 --- a/espresso/scripts/prepare-allocs.sh +++ b/espresso/scripts/prepare-allocs.sh @@ -111,6 +111,41 @@ BATCH_AUTHENTICATOR_OWNER_ADDRESS="${BATCH_AUTHENTICATOR_OWNER_ADDRESS}" op-depl --workdir "${DEPLOYER_DIR}" \ --private-key="${OPERATOR_PRIVATE_KEY}" +# --------------------------------------------------------------------- +# Authorize the Espresso batcher from L1 genesis (block 0). +# +# BatchAuthenticator.initialize() records the batcher in a Checkpoints.Trace160 +# history keyed by block.number at deploy time (some block N > 0 on this +# throwaway anvil). That state is then reloaded as the devnet L1 genesis starting +# at block 0, so espressoBatcherAtBlock() returns address(0) for every L1 block in +# [0, N) -- a dead zone where the streamer drops all batches with "invalid +# espresso batcher". Zero the checkpoint's fromBlock key so the batcher is +# authorized from genesis. Affects both the plain-devnet and TEE deployments, +# which share this genesis. (In production the contract is deployed on a +# persistent L1 and never reloaded from block 0, so this gap does not arise.) +# +# _espressoBatcherHistory is at storage slot 2; its dynamic array element 0 lives +# at keccak256(bytes32(2)). The word is packed big-endian as +# [uint160 batcher | uint96 fromBlock], so keep the high 20 bytes and zero the +# low 12. A before/after check fails loudly if the storage layout ever changes. +BATCH_AUTH_PROXY=$(jq -r '.opChainDeployments[0].batchAuthenticatorAddress' "${DEPLOYER_DIR}/state.json") +CKPT_SLOT=$(cast keccak 0x0000000000000000000000000000000000000000000000000000000000000002) +CKPT_VAL=$(cast storage "${BATCH_AUTH_PROXY}" "${CKPT_SLOT}" --rpc-url "${ANVIL_URL}") +CKPT_BATCHER=${CKPT_VAL:0:42} +if [ "${CKPT_BATCHER}" = "0x0000000000000000000000000000000000000000" ]; then + echo "ERROR: BatchAuthenticator checkpoint at slot ${CKPT_SLOT} is empty; storage layout changed?" >&2 + exit 1 +fi +cast rpc anvil_setStorageAt "${BATCH_AUTH_PROXY}" "${CKPT_SLOT}" \ + "${CKPT_BATCHER}000000000000000000000000" --rpc-url "${ANVIL_URL}" > /dev/null +GOT0=$(cast call "${BATCH_AUTH_PROXY}" 'espressoBatcherAtBlock(uint64)(address)' 0 --rpc-url "${ANVIL_URL}") +LATEST=$(cast call "${BATCH_AUTH_PROXY}" 'espressoBatcher()(address)' --rpc-url "${ANVIL_URL}") +if [ "${GOT0}" != "${LATEST}" ]; then + echo "ERROR: after zeroing checkpoint, espressoBatcherAtBlock(0)=${GOT0} != espressoBatcher()=${LATEST}" >&2 + exit 1 +fi +echo "Espresso batcher authorized from block 0: ${LATEST}" + # ===================================================================== # Generate succinct.env with contract addresses for the fdg-deployer # container and the succinct-proposer/challenger services. From bfaa72bceb6b59814b5899ebdc041a6a362efd0c Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Mon, 27 Jul 2026 19:27:57 -0400 Subject: [PATCH 4/4] cleanup --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 33d16a2c683..67aab520fd7 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( ) require ( - github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb + github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260727232637-568e66da84de github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/joho/godotenv v1.5.1 ) diff --git a/go.sum b/go.sum index 6ce9cfd01d4..6619b266932 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e h1:ZIWapoIRN1VqT8GR github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513 h1:4KqlmnDg8pPPj1hCtiLnN0Fh9ltp+PPKOft9I06zQOA= github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513/go.mod h1:kaxR08mJb5Mijy7a2RhWCIWOevFI4PcXwDkzoEbsVTk= -github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb h1:Q0bYjxAWNBgc2qz6jubFa3TzqUaxbgSxrQ+qsmRoWm8= -github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260724035247-425f7a5b46eb/go.mod h1:Op3SNwQnZ3bqwrUXMAORnL2/pNiFzpfOED4ltYs5o/U= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260727232637-568e66da84de h1:nSR5qwVbw02nOrrLM2Gc8xg9p12g8bnwK+OJPRp6ZLU= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260727232637-568e66da84de/go.mod h1:Op3SNwQnZ3bqwrUXMAORnL2/pNiFzpfOED4ltYs5o/U= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=