diff --git a/build.gradle b/build.gradle index 58c7c31ec2..d71ffe58cc 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,7 @@ buildscript { // Our version: bump this on release. ext.corda_release_version = "3.0-SNAPSHOT" // Increment this on any release that changes public APIs anywhere in the Corda platform - // TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal - ext.corda_platform_version = 2 + ext.corda_platform_version = constants.getProperty("platformVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") // Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things. diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java index 31daccc9c2..692cfe381c 100644 --- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -74,7 +74,7 @@ public class StandaloneCordaRPCJavaClientTest { } private void copyFinanceCordapp() { - Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps")); + Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME)); try { Files.createDirectories(cordappsDir); } catch (IOException ex) { diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 36323ec068..23018f7bfb 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -86,7 +86,7 @@ class StandaloneCordaRPClientTest { } private fun copyFinanceCordapp() { - val cordappsDir = (factory.baseDirectory(notaryConfig) / "cordapps").createDirectories() + val cordappsDir = (factory.baseDirectory(notaryConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories() // Find the finance jar file for the smoke tests of this module val financeJar = Paths.get("build", "resources", "smokeTest").list { it.filter { "corda-finance" in it.toString() }.toList().single() diff --git a/constants.properties b/constants.properties index f278032d63..211f29e563 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,6 @@ gradlePluginsVersion=2.0.9 kotlinVersion=1.1.60 +platformVersion=2 guavaVersion=21.0 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 \ No newline at end of file diff --git a/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt b/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt new file mode 100644 index 0000000000..f9844fbce0 --- /dev/null +++ b/core/src/smoke-test/kotlin/net/corda/core/NodeVersioningTest.kt @@ -0,0 +1,75 @@ +package net.corda.core + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.* +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.nodeapi.User +import net.corda.smoketesting.NodeConfig +import net.corda.smoketesting.NodeProcess +import net.corda.testing.common.internal.ProjectStructure +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.nio.file.Paths +import java.util.* +import java.util.concurrent.atomic.AtomicInteger +import java.util.jar.JarFile +import kotlin.streams.toList + +class NodeVersioningTest { + private companion object { + val user = User("user1", "test", permissions = setOf("ALL")) + val port = AtomicInteger(15100) + + val expectedPlatformVersion = (ProjectStructure.projectRootDir / "constants.properties").read { + val constants = Properties() + constants.load(it) + constants.getProperty("platformVersion").toInt() + } + } + + private val factory = NodeProcess.Factory() + + private val aliceConfig = NodeConfig( + legalName = CordaX500Name(organisation = "Alice Corp", locality = "Madrid", country = "ES"), + p2pPort = port.andIncrement, + rpcPort = port.andIncrement, + webPort = port.andIncrement, + isNotary = false, + users = listOf(user) + ) + + @Test + fun `platform version in manifest file`() { + val manifest = JarFile(factory.cordaJar.toFile()).manifest + assertThat(manifest.mainAttributes.getValue("Corda-Platform-Version").toInt()).isEqualTo(expectedPlatformVersion) + } + + @Test + fun `platform version from RPC`() { + val cordappsDir = (factory.baseDirectory(aliceConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories() + // Find the jar file for the smoke tests of this module + val selfCordapp = Paths.get("build", "libs").list { + it.filter { "-smokeTests" in it.toString() }.toList().single() + } + selfCordapp.copyToDirectory(cordappsDir) + + factory.create(aliceConfig).use { alice -> + alice.connect().use { + val rpc = it.proxy + assertThat(rpc.protocolVersion).isEqualTo(expectedPlatformVersion) + assertThat(rpc.nodeInfo().platformVersion).isEqualTo(expectedPlatformVersion) + assertThat(rpc.startFlow(NodeVersioningTest::GetPlatformVersionFlow).returnValue.getOrThrow()).isEqualTo(expectedPlatformVersion) + } + } + } + + @StartableByRPC + class GetPlatformVersionFlow : FlowLogic() { + @Suspendable + override fun call(): Int = serviceHub.myInfo.platformVersion + } +} diff --git a/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt index 2f1991af12..2f826b5f54 100644 --- a/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt +++ b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.unwrap import net.corda.nodeapi.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess +import net.corda.smoketesting.NodeProcess.Companion.CORDAPPS_DIR_NAME import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Paths @@ -22,7 +23,6 @@ import kotlin.streams.toList class CordappSmokeTest { private companion object { - private const val CORDAPPS_DIR_NAME = "cordapps" val user = User("user1", "test", permissions = setOf("ALL")) val port = AtomicInteger(15100) } @@ -38,7 +38,6 @@ class CordappSmokeTest { users = listOf(user) ) - @Test fun `FlowContent appName returns the filename of the CorDapp jar`() { val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories() diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 9973be0bf7..a7561d331f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -10,7 +10,7 @@ import net.corda.node.internal.NodeStartup import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE -import net.corda.testing.ProjectStructure.projectRootDir +import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 4d91e00ec6..468de4d8f5 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -190,11 +190,13 @@ fun MessagingService.onNext(topic: String, sessionId: Long): CordaFutu return messageFuture } -fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) - = send(TopicSession(topic, sessionID), payload, to, uuid) +fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) { + send(TopicSession(topic, sessionID), payload, to, uuid) +} -fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) - = send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId) +fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) { + send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId) +} interface MessageHandlerRegistration diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 150234674a..a4ef0e5dd0 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -1,9 +1,6 @@ package net.corda.node.services.messaging -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair -import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.internal.concurrent.openFuture import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl @@ -27,13 +24,13 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.net.ServerSocket +import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit.MILLISECONDS import kotlin.concurrent.thread import kotlin.test.assertEquals import kotlin.test.assertNull -//TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment class ArtemisMessagingTests { companion object { const val TOPIC = "platform.self" @@ -54,21 +51,19 @@ class ArtemisMessagingTests { private lateinit var config: NodeConfiguration private lateinit var database: CordaPersistence private lateinit var userService: RPCUserService - private lateinit var networkMapRegistrationFuture: CordaFuture private var messagingClient: P2PMessagingClient? = null private var messagingServer: ArtemisMessagingServer? = null private lateinit var networkMapCache: NetworkMapCacheImpl + @Before fun setUp() { - val baseDirectory = temporaryFolder.root.toPath() userService = RPCUserServiceImpl(emptyList()) config = testNodeConfiguration( - baseDirectory = baseDirectory, + baseDirectory = temporaryFolder.root.toPath(), myLegalName = ALICE.name) LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) - networkMapRegistrationFuture = doneFuture(Unit) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) } @@ -76,8 +71,6 @@ class ArtemisMessagingTests { fun cleanUp() { messagingClient?.stop() messagingServer?.stop() - messagingClient = null - messagingServer = null database.close() LogHelper.reset(PersistentUniquenessProvider::class) } @@ -120,9 +113,7 @@ class ArtemisMessagingTests { @Test fun `client should be able to send message to itself`() { - val receivedMessages = LinkedBlockingQueue() - - val messagingClient = createAndStartClientAndServer(receivedMessages) + val (messagingClient, receivedMessages) = createAndStartClientAndServer() val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) messagingClient.send(message, messagingClient.myAddress) @@ -132,76 +123,45 @@ class ArtemisMessagingTests { } @Test - fun `client should be able to send message to itself before network map is available, and receive after`() { - val settableFuture = openFuture() - networkMapRegistrationFuture = settableFuture - - val receivedMessages = LinkedBlockingQueue() - - val messagingClient = createAndStartClientAndServer(receivedMessages) + fun `platform version is included in the message`() { + val (messagingClient, receivedMessages) = createAndStartClientAndServer(platformVersion = 3) val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) messagingClient.send(message, messagingClient.myAddress) - settableFuture.set(Unit) - val firstActual: Message = receivedMessages.take() - assertEquals("first msg", String(firstActual.data)) - assertNull(receivedMessages.poll(200, MILLISECONDS)) - } - - @Test - fun `client should be able to send large numbers of messages to itself before network map is available and survive restart, then receive messages`() { - // Crank the iteration up as high as you want... just takes longer to run. - val iterations = 100 - networkMapRegistrationFuture = openFuture() - - val receivedMessages = LinkedBlockingQueue() - - val messagingClient = createAndStartClientAndServer(receivedMessages) - for (iter in 1..iterations) { - val message = messagingClient.createMessage(TOPIC, data = "first msg $iter".toByteArray()) - messagingClient.send(message, messagingClient.myAddress) - } - - // Stop client and server and create afresh. - messagingClient.stop() - messagingServer?.stop() - - networkMapRegistrationFuture = doneFuture(Unit) - - createAndStartClientAndServer(receivedMessages) - for (iter in 1..iterations) { - val firstActual: Message = receivedMessages.take() - assertThat(String(firstActual.data)).isEqualTo("first msg $iter") - } - assertNull(receivedMessages.poll(200, MILLISECONDS)) + val received = receivedMessages.take() + assertThat(received.platformVersion).isEqualTo(3) } private fun startNodeMessagingClient() { messagingClient!!.start() } - private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue): P2PMessagingClient { + private fun createAndStartClientAndServer(platformVersion: Int = 1): Pair> { + val receivedMessages = LinkedBlockingQueue() + createMessagingServer().start() - val messagingClient = createMessagingClient() + val messagingClient = createMessagingClient(platformVersion = platformVersion) startNodeMessagingClient() messagingClient.addMessageHandler(TOPIC) { message, _ -> receivedMessages.add(message) } // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. - thread { messagingClient.run() } - return messagingClient + thread(isDaemon = true) { messagingClient.run() } + + return Pair(messagingClient, receivedMessages) } - private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): P2PMessagingClient { + private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort), platformVersion: Int = 1): P2PMessagingClient { return database.transaction { P2PMessagingClient( config, - MOCK_VERSION_INFO, + MOCK_VERSION_INFO.copy(platformVersion = platformVersion), server, identity.public, ServiceAffinityExecutor("ArtemisMessagingTests", 1), - database).apply { + database + ).apply { config.configureWithDevSSLCertificate() messagingClient = this } diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 39500c7834..e6d581f236 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -9,7 +9,7 @@ import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR -import net.corda.testing.ProjectStructure.projectRootDir +import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index b6656977b7..17ccd5a689 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -21,7 +21,8 @@ class NodeProcess( private val node: Process, private val client: CordaRPCClient ) : AutoCloseable { - private companion object { + companion object { + const val CORDAPPS_DIR_NAME = "cordapps" private val log = contextLogger() } @@ -42,9 +43,11 @@ class NodeProcess( (nodeDir / "artemis").toFile().deleteRecursively() } + // TODO All use of this factory have duplicate code which is either bundling the calling module or a 3rd party module + // as a CorDapp for the nodes. class Factory( - private val buildDirectory: Path = Paths.get("build"), - private val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI()) + val buildDirectory: Path = Paths.get("build"), + val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI()) ) { private companion object { val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") @@ -71,36 +74,37 @@ class NodeProcess( val process = startNode(nodeDir) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) - val user = config.users[0] + waitForNode(process, config, client) + return NodeProcess(config, nodeDir, process, client) + } - val setupExecutor = Executors.newSingleThreadScheduledExecutor() + private fun waitForNode(process: Process, config: NodeConfig, client: CordaRPCClient) { + val executor = Executors.newSingleThreadScheduledExecutor() try { - setupExecutor.scheduleWithFixedDelay({ + executor.scheduleWithFixedDelay({ try { if (!process.isAlive) { log.error("Node '${config.commonName}' has died.") return@scheduleWithFixedDelay } - val conn = client.start(user.username, user.password) - conn.close() + val rpcConnection = config.users[0].let { client.start(it.username, it.password) } + rpcConnection.close() // Cancel the "setup" task now that we've created the RPC client. - setupExecutor.shutdown() + executor.shutdown() } catch (e: Exception) { log.warn("Node '{}' not ready yet (Error: {})", config.commonName, e.message) } }, 5, 1, SECONDS) - val setupOK = setupExecutor.awaitTermination(120, SECONDS) + val setupOK = executor.awaitTermination(120, SECONDS) check(setupOK && process.isAlive) { "Failed to create RPC connection" } } catch (e: Exception) { process.destroyForcibly() throw e } finally { - setupExecutor.shutdownNow() + executor.shutdownNow() } - - return NodeProcess(config, nodeDir, process, client) } private fun startNode(nodeDir: Path): Process { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ProjectStructure.kt similarity index 89% rename from testing/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt rename to testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ProjectStructure.kt index 1809d4dc16..4b4d5cac34 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ProjectStructure.kt @@ -1,4 +1,4 @@ -package net.corda.testing +package net.corda.testing.common.internal import net.corda.core.internal.div import net.corda.core.internal.isDirectory