diff --git a/build.gradle b/build.gradle index 6a028e30d6..7658a7c50c 100644 --- a/build.gradle +++ b/build.gradle @@ -369,6 +369,7 @@ bintrayConfig { 'corda-rpc', 'corda-core', 'corda-core-deterministic', + 'corda-deterministic-verifier', 'corda-djvm', 'corda', 'corda-finance', diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index 2b0aaf1433..d2be073cc3 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -11,8 +11,8 @@ def javaHome = System.getProperty('java.home') def jarBaseName = "corda-${project.name}".toString() configurations { - runtimeLibraries - runtimeArtifacts.extendsFrom runtimeLibraries + deterministicLibraries + deterministicArtifacts.extendsFrom deterministicLibraries } dependencies { @@ -20,14 +20,14 @@ dependencies { // Configure these by hand. It should be a minimal subset of core's dependencies, // and without any obviously non-deterministic ones such as Hibernate. - runtimeLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - runtimeLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - runtimeLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" - runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" - runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version" - runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version" - runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version" - runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version" + deterministicLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + deterministicLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + deterministicLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + deterministicLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" + deterministicLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version" + deterministicLibraries "com.google.code.findbugs:jsr305:$jsr305_version" + deterministicLibraries "net.i2p.crypto:eddsa:$eddsa_version" + deterministicLibraries "org.slf4j:slf4j-api:$slf4j_version" } jar { @@ -115,7 +115,7 @@ task determinise(type: ProGuardTask) { libraryjars file("$javaHome/lib/rt.jar") libraryjars file("$javaHome/lib/jce.jar") - configurations.runtimeLibraries.forEach { + configurations.deterministicLibraries.forEach { libraryjars it, filter: '!META-INF/versions/**' } @@ -155,7 +155,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { libraryjars deterministic_rt_jar - configurations.runtimeLibraries.forEach { + configurations.deterministicLibraries.forEach { libraryjars it, filter: '!META-INF/versions/**' } @@ -176,12 +176,12 @@ assemble.dependsOn checkDeterminism def deterministicJar = metafix.outputs.files.singleFile artifacts { - runtimeArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix + deterministicArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix } publish { - dependenciesFrom configurations.runtimeArtifacts + dependenciesFrom configurations.deterministicArtifacts publishSources = false publishJavadoc = false name jarBaseName diff --git a/core-deterministic/testing/build.gradle b/core-deterministic/testing/build.gradle index a472f5cde5..bb007715eb 100644 --- a/core-deterministic/testing/build.gradle +++ b/core-deterministic/testing/build.gradle @@ -1,10 +1,10 @@ apply plugin: 'kotlin' dependencies { - testCompile project(path: ':core-deterministic', configuration: 'runtimeArtifacts') - testCompile project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts') + testCompile project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + testCompile project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') + testCompile project(path: ':core-deterministic:testing:verifier', configuration: 'deterministicArtifacts') testCompile project(path: ':core-deterministic:testing:data', configuration: 'testData') - testCompile project(':core-deterministic:testing:common') testCompile(project(':finance')) { transitive = false } diff --git a/core-deterministic/testing/common/build.gradle b/core-deterministic/testing/common/build.gradle deleted file mode 100644 index f7c48a6c7a..0000000000 --- a/core-deterministic/testing/common/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply from: '../../../deterministic.gradle' -apply plugin: 'idea' - -dependencies { - compileOnly project(path: ':core-deterministic', configuration: 'runtimeArtifacts') - compileOnly project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts') - compileOnly "junit:junit:$junit_version" -} - -idea { - module { - if (project.hasProperty("deterministic_idea_sdk")) { - jdkName project.property("deterministic_idea_sdk") as String - } - } -} diff --git a/core-deterministic/testing/data/build.gradle b/core-deterministic/testing/data/build.gradle index d203ae5572..59992d92eb 100644 --- a/core-deterministic/testing/data/build.gradle +++ b/core-deterministic/testing/data/build.gradle @@ -8,7 +8,7 @@ dependencies { testCompile project(':core') testCompile project(':finance') testCompile project(':node-driver') - testCompile project(':core-deterministic:testing:common') + testCompile project(path: ':core-deterministic:testing:verifier', configuration: 'runtimeArtifacts') testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile "org.jetbrains.kotlin:kotlin-reflect" diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt index 0304661183..1ada19e231 100644 --- a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/GenerateData.kt @@ -1,8 +1,8 @@ package net.corda.deterministic.data import net.corda.core.serialization.deserialize -import net.corda.deterministic.common.LocalSerializationRule -import net.corda.deterministic.common.TransactionVerificationRequest +import net.corda.deterministic.verifier.LocalSerializationRule +import net.corda.deterministic.verifier.TransactionVerificationRequest import org.junit.Before import org.junit.Rule import org.junit.Test diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt index a6b704077e..d777bf9df1 100644 --- a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt @@ -7,9 +7,9 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.serialization.serialize -import net.corda.deterministic.common.MockContractAttachment -import net.corda.deterministic.common.SampleCommandData -import net.corda.deterministic.common.TransactionVerificationRequest +import net.corda.deterministic.verifier.MockContractAttachment +import net.corda.deterministic.verifier.SampleCommandData +import net.corda.deterministic.verifier.TransactionVerificationRequest import net.corda.finance.POUNDS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash.* diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt index 5935c13b3c..4825d4787f 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt @@ -3,7 +3,7 @@ package net.corda.deterministic.crypto import net.corda.core.crypto.* import net.corda.deterministic.KeyStoreProvider import net.corda.deterministic.CheatingSecurityProvider -import net.corda.deterministic.common.LocalSerializationRule +import net.corda.deterministic.verifier.LocalSerializationRule import org.junit.* import org.junit.rules.RuleChain import java.security.* diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/VerifyTransactionTest.kt similarity index 55% rename from core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt rename to core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/VerifyTransactionTest.kt index 84bab1d1b7..6526ca3c51 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/VerifyTransactionTest.kt @@ -1,29 +1,29 @@ package net.corda.deterministic.txverify import net.corda.deterministic.bytesOfResource -import net.corda.deterministic.common.LocalSerializationRule -import net.corda.deterministic.common.verifyInEnclave +import net.corda.deterministic.verifier.LocalSerializationRule +import net.corda.deterministic.verifier.verifyTransaction import net.corda.finance.contracts.asset.Cash.Commands.* import org.assertj.core.api.Assertions.assertThat import org.junit.ClassRule import org.junit.Test import kotlin.test.assertFailsWith -class EnclaveletTest { +class VerifyTransactionTest { companion object { @ClassRule @JvmField - val serialization = LocalSerializationRule(EnclaveletTest::class) + val serialization = LocalSerializationRule(VerifyTransactionTest::class) } @Test fun success() { - verifyInEnclave(bytesOfResource("txverify/tx-success.bin")) + verifyTransaction(bytesOfResource("txverify/tx-success.bin")) } @Test fun failure() { - val e = assertFailsWith { verifyInEnclave(bytesOfResource("txverify/tx-failure.bin")) } + val e = assertFailsWith { verifyTransaction(bytesOfResource("txverify/tx-failure.bin")) } assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command") } } diff --git a/core-deterministic/testing/verifier/build.gradle b/core-deterministic/testing/verifier/build.gradle new file mode 100644 index 0000000000..259a9c3110 --- /dev/null +++ b/core-deterministic/testing/verifier/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'java-library' +apply from: '../../../deterministic.gradle' +apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'idea' + +description 'Test utilities for deterministic contract verification' + +configurations { + deterministicArtifacts + runtimeArtifacts.extendsFrom api +} + +dependencies { + deterministicArtifacts project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') + deterministicArtifacts project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + + runtimeArtifacts project(':serialization') + runtimeArtifacts project(':core') + + // Compile against the deterministic artifacts to ensure that we use only the deterministic API subset. + compileOnly configurations.deterministicArtifacts + api "junit:junit:$junit_version" +} + +jar { + baseName 'corda-deterministic-verifier' +} + +artifacts { + deterministicArtifacts jar + runtimeArtifacts jar + publish jar +} + +publish { + // Our published POM will contain dependencies on the non-deterministic Corda artifacts. + dependenciesFrom configurations.runtimeArtifacts + name jar.baseName +} + +idea { + module { + if (project.hasProperty("deterministic_idea_sdk")) { + jdkName project.property("deterministic_idea_sdk") as String + } + } +} diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt similarity index 98% rename from core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt rename to core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt index 05c8c3bc5c..15848a4be4 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/LocalSerializationRule.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/LocalSerializationRule.kt @@ -1,4 +1,4 @@ -package net.corda.deterministic.common +package net.corda.deterministic.verifier import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext @@ -83,4 +83,4 @@ class LocalSerializationRule(private val label: String) : TestRule { return canDeserializeVersion(magic) && target == P2P } } -} \ No newline at end of file +} diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/MockContractAttachment.kt similarity index 93% rename from core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt rename to core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/MockContractAttachment.kt index 1526825683..0e28c9647e 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/MockContractAttachment.kt @@ -1,4 +1,4 @@ -package net.corda.deterministic.common +package net.corda.deterministic.verifier import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractClassName diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/SampleData.kt similarity index 76% rename from core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt rename to core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/SampleData.kt index 025fa148fa..9c4cfdcb59 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/SampleData.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/SampleData.kt @@ -1,5 +1,5 @@ @file:JvmName("SampleData") -package net.corda.deterministic.common +package net.corda.deterministic.verifier import net.corda.core.contracts.TypeOnlyCommandData diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/TransactionVerificationRequest.kt similarity index 97% rename from core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt rename to core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/TransactionVerificationRequest.kt index f2c825ba61..96a886a618 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/TransactionVerificationRequest.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/TransactionVerificationRequest.kt @@ -1,4 +1,4 @@ -package net.corda.deterministic.common +package net.corda.deterministic.verifier import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/Verifier.kt similarity index 84% rename from core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt rename to core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/Verifier.kt index 720a2bc4ff..e7a710d707 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt +++ b/core-deterministic/testing/verifier/src/main/kotlin/net/corda/deterministic/verifier/Verifier.kt @@ -1,5 +1,5 @@ -@file:JvmName("Enclavelet") -package net.corda.deterministic.common +@file:JvmName("Verifier") +package net.corda.deterministic.verifier import net.corda.core.serialization.deserialize import net.corda.core.transactions.LedgerTransaction @@ -11,7 +11,7 @@ import net.corda.core.transactions.LedgerTransaction * TODO: Transaction data is meant to be encrypted under an enclave-private key. */ @Throws(Exception::class) -fun verifyInEnclave(reqBytes: ByteArray) { +fun verifyTransaction(reqBytes: ByteArray) { deserialize(reqBytes).verify() } diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index e755cf1ad4..f27e76f405 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -49,20 +49,4 @@ interface Cordapp { val cordappClasses: List val info: Info val jarHash: SecureHash.SHA256 - - /** - * CorDapp's information, including vendor and version. - * - * @property shortName Cordapp's shortName - * @property vendor Cordapp's vendor - * @property version Cordapp's version - */ - @DoNotImplement - interface Info { - val shortName: String - val vendor: String - val version: String - - fun hasUnknownFields(): Boolean - } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index a08f522da5..5a459783e8 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -24,26 +24,28 @@ data class CordappImpl( override val customSchemas: Set, override val allFlows: List>>, override val jarPath: URL, - override val info: Cordapp.Info = CordappImpl.Info.UNKNOWN, - override val jarHash: SecureHash.SHA256, - override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar") ) : Cordapp { + val info: Info, + override val jarHash: SecureHash.SHA256) : Cordapp { + override val name: String = jarName(jarPath) + + companion object { + fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar") + } /** * An exhaustive list of all classes relevant to the node within this CorDapp * * TODO: Also add [SchedulableFlow] as a Cordapp class */ - override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames) + override val cordappClasses: List = (rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames - data class Info(override val shortName: String, override val vendor: String, override val version: String): Cordapp.Info { + // TODO Why a seperate Info class and not just have the fields directly in CordappImpl? + data class Info(val shortName: String, val vendor: String, val version: String) { companion object { private const val UNKNOWN_VALUE = "Unknown" - val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE) } - override fun hasUnknownFields(): Boolean { - return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } - } + fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } } } diff --git a/djvm/build.gradle b/djvm/build.gradle index b3447b63ac..2cf5b11d72 100644 --- a/djvm/build.gradle +++ b/djvm/build.gradle @@ -49,6 +49,5 @@ artifacts { publish { dependenciesFrom configurations.shadow - disableDefaultJar true name shadowJar.baseName } diff --git a/finance/build.gradle b/finance/build.gradle index e90a979d98..7bb868e5a7 100644 --- a/finance/build.gradle +++ b/finance/build.gradle @@ -75,19 +75,17 @@ jar { manifest { attributes( "Manifest-Version": "1.0", - "Name": "net/corda/finance", "Specification-Title": description, "Specification-Version": version, "Specification-Vendor": "R3", "Implementation-Title": "$group.$baseName", - "Implementation-Version": version, - "Implementation-Vendor": "R3" ) } } cordapp { info { + name "net/corda/finance" vendor "R3" } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index fffcd4ff76..9348f98d5e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -93,7 +93,7 @@ class ArtemisMessagingTest { } LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeInternalTestDataSourceProperties(configSupplier = { ConfigFactory.empty() }), DatabaseConfig(runMigration = true), { null }, { null }) - networkMapCache = PersistentNetworkMapCache(database, rigorousMock(), ALICE_NAME).apply { start(emptyList()) } + networkMapCache = PersistentNetworkMapCache(database, rigorousMock()).apply { start(emptyList()) } } @After diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 69da499664..9c7469193e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -1,7 +1,6 @@ package net.corda.node.services.network import net.corda.core.node.NodeInfo -import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.configureDatabase import net.corda.node.internal.schemas.NodeInfoSchemaV1 @@ -45,7 +44,7 @@ class PersistentNetworkMapCacheTest : IntegrationTest() { //Enterprise only - for test in database mode ensure the remote database is setup before creating CordaPersistence super.setUp() database = configureDatabase(makeTestDataSourceProperties(CHARLIE_NAME.toDatabaseSchemaName()), makeTestDatabaseProperties(CHARLIE_NAME.toDatabaseSchemaName()), { null }, { null }) - charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate), CHARLIE.name) + charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate)) } @After @@ -57,7 +56,6 @@ class PersistentNetworkMapCacheTest : IntegrationTest() { fun addNode() { val alice = createNodeInfo(listOf(ALICE)) charlieNetMapCache.addNode(alice) - assertThat(charlieNetMapCache.nodeReady).isDone() val fromDb = database.transaction { session.createQuery( "from ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name}", @@ -67,32 +65,6 @@ class PersistentNetworkMapCacheTest : IntegrationTest() { assertThat(fromDb).containsOnly(alice) } - @Test - fun `adding the node's own node-info doesn't complete the nodeReady future`() { - val charlie = createNodeInfo(listOf(CHARLIE)) - charlieNetMapCache.addNode(charlie) - assertThat(charlieNetMapCache.nodeReady).isNotDone() - assertThat(charlieNetMapCache.getNodeByLegalName(CHARLIE.name)).isEqualTo(charlie) - } - - @Test - fun `starting with just the node's own node-info in the db`() { - val charlie = createNodeInfo(listOf(CHARLIE)) - saveNodeInfoIntoDb(charlie) - assertThat(charlieNetMapCache.allNodes).containsOnly(charlie) - charlieNetMapCache.start(emptyList()) - assertThat(charlieNetMapCache.nodeReady).isNotDone() - } - - @Test - fun `starting with another node-info in the db`() { - val alice = createNodeInfo(listOf(ALICE)) - saveNodeInfoIntoDb(alice) - assertThat(charlieNetMapCache.allNodes).containsOnly(alice) - charlieNetMapCache.start(emptyList()) - assertThat(charlieNetMapCache.nodeReady).isDone() - } - @Test fun `unknown legal name`() { charlieNetMapCache.addNode(createNodeInfo(listOf(ALICE))) @@ -154,19 +126,4 @@ class PersistentNetworkMapCacheTest : IntegrationTest() { serial = 1 ) } - - private fun saveNodeInfoIntoDb(nodeInfo: NodeInfo) { - database.transaction { - session.save(NodeInfoSchemaV1.PersistentNodeInfo( - id = 0, - hash = nodeInfo.serialize().hash.toString(), - addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) }, - legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem -> - NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0) - }, - platformVersion = nodeInfo.platformVersion, - serial = nodeInfo.serial - )) - } - } } diff --git a/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt index 8f472c8922..8a8feffc2d 100644 --- a/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt @@ -2,6 +2,7 @@ package net.corda.node.cordapp import net.corda.core.cordapp.Cordapp import net.corda.core.flows.FlowLogic +import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.schemas.MappedSchema /** @@ -12,7 +13,7 @@ interface CordappLoader { /** * Returns all [Cordapp]s found. */ - val cordapps: List + val cordapps: List /** * Returns a [ClassLoader] containing all types from all [Cordapp]s. 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 f10930dd75..8ae7713d49 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -151,7 +151,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // TODO Break cyclic dependency identityService.database = database } - val networkMapCache = PersistentNetworkMapCache(database, identityService, configuration.myLegalName).tokenize() + val networkMapCache = PersistentNetworkMapCache(database, identityService).tokenize() val checkpointStorage = DBCheckpointStorage() @Suppress("LeakingThis") val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize() @@ -222,7 +222,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } /** Set to non-null once [start] has been successfully called. */ - open val started get() = _started + open val started: S? get() = _started @Volatile private var _started: S? = null @@ -312,6 +312,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, "Node's platform version is lower than network's required minimumPlatformVersion" } servicesForResolution.start(netParams) + networkMapCache.start(netParams.notaries) startDatabase() val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) @@ -333,7 +334,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction { - networkMapCache.start(netParams.notaries) updateNodeInfo(identity, identityKeyPair, publish = true) } @@ -481,7 +481,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } else { 1.days } - val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater", Executors.defaultThreadFactory())) + val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater")) executor.submit(object : Runnable { override fun run() { val republishInterval = try { diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index acb5677618..16410ebf81 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -137,6 +137,7 @@ open class Node(configuration: NodeConfiguration, return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo) } + // TODO: make this configurable. const val MAX_RPC_MESSAGE_SIZE = 10485760 } diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 4ae3a2b7fd..3ae659cc68 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -11,6 +11,7 @@ import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.Crypto import net.corda.core.internal.* import net.corda.core.internal.concurrent.thenMatch +import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.errors.AddressBindingException import net.corda.core.utilities.Try import net.corda.core.utilities.loggerFor @@ -329,7 +330,6 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { val nodeInfo = node.start() logLoadedCorDapps(node.services.cordappProvider.cordapps) - Node.printBasicNodeInfo("Loaded CorDapps", node.services.cordappProvider.cordapps.joinToString { it.name }) node.nodeReadyFuture.thenMatch({ val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 @@ -424,10 +424,10 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { ) } - open protected fun logLoadedCorDapps(corDapps: List) { - fun Cordapp.Info.description() = "$shortName version $version by $vendor" + open protected fun logLoadedCorDapps(corDapps: List) { + fun CordappImpl.Info.description() = "$shortName version $version by $vendor" - Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", ", transform = Cordapp.Info::description)) + Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", ", transform = CordappImpl.Info::description)) corDapps.map { it.info }.filter { it.hasUnknownFields() }.let { malformed -> if (malformed.isNotEmpty()) { logger.warn("Found ${malformed.size} CorDapp(s) with unknown information. They will be unable to run on Corda in the future.") diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index 4241b6aea2..5ab49c53a0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -8,6 +8,7 @@ import net.corda.core.cordapp.CordappContext import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER +import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.createCordappContext import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage @@ -34,7 +35,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, /** * Current known CorDapps loaded on this node */ - override val cordapps get() = cordappLoader.cordapps + override val cordapps: List get() = cordappLoader.cordapps fun start(whitelistedContractImplementations: Map>) { cordappAttachments.putAll(loadContractsIntoAttachmentStore()) diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt index 01b88312e6..fcd6e4d602 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt @@ -3,8 +3,9 @@ package net.corda.node.internal.cordapp import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowLogic +import net.corda.core.internal.cordapp.CordappImpl interface CordappProviderInternal : CordappProvider { - val cordapps: List + val cordapps: List fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp? } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index 772dd2c1ae..1cbfbe75ab 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -15,6 +15,7 @@ import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger +import net.corda.node.VersionInfo import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.classloading.requireAnnotation import net.corda.nodeapi.internal.coreContractClasses @@ -36,7 +37,7 @@ import kotlin.streams.toList */ class JarScanningCordappLoader private constructor(private val cordappJarPaths: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() { - override val cordapps: List by lazy { loadCordapps() + coreCordapp } + override val cordapps: List by lazy { loadCordapps() + coreCordapp } override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) @@ -57,7 +58,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * @param corDappDirectories Directories used to scan for CorDapp JARs. */ fun fromDirectories(corDappDirectories: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader { - logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo) } @@ -67,7 +67,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. */ - fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo) + fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader { + return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo) + } private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) @@ -108,14 +110,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: jarHash = SecureHash.allOnesHash ) - private fun loadCordapps(): List { - return cordappJarPaths.map { scanCordapp(it).toCordapp(it) } - } + private fun loadCordapps(): List = cordappJarPaths.map { scanCordapp(it).toCordapp(it) } - private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp { - - val name = url.url.toPath().fileName.toString().removeSuffix(".jar") - val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name) + private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl { + val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(CordappImpl.jarName(url.url)) return CordappImpl( findContractClassNames(this), findInitiatedFlows(this), @@ -275,24 +273,23 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: class MultipleCordappsForFlowException(message: String) : Exception(message) abstract class CordappLoaderTemplate : CordappLoader { - override val flowCordappMap: Map>, Cordapp> by lazy { cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } .groupBy { it.first } - .mapValues { - if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") } - it.value.single().second + .mapValues { entry -> + if (entry.value.size > 1) { + throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " + + "${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.name }} ].") + } + entry.value.single().second } } override val cordappSchemas: Set by lazy { - cordapps.flatMap { it.customSchemas }.toSet() } override val appClassLoader: ClassLoader by lazy { - URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader) } } - diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt index 435da4bef0..eb54399eea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt @@ -1,13 +1,10 @@ package net.corda.node.internal.cordapp -import net.corda.core.cordapp.Cordapp import net.corda.core.internal.cordapp.CordappImpl -import java.util.* import java.util.jar.Attributes import java.util.jar.Manifest fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest { - val manifest = Manifest() // Mandatory manifest attribute. If not present, all other entries are silently skipped. @@ -32,21 +29,19 @@ internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Man } operator fun Manifest.set(key: String, value: String) { - mainAttributes.putValue(key, value) } -internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info { - - var unknown = CordappImpl.Info.UNKNOWN +fun Manifest?.toCordappInfo(defaultShortName: String): CordappImpl.Info { + var info = CordappImpl.Info.UNKNOWN (this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName -> - unknown = unknown.copy(shortName = shortName) + info = info.copy(shortName = shortName) } this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor -> - unknown = unknown.copy(vendor = vendor) + info = info.copy(vendor = vendor) } this?.mainAttributes?.getValue("Implementation-Version")?.let { version -> - unknown = unknown.copy(version = version) + info = info.copy(version = version) } - return unknown -} \ No newline at end of file + return info +} diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index cb555fb671..674ba91b89 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo @@ -28,6 +29,8 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import java.security.PublicKey interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase { + override val nodeReady: OpenFuture + val allNodeHashes: List fun getNodeByHash(nodeHash: SecureHash): NodeInfo? diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index 06ef292b0d..9194b413e2 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -24,13 +24,12 @@ import java.nio.file.StandardCopyOption import java.security.cert.X509Certificate import java.time.Duration import java.util.* -import java.util.concurrent.Executors import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit import kotlin.system.exitProcess class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, - private val fileWatcher: NodeInfoWatcher, + private val nodeInfoWatcher: NodeInfoWatcher, private val networkMapClient: NetworkMapClient?, private val baseDirectory: Path, private val extraNetworkMapKeys: List @@ -40,8 +39,10 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val defaultRetryInterval = 1.minutes } - private val parametersUpdatesTrack: PublishSubject = PublishSubject.create() - private val executor = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory())) + private val parametersUpdatesTrack = PublishSubject.create() + private val networkMapPoller = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread")).apply { + executeExistingDelayedTasksAfterShutdownPolicy = false + } private var newNetworkParameters: Pair? = null private var fileWatcherSubscription: Subscription? = null private lateinit var trustRoot: X509Certificate @@ -50,7 +51,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, override fun close() { fileWatcherSubscription?.unsubscribe() - MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS) + MoreExecutors.shutdownAndAwaitTermination(networkMapPoller, 50, TimeUnit.SECONDS) } fun start(trustRoot: X509Certificate, currentParametersHash: SecureHash, ourNodeInfoHash: SecureHash) { @@ -58,26 +59,38 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, this.trustRoot = trustRoot this.currentParametersHash = currentParametersHash this.ourNodeInfoHash = ourNodeInfoHash - // Subscribe to file based networkMap - fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe { - when (it) { - is NodeInfoUpdate.Add -> { - networkMapCache.addNode(it.nodeInfo) - } - is NodeInfoUpdate.Remove -> { - if (it.hash != ourNodeInfoHash) { - val nodeInfo = networkMapCache.getNodeByHash(it.hash) - nodeInfo?.let { networkMapCache.removeNode(it) } + watchForNodeInfoFiles() + if (networkMapClient != null) { + watchHttpNetworkMap() + } + } + + private fun watchForNodeInfoFiles() { + nodeInfoWatcher + .nodeInfoUpdates() + .subscribe { + for (update in it) { + when (update) { + is NodeInfoUpdate.Add -> networkMapCache.addNode(update.nodeInfo) + is NodeInfoUpdate.Remove -> { + if (update.hash != ourNodeInfoHash) { + val nodeInfo = networkMapCache.getNodeByHash(update.hash) + nodeInfo?.let(networkMapCache::removeNode) + } + } + } + } + if (networkMapClient == null) { + // Mark the network map cache as ready on a successful poll of the node infos dir if not using + // the HTTP network map even if there aren't any node infos + networkMapCache.nodeReady.set(null) } } - } - } + } - if (networkMapClient == null) return - - // Subscribe to remote network map if configured. - executor.executeExistingDelayedTasksAfterShutdownPolicy = false - executor.submit(object : Runnable { + private fun watchHttpNetworkMap() { + // The check may be expensive, so always run it in the background even the first time. + networkMapPoller.submit(object : Runnable { override fun run() { val nextScheduleDelay = try { updateNetworkMapCache() @@ -86,9 +99,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, defaultRetryInterval } // Schedule the next update. - executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS) + networkMapPoller.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS) } - }) // The check may be expensive, so always run it in the background even the first time. + }) } fun trackParametersUpdate(): DataFeed { @@ -99,9 +112,13 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } fun updateNetworkMapCache(): Duration { - if (networkMapClient == null) throw CordaRuntimeException("Network map cache can be updated only if network map/compatibility zone URL is specified") + if (networkMapClient == null) { + throw CordaRuntimeException("Network map cache can be updated only if network map/compatibility zone URL is specified") + } + val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap() globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) } + val additionalHashes = extraNetworkMapKeys.flatMap { try { networkMapClient.getNetworkMap(it).payload.nodeInfoHashes @@ -111,6 +128,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, emptyList() } } + val allHashesFromNetworkMap = (globalNetworkMap.nodeInfoHashes + additionalHashes).toSet() if (currentParametersHash != globalNetworkMap.networkParameterHash) { @@ -120,12 +138,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, val currentNodeHashes = networkMapCache.allNodeHashes // Remove node info from network map. - (currentNodeHashes - allHashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) - .mapNotNull { - if (it != ourNodeInfoHash) { - networkMapCache.getNodeByHash(it) - } else null - }.forEach(networkMapCache::removeNode) + (currentNodeHashes - allHashesFromNetworkMap - nodeInfoWatcher.processedNodeInfoHashes) + .mapNotNull { if (it != ourNodeInfoHash) networkMapCache.getNodeByHash(it) else null } + .forEach(networkMapCache::removeNode) (allHashesFromNetworkMap - currentNodeHashes).mapNotNull { // Download new node info from network map @@ -141,6 +156,10 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, networkMapCache.addNode(it) } + // Mark the network map cache as ready on a successful poll of the HTTP network map, even on the odd chance that + // it's empty + networkMapCache.nodeReady.set(null) + return cacheTimeout } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index cdd370ff8f..814e193b25 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -3,23 +3,16 @@ package net.corda.node.services.network import net.corda.core.crypto.SecureHash import net.corda.core.internal.* import net.corda.core.node.NodeInfo -import net.corda.core.serialization.internal.SerializationEnvironmentImpl -import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.core.utilities.seconds -import net.corda.node.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY import net.corda.nodeapi.internal.NodeInfoAndSigned -import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.NodeInfoFilesCopier -import net.corda.serialization.internal.AMQP_P2P_CONTEXT -import net.corda.serialization.internal.SerializationFactoryImpl import rx.Observable import rx.Scheduler import java.nio.file.Path -import java.nio.file.Paths import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.attribute.FileTime import java.time.Duration @@ -63,7 +56,7 @@ class NodeInfoWatcher(private val nodePath: Path, } } - internal data class NodeInfoFromFile(val nodeInfohash: SecureHash, val lastModified: FileTime) + private data class NodeInfoFromFile(val nodeInfohash: SecureHash, val lastModified: FileTime) private val nodeInfosDir = nodePath / NODE_INFO_DIRECTORY private val nodeInfoFilesMap = HashMap() @@ -75,20 +68,16 @@ class NodeInfoWatcher(private val nodePath: Path, } /** - * Read all the files contained in [nodePath] / [NODE_INFO_DIRECTORY] and keep watching - * the folder for further updates. + * Read all the files contained in [nodePath] / [NODE_INFO_DIRECTORY] and keep watching the folder for further updates. * - * We simply list the directory content every 5 seconds, the Java implementation of WatchService has been proven to - * be unreliable on MacOs and given the fairly simple use case we have, this simple implementation should do. - * - * @return an [Observable] returning [NodeInfoUpdate]s, at most one [NodeInfo] is returned for each processed file. + * @return an [Observable] that emits lists of [NodeInfoUpdate]s. Each emitted list is a poll event of the folder and + * may be empty if no changes were detected. */ - fun nodeInfoUpdates(): Observable { - return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler) - .flatMapIterable { loadFromDirectory() } + fun nodeInfoUpdates(): Observable> { + return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler).map { pollDirectory() } } - private fun loadFromDirectory(): List { + private fun pollDirectory(): List { val processedPaths = HashSet() val result = nodeInfosDir.list { paths -> paths @@ -122,14 +111,3 @@ class NodeInfoWatcher(private val nodePath: Path, return result.map { NodeInfoUpdate.Add(it.nodeInfo) } + removedHashes } } - -// TODO Remove this once we have a tool that can read AMQP serialised files -fun main(args: Array) { - _contextSerializationEnv.set(SerializationEnvironmentImpl( - SerializationFactoryImpl().apply { - registerScheme(AMQPServerSerializationScheme()) - }, - AMQP_P2P_CONTEXT) - ) - println(Paths.get(args[0]).readObject().verified()) -} diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 67a9736f50..aaca1de889 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -1,6 +1,5 @@ package net.corda.node.services.network -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.SecureHash import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty @@ -8,6 +7,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.bufferUntilSubscribed +import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo @@ -37,8 +37,7 @@ import javax.annotation.concurrent.ThreadSafe /** Database-based network map cache. */ @ThreadSafe open class PersistentNetworkMapCache(private val database: CordaPersistence, - private val identityService: IdentityService, - private val myLegalName: CordaX500Name) : NetworkMapCacheInternal, SingletonSerializeAsToken() { + private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken() { companion object { private val logger = contextLogger() } @@ -48,10 +47,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, override val changed: Observable = _changed.wrapWithDatabaseTransaction() private val changePublisher: rx.Observer get() = _changed.bufferUntilDatabaseCommit() - // TODO revisit the logic under which nodeReady and loadDBSuccess are set. - // with the NetworkMapService redesign their meaning is not too well defined. - private val _nodeReady = openFuture() - override val nodeReady: CordaFuture = _nodeReady + override val nodeReady: OpenFuture = openFuture() + private lateinit var notaries: List override val notaryIdentities: List get() = notaries.map { it.identity } @@ -71,15 +68,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, fun start(notaries: List) { this.notaries = notaries - val otherNodeInfoCount = database.transaction { - session.createQuery( - "select count(*) from ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n join n.legalIdentitiesAndCerts i where i.name != :myLegalName") - .setParameter("myLegalName", myLegalName.toString()) - .singleResult as Long - } - if (otherNodeInfoCount > 0) { - _nodeReady.set(null) - } } override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { @@ -193,9 +181,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, logger.info("Previous node was identical to incoming one - doing nothing") } } - if (node.legalIdentities[0].name != myLegalName) { - _nodeReady.set(null) - } logger.debug { "Done adding node with info: $node" } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt index 9d3734776d..3782a1e8f7 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt @@ -1,6 +1,5 @@ package net.corda.node.utilities -import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger @@ -10,19 +9,12 @@ import java.util.concurrent.atomic.AtomicInteger * via an executor. It will use an underlying thread factory to create the actual thread * and then override the thread name with the prefix and an ever increasing number */ -class NamedThreadFactory(private val name: String, private val underlyingFactory: ThreadFactory) : ThreadFactory { - val threadNumber = AtomicInteger(1) - override fun newThread(runnable: Runnable?): Thread { - val thread = underlyingFactory.newThread(runnable) +class NamedThreadFactory(private val name: String, + private val delegate: ThreadFactory = Executors.defaultThreadFactory()) : ThreadFactory { + private val threadNumber = AtomicInteger(1) + override fun newThread(runnable: Runnable): Thread { + val thread = delegate.newThread(runnable) thread.name = name + "-" + threadNumber.getAndIncrement() return thread } } - -/** - * Create a single thread executor with a NamedThreadFactory based on the default thread factory - * defined in java.util.concurrent.Executors - */ -fun newNamedSingleThreadExecutor(name: String): ExecutorService { - return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory())) -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 1c75c13524..5cf1aa2b02 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -9,6 +9,7 @@ import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.* +import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize @@ -27,8 +28,8 @@ import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned import net.corda.testing.node.internal.network.NetworkMapServer -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Before import org.junit.Rule @@ -55,6 +56,7 @@ class NetworkMapUpdaterTest { private val nodeInfoDir = baseDir / NODE_INFO_DIRECTORY private val scheduler = TestScheduler() private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) + private val nodeReadyFuture = openFuture() private val networkMapCache = createMockNetworkMapCache() private lateinit var server: NetworkMapServer private lateinit var networkMapClient: NetworkMapClient @@ -100,16 +102,18 @@ class NetworkMapUpdaterTest { startUpdater() networkMapClient.publish(signedNodeInfo2) + assertThat(nodeReadyFuture).isNotDone() // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) verify(networkMapCache, times(2)).addNode(any()) verify(networkMapCache, times(1)).addNode(nodeInfo1) verify(networkMapCache, times(1)).addNode(nodeInfo2) + assertThat(nodeReadyFuture).isDone() NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned) networkMapClient.publish(signedNodeInfo3) networkMapClient.publish(signedNodeInfo4) - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) // 4 node info from network map, and 1 from file. @@ -136,7 +140,7 @@ class NetworkMapUpdaterTest { networkMapClient.publish(signedNodeInfo4) startUpdater() - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) @@ -162,7 +166,7 @@ class NetworkMapUpdaterTest { @Test fun `receive node infos from directory, without a network map`() { - setUpdater() + setUpdater(netMapClient = null) val fileNodeInfoAndSigned = createNodeInfoAndSigned("Info from file") // Not subscribed yet. @@ -171,10 +175,12 @@ class NetworkMapUpdaterTest { startUpdater() NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned) - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + assertThat(nodeReadyFuture).isNotDone() + advanceTime() verify(networkMapCache, times(1)).addNode(any()) verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned.nodeInfo) + assertThat(nodeReadyFuture).isDone() assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfoAndSigned.nodeInfo.serialize().hash) } @@ -223,7 +229,7 @@ class NetworkMapUpdaterTest { fun `fetch nodes from private network`() { setUpdater(extraNetworkMapKeys = listOf(privateNetUUID)) server.addNodesToPrivateNetwork(privateNetUUID, listOf(ALICE_NAME)) - Assertions.assertThatThrownBy { networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes } + assertThatThrownBy { networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes } .isInstanceOf(IOException::class.java) .hasMessageContaining("Response Code 404") val (aliceInfo, signedAliceInfo) = createNodeInfoAndSigned(ALICE_NAME) // Goes to private network map @@ -245,7 +251,7 @@ class NetworkMapUpdaterTest { NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned1) NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned2) - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() verify(networkMapCache, times(2)).addNode(any()) verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned1.nodeInfo) verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned2.nodeInfo) @@ -253,7 +259,7 @@ class NetworkMapUpdaterTest { // Remove one of the nodes val fileName1 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${fileNodeInfoAndSigned1.nodeInfo.legalIdentities[0].name.serialize().hash}" (nodeInfoDir / fileName1).delete() - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() verify(networkMapCache, times(1)).removeNode(any()) verify(networkMapCache, times(1)).removeNode(fileNodeInfoAndSigned1.nodeInfo) assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfoAndSigned2.signed.raw.hash) @@ -275,14 +281,14 @@ class NetworkMapUpdaterTest { // Publish to network map the one with lower serial. networkMapClient.publish(serverSignedNodeInfo) startUpdater() - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() verify(networkMapCache, times(1)).addNode(localNodeInfo) Thread.sleep(2L * cacheExpiryMs) // Node from file has higher serial than the one from NetworkMapServer assertThat(networkMapCache.allNodeHashes).containsOnly(localSignedNodeInfo.signed.raw.hash) val fileName = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${localNodeInfo.legalIdentities[0].name.serialize().hash}" (nodeInfoDir / fileName).delete() - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() verify(networkMapCache, times(1)).removeNode(any()) verify(networkMapCache).removeNode(localNodeInfo) Thread.sleep(2L * cacheExpiryMs) @@ -331,7 +337,7 @@ class NetworkMapUpdaterTest { assert(networkMapCache.allNodeHashes.size == 1) networkMapClient.publish(signedNodeInfo2) Thread.sleep(2L * cacheExpiryMs) - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + advanceTime() verify(networkMapCache, times(1)).addNode(signedNodeInfo2.verified()) verify(networkMapCache, times(1)).removeNode(signedNodeInfo1.verified()) @@ -341,6 +347,7 @@ class NetworkMapUpdaterTest { private fun createMockNetworkMapCache(): NetworkMapCacheInternal { return mock { + on { nodeReady }.thenReturn(nodeReadyFuture) val data = ConcurrentHashMap() on { addNode(any()) }.then { val nodeInfo = it.arguments[0] as NodeInfo @@ -359,4 +366,8 @@ class NetworkMapUpdaterTest { private fun createNodeInfoAndSigned(org: String): NodeInfoAndSigned { return createNodeInfoAndSigned(CordaX500Name(org, "London", "GB")) } -} \ No newline at end of file + + private fun advanceTime() { + scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + } +} diff --git a/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 6fc7abc537..034afa9121 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -36,7 +36,7 @@ class NodeInfoWatcherTest { val tempFolder = TemporaryFolder() private val scheduler = TestScheduler() - private val testSubscriber = TestSubscriber() + private val testSubscriber = TestSubscriber>() private lateinit var nodeInfoAndSigned: NodeInfoAndSigned private lateinit var nodeInfoPath: Path @@ -83,7 +83,7 @@ class NodeInfoWatcherTest { val subscription = nodeInfoWatcher.nodeInfoUpdates().subscribe(testSubscriber) try { advanceTime() - val readNodes = testSubscriber.onNextEvents.distinct() + val readNodes = testSubscriber.onNextEvents.distinct().flatten() assertEquals(0, readNodes.size) } finally { subscription.unsubscribe() @@ -98,7 +98,7 @@ class NodeInfoWatcherTest { advanceTime() try { - val readNodes = testSubscriber.onNextEvents.distinct() + val readNodes = testSubscriber.onNextEvents.distinct().flatten() assertEquals(1, readNodes.size) assertEquals(nodeInfoAndSigned.nodeInfo, (readNodes.first() as? NodeInfoUpdate.Add)?.nodeInfo) } finally { @@ -116,7 +116,8 @@ class NodeInfoWatcherTest { // Ensure the watch service is started. advanceTime() // Check no nodeInfos are read. - assertEquals(0, testSubscriber.valueCount) + + assertEquals(0, testSubscriber.onNextEvents.distinct().flatten().size) createNodeInfoFileInPath() advanceTime() @@ -124,7 +125,7 @@ class NodeInfoWatcherTest { // We need the WatchService to report a change and that might not happen immediately. testSubscriber.awaitValueCount(1, 5, TimeUnit.SECONDS) // The same folder can be reported more than once, so take unique values. - val readNodes = testSubscriber.onNextEvents.distinct() + val readNodes = testSubscriber.onNextEvents.distinct().flatten() assertEquals(nodeInfoAndSigned.nodeInfo, (readNodes.first() as? NodeInfoUpdate.Add)?.nodeInfo) } finally { subscription.unsubscribe() diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index 4b146c6b80..74190fd20a 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -11,8 +11,8 @@ def javaHome = System.getProperty('java.home') def jarBaseName = "corda-${project.name}".toString() configurations { - runtimeLibraries - runtimeArtifacts.extendsFrom runtimeLibraries + deterministicLibraries + deterministicArtifacts.extendsFrom deterministicLibraries } dependencies { @@ -20,10 +20,10 @@ dependencies { // Configure these by hand. It should be a minimal subset of dependencies, // and without any obviously non-deterministic ones such as Hibernate. - runtimeLibraries project(path: ':core-deterministic', configuration: 'runtimeArtifacts') - runtimeLibraries "org.apache.qpid:proton-j:$protonj_version" - runtimeLibraries "org.iq80.snappy:snappy:$snappy_version" - runtimeLibraries "com.google.guava:guava:$guava_version" + deterministicLibraries project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + deterministicLibraries "org.apache.qpid:proton-j:$protonj_version" + deterministicLibraries "org.iq80.snappy:snappy:$snappy_version" + deterministicLibraries "com.google.guava:guava:$guava_version" } jar { @@ -108,7 +108,7 @@ task determinise(type: ProGuardTask) { libraryjars file("$javaHome/lib/rt.jar") libraryjars file("$javaHome/lib/jce.jar") - configurations.runtimeLibraries.forEach { + configurations.deterministicLibraries.forEach { libraryjars it, filter: '!META-INF/versions/**' } @@ -142,7 +142,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { libraryjars deterministic_rt_jar - configurations.runtimeLibraries.forEach { + configurations.deterministicLibraries.forEach { libraryjars it, filter: '!META-INF/versions/**' } @@ -162,12 +162,12 @@ assemble.dependsOn checkDeterminism def deterministicJar = metafix.outputs.files.singleFile artifacts { - runtimeArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix + deterministicArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix } publish { - dependenciesFrom configurations.runtimeArtifacts + dependenciesFrom configurations.deterministicArtifacts publishSources = false publishJavadoc = false name jarBaseName diff --git a/settings.gradle b/settings.gradle index d4da8b085a..ac3a959bbf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -83,7 +83,7 @@ include 'tools:notary-healthcheck:client' if (JavaVersion.current() == JavaVersion.VERSION_1_8) { include 'core-deterministic' include 'core-deterministic:testing' - include 'core-deterministic:testing:common' include 'core-deterministic:testing:data' + include 'core-deterministic:testing:verifier' include 'serialization-deterministic' } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt index 508ace7fb4..9874799f6d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt @@ -33,6 +33,7 @@ class MockCordappProvider( serializationCustomSerializers = emptyList(), customSchemas = emptySet(), jarPath = Paths.get("").toUri().toURL(), + info = CordappImpl.Info.UNKNOWN, allFlows = emptyList(), jarHash = SecureHash.allOnesHash) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { @@ -40,7 +41,9 @@ class MockCordappProvider( } } - override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName) + override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? { + return cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName) + } private fun findOrImportAttachment(contractClassNames: List, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { val existingAttachment = attachments.files.filter {