mirror of
https://github.com/corda/corda.git
synced 2025-03-15 16:46:12 +00:00
commit
4ffe2960db
@ -369,6 +369,7 @@ bintrayConfig {
|
||||
'corda-rpc',
|
||||
'corda-core',
|
||||
'corda-core-deterministic',
|
||||
'corda-deterministic-verifier',
|
||||
'corda-djvm',
|
||||
'corda',
|
||||
'corda-finance',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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.*
|
||||
|
@ -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.*
|
||||
|
@ -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<Exception> { verifyInEnclave(bytesOfResource("txverify/tx-failure.bin")) }
|
||||
val e = assertFailsWith<Exception> { verifyTransaction(bytesOfResource("txverify/tx-failure.bin")) }
|
||||
assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command")
|
||||
}
|
||||
}
|
48
core-deterministic/testing/verifier/build.gradle
Normal file
48
core-deterministic/testing/verifier/build.gradle
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -1,5 +1,5 @@
|
||||
@file:JvmName("SampleData")
|
||||
package net.corda.deterministic.common
|
||||
package net.corda.deterministic.verifier
|
||||
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
|
@ -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
|
@ -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()
|
||||
}
|
||||
|
@ -47,22 +47,5 @@ interface Cordapp {
|
||||
val allFlows: List<Class<out FlowLogic<*>>>
|
||||
val jarPath: URL
|
||||
val cordappClasses: List<String>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,26 +24,28 @@ data class CordappImpl(
|
||||
override val customSchemas: Set<MappedSchema>,
|
||||
override val allFlows: List<Class<out FlowLogic<*>>>,
|
||||
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<String> = (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 }
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,5 @@ artifacts {
|
||||
|
||||
publish {
|
||||
dependenciesFrom configurations.shadow
|
||||
disableDefaultJar true
|
||||
name shadowJar.baseName
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, overri
|
||||
identityService.database = database
|
||||
}
|
||||
|
||||
override val networkMapCache = PersistentNetworkMapCache(database, identityService, myInfo.legalIdentities[0].name)
|
||||
override val networkMapCache = PersistentNetworkMapCache(database, identityService)
|
||||
private val checkpointStorage = DBCheckpointStorage()
|
||||
@Suppress("LeakingThis")
|
||||
override val validatedTransactions: WritableTransactionStorage = DBTransactionStorage(configuration.transactionCacheSizeBytes, database).tokenize()
|
||||
|
@ -36,7 +36,10 @@ import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.network.*
|
||||
import net.corda.node.services.network.NetworkMapClient
|
||||
import net.corda.node.services.network.NetworkMapUpdater
|
||||
import net.corda.node.services.network.NodeInfoWatcher
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.node.services.persistence.DBTransactionMappingStorage
|
||||
import net.corda.node.services.persistence.DBTransactionStorage
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
@ -84,7 +87,7 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, overrid
|
||||
identityService.database = database
|
||||
}
|
||||
|
||||
override val networkMapCache = PersistentNetworkMapCache(database, identityService, myInfo.legalIdentities[0].name)
|
||||
override val networkMapCache = PersistentNetworkMapCache(database, identityService)
|
||||
@Suppress("LeakingThis")
|
||||
override val validatedTransactions: WritableTransactionStorage = DBTransactionStorage(configuration.transactionCacheSizeBytes, database)
|
||||
private val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,13 +1,12 @@
|
||||
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
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.IntegrationTest
|
||||
@ -23,7 +22,6 @@ class PersistentNetworkMapCacheTest : IntegrationTest() {
|
||||
private companion object {
|
||||
val ALICE = TestIdentity(ALICE_NAME, 70)
|
||||
val BOB = TestIdentity(BOB_NAME, 80)
|
||||
val CHARLIE = TestIdentity(CHARLIE_NAME, 90)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
@ -38,14 +36,14 @@ class PersistentNetworkMapCacheTest : IntegrationTest() {
|
||||
|
||||
//Enterprise only - objects created in the setup method, below initialized with dummy values to avoid need for nullable type declaration
|
||||
private var database = CordaPersistence(DatabaseConfig(), emptySet())
|
||||
private var charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate), CHARLIE.name)
|
||||
private var charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate))
|
||||
|
||||
@Before()
|
||||
fun setup() {
|
||||
//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 +55,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 +64,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 +125,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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Cordapp>
|
||||
val cordapps: List<CordappImpl>
|
||||
|
||||
/**
|
||||
* Returns a [ClassLoader] containing all types from all [Cordapp]s.
|
||||
|
@ -150,7 +150,7 @@ abstract class AbstractNode<S>(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()
|
||||
@ -221,7 +221,7 @@ abstract class AbstractNode<S>(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
|
||||
|
||||
@ -311,6 +311,7 @@ abstract class AbstractNode<S>(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)
|
||||
@ -332,7 +333,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
|
||||
networkMapCache.start(netParams.notaries)
|
||||
updateNodeInfo(identity, identityKeyPair, publish = true)
|
||||
}
|
||||
|
||||
@ -480,7 +480,7 @@ abstract class AbstractNode<S>(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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<Cordapp>) {
|
||||
fun Cordapp.Info.description() = "$shortName version $version by $vendor"
|
||||
open protected fun logLoadedCorDapps(corDapps: List<CordappImpl>) {
|
||||
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.")
|
||||
|
@ -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<CordappImpl> get() = cordappLoader.cordapps
|
||||
|
||||
fun start(whitelistedContractImplementations: Map<String, List<AttachmentId>>) {
|
||||
cordappAttachments.putAll(loadContractsIntoAttachmentStore())
|
||||
|
@ -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<Cordapp>
|
||||
val cordapps: List<CordappImpl>
|
||||
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import net.corda.core.flows.*
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
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 +36,7 @@ import kotlin.streams.toList
|
||||
*/
|
||||
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() {
|
||||
|
||||
override val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
|
||||
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + coreCordapp }
|
||||
|
||||
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
|
||||
|
||||
@ -57,7 +57,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
* @param corDappDirectories Directories used to scan for CorDapp JARs.
|
||||
*/
|
||||
fun fromDirectories(corDappDirectories: Iterable<Path>, 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 +66,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<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
|
||||
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
|
||||
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
|
||||
}
|
||||
|
||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
||||
|
||||
@ -108,14 +109,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
jarHash = SecureHash.allOnesHash
|
||||
)
|
||||
|
||||
private fun loadCordapps(): List<Cordapp> {
|
||||
return cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
|
||||
}
|
||||
private fun loadCordapps(): List<CordappImpl> = 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),
|
||||
@ -129,8 +126,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
findAllFlows(this),
|
||||
url.url,
|
||||
info,
|
||||
getJarHash(url.url),
|
||||
name
|
||||
getJarHash(url.url)
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,24 +271,23 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
class MultipleCordappsForFlowException(message: String) : Exception(message)
|
||||
|
||||
abstract class CordappLoaderTemplate : CordappLoader {
|
||||
|
||||
override val flowCordappMap: Map<Class<out FlowLogic<*>>, 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<MappedSchema> by lazy {
|
||||
|
||||
cordapps.flatMap { it.customSchemas }.toSet()
|
||||
}
|
||||
|
||||
override val appClassLoader: ClassLoader by lazy {
|
||||
|
||||
URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
@ -26,27 +23,20 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
|
||||
return manifest
|
||||
}
|
||||
|
||||
internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Manifest {
|
||||
|
||||
return createTestManifest(name, title, "test-$jarUUID", "R3")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
@ -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<Void?>
|
||||
|
||||
val allNodeHashes: List<SecureHash>
|
||||
|
||||
fun getNodeByHash(nodeHash: SecureHash): NodeInfo?
|
||||
|
@ -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<UUID>
|
||||
@ -40,8 +39,10 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
private val defaultRetryInterval = 1.minutes
|
||||
}
|
||||
|
||||
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
|
||||
private val executor = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
|
||||
private val parametersUpdatesTrack = PublishSubject.create<ParametersUpdateInfo>()
|
||||
private val networkMapPoller = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread")).apply {
|
||||
executeExistingDelayedTasksAfterShutdownPolicy = false
|
||||
}
|
||||
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = 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<ParametersUpdateInfo?, ParametersUpdateInfo> {
|
||||
@ -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<SecureHash>()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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<Path, NodeInfoFromFile>()
|
||||
@ -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<NodeInfoUpdate> {
|
||||
return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler)
|
||||
.flatMapIterable { loadFromDirectory() }
|
||||
fun nodeInfoUpdates(): Observable<List<NodeInfoUpdate>> {
|
||||
return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler).map { pollDirectory() }
|
||||
}
|
||||
|
||||
private fun loadFromDirectory(): List<NodeInfoUpdate> {
|
||||
private fun pollDirectory(): List<NodeInfoUpdate> {
|
||||
val processedPaths = HashSet<Path>()
|
||||
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<String>) {
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
AMQP_P2P_CONTEXT)
|
||||
)
|
||||
println(Paths.get(args[0]).readObject<SignedNodeInfo>().verified())
|
||||
}
|
||||
|
@ -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<MapChange> = _changed.wrapWithDatabaseTransaction()
|
||||
private val changePublisher: rx.Observer<MapChange> 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<Void?>()
|
||||
override val nodeReady: CordaFuture<Void?> = _nodeReady
|
||||
override val nodeReady: OpenFuture<Void?> = openFuture()
|
||||
|
||||
private lateinit var notaries: List<NotaryInfo>
|
||||
|
||||
override val notaryIdentities: List<Party> get() = notaries.map { it.identity }
|
||||
@ -71,15 +68,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
|
||||
|
||||
fun start(notaries: List<NotaryInfo>) {
|
||||
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" }
|
||||
}
|
||||
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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<Void?>()
|
||||
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<Party, NodeInfo>()
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun advanceTime() {
|
||||
scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class NodeInfoWatcherTest {
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
private val scheduler = TestScheduler()
|
||||
private val testSubscriber = TestSubscriber<NodeInfoUpdate>()
|
||||
private val testSubscriber = TestSubscriber<List<NodeInfoUpdate>>()
|
||||
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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<ContractClassName>, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId {
|
||||
val existingAttachment = attachments.files.filter {
|
||||
|
Loading…
x
Reference in New Issue
Block a user