You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2020/09/11 10:17:12 UTC

[ignite] branch master updated: IGNITE-13414 JDBC Thin: Compatibility fix for 2.8-2.9 versions - Fixes #8227.

This is an automated email from the ASF dual-hosted git repository.

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 23cecab  IGNITE-13414 JDBC Thin: Compatibility fix for 2.8-2.9 versions - Fixes #8227.
23cecab is described below

commit 23cecab5a8846939dbf12ddc62c763c0b1ffa320
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Fri Sep 11 13:15:21 2020 +0300

    IGNITE-13414 JDBC Thin: Compatibility fix for 2.8-2.9 versions - Fixes #8227.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../jdbc/JdbcThinCompatibilityTest.java            | 197 +++++++++++++++++++++
 .../ignite/compatibility/jdbc/package-info.java    |  22 +++
 .../junits/IgniteCompatibilityAbstractTest.java    |  87 ++++-----
 .../IgniteCompatibilityBasicTestSuite.java         |   4 +-
 .../ignite/internal/jdbc/thin/JdbcThinTcpIo.java   |  21 ++-
 .../odbc/jdbc/JdbcConnectionContext.java           |  12 +-
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |   4 +-
 7 files changed, 289 insertions(+), 58 deletions(-)

diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java
new file mode 100644
index 0000000..50254e6
--- /dev/null
+++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.compatibility.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Collection;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.compatibility.testframework.junits.Dependency;
+import org.apache.ignite.compatibility.testframework.junits.IgniteCompatibilityAbstractTest;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteVersionUtils;
+import org.apache.ignite.internal.util.GridJavaProcess;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that current client version can connect to the server with specified version and
+ * specified client version can connect to the current server version.
+ */
+@RunWith(Parameterized.class)
+public class JdbcThinCompatibilityTest extends IgniteCompatibilityAbstractTest {
+    /** Table name. */
+    private static final String TABLE_NAME = "test_table";
+
+    /** URL. */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+
+    /** Rows count. */
+    private static final int ROWS_CNT = 10;
+
+    /** Parameters. */
+    @Parameterized.Parameters(name = "Version {0}")
+    public static Iterable<String[]> versions() {
+        return Arrays.asList(
+                new String[] {"2.7.0"},
+                new String[] {"2.7.5"},
+                new String[] {"2.7.6"},
+                new String[] {"2.8.0"},
+                new String[] {"2.8.1"}
+        );
+    }
+
+    /** Old Ignite version. */
+    @Parameterized.Parameter
+    public String ver;
+
+    /** {@inheritDoc} */
+    @Override protected @NotNull Collection<Dependency> getDependencies(String igniteVer) {
+        Collection<Dependency> dependencies = super.getDependencies(igniteVer);
+
+        dependencies.add(new Dependency("indexing", "ignite-indexing", false));
+
+        return dependencies;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testOldClientToCurrentServer() throws Exception {
+        try (Ignite ignite = startGrid(0)) {
+            initTable(ignite);
+
+            GridJavaProcess proc = GridJavaProcess.exec(
+                JdbcThinQueryRunner.class.getName(),
+                null,
+                log,
+                log::info,
+                null,
+                null,
+                getProcessProxyJvmArgs(ver),
+                null
+            );
+
+            try {
+                GridTestUtils.waitForCondition(() -> !proc.getProcess().isAlive(), 5_000L);
+
+                assertEquals(0, proc.getProcess().exitValue());
+            }
+            finally {
+                if (proc.getProcess().isAlive())
+                    proc.kill();
+            }
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testCurrentClientToOldServer() throws Exception {
+        IgniteProcessProxy proxy = null;
+
+        try {
+            Ignite ignite = startGrid(1, ver,
+                cfg -> cfg
+                    .setLocalHost("127.0.0.1")
+                    .setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(new TcpDiscoveryVmIpFinder(true))),
+                JdbcThinCompatibilityTest::initTable);
+
+            proxy = IgniteProcessProxy.ignite(ignite.name());
+
+            testJdbcQuery();
+        }
+        finally {
+            stopAllGrids();
+
+            if (proxy != null) {
+                Process proc = proxy.getProcess().getProcess();
+
+                // We should wait until process exits, or it can affect next tests.
+                GridTestUtils.waitForCondition(() -> !proc.isAlive(), 5_000L);
+            }
+        }
+    }
+
+    /** Execute sql. */
+    private static void executeSql(IgniteEx igniteEx, String sql) {
+        igniteEx.context().query().querySqlFields(new SqlFieldsQuery(sql), false).getAll();
+    }
+
+    /** */
+    private static void initTable(Ignite ignite) {
+        IgniteEx igniteEx = (IgniteEx)ignite;
+
+        executeSql(igniteEx, "CREATE TABLE " + TABLE_NAME + " (id int primary key, name varchar)");
+
+        for (int i = 0; i < ROWS_CNT; i++)
+            executeSql(igniteEx, "INSERT INTO " + TABLE_NAME + " (id, name) VALUES(" + i + ", 'name" + i + "')");
+    }
+
+    /** */
+    private static void testJdbcQuery() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL); Statement stmt = conn.createStatement()) {
+            ResultSet rs = stmt.executeQuery("SELECT id, name FROM " + TABLE_NAME + " ORDER BY id");
+
+            assertNotNull(rs);
+
+            int cnt = 0;
+
+            while (rs.next()) {
+                int id = rs.getInt("id");
+                String name = rs.getString("name");
+
+                assertEquals(cnt, id);
+                assertEquals("name" + cnt, name);
+
+                cnt++;
+            }
+
+            assertEquals(ROWS_CNT, cnt);
+        }
+    }
+
+    /**
+     * Runner class to test query from remote JVM process with old Ignite version as dependencies in class path.
+     */
+    public static class JdbcThinQueryRunner {
+        /** */
+        public static void main(String[] args) throws Exception {
+            X.println(GridJavaProcess.PID_MSG_PREFIX + U.jvmPid());
+            X.println("Start JDBC connection with Ignite version: " + IgniteVersionUtils.VER);
+
+            testJdbcQuery();
+
+            X.println("Success");
+        }
+    }
+}
diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java
new file mode 100644
index 0000000..50d961b
--- /dev/null
+++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Contains compatibility tests related to JDBC.
+ */
+
+package org.apache.ignite.compatibility.jdbc;
diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
index 2b5fd3c..e3f693a 100644
--- a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
+++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
@@ -159,46 +159,7 @@ public abstract class IgniteCompatibilityAbstractTest extends GridCommonAbstract
             }
 
             @Override protected Collection<String> filteredJvmArgs() throws Exception {
-                Collection<String> filteredJvmArgs = new ArrayList<>();
-
-                filteredJvmArgs.add("-ea");
-
-                for (String arg : U.jvmArgs()) {
-                    if (arg.startsWith("-Xmx") || arg.startsWith("-Xms"))
-                        filteredJvmArgs.add(arg);
-                }
-
-                final Collection<Dependency> dependencies = getDependencies(ver);
-
-                Set<String> excluded = getExcluded(ver, dependencies);
-
-                StringBuilder pathBuilder = new StringBuilder();
-
-                for (URL url : CompatibilityTestsUtils.classLoaderUrls(CLASS_LOADER)) {
-                    String path = url.getPath();
-
-                    if (excluded.stream().noneMatch(path::contains))
-                        pathBuilder.append(path).append(File.pathSeparator);
-                }
-
-                for (Dependency dependency : dependencies) {
-                    final String artifactVer = Optional.ofNullable(dependency.version()).orElse(ver);
-
-                    String pathToArtifact = MavenUtils.getPathToIgniteArtifact(dependency.groupId(),
-                        dependency.artifactId(), artifactVer, dependency.classifier());
-
-                    pathBuilder.append(pathToArtifact).append(File.pathSeparator);
-                }
-
-                filteredJvmArgs.add("-cp");
-                filteredJvmArgs.add(pathBuilder.toString());
-
-                final Collection<String> jvmParms = getJvmParams();
-
-                if (jvmParms != null)
-                    filteredJvmArgs.addAll(jvmParms);
-
-                return filteredJvmArgs;
+                return getProcessProxyJvmArgs(ver);
             }
         };
 
@@ -227,6 +188,52 @@ public abstract class IgniteCompatibilityAbstractTest extends GridCommonAbstract
     }
 
     /**
+     * Creates list of JVM arguments to be used to start new Ignite process in separate JVM.
+     */
+    protected Collection<String> getProcessProxyJvmArgs(String ver) throws Exception {
+        Collection<String> filteredJvmArgs = new ArrayList<>();
+
+        filteredJvmArgs.add("-ea");
+
+        for (String arg : U.jvmArgs()) {
+            if (arg.startsWith("-Xmx") || arg.startsWith("-Xms"))
+                filteredJvmArgs.add(arg);
+        }
+
+        final Collection<Dependency> dependencies = getDependencies(ver);
+
+        Set<String> excluded = getExcluded(ver, dependencies);
+
+        StringBuilder pathBuilder = new StringBuilder();
+
+        for (URL url : CompatibilityTestsUtils.classLoaderUrls(CLASS_LOADER)) {
+            String path = url.getPath();
+
+            if (excluded.stream().noneMatch(path::contains))
+                pathBuilder.append(path).append(File.pathSeparator);
+        }
+
+        for (Dependency dependency : dependencies) {
+            final String artifactVer = Optional.ofNullable(dependency.version()).orElse(ver);
+
+            String pathToArtifact = MavenUtils.getPathToIgniteArtifact(dependency.groupId(),
+                    dependency.artifactId(), artifactVer, dependency.classifier());
+
+            pathBuilder.append(pathToArtifact).append(File.pathSeparator);
+        }
+
+        filteredJvmArgs.add("-cp");
+        filteredJvmArgs.add(pathBuilder.toString());
+
+        final Collection<String> jvmParms = getJvmParams();
+
+        if (jvmParms != null)
+            filteredJvmArgs.addAll(jvmParms);
+
+        return filteredJvmArgs;
+    }
+
+    /**
      * Total amount of milliseconds.
      *
      * @return timeout in ms.
diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java
index 27fdb1a..4da7401a 100644
--- a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java
+++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.compatibility.testsuites;
 
 import org.apache.ignite.compatibility.cache.LocalCacheTest;
+import org.apache.ignite.compatibility.jdbc.JdbcThinCompatibilityTest;
 import org.apache.ignite.compatibility.persistence.FoldersReuseCompatibilityTest;
 import org.apache.ignite.compatibility.persistence.MetaStorageCompatibilityTest;
 import org.apache.ignite.compatibility.persistence.MigratingToWalV2SerializerWithCompactionTest;
@@ -36,7 +37,8 @@ import org.junit.runners.Suite;
     MigratingToWalV2SerializerWithCompactionTest.class,
     MetaStorageCompatibilityTest.class,
     LocalCacheTest.class,
-    MoveBinaryMetadataCompatibility.class
+    MoveBinaryMetadataCompatibility.class,
+    JdbcThinCompatibilityTest.class
 })
 public class IgniteCompatibilityBasicTestSuite {
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
index 9129666..b8c5cd4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
@@ -94,11 +94,11 @@ public class JdbcThinTcpIo {
     /** Version 2.8.0. */
     private static final ClientListenerProtocolVersion VER_2_8_0 = ClientListenerProtocolVersion.create(2, 8, 0);
 
-    /** Version 2.8.1. Adds features flags support. */
-    private static final ClientListenerProtocolVersion VER_2_8_1 = ClientListenerProtocolVersion.create(2, 8, 1);
+    /** Version 2.9.0. Adds user attributes support. Adds features flags support. */
+    private static final ClientListenerProtocolVersion VER_2_9_0 = ClientListenerProtocolVersion.create(2, 9, 0);
 
     /** Current version. */
-    private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_8_1;
+    private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_9_0;
 
     /** Initial output stream capacity for handshake. */
     private static final int HANDSHAKE_MSG_SIZE = 13;
@@ -285,7 +285,9 @@ public class JdbcThinTcpIo {
             writer.writeByte(nullableBooleanToByte(connProps.isDataPageScanEnabled()));
 
             JdbcUtils.writeNullableInteger(writer, connProps.getUpdateBatchSize());
+        }
 
+        if (ver.compareTo(VER_2_9_0) >= 0) {
             String userAttrs = connProps.getUserAttributesFactory();
 
             if (F.isEmpty(userAttrs))
@@ -304,10 +306,9 @@ public class JdbcThinTcpIo {
                         SqlStateCode.CLIENT_CONNECTION_FAILED, e);
                 }
             }
-        }
 
-        if (ver.compareTo(VER_2_8_1) >= 0)
             writer.writeByteArray(ThinProtocolFeature.featuresAsBytes(enabledFeatures()));
+        }
 
         if (!F.isEmpty(connProps.getUsername())) {
             assert ver.compareTo(VER_2_5_0) >= 0 : "Authentication is supported since 2.5";
@@ -341,7 +342,7 @@ public class JdbcThinTcpIo {
 
                 handshakeRes.igniteVersion(new IgniteProductVersion(maj, min, maintenance, stage, ts, hash));
 
-                if (ver.compareTo(VER_2_8_1) >= 0) {
+                if (ver.compareTo(VER_2_9_0) >= 0) {
                     byte[] srvFeatures = reader.readByteArray();
 
                     EnumSet<JdbcThinFeature> features = JdbcThinFeature.enumSet(srvFeatures);
@@ -373,7 +374,8 @@ public class JdbcThinTcpIo {
                     + ", url=" + connProps.getUrl() + " address=" + sockAddr + ']', SqlStateCode.CONNECTION_REJECTED);
             }
 
-            if (VER_2_7_0.equals(srvProtoVer0)
+            if (VER_2_8_0.equals(srvProtoVer0)
+                || VER_2_7_0.equals(srvProtoVer0)
                 || VER_2_5_0.equals(srvProtoVer0)
                 || VER_2_4_0.equals(srvProtoVer0)
                 || VER_2_3_0.equals(srvProtoVer0)
@@ -736,8 +738,9 @@ public class JdbcThinTcpIo {
     }
 
     /**
-     * Set of features enabled on clien side. To get features
-     * supported by both sides use {@link #protoCtx}.
+     * Set of features enabled on client side. To get features supported by both sides use {@link #protoCtx}.
+     * Since {@link #protoCtx} only available after handshake, any handshake protocol change should not use features,
+     * but increment protocol version.
      */
     private EnumSet<JdbcThinFeature> enabledFeatures() {
         EnumSet<JdbcThinFeature> features = JdbcThinFeature.allFeaturesAsEnumSet();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
index 2359e98..0d9ea39 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
@@ -65,11 +65,11 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
     /** Version 2.8.0: adds query id in order to implement cancel feature, partition awareness support: IEP-23.*/
     static final ClientListenerProtocolVersion VER_2_8_0 = ClientListenerProtocolVersion.create(2, 8, 0);
 
-    /** Version 2.8.1: adds features flags support.*/
-    static final ClientListenerProtocolVersion VER_2_8_1 = ClientListenerProtocolVersion.create(2, 8, 1);
+    /** Version 2.9.0: adds user attributes, adds features flags support. */
+    static final ClientListenerProtocolVersion VER_2_9_0 = ClientListenerProtocolVersion.create(2, 9, 0);
 
     /** Current version. */
-    public static final ClientListenerProtocolVersion CURRENT_VER = VER_2_8_1;
+    public static final ClientListenerProtocolVersion CURRENT_VER = VER_2_9_0;
 
     /** Supported versions. */
     private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>();
@@ -97,7 +97,7 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
 
     static {
         SUPPORTED_VERS.add(CURRENT_VER);
-        SUPPORTED_VERS.add(VER_2_8_1);
+        SUPPORTED_VERS.add(VER_2_9_0);
         SUPPORTED_VERS.add(VER_2_8_0);
         SUPPORTED_VERS.add(VER_2_7_0);
         SUPPORTED_VERS.add(VER_2_5_0);
@@ -179,11 +179,11 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
             dataPageScanEnabled = nullableBooleanFromByte(reader.readByte());
 
             updateBatchSize = JdbcUtils.readNullableInteger(reader);
+        }
 
+        if (ver.compareTo(VER_2_9_0) >= 0) {
             userAttrs = reader.readMap();
-        }
 
-        if (ver.compareTo(VER_2_8_1) >= 0) {
             byte[] cliFeatures = reader.readByteArray();
 
             features = JdbcThinFeature.enumSet(cliFeatures);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
index c64e776..ecf6fbf 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
@@ -91,7 +91,7 @@ import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionCont
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_4_0;
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_7_0;
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_8_0;
-import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_8_1;
+import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_9_0;
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BATCH_EXEC;
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BATCH_EXEC_ORDERED;
 import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BINARY_TYPE_GET;
@@ -534,7 +534,7 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
             writer.writeUuid(connCtx.kernalContext().localNodeId());
 
         // Write all features supported by the node.
-        if (protocolVer.compareTo(VER_2_8_1) >= 0)
+        if (protocolVer.compareTo(VER_2_9_0) >= 0)
             writer.writeByteArray(ThinProtocolFeature.featuresAsBytes(connCtx.protocolContext().features()));
     }