diff --git a/constants.properties b/constants.properties index b0e26108cd..cc44a892fd 100644 --- a/constants.properties +++ b/constants.properties @@ -11,7 +11,7 @@ java8MinUpdateVersion=171 # When incrementing platformVersion make sure to update # # net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. # # ***************************************************************# -platformVersion=8 +platformVersion=9 guavaVersion=28.0-jre # Quasar version to use with Java 8: quasarVersion=0.7.13_r3 diff --git a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt index af2a20b40c..cda6b328e2 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt @@ -28,7 +28,7 @@ import java.util.jar.JarInputStream // *Internal* Corda-specific utilities. -const val PLATFORM_VERSION = 8 +const val PLATFORM_VERSION = 9 fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) { checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature) diff --git a/core/src/main/kotlin/net/corda/core/internal/PlatformVersionSwitches.kt b/core/src/main/kotlin/net/corda/core/internal/PlatformVersionSwitches.kt index 9ab3269653..4370f998b9 100644 --- a/core/src/main/kotlin/net/corda/core/internal/PlatformVersionSwitches.kt +++ b/core/src/main/kotlin/net/corda/core/internal/PlatformVersionSwitches.kt @@ -16,4 +16,5 @@ object PlatformVersionSwitches { const val LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS = 5 const val BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN = 6 const val ENABLE_P2P_COMPRESSION = 7 + const val CERTIFICATE_ROTATION = 9 } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/identity/CertificateRotationTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/identity/CertificateRotationTest.kt index 73e8b18eb0..98a518d7bc 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/identity/CertificateRotationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/identity/CertificateRotationTest.kt @@ -1,5 +1,6 @@ package net.corda.node.services.identity +import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.div import net.corda.core.utilities.OpaqueBytes import net.corda.coretesting.internal.stubs.CertificateStoreStubs @@ -14,6 +15,7 @@ import net.corda.node.services.keys.KeyManagementServiceInternal import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS import net.corda.nodeapi.internal.storeLegalIdentity +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.CHARLIE_NAME @@ -21,6 +23,7 @@ import net.corda.testing.node.internal.FINANCE_CORDAPPS import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.TestStartedNode import net.corda.testing.node.internal.startFlow +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test import java.nio.file.Path @@ -78,7 +81,10 @@ class CertificateRotationTest { @Test(timeout = 300_000) fun `restart with rotated key for one node`() { - mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS) + mockNet = InternalMockNetwork( + cordappsForAllNodes = FINANCE_CORDAPPS, + initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION) + ) val alice = mockNet.createPartyNode(ALICE_NAME) val bob = mockNet.createPartyNode(BOB_NAME) @@ -111,9 +117,24 @@ class CertificateRotationTest { assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP)) } + @Test(timeout = 300_000) + fun `fail to restart with rotated key and wrong minimum platform version`() { + mockNet = InternalMockNetwork( + cordappsForAllNodes = FINANCE_CORDAPPS, + initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 8) + ) + val alice = mockNet.createPartyNode(ALICE_NAME) + assertThatThrownBy { + mockNet.restartNodeWithRotateIdentityKey(alice) + }.hasMessageContaining("Failed to change node legal identity key") + } + @Test(timeout = 300_000) fun `backchain resolution with rotated issuer key`() { - mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS) + mockNet = InternalMockNetwork( + cordappsForAllNodes = FINANCE_CORDAPPS, + initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION) + ) val alice = mockNet.createPartyNode(ALICE_NAME) val bob = mockNet.createPartyNode(BOB_NAME) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 1ee36656af..10eecb5673 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -14,6 +14,7 @@ import net.corda.core.context.InvocationContext import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.newSecureRandom +import net.corda.core.crypto.toStringShort import net.corda.core.flows.ContractUpgradeFlow import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic @@ -31,6 +32,7 @@ import net.corda.core.internal.FlowStateMachineHandle import net.corda.core.internal.NODE_INFO_DIRECTORY import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NetworkParametersStorage +import net.corda.core.internal.PlatformVersionSwitches import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.map @@ -568,7 +570,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, identityService.start(trustRoots, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache) val nodeInfoAndSigned = database.transaction { - updateNodeInfo(publish = true) + updateNodeInfo(publish = true, minimumPlatformVersion = netParams.minimumPlatformVersion) } val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned @@ -693,7 +695,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - private fun updateNodeInfo(publish: Boolean): NodeInfoAndSigned { + private fun updateNodeInfo(publish: Boolean, minimumPlatformVersion: Int = Int.MAX_VALUE): NodeInfoAndSigned { val potentialNodeInfo = NodeInfo( myAddresses(), setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(), @@ -709,6 +711,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, nodeInfoFromDb } else { log.info("Node-info has changed so submitting update. Old node-info was $nodeInfoFromDb") + if (minimumPlatformVersion < PlatformVersionSwitches.CERTIFICATE_ROTATION && nodeInfoFromDb != null) { + requireSameNodeIdentity(nodeInfoFromDb, potentialNodeInfo) + } val newNodeInfo = potentialNodeInfo.copy(serial = platformClock.millis()) networkMapCache.addOrUpdateNode(newNodeInfo) log.info("New node-info: $newNodeInfo") @@ -745,6 +750,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } + private fun requireSameNodeIdentity(oldNodeInfo: NodeInfo, newNodeInfo: NodeInfo) { + val oldIdentity = oldNodeInfo.legalIdentities.first() + val newIdentity = newNodeInfo.legalIdentities.first() + require(oldIdentity == newIdentity || oldIdentity.name != newIdentity.name) { + "Failed to change node legal identity key from ${oldIdentity.owningKey.toStringShort()}"+ + " to ${newIdentity.owningKey.toStringShort()}," + + " as it requires minimumPlatformVersion >= ${PlatformVersionSwitches.CERTIFICATE_ROTATION}." + } + } + // Publish node info on startup and start task that sends every day a heartbeat - republishes node info. private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) { // By default heartbeat interval should be set to 1 day, but for testing we may change it. diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowMetadataRecordingTest.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowMetadataRecordingTest.kt index 63b1cfa2ce..457693d4ac 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowMetadataRecordingTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowMetadataRecordingTest.kt @@ -262,7 +262,7 @@ class FlowMetadataRecordingTest { it.initialParameters.deserialize(context = SerializationDefaults.STORAGE_CONTEXT) ) assertThat(it.launchingCordapp).contains("custom-cordapp") - assertEquals(8, it.platformVersion) + assertEquals(PLATFORM_VERSION, it.platformVersion) assertEquals(nodeAHandle.nodeInfo.singleIdentity().name.toString(), it.startedBy) assertEquals(context!!.trace.invocationId.timestamp.truncatedTo(ChronoUnit.MILLIS), it.invocationInstant.truncatedTo(ChronoUnit.MILLIS))