From 4183d55650d0f88f3a144ab27167920aaf86a1d5 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Fri, 7 Sep 2018 15:07:06 +0100 Subject: [PATCH 1/3] ENT-1906: Publish corda-deterministic-verifier artifact for testing cordapps. (#3910) --- build.gradle | 1 + core-deterministic/build.gradle | 28 +++++------ core-deterministic/testing/build.gradle | 6 +-- .../testing/common/build.gradle | 16 ------- core-deterministic/testing/data/build.gradle | 2 +- .../corda/deterministic/data/GenerateData.kt | 4 +- .../data/TransactionGenerator.kt | 6 +-- .../crypto/TransactionSignatureTest.kt | 2 +- ...aveletTest.kt => VerifyTransactionTest.kt} | 12 ++--- .../testing/verifier/build.gradle | 48 +++++++++++++++++++ .../verifier}/LocalSerializationRule.kt | 4 +- .../verifier}/MockContractAttachment.kt | 2 +- .../deterministic/verifier}/SampleData.kt | 2 +- .../TransactionVerificationRequest.kt | 2 +- .../corda/deterministic/verifier/Verifier.kt} | 6 +-- djvm/build.gradle | 1 - serialization-deterministic/build.gradle | 20 ++++---- settings.gradle | 2 +- 18 files changed, 98 insertions(+), 66 deletions(-) delete mode 100644 core-deterministic/testing/common/build.gradle rename core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/{EnclaveletTest.kt => VerifyTransactionTest.kt} (55%) create mode 100644 core-deterministic/testing/verifier/build.gradle rename core-deterministic/testing/{common/src/main/kotlin/net/corda/deterministic/common => verifier/src/main/kotlin/net/corda/deterministic/verifier}/LocalSerializationRule.kt (98%) rename core-deterministic/testing/{common/src/main/kotlin/net/corda/deterministic/common => verifier/src/main/kotlin/net/corda/deterministic/verifier}/MockContractAttachment.kt (93%) rename core-deterministic/testing/{common/src/main/kotlin/net/corda/deterministic/common => verifier/src/main/kotlin/net/corda/deterministic/verifier}/SampleData.kt (76%) rename core-deterministic/testing/{common/src/main/kotlin/net/corda/deterministic/common => verifier/src/main/kotlin/net/corda/deterministic/verifier}/TransactionVerificationRequest.kt (97%) rename core-deterministic/testing/{common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt => verifier/src/main/kotlin/net/corda/deterministic/verifier/Verifier.kt} (84%) diff --git a/build.gradle b/build.gradle index 0a6da2a708..a1ae6ac906 100644 --- a/build.gradle +++ b/build.gradle @@ -331,6 +331,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 6627a80cd8..839384fdd7 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 { @@ -112,7 +112,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/**' } @@ -152,7 +152,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { libraryjars deterministic_rt_jar - configurations.runtimeLibraries.forEach { + configurations.deterministicLibraries.forEach { libraryjars it, filter: '!META-INF/versions/**' } @@ -173,12 +173,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/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/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 af4fa37e68..596d28d790 100644 --- a/settings.gradle +++ b/settings.gradle @@ -63,7 +63,7 @@ include 'serialization' 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' } From 83e66d542d2d2f3c988903fdb566b14ac84a614b Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 10 Sep 2018 10:43:00 +0100 Subject: [PATCH 2/3] Syncing Cordapp info code from ENT so that ENT-1731 is fully ported (#3914) Also, Cordapp.Info has been made internal as it's not used in the public API --- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 18 +---- .../core/internal/cordapp/CordappImpl.kt | 17 +++-- finance/build.gradle | 13 +++- .../main/kotlin/net/corda/node/VersionInfo.kt | 7 +- .../net/corda/node/cordapp/CordappLoader.kt | 3 +- .../kotlin/net/corda/node/internal/Node.kt | 8 ++- .../net/corda/node/internal/NodeStartup.kt | 15 +++- .../internal/cordapp/CordappProviderImpl.kt | 3 +- .../cordapp/CordappProviderInternal.kt | 3 +- .../cordapp/JarScanningCordappLoader.kt | 71 ++++++++++--------- .../node/internal/cordapp/ManifestUtils.kt | 19 ++--- .../net/corda/node/internal/NodeTest.kt | 16 ++--- .../cordapp/JarScanningCordappLoaderTest.kt | 6 +- .../attachmentdemo/AttachmentDemoTest.kt | 2 +- .../testing/internal/MockCordappProvider.kt | 5 +- 15 files changed, 113 insertions(+), 93 deletions(-) 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 8e23c5c01b..ddf369b668 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -48,20 +48,4 @@ interface Cordapp { val jarPath: URL val cordappClasses: List 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 a8021e8663..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,25 +24,28 @@ data class CordappImpl( override val customSchemas: Set, override val allFlows: List>>, override val jarPath: URL, + val info: Info, override val jarHash: SecureHash.SHA256) : Cordapp { - override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar") + 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/finance/build.gradle b/finance/build.gradle index 493b3babfb..b55819f201 100644 --- a/finance/build.gradle +++ b/finance/build.gradle @@ -71,11 +71,22 @@ jar { exclude "META-INF/*.MF" exclude "META-INF/LICENSE" exclude "META-INF/NOTICE" + + manifest { + attributes( + "Manifest-Version": "1.0", + "Specification-Title": description, + "Specification-Version": version, + "Specification-Vendor": "Corda Open Source", + "Implementation-Title": "$group.$baseName", + ) + } } cordapp { info { - vendor "R3" + name "net/corda/finance" + vendor "Corda Open Source" } } diff --git a/node/src/main/kotlin/net/corda/node/VersionInfo.kt b/node/src/main/kotlin/net/corda/node/VersionInfo.kt index c51df32229..eacbb25133 100644 --- a/node/src/main/kotlin/net/corda/node/VersionInfo.kt +++ b/node/src/main/kotlin/net/corda/node/VersionInfo.kt @@ -14,4 +14,9 @@ data class VersionInfo( /** The exact version control commit ID of the node build. */ val revision: String, /** The node vendor */ - val vendor: String) \ No newline at end of file + val vendor: String) { + + companion object { + val UNKNOWN = VersionInfo(1, "Unknown", "Unknown", "Unknown") + } +} 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/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 52ab905ae7..d32b399256 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -88,7 +88,7 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) { open class Node(configuration: NodeConfiguration, versionInfo: VersionInfo, private val initialiseSerialization: Boolean = true, - cordappLoader: CordappLoader = makeCordappLoader(configuration) + cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo) ) : AbstractNode( configuration, createClock(configuration), @@ -130,9 +130,11 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { - return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories) + + private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { + 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 f8d6681e4c..3ccb4792c3 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -10,6 +10,7 @@ import net.corda.cliutils.ExitCodes 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 @@ -325,7 +326,8 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { } val nodeInfo = node.start() - Node.printBasicNodeInfo("Loaded CorDapps", node.services.cordappProvider.cordapps.joinToString { it.name }) + logLoadedCorDapps(node.services.cordappProvider.cordapps) + node.nodeReadyFuture.thenMatch({ val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation @@ -407,6 +409,17 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { ) } + 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 = 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.") + } + } + } + private fun enforceSingleNodeIsRunning(baseDirectory: Path) { // Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a // file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already 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 94dc45a0fb..be3c8e4deb 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 @@ -14,6 +14,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 @@ -24,6 +25,7 @@ import java.net.URL import java.net.URLClassLoader import java.nio.file.Path import java.util.* +import java.util.jar.JarInputStream import kotlin.reflect.KClass import kotlin.streams.toList @@ -32,9 +34,10 @@ import kotlin.streams.toList * * @property cordappJarPaths The classpath of cordapp JARs */ -class JarScanningCordappLoader private constructor(private val cordappJarPaths: List) : CordappLoaderTemplate() { +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) @@ -54,10 +57,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * * @param corDappDirectories Directories used to scan for CorDapp JARs. */ - fun fromDirectories(corDappDirectories: Iterable): CordappLoader { - + 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() }) + return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo) } /** @@ -65,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) = JarScanningCordappLoader(scanJars.map { it.restricted() }) + 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) @@ -86,31 +90,30 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: ContractUpgradeFlow.Initiate::class.java, ContractUpgradeFlow.Authorise::class.java, ContractUpgradeFlow.Deauthorise::class.java) - - /** A Cordapp representing the core package which is not scanned automatically. */ - @VisibleForTesting - internal val coreCordapp = CordappImpl( - contractClassNames = listOf(), - initiatedFlows = listOf(), - rpcFlows = coreRPCFlows, - serviceFlows = listOf(), - schedulableFlows = listOf(), - services = listOf(), - serializationWhitelists = listOf(), - serializationCustomSerializers = listOf(), - customSchemas = setOf(), - allFlows = listOf(), - jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location - jarHash = SecureHash.allOnesHash - ) } - private fun loadCordapps(): List { - return cordappJarPaths.map { scanCordapp(it).toCordapp(it) } - } + /** A Cordapp representing the core package which is not scanned automatically. */ + @VisibleForTesting + internal val coreCordapp = CordappImpl( + contractClassNames = listOf(), + initiatedFlows = listOf(), + rpcFlows = coreRPCFlows, + serviceFlows = listOf(), + schedulableFlows = listOf(), + services = listOf(), + serializationWhitelists = listOf(), + serializationCustomSerializers = listOf(), + customSchemas = setOf(), + info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion), + allFlows = listOf(), + jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location + jarHash = SecureHash.allOnesHash + ) - private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp { + private fun loadCordapps(): List = cordappJarPaths.map { scanCordapp(it).toCordapp(it) } + 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), @@ -123,6 +126,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: findCustomSchemas(this), findAllFlows(this), url.url, + info, getJarHash(url.url) ) } @@ -268,24 +272,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 86d6a3168b..08dd924243 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. @@ -27,21 +24,19 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str } 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/test/kotlin/net/corda/node/internal/NodeTest.kt b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt index f0e9b2b17d..6134aa348f 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt @@ -58,12 +58,9 @@ class NodeTest { @Test fun `generateAndSaveNodeInfo works`() { val configuration = createConfig(ALICE_NAME) - val platformVersion = 789 + val info = VersionInfo(789, "3.0", "SNAPSHOT", "R3") configureDatabase(configuration.dataSourceProperties, configuration.database, { null }, { null }).use { - val versionInfo = rigorousMock().also { - doReturn(platformVersion).whenever(it).platformVersion - } - val node = Node(configuration, versionInfo, initialiseSerialization = false) + val node = Node(configuration, info, initialiseSerialization = false) assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial) } } @@ -87,9 +84,8 @@ class NodeTest { // Save some NodeInfo session.save(persistentNodeInfo) } - val node = Node(configuration, rigorousMock().also { - doReturn(10).whenever(it).platformVersion - }, initialiseSerialization = false) + val versionInfo = VersionInfo(10, "3.0", "SNAPSHOT", "R3") + val node = Node(configuration, versionInfo, initialiseSerialization = false) assertThat(getAllInfos(it)).isNotEmpty node.clearNetworkMapCache() assertThat(getAllInfos(it)).isEmpty() @@ -97,7 +93,7 @@ class NodeTest { } @Test - fun `Node can start with multiple keypairs for it's identity`() { + fun `Node can start with multiple keypairs for its identity`() { val configuration = createConfig(ALICE_NAME) val (nodeInfo1, _) = createNodeInfoAndSigned(ALICE_NAME) val (nodeInfo2, _) = createNodeInfoAndSigned(ALICE_NAME) @@ -135,6 +131,8 @@ class NodeTest { val node = Node(configuration, rigorousMock().also { doReturn(10).whenever(it).platformVersion + doReturn("test-vendor").whenever(it).vendor + doReturn("1.0").whenever(it).releaseVersion }, initialiseSerialization = false) //this throws an exception with old behaviour diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index 1ab3482533..e46cb15278 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -46,7 +46,7 @@ class JarScanningCordappLoaderTest { fun `test that classes that aren't in cordapps aren't loaded`() { // Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get("."))) - assertThat(loader.cordapps).containsOnly(JarScanningCordappLoader.coreCordapp) + assertThat(loader.cordapps).containsOnly(loader.coreCordapp) } @Test @@ -57,7 +57,7 @@ class JarScanningCordappLoaderTest { val actual = loader.cordapps.toTypedArray() assertThat(actual).hasSize(2) - val actualCordapp = actual.single { it != JarScanningCordappLoader.coreCordapp } + val actualCordapp = actual.single { it != loader.coreCordapp } assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId)) assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor") assertThat(actualCordapp.rpcFlows).isEmpty() @@ -91,7 +91,7 @@ class JarScanningCordappLoaderTest { @Test fun `sub-packages are ignored`() { - val loader = cordappLoaderForPackages(listOf("net.corda", testScanPackage)) + val loader = cordappLoaderForPackages(listOf("net.corda.core", testScanPackage)) val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } assertThat(cordapps).hasSize(1) } diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index e47bc11f0d..5e2d1aa04b 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -17,7 +17,7 @@ class AttachmentDemoTest { @Test fun `attachment demo using a 10MB zip file`() { val numOfExpectedBytes = 10_000_000 - driver(DriverParameters(portAllocation = PortAllocation.Incremental(20000))) { + driver(DriverParameters(portAllocation = PortAllocation.Incremental(20000), startNodesInProcess = true)) { val demoUser = listOf(User("demo", "demo", setOf(all()))) val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"), 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 { From d56a80d1599f2cc74849c697d71974d934962d7f Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 10 Sep 2018 10:43:40 +0100 Subject: [PATCH 3/3] CORDA-1958: The node ready future completes on the first poll of the network map sources, even if they return empty. (#3904) This is to allow the first node in a test environment to fully start up. --- .../messaging/ArtemisMessagingTest.kt | 2 +- .../network/PersistentNetworkMapCacheTest.kt | 46 +---------- .../net/corda/node/internal/AbstractNode.kt | 8 +- .../node/services/api/ServiceHubInternal.kt | 3 + .../services/network/NetworkMapUpdater.kt | 81 ++++++++++++------- .../node/services/network/NodeInfoWatcher.kt | 36 ++------- .../network/PersistentNetworkMapCache.kt | 23 +----- .../node/utilities/NamedThreadFactory.kt | 18 ++--- .../services/network/NetworkMapUpdaterTest.kt | 35 +++++--- .../services/network/NodeInfoWatcherTest.kt | 11 +-- 10 files changed, 104 insertions(+), 159 deletions(-) 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 c2d8de19d2..1ddef593f5 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 @@ -88,7 +88,7 @@ class ArtemisMessagingTest { } LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { 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 7033b3efa9..6ac68ccf5b 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 @@ -20,7 +19,6 @@ class PersistentNetworkMapCacheTest { private companion object { val ALICE = TestIdentity(ALICE_NAME, 70) val BOB = TestIdentity(BOB_NAME, 80) - val CHARLIE = TestIdentity(CHARLIE_NAME, 90) } @Rule @@ -29,7 +27,7 @@ class PersistentNetworkMapCacheTest { private var portCounter = 1000 private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }) - private val charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate), CHARLIE.name) + private val charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate)) @After fun cleanUp() { @@ -40,7 +38,6 @@ class PersistentNetworkMapCacheTest { 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}", @@ -50,32 +47,6 @@ class PersistentNetworkMapCacheTest { 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))) @@ -137,19 +108,4 @@ class PersistentNetworkMapCacheTest { 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/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index a7b81fbedd..c404ece804 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -146,7 +146,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() @@ -217,7 +217,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 @@ -302,13 +302,13 @@ 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) identityService.start(trustRoot, listOf(identity.certificate, nodeCa)) val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction { - networkMapCache.start(netParams.notaries) updateNodeInfo(identity, identityKeyPair, publish = true) } @@ -456,7 +456,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/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 75943d7704..4cacee3a59 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()