diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..e34f61502 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,42 @@ +--- +name: Benchmarks +permissions: {} +"on": + push: + branches: + - main + workflow_dispatch: + +jobs: + evm-benchmark: + name: EVM Contract Benchmark + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + issues: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version-file: ./go.mod + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + - name: Build binaries + run: make build-evm build-da + - name: Run EVM benchmarks + run: | + cd test/e2e && go test -tags evm -bench=. -benchmem -run='^$' \ + -timeout=10m --evm-binary=../../build/evm | tee output.txt + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7 + with: + name: EVM Contract Roundtrip + tool: 'go' + output-file-path: test/e2e/output.txt + auto-push: true + github-token: ${{ secrets.GITHUB_TOKEN }} + alert-threshold: '150%' + fail-on-alert: true + comment-on-alert: true diff --git a/execution/evm/test/go.mod b/execution/evm/test/go.mod index 744c26377..e9f8d7129 100644 --- a/execution/evm/test/go.mod +++ b/execution/evm/test/go.mod @@ -10,6 +10,7 @@ require ( github.com/ipfs/go-datastore v0.9.0 github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 + go.uber.org/zap v1.27.1 ) require ( @@ -179,7 +180,6 @@ require ( go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.48.0 // indirect diff --git a/execution/evm/test/test_helpers.go b/execution/evm/test/test_helpers.go index d2c050052..1aa4ec317 100644 --- a/execution/evm/test/test_helpers.go +++ b/execution/evm/test/test_helpers.go @@ -18,6 +18,8 @@ import ( "github.com/celestiaorg/tastora/framework/types" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" ) // Test-scoped Docker client/network mapping to avoid conflicts between tests @@ -39,7 +41,7 @@ func randomString(n int) string { } // getTestScopedDockerSetup returns a Docker client and network ID that are scoped to the specific test. -func getTestScopedDockerSetup(t *testing.T) (types.TastoraDockerClient, string) { +func getTestScopedDockerSetup(t testing.TB) (types.TastoraDockerClient, string) { t.Helper() testKey := t.Name() @@ -59,13 +61,22 @@ func getTestScopedDockerSetup(t *testing.T) (types.TastoraDockerClient, string) } // SetupTestRethNode creates a single Reth node for testing purposes. -func SetupTestRethNode(t *testing.T) *reth.Node { +func SetupTestRethNode(t testing.TB) *reth.Node { t.Helper() ctx := context.Background() dockerCli, dockerNetID := getTestScopedDockerSetup(t) - n, err := reth.NewNodeBuilderWithTestName(t, fmt.Sprintf("%s-%s", t.Name(), randomString(6))). + testName := fmt.Sprintf("%s-%s", t.Name(), randomString(6)) + logger := zap.NewNop() + if testing.Verbose() { + logger = zaptest.NewLogger(t) + } + n, err := new(reth.NodeBuilder). + WithTestName(testName). + WithLogger(logger). + WithImage(reth.DefaultImage()). + WithBin("ev-reth"). WithDockerClient(dockerCli). WithDockerNetworkID(dockerNetID). WithGenesis([]byte(reth.DefaultEvolveGenesisJSON())). @@ -88,7 +99,7 @@ func SetupTestRethNode(t *testing.T) *reth.Node { } // waitForRethContainer waits for the Reth container to be ready by polling the provided endpoints with JWT authentication. -func waitForRethContainer(t *testing.T, jwtSecret, ethURL, engineURL string) error { +func waitForRethContainer(t testing.TB, jwtSecret, ethURL, engineURL string) error { t.Helper() client := &http.Client{Timeout: 100 * time.Millisecond} timer := time.NewTimer(30 * time.Second) diff --git a/execution/evm/test_helpers.go b/execution/evm/test_helpers.go index 1e97d446d..157f028b2 100644 --- a/execution/evm/test_helpers.go +++ b/execution/evm/test_helpers.go @@ -16,7 +16,7 @@ import ( // Transaction Helpers // GetRandomTransaction creates and signs a random Ethereum legacy transaction using the provided private key, recipient, chain ID, gas limit, and nonce. -func GetRandomTransaction(t *testing.T, privateKeyHex, toAddressHex, chainID string, gasLimit uint64, lastNonce *uint64) *types.Transaction { +func GetRandomTransaction(t testing.TB, privateKeyHex, toAddressHex, chainID string, gasLimit uint64, lastNonce *uint64) *types.Transaction { t.Helper() privateKey, err := crypto.HexToECDSA(privateKeyHex) require.NoError(t, err) diff --git a/test/e2e/evm_contract_bench_test.go b/test/e2e/evm_contract_bench_test.go new file mode 100644 index 000000000..70b40cd8b --- /dev/null +++ b/test/e2e/evm_contract_bench_test.go @@ -0,0 +1,294 @@ +//go:build evm + +package e2e + +import ( + "context" + "encoding/json" + "fmt" + "io" + "math/big" + "net" + "net/http" + "path/filepath" + "sort" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + collpb "go.opentelemetry.io/proto/otlp/collector/trace/v1" + tracepb "go.opentelemetry.io/proto/otlp/trace/v1" +) + +// BenchmarkEvmContractRoundtrip measures the store → retrieve roundtrip latency +// against a real reth node with a pre-deployed contract. +// +// The node is started with OpenTelemetry tracing enabled, exporting to an +// in-process OTLP/HTTP receiver. After the timed loop, the collected spans are +// aggregated into a hierarchical timing report showing where time is spent +// inside ev-node (Engine API calls, executor, sequencer, etc). +// +// Run with (after building local-da and evm binaries): +// +// PATH="/path/to/binaries:$PATH" go test -tags evm \ +// -bench BenchmarkEvmContractRoundtrip -benchmem -benchtime=5x \ +// -run='^$' -timeout=10m -v --evm-binary=/path/to/evm . +func BenchmarkEvmContractRoundtrip(b *testing.B) { + workDir := b.TempDir() + sequencerHome := filepath.Join(workDir, "evm-bench-sequencer") + + // Start an in-process OTLP/HTTP receiver to collect traces from ev-node. + collector := newOTLPCollector(b) + defer collector.close() // nolint: errcheck // test only + + // Start sequencer with tracing enabled, exporting to our in-process collector. + client, _, cleanup := setupTestSequencer(b, sequencerHome, + "--evnode.instrumentation.tracing=true", + "--evnode.instrumentation.tracing_endpoint", collector.endpoint(), + "--evnode.instrumentation.tracing_sample_rate", "1.0", + "--evnode.instrumentation.tracing_service_name", "ev-node-bench", + ) + defer cleanup() + + ctx := b.Context() + privateKey, err := crypto.HexToECDSA(TestPrivateKey) + require.NoError(b, err) + chainID, ok := new(big.Int).SetString(DefaultChainID, 10) + require.True(b, ok) + signer := types.NewEIP155Signer(chainID) + + // Deploy contract once during setup. + contractAddr, nonce := deployContract(b, ctx, client, StorageContractBytecode, 0, privateKey, chainID) + + // Pre-build signed store(42) transactions for all iterations. + storeData, err := hexutil.Decode("0x000000000000000000000000000000000000000000000000000000000000002a") + require.NoError(b, err) + + const maxIter = 1024 + signedTxs := make([]*types.Transaction, maxIter) + for i := range maxIter { + tx := types.NewTx(&types.LegacyTx{ + Nonce: nonce + uint64(i), + To: &contractAddr, + Value: big.NewInt(0), + Gas: 500000, + GasPrice: big.NewInt(30000000000), + Data: storeData, + }) + signedTxs[i], err = types.SignTx(tx, signer, privateKey) + require.NoError(b, err) + } + + expected := common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a").Bytes() + callMsg := ethereum.CallMsg{To: &contractAddr, Data: []byte{}} + + b.ResetTimer() + b.ReportAllocs() + + var i int + for b.Loop() { + require.Less(b, i, maxIter, "increase maxIter for longer benchmark runs") + + // 1. Submit pre-signed store(42) transaction. + err = client.SendTransaction(ctx, signedTxs[i]) + require.NoError(b, err) + + // 2. Wait for inclusion. + waitForReceipt(b, ctx, client, signedTxs[i].Hash()) + + // 3. Retrieve and verify. + result, err := client.CallContract(ctx, callMsg, nil) + require.NoError(b, err) + require.Equal(b, expected, result, "retrieve() should return 42") + + i++ + } + + b.StopTimer() + + // Give the node a moment to flush pending span batches. + time.Sleep(2 * time.Second) + + // Print the trace breakdown from the collected spans. + printCollectedTraceReport(b, collector) +} + +// --- In-process OTLP/HTTP Collector --- + +// otlpCollector is a lightweight OTLP/HTTP receiver that collects trace spans +// in memory. It serves the /v1/traces endpoint that the node's OTLP exporter +// posts protobuf-encoded ExportTraceServiceRequest messages to. +type otlpCollector struct { + mu sync.Mutex + spans []*tracepb.Span + server *http.Server + addr string +} + +func newOTLPCollector(t testing.TB) *otlpCollector { + t.Helper() + + c := &otlpCollector{} + + mux := http.NewServeMux() + mux.HandleFunc("/v1/traces", c.handleTraces) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + c.addr = listener.Addr().String() + + c.server = &http.Server{Handler: mux} + go func() { _ = c.server.Serve(listener) }() + + t.Logf("OTLP collector listening on %s", c.addr) + return c +} + +func (c *otlpCollector) endpoint() string { + return "http://" + c.addr +} + +func (c *otlpCollector) close() error { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + return c.server.Shutdown(ctx) +} + +func (c *otlpCollector) handleTraces(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Try protobuf first (default for otlptracehttp). + var req collpb.ExportTraceServiceRequest + if err := proto.Unmarshal(body, &req); err != nil { + // Fallback: try JSON (some configurations use JSON encoding). + if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + http.Error(w, fmt.Sprintf("proto: %v; json: %v", err, jsonErr), http.StatusBadRequest) + return + } + } + + c.mu.Lock() + for _, rs := range req.GetResourceSpans() { + for _, ss := range rs.GetScopeSpans() { + c.spans = append(c.spans, ss.GetSpans()...) + } + } + c.mu.Unlock() + + // Respond with an empty ExportTraceServiceResponse (protobuf). + resp := &collpb.ExportTraceServiceResponse{} + out, _ := proto.Marshal(resp) + w.Header().Set("Content-Type", "application/x-protobuf") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(out) +} + +func (c *otlpCollector) getSpans() []*tracepb.Span { + c.mu.Lock() + defer c.mu.Unlock() + cp := make([]*tracepb.Span, len(c.spans)) + copy(cp, c.spans) + return cp +} + +// printCollectedTraceReport aggregates collected spans by operation name and +// prints a timing breakdown. +func printCollectedTraceReport(b testing.TB, collector *otlpCollector) { + b.Helper() + + spans := collector.getSpans() + if len(spans) == 0 { + b.Logf("WARNING: no spans collected from ev-node") + return + } + + type stats struct { + count int + total time.Duration + min time.Duration + max time.Duration + } + m := make(map[string]*stats) + + for _, span := range spans { + // Duration: end - start in nanoseconds. + d := time.Duration(span.GetEndTimeUnixNano()-span.GetStartTimeUnixNano()) * time.Nanosecond + if d <= 0 { + continue + } + name := span.GetName() + s, ok := m[name] + if !ok { + s = &stats{min: d, max: d} + m[name] = s + } + s.count++ + s.total += d + if d < s.min { + s.min = d + } + if d > s.max { + s.max = d + } + } + + // Sort by total time descending. + names := make([]string, 0, len(m)) + for name := range m { + names = append(names, name) + } + sort.Slice(names, func(i, j int) bool { + return m[names[i]].total > m[names[j]].total + }) + + // Calculate overall total for percentages. + var overallTotal time.Duration + for _, s := range m { + overallTotal += s.total + } + + b.Logf("\n--- ev-node Trace Breakdown (%d spans collected) ---", len(spans)) + b.Logf("%-40s %6s %12s %12s %12s %7s", "OPERATION", "COUNT", "AVG", "MIN", "MAX", "% TOTAL") + for _, name := range names { + s := m[name] + avg := s.total / time.Duration(s.count) + pct := float64(s.total) / float64(overallTotal) * 100 + b.Logf("%-40s %6d %12s %12s %12s %6.1f%%", name, s.count, avg, s.min, s.max, pct) + } + + b.Logf("\n--- Time Distribution ---") + for _, name := range names { + s := m[name] + pct := float64(s.total) / float64(overallTotal) * 100 + bar := "" + for range int(pct / 2) { + bar += "█" + } + b.Logf("%-40s %5.1f%% %s", name, pct, bar) + } +} + +// waitForReceipt polls for a transaction receipt until it is available. +func waitForReceipt(t testing.TB, ctx context.Context, client *ethclient.Client, txHash common.Hash) *types.Receipt { + t.Helper() + var receipt *types.Receipt + var err error + require.Eventually(t, func() bool { + receipt, err = client.TransactionReceipt(ctx, txHash) + return err == nil && receipt != nil + }, 2*time.Second, 50*time.Millisecond, "transaction %s not included", txHash.Hex()) + return receipt +} diff --git a/test/e2e/evm_contract_e2e_test.go b/test/e2e/evm_contract_e2e_test.go index 0203ca634..477b0801b 100644 --- a/test/e2e/evm_contract_e2e_test.go +++ b/test/e2e/evm_contract_e2e_test.go @@ -240,10 +240,10 @@ func TestEvmContractEvents(t *testing.T) { // setupTestSequencer sets up a single sequencer node for testing. // Returns the ethclient, genesis hash, and a cleanup function. -func setupTestSequencer(t *testing.T, homeDir string) (*ethclient.Client, string, func()) { +func setupTestSequencer(t testing.TB, homeDir string, extraArgs ...string) (*ethclient.Client, string, func()) { sut := NewSystemUnderTest(t) - genesisHash, seqEthURL := setupSequencerOnlyTest(t, sut, homeDir) + genesisHash, seqEthURL := setupSequencerOnlyTest(t, sut, homeDir, extraArgs...) t.Logf("Sequencer started at %s (Genesis: %s)", seqEthURL, genesisHash) client, err := ethclient.Dial(seqEthURL) @@ -257,7 +257,7 @@ func setupTestSequencer(t *testing.T, homeDir string) (*ethclient.Client, string // deployContract helps deploy a contract and waits for its inclusion. // Returns the deployed contract address and the next nonce. -func deployContract(t *testing.T, ctx context.Context, client *ethclient.Client, bytecodeStr string, nonce uint64, privateKey *ecdsa.PrivateKey, chainID *big.Int) (common.Address, uint64) { +func deployContract(t testing.TB, ctx context.Context, client *ethclient.Client, bytecodeStr string, nonce uint64, privateKey *ecdsa.PrivateKey, chainID *big.Int) (common.Address, uint64) { bytecode, err := hexutil.Decode("0x" + bytecodeStr) require.NoError(t, err) diff --git a/test/e2e/evm_test_common.go b/test/e2e/evm_test_common.go index d5a721516..33518097f 100644 --- a/test/e2e/evm_test_common.go +++ b/test/e2e/evm_test_common.go @@ -55,7 +55,7 @@ func getAvailablePort() (int, net.Listener, error) { } // same as getAvailablePort but fails test if not successful -func mustGetAvailablePort(t *testing.T) int { +func mustGetAvailablePort(t testing.TB) int { t.Helper() port, listener, err := getAvailablePort() require.NoError(t, err) @@ -221,7 +221,7 @@ const ( // createPassphraseFile creates a temporary passphrase file and returns its path. // The file is created in the provided directory with secure permissions (0600). // If the directory doesn't exist, it will be created with 0755 permissions. -func createPassphraseFile(t *testing.T, dir string) string { +func createPassphraseFile(t testing.TB, dir string) string { t.Helper() // Ensure the directory exists err := os.MkdirAll(dir, 0755) @@ -236,7 +236,7 @@ func createPassphraseFile(t *testing.T, dir string) string { // createJWTSecretFile creates a temporary JWT secret file and returns its path. // The file is created in the provided directory with secure permissions (0600). // If the directory doesn't exist, it will be created with 0755 permissions. -func createJWTSecretFile(t *testing.T, dir, jwtSecret string) string { +func createJWTSecretFile(t testing.TB, dir, jwtSecret string) string { t.Helper() // Ensure the directory exists err := os.MkdirAll(dir, 0755) @@ -256,7 +256,7 @@ func createJWTSecretFile(t *testing.T, dir, jwtSecret string) string { // - rpcPort: Optional RPC port to use (if empty, uses default port) // // Returns: The full P2P address (e.g., /ip4/127.0.0.1/tcp/7676/p2p/12D3KooW...) -func getNodeP2PAddress(t *testing.T, sut *SystemUnderTest, nodeHome string, rpcPort ...string) string { +func getNodeP2PAddress(t testing.TB, sut *SystemUnderTest, nodeHome string, rpcPort ...string) string { t.Helper() // Build command arguments @@ -313,7 +313,7 @@ func getNodeP2PAddress(t *testing.T, sut *SystemUnderTest, nodeHome string, rpcP // - jwtSecret: JWT secret for authenticating with EVM engine // - genesisHash: Hash of the genesis block for chain validation // - endpoints: TestEndpoints struct containing unique port assignments -func setupSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { +func setupSequencerNode(t testing.TB, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints, extraArgs ...string) { t.Helper() // Create passphrase file @@ -350,6 +350,7 @@ func setupSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSe "--evm.engine-url", endpoints.GetSequencerEngineURL(), "--evm.eth-url", endpoints.GetSequencerEthURL(), } + args = append(args, extraArgs...) sut.ExecCmd(evmSingleBinaryPath, args...) sut.AwaitNodeUp(t, endpoints.GetRollkitRPCAddress(), NodeStartupTimeout) } @@ -357,7 +358,7 @@ func setupSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSe // setupSequencerNodeLazy initializes and starts the sequencer node in lazy mode. // In lazy mode, blocks are only produced when transactions are available, // not on a regular timer. -func setupSequencerNodeLazy(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { +func setupSequencerNodeLazy(t testing.TB, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { t.Helper() // Create passphrase file @@ -417,7 +418,7 @@ func setupSequencerNodeLazy(t *testing.T, sut *SystemUnderTest, sequencerHome, j // - genesisHash: Hash of the genesis block for chain validation // - sequencerP2PAddress: P2P address of the sequencer node to connect to // - endpoints: TestEndpoints struct containing unique port assignments -func setupFullNode(t *testing.T, sut *SystemUnderTest, fullNodeHome, sequencerHome, fullNodeJwtSecret, genesisHash, sequencerP2PAddress string, endpoints *TestEndpoints) { +func setupFullNode(t testing.TB, sut *SystemUnderTest, fullNodeHome, sequencerHome, fullNodeJwtSecret, genesisHash, sequencerP2PAddress string, endpoints *TestEndpoints) { t.Helper() // Initialize full node @@ -478,7 +479,7 @@ var globalNonce uint64 = 0 // // This is used in full node sync tests to verify that both nodes // include the same transaction in the same block number. -func submitTransactionAndGetBlockNumber(t *testing.T, sequencerClients ...*ethclient.Client) (common.Hash, uint64) { +func submitTransactionAndGetBlockNumber(t testing.TB, sequencerClients ...*ethclient.Client) (common.Hash, uint64) { t.Helper() // Submit transaction to sequencer EVM with unique nonce @@ -512,7 +513,7 @@ func submitTransactionAndGetBlockNumber(t *testing.T, sequencerClients ...*ethcl // - daPort: optional DA port to use (if empty, uses default) // // Returns: jwtSecret, fullNodeJwtSecret (empty if needsFullNode=false), genesisHash -func setupCommonEVMTest(t *testing.T, sut *SystemUnderTest, needsFullNode bool, _ ...string) (string, string, string, *TestEndpoints) { +func setupCommonEVMTest(t testing.TB, sut *SystemUnderTest, needsFullNode bool, _ ...string) (string, string, string, *TestEndpoints) { t.Helper() // Reset global nonce for each test to ensure clean state @@ -570,7 +571,7 @@ func setupCommonEVMTest(t *testing.T, sut *SystemUnderTest, needsFullNode bool, // - blockHeight: Height of the block to retrieve (use nil for latest) // // Returns: block hash, state root, transaction count, block number, and error -func checkBlockInfoAt(t *testing.T, ethURL string, blockHeight *uint64) (common.Hash, common.Hash, int, uint64, error) { +func checkBlockInfoAt(t testing.TB, ethURL string, blockHeight *uint64) (common.Hash, common.Hash, int, uint64, error) { t.Helper() ctx := context.Background() @@ -613,14 +614,14 @@ func checkBlockInfoAt(t *testing.T, ethURL string, blockHeight *uint64) (common. // - nodeHome: Directory path for sequencer node data // // Returns: genesisHash for the sequencer -func setupSequencerOnlyTest(t *testing.T, sut *SystemUnderTest, nodeHome string) (string, string) { +func setupSequencerOnlyTest(t testing.TB, sut *SystemUnderTest, nodeHome string, extraArgs ...string) (string, string) { t.Helper() // Use common setup (no full node needed) jwtSecret, _, genesisHash, endpoints := setupCommonEVMTest(t, sut, false) // Initialize and start sequencer node - setupSequencerNode(t, sut, nodeHome, jwtSecret, genesisHash, endpoints) + setupSequencerNode(t, sut, nodeHome, jwtSecret, genesisHash, endpoints, extraArgs...) t.Log("Sequencer node is up") return genesisHash, endpoints.GetSequencerEthURL() @@ -635,7 +636,7 @@ func setupSequencerOnlyTest(t *testing.T, sut *SystemUnderTest, nodeHome string) // - sequencerHome: Directory path for sequencer node data // - jwtSecret: JWT secret for sequencer's EVM engine authentication // - genesisHash: Hash of the genesis block for chain validation -func restartDAAndSequencer(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { +func restartDAAndSequencer(t testing.TB, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { t.Helper() // First restart the local DA @@ -685,7 +686,7 @@ func restartDAAndSequencer(t *testing.T, sut *SystemUnderTest, sequencerHome, jw // - sequencerHome: Directory path for sequencer node data // - jwtSecret: JWT secret for sequencer's EVM engine authentication // - genesisHash: Hash of the genesis block for chain validation -func restartDAAndSequencerLazy(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { +func restartDAAndSequencerLazy(t testing.TB, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string, endpoints *TestEndpoints) { t.Helper() // First restart the local DA @@ -736,7 +737,7 @@ func restartDAAndSequencerLazy(t *testing.T, sut *SystemUnderTest, sequencerHome // - sequencerHome: Directory path for sequencer node data // - jwtSecret: JWT secret for sequencer's EVM engine authentication // - genesisHash: Hash of the genesis block for chain validation -func restartSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string) { +func restartSequencerNode(t testing.TB, sut *SystemUnderTest, sequencerHome, jwtSecret, genesisHash string) { t.Helper() // Start sequencer node (without init - node already exists) @@ -772,7 +773,7 @@ func restartSequencerNode(t *testing.T, sut *SystemUnderTest, sequencerHome, jwt // - nodeName: Human-readable name for logging (e.g., "sequencer", "full node") // // This function ensures that during lazy mode idle periods, no automatic block production occurs. -func verifyNoBlockProduction(t *testing.T, client *ethclient.Client, duration time.Duration, nodeName string) { +func verifyNoBlockProduction(t testing.TB, client *ethclient.Client, duration time.Duration, nodeName string) { t.Helper() ctx := context.Background() diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 9ef0dae15..53ffbd183 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -15,6 +15,7 @@ require ( github.com/libp2p/go-libp2p v0.47.0 github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/proto/otlp v1.9.0 google.golang.org/protobuf v1.36.11 ) @@ -294,7 +295,6 @@ require ( go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect go.uber.org/mock v0.6.0 // indirect diff --git a/test/e2e/sut_helper.go b/test/e2e/sut_helper.go index beba2b619..f5783da8b 100644 --- a/test/e2e/sut_helper.go +++ b/test/e2e/sut_helper.go @@ -33,7 +33,7 @@ var WorkDir = "." // SystemUnderTest is used to manage processes and logs during test execution. type SystemUnderTest struct { - t *testing.T + t testing.TB outBuff *ring.Ring errBuff *ring.Ring @@ -45,7 +45,7 @@ type SystemUnderTest struct { } // NewSystemUnderTest constructor -func NewSystemUnderTest(t *testing.T) *SystemUnderTest { +func NewSystemUnderTest(t testing.TB) *SystemUnderTest { r := &SystemUnderTest{ t: t, pids: make(map[int]struct{}), @@ -103,7 +103,7 @@ func (s *SystemUnderTest) ExecCmdWithLogPrefix(prefix, cmd string, args ...strin // AwaitNodeUp waits until a node is operational by checking both liveness and readiness. // Fails tests when node is not up within the specified timeout. -func (s *SystemUnderTest) AwaitNodeUp(t *testing.T, rpcAddr string, timeout time.Duration) { +func (s *SystemUnderTest) AwaitNodeUp(t testing.TB, rpcAddr string, timeout time.Duration) { t.Helper() t.Logf("Await node is up: %s", rpcAddr) require.EventuallyWithT(t, func(t *assert.CollectT) { @@ -120,7 +120,7 @@ func (s *SystemUnderTest) AwaitNodeUp(t *testing.T, rpcAddr string, timeout time } // AwaitNodeLive waits until a node is alive (liveness check only). -func (s *SystemUnderTest) AwaitNodeLive(t *testing.T, rpcAddr string, timeout time.Duration) { +func (s *SystemUnderTest) AwaitNodeLive(t testing.TB, rpcAddr string, timeout time.Duration) { t.Helper() t.Logf("Await node is live: %s", rpcAddr) require.EventuallyWithT(t, func(t *assert.CollectT) { @@ -132,7 +132,7 @@ func (s *SystemUnderTest) AwaitNodeLive(t *testing.T, rpcAddr string, timeout ti } // AwaitNBlocks waits until the node has produced at least `n` blocks. -func (s *SystemUnderTest) AwaitNBlocks(t *testing.T, n uint64, rpcAddr string, timeout time.Duration) { +func (s *SystemUnderTest) AwaitNBlocks(t testing.TB, n uint64, rpcAddr string, timeout time.Duration) { t.Helper() ctx, done := context.WithTimeout(context.Background(), timeout) defer done() @@ -344,7 +344,7 @@ func locateExecutable(file string) string { } // MustCopyFile copies the file from the source path `src` to the destination path `dest` and returns an open file handle to `dest`. -func MustCopyFile(t *testing.T, src, dest string) *os.File { +func MustCopyFile(t testing.TB, src, dest string) *os.File { t.Helper() in, err := os.Open(src) // nolint: gosec // used by tests only require.NoError(t, err) @@ -362,11 +362,11 @@ func MustCopyFile(t *testing.T, src, dest string) *os.File { } // NodeID generates and returns the peer ID from the node's private key. -func NodeID(t *testing.T, nodeDir string) peer.ID { +func NodeID(t testing.TB, nodeDir string) peer.ID { t.Helper() node1Key, err := key.LoadNodeKey(filepath.Join(nodeDir, "config")) require.NoError(t, err) node1ID, err := peer.IDFromPrivateKey(node1Key.PrivKey) require.NoError(t, err) return node1ID -} \ No newline at end of file +}