Merge pull request #1473 from corda/andrius/merge-10-10

Andrius/merge 10 10
This commit is contained in:
Andrius Dagys 2018-10-10 17:35:16 +01:00 committed by GitHub
commit 9b5b4b62b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 741 additions and 437 deletions

10
.idea/compiler.xml generated
View File

@ -195,6 +195,8 @@
<module name="loadtest_test" target="1.8" /> <module name="loadtest_test" target="1.8" />
<module name="mock_main" target="1.8" /> <module name="mock_main" target="1.8" />
<module name="mock_test" target="1.8" /> <module name="mock_test" target="1.8" />
<module name="mysql_main" target="1.8" />
<module name="mysql_test" target="1.8" />
<module name="net.corda_buildSrc_main" target="1.8" /> <module name="net.corda_buildSrc_main" target="1.8" />
<module name="net.corda_buildSrc_test" target="1.8" /> <module name="net.corda_buildSrc_test" target="1.8" />
<module name="net.corda_canonicalizer_main" target="1.8" /> <module name="net.corda_canonicalizer_main" target="1.8" />
@ -218,6 +220,8 @@
<module name="node_main" target="1.8" /> <module name="node_main" target="1.8" />
<module name="node_smokeTest" target="1.8" /> <module name="node_smokeTest" target="1.8" />
<module name="node_test" target="1.8" /> <module name="node_test" target="1.8" />
<module name="notary-bft-smart_main" target="1.8" />
<module name="notary-bft-smart_test" target="1.8" />
<module name="notary-demo_main" target="1.8" /> <module name="notary-demo_main" target="1.8" />
<module name="notary-demo_test" target="1.8" /> <module name="notary-demo_test" target="1.8" />
<module name="notary-healthcheck-client_main" target="1.8" /> <module name="notary-healthcheck-client_main" target="1.8" />
@ -227,6 +231,10 @@
<module name="notary-healthcheck-cordapp_test" target="1.8" /> <module name="notary-healthcheck-cordapp_test" target="1.8" />
<module name="notary-healthcheck_main" target="1.8" /> <module name="notary-healthcheck_main" target="1.8" />
<module name="notary-healthcheck_test" target="1.8" /> <module name="notary-healthcheck_test" target="1.8" />
<module name="notary-raft_main" target="1.8" />
<module name="notary-raft_test" target="1.8" />
<module name="notary_main" target="1.8" />
<module name="notary_test" target="1.8" />
<module name="notarytest_main" target="1.8" /> <module name="notarytest_main" target="1.8" />
<module name="notarytest_test" target="1.8" /> <module name="notarytest_test" target="1.8" />
<module name="perftestcordapp_integrationTest" target="1.8" /> <module name="perftestcordapp_integrationTest" target="1.8" />
@ -328,4 +336,4 @@
<component name="JavacSettings"> <component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" /> <option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component> </component>
</project> </project>

View File

@ -403,7 +403,9 @@ bintrayConfig {
'corda-notary-healthcheck-contract', 'corda-notary-healthcheck-contract',
'corda-notary-healthcheck-cordapp', 'corda-notary-healthcheck-cordapp',
'corda-notary-healthcheck-client', 'corda-notary-healthcheck-client',
'corda-tools-cliutils' 'corda-tools-cliutils',
'corda-notary-raft',
'corda-notary-bft-smart'
] ]
license { license {
name = 'Apache-2.0' name = 'Apache-2.0'

View File

@ -4,6 +4,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.notary.NotaryService
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
@ -48,4 +49,5 @@ interface Cordapp {
val jarPath: URL val jarPath: URL
val cordappClasses: List<String> val cordappClasses: List<String>
val jarHash: SecureHash.SHA256 val jarHash: SecureHash.SHA256
val notaryService: Class<out NotaryService>?
} }

View File

@ -4,6 +4,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.toPath import net.corda.core.internal.toPath
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
@ -25,7 +26,10 @@ data class CordappImpl(
override val allFlows: List<Class<out FlowLogic<*>>>, override val allFlows: List<Class<out FlowLogic<*>>>,
override val jarPath: URL, override val jarPath: URL,
val info: Info, val info: Info,
override val jarHash: SecureHash.SHA256) : Cordapp { override val jarHash: SecureHash.SHA256,
override val notaryService: Class<out NotaryService>? = null,
/** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */
val isLoaded: Boolean = true) : Cordapp {
override val name: String = jarName(jarPath) override val name: String = jarName(jarPath)
companion object { companion object {
@ -37,7 +41,10 @@ data class CordappImpl(
* *
* TODO: Also add [SchedulableFlow] as a Cordapp class * TODO: Also add [SchedulableFlow] as a Cordapp class
*/ */
override val cordappClasses: List<String> = (rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames override val cordappClasses: List<String> = run {
val classList = rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass } + notaryService
classList.mapNotNull { it?.name } + contractClassNames
}
// TODO Why a seperate Info class and not just have the fields directly in CordappImpl? // 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, val minimumPlatformVersion: Int, val targetPlatformVersion: Int) { data class Info(val shortName: String, val vendor: String, val version: String, val minimumPlatformVersion: Int, val targetPlatformVersion: Int) {
@ -48,4 +55,4 @@ data class CordappImpl(
fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
} }
} }

View File

@ -128,7 +128,7 @@ class AttachmentTests : WithMockNet {
// Makes a node that doesn't do sanity checking at load time. // Makes a node that doesn't do sanity checking at load time.
private fun makeBadNode(name: CordaX500Name) = mockNet.createNode( private fun makeBadNode(name: CordaX500Name) = mockNet.createNode(
InternalMockNodeParameters(legalName = makeUnique(name)), InternalMockNodeParameters(legalName = makeUnique(name)),
nodeFactory = { args, _ -> nodeFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false }
} }

View File

@ -161,7 +161,7 @@ class AttachmentSerializationTest {
} }
private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String {
client = mockNet.restartNode(client) { args, _ -> client = mockNet.restartNode(client) { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad }
} }

View File

@ -117,7 +117,7 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, overri
private val metricRegistry = MetricRegistry() private val metricRegistry = MetricRegistry()
override val cacheFactory = EnterpriseNamedCacheFactory(configuration.enterpriseConfiguration.getTracingConfig()).bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize() override val cacheFactory = EnterpriseNamedCacheFactory(configuration.enterpriseConfiguration.getTracingConfig()).bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize()
override val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, false).tokenize() override val schemaService = NodeSchemaService(cordappLoader.cordappSchemas).tokenize()
override val identityService = PersistentIdentityService(cacheFactory).tokenize() override val identityService = PersistentIdentityService(cacheFactory).tokenize()
override val database: CordaPersistence = createCordaPersistence( override val database: CordaPersistence = createCordaPersistence(
configuration.database, configuration.database,

View File

@ -0,0 +1,35 @@
apply plugin: 'kotlin'
apply plugin: 'kotlin-jpa'
apply plugin: 'idea'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.publish-utils'
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
dependencies {
cordaCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// Corda integration dependencies
cordaCompile project(':node')
// BFT-SMaRt
compile 'commons-codec:commons-codec:1.10'
compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87'
testCompile "junit:junit:$junit_version"
testCompile project(':node-driver')
}
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
}
publish {
name 'corda-notary-bft-smart'
}

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.bftsmart
import bftsmart.communication.ServerCommunicationSystem import bftsmart.communication.ServerCommunicationSystem
import bftsmart.communication.client.netty.NettyClientServerCommunicationSystemClientSide import bftsmart.communication.client.netty.NettyClientServerCommunicationSystemClientSide
@ -34,10 +34,11 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.BFTSMaRt.Client import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.BFTSMaRt.Replica
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.notary.bftsmart.BFTSMaRt.Client
import net.corda.notary.bftsmart.BFTSMaRt.Replica
import java.nio.file.Path import java.nio.file.Path
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -78,7 +79,7 @@ object BFTSMaRt {
fun waitUntilAllReplicasHaveInitialized() fun waitUntilAllReplicasHaveInitialized()
} }
class Client(config: BFTSMaRtConfig, private val clientId: Int, private val cluster: Cluster, private val notaryService: BFTNonValidatingNotaryService) : SingletonSerializeAsToken() { class Client(config: BFTSMaRtConfig, private val clientId: Int, private val cluster: Cluster, private val notaryService: BftSmartNotaryService) : SingletonSerializeAsToken() {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
} }
@ -178,7 +179,7 @@ object BFTSMaRt {
abstract class Replica(config: BFTSMaRtConfig, abstract class Replica(config: BFTSMaRtConfig,
replicaId: Int, replicaId: Int,
createMap: () -> AppendOnlyPersistentMap<StateRef, SecureHash, createMap: () -> AppendOnlyPersistentMap<StateRef, SecureHash,
BFTNonValidatingNotaryService.CommittedState, PersistentStateRef>, BftSmartNotaryService.CommittedState, PersistentStateRef>,
protected val services: ServiceHubInternal, protected val services: ServiceHubInternal,
protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() { protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() {
companion object { companion object {

View File

@ -1,10 +1,11 @@
package net.corda.node.services.transactions package net.corda.notary.bftsmart
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.writer import net.corda.core.internal.writer
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.services.transactions.PathManager
import java.io.PrintWriter import java.io.PrintWriter
import java.net.InetAddress import java.net.InetAddress
import java.net.Socket import java.net.Socket

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.bftsmart
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.notary.NotaryInternalException import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.verifySignature import net.corda.core.internal.notary.verifySignature
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -21,6 +22,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import java.security.PublicKey import java.security.PublicKey
@ -33,16 +35,30 @@ import kotlin.concurrent.thread
* *
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and time-window validity. * A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and time-window validity.
*/ */
class BFTNonValidatingNotaryService( class BftSmartNotaryService(
override val services: ServiceHubInternal, override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey, override val notaryIdentityKey: PublicKey
private val bftSMaRtConfig: BFTSMaRtConfiguration,
cluster: BFTSMaRt.Cluster
) : NotaryService() { ) : NotaryService() {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
} }
private val notaryConfig = services.configuration.notary
?: throw IllegalArgumentException("Failed to register ${this::class.java}: notary configuration not present")
private val bftSMaRtConfig = notaryConfig.bftSMaRt
?: throw IllegalArgumentException("Failed to register ${this::class.java}: raft configuration not present")
private val cluster: BFTSMaRt.Cluster = makeBFTCluster(notaryIdentityKey, bftSMaRtConfig)
protected open fun makeBFTCluster(notaryKey: PublicKey, bftSMaRtConfig: BFTSMaRtConfiguration): BFTSMaRt.Cluster {
return object : BFTSMaRt.Cluster {
override fun waitUntilAllReplicasHaveInitialized() {
log.warn("A BFT replica may still be initializing, in which case the upcoming consensus change may cause it to spin.")
}
}
}
private val client: BFTSMaRt.Client private val client: BFTSMaRt.Client
private val replicaHolder = SettableFuture.create<Replica>() private val replicaHolder = SettableFuture.create<Replica>()
@ -71,7 +87,7 @@ class BFTNonValidatingNotaryService(
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ServiceFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ServiceFlow(otherPartySession, this)
private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() { private class ServiceFlow(val otherSideSession: FlowSession, val service: BftSmartNotaryService) : FlowLogic<Void?>() {
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it } val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it }

View File

@ -0,0 +1,20 @@
package net.corda.notary.bftsmart
import net.corda.core.schemas.MappedSchema
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.notary.bftsmart.BftSmartNotaryService
object BftSmartNotarySchema
object BftSmartNotarySchemaV1 : MappedSchema(
schemaFamily = BftSmartNotarySchema.javaClass,
version = 1,
mappedTypes = listOf(
PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
BftSmartNotaryService.CommittedState::class.java
)
) {
override val migrationResource: String?
get() = "notary-bft-smart.changelog-master"
}

View File

@ -0,0 +1,45 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="1511451595465-6">
<createTable tableName="node_bft_committed_states">
<column name="output_index" type="INT">
<constraints nullable="false"/>
</column>
<column name="transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-17">
<createTable tableName="node_notary_request_log">
<column name="id" type="INT">
<constraints nullable="false"/>
</column>
<column name="consuming_transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="requesting_party_name" type="NVARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="request_timestamp" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="request_signature" type="BLOB">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-31">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_bft_states_pkey"
tableName="node_bft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-48">
<addPrimaryKey columnNames="id" constraintName="node_notary_request_log_pkey"
tableName="node_notary_request_log"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,9 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<include file="migration/node-bft-smart.changelog-init.xml"/>
<include file="migration/node-bft-smart.changelog-v1.xml"/>
<include file="migration/node-bft-smart.changelog-pkey.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="non-clustered_pk-bft_stae" author="R3.Corda" onValidationFail="MARK_RAN">
<dropPrimaryKey tableName="node_bft_committed_states" constraintName="node_bft_states_pkey"/>
<addPrimaryKey tableName="node_bft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_bft_states_pkey" clustered="false"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="nullability">
<addNotNullConstraint tableName="node_bft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
</changeSet>
</databaseChangeLog>

View File

@ -1,4 +1,4 @@
package net.corda.node.services package net.corda.notary.bftsmart
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
@ -23,8 +23,6 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -94,7 +92,11 @@ class BFTNotaryServiceTests {
val nodes = replicaIds.map { replicaId -> val nodes = replicaIds.map { replicaId ->
mockNet.createUnstartedNode(InternalMockNodeParameters(configOverrides = { mockNet.createUnstartedNode(InternalMockNodeParameters(configOverrides = {
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) val notary = NotaryConfig(
validating = false,
bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces),
className = "net.corda.notary.bftsmart.BftSmartNotaryService"
)
doReturn(notary).whenever(it).notary doReturn(notary).whenever(it).notary
})) }))
} + mockNet.createUnstartedNode() } + mockNet.createUnstartedNode()

View File

@ -1,7 +1,7 @@
package net.corda.node.services.transactions package net.corda.notary.bftsmart
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.transactions.BFTSMaRtConfig.Companion.portIsClaimedFormat import net.corda.notary.bftsmart.BFTSMaRtConfig.Companion.portIsClaimedFormat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -0,0 +1,35 @@
apply plugin: 'kotlin'
apply plugin: 'idea'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.publish-utils'
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
dependencies {
cordaCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// Corda integration dependencies
cordaCompile project(':node')
// Java Atomix: RAFT library
compile 'io.atomix.copycat:copycat-client:1.2.8'
compile 'io.atomix.copycat:copycat-server:1.2.8'
compile 'io.atomix.catalyst:catalyst-netty:1.2.1'
testCompile "junit:junit:$junit_version"
testCompile project(':node-driver')
}
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
}
publish {
name 'corda-notary-raft'
}

View File

@ -0,0 +1,46 @@
package net.corda.notary.raft
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.NonValidatingNotaryFlow
import net.corda.node.services.transactions.ValidatingNotaryFlow
import java.security.PublicKey
/** A highly available notary service using the Raft algorithm to achieve consensus. */
class RaftNotaryService(
override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey
) : TrustedAuthorityNotaryService() {
private val notaryConfig = services.configuration.notary
?: throw IllegalArgumentException("Failed to register ${this::class.java}: notary configuration not present")
override val uniquenessProvider = with(services) {
val raftConfig = notaryConfig.raft
?: throw IllegalArgumentException("Failed to register ${this::class.java}: raft configuration not present")
RaftUniquenessProvider(
configuration.baseDirectory,
configuration.p2pSslOptions,
database,
clock,
monitoringService.metrics,
services.cacheFactory,
raftConfig
)
}
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return if (notaryConfig.validating) {
ValidatingNotaryFlow(otherPartySession, this)
} else NonValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
uniquenessProvider.start()
}
override fun stop() {
uniquenessProvider.stop()
}
}

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.raft
import io.atomix.catalyst.buffer.BufferInput import io.atomix.catalyst.buffer.BufferInput
import io.atomix.catalyst.buffer.BufferOutput import io.atomix.catalyst.buffer.BufferOutput
@ -27,6 +27,7 @@ import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.persistence.currentDBSession
@ -111,7 +112,7 @@ class RaftTransactionCommitLog<E, EK>(
} }
} }
private fun logRequest(commitCommand: RaftTransactionCommitLog.Commands.CommitTransaction) { private fun logRequest(commitCommand: Commands.CommitTransaction) {
val request = PersistentUniquenessProvider.Request( val request = PersistentUniquenessProvider.Request(
consumingTxHash = commitCommand.txId.toString(), consumingTxHash = commitCommand.txId.toString(),
partyName = commitCommand.requestingParty, partyName = commitCommand.requestingParty,
@ -192,8 +193,8 @@ class RaftTransactionCommitLog<E, EK>(
registerAbstract(SecureHash::class.java, CordaKryoSerializer::class.java) registerAbstract(SecureHash::class.java, CordaKryoSerializer::class.java)
registerAbstract(TimeWindow::class.java, CordaKryoSerializer::class.java) registerAbstract(TimeWindow::class.java, CordaKryoSerializer::class.java)
registerAbstract(NotaryError::class.java, CordaKryoSerializer::class.java) registerAbstract(NotaryError::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.CommitTransaction::class.java, CordaKryoSerializer::class.java) register(Commands.CommitTransaction::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.Get::class.java, CordaKryoSerializer::class.java) register(Commands.Get::class.java, CordaKryoSerializer::class.java)
register(StateRef::class.java, CordaKryoSerializer::class.java) register(StateRef::class.java, CordaKryoSerializer::class.java)
register(LinkedHashMap::class.java, CordaKryoSerializer::class.java) register(LinkedHashMap::class.java, CordaKryoSerializer::class.java)
} }

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.raft
import com.codahale.metrics.Gauge import com.codahale.metrics.Gauge
import com.codahale.metrics.MetricRegistry import com.codahale.metrics.MetricRegistry
@ -26,12 +26,12 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.services.config.RaftConfig import net.corda.node.services.config.RaftConfig
import net.corda.node.services.transactions.RaftTransactionCommitLog.Commands.CommitTransaction
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.notary.raft.RaftTransactionCommitLog.Commands.CommitTransaction
import java.nio.file.Path import java.nio.file.Path
import java.time.Clock import java.time.Clock
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture

View File

@ -0,0 +1,19 @@
package net.corda.notary.raft
import net.corda.core.schemas.MappedSchema
import net.corda.node.services.transactions.PersistentUniquenessProvider
object RaftNotarySchema
object RaftNotarySchemaV1 : MappedSchema(
schemaFamily = RaftNotarySchema.javaClass,
version = 1,
mappedTypes = listOf(
PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
RaftUniquenessProvider.CommittedState::class.java
)
) {
override val migrationResource: String?
get() = "notary-raft.changelog-master"
}

View File

@ -0,0 +1,46 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="1511451595465-18">
<createTable tableName="node_raft_committed_states">
<column name="transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="output_index" type="INT">
<constraints nullable="false"/>
</column>
<column name="raft_log_index" type="BIGINT"/>
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-17">
<createTable tableName="node_notary_request_log">
<column name="id" type="INT">
<constraints nullable="false"/>
</column>
<column name="consuming_transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="requesting_party_name" type="NVARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="request_timestamp" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="request_signature" type="BLOB">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-43">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_raft_state_pkey"
tableName="node_raft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-48">
<addPrimaryKey columnNames="id" constraintName="node_notary_request_log_pkey"
tableName="node_notary_request_log"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<include file="migration/node-notary-raft.changelog-init.xml"/>
<include file="migration/node-notary-raft.changelog-v1.xml"/>
<include file="migration/node-notary-raft.changelog-pkey.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="non-clustered_pk-raft_state" author="R3.Corda" onValidationFail="MARK_RAN">
<dropPrimaryKey tableName="node_raft_committed_states" constraintName="node_raft_state_pkey"/>
<addPrimaryKey tableName="node_raft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_raft_state_pkey" clustered="false"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="nullability">
<addNotNullConstraint tableName="node_raft_committed_states" columnName="raft_log_index" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
</changeSet>
</databaseChangeLog>

View File

@ -1,4 +1,4 @@
package net.corda.node.services package net.corda.notary.raft
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.raft
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import io.atomix.catalyst.transport.Address import io.atomix.catalyst.transport.Address
@ -17,7 +17,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
@ -156,7 +156,7 @@ class RaftTransactionCommitLogTests {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port) val address = Address(myAddress.host, myAddress.port)
// Enterprise - OS difference: below configureDatabase parameters differs with OS intentionally to be able run test in remote database // Enterprise - OS difference: below configureDatabase parameters differs with OS intentionally to be able run test in remote database
val database = configureDatabase(makeInternalTestDataSourceProperties( configSupplier = { ConfigFactory.empty() }), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(includeNotarySchemas = true)) val database = configureDatabase(makeInternalTestDataSourceProperties( configSupplier = { ConfigFactory.empty() }), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(extraSchemas = setOf(RaftNotarySchemaV1)))
databases.add(database) databases.add(database)
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) } val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) }

View File

@ -75,7 +75,7 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, overrid
private val metricRegistry = MetricRegistry() private val metricRegistry = MetricRegistry()
override val cacheFactory = EnterpriseNamedCacheFactory(configuration.enterpriseConfiguration.getTracingConfig()).bindWithConfig(configuration).bindWithMetrics(metricRegistry) override val cacheFactory = EnterpriseNamedCacheFactory(configuration.enterpriseConfiguration.getTracingConfig()).bindWithConfig(configuration).bindWithMetrics(metricRegistry)
override val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, false) override val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
override val identityService = PersistentIdentityService(cacheFactory) override val identityService = PersistentIdentityService(cacheFactory)
override val database: CordaPersistence = createCordaPersistence( override val database: CordaPersistence = createCordaPersistence(
configuration.database, configuration.database,

View File

@ -141,9 +141,6 @@ dependencies {
// For H2 database support in persistence // For H2 database support in persistence
compile "com.h2database:h2:$h2_version" compile "com.h2database:h2:$h2_version"
// For the MySQLUniquenessProvider
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
// SQL connection pooling library // SQL connection pooling library
compile "com.zaxxer:HikariCP:${hikari_version}" compile "com.zaxxer:HikariCP:${hikari_version}"
@ -155,18 +152,9 @@ dependencies {
// We only need this dependency to compile our Caplet against. // We only need this dependency to compile our Caplet against.
compileOnly "co.paralleluniverse:capsule:$capsule_version" compileOnly "co.paralleluniverse:capsule:$capsule_version"
// Java Atomix: RAFT library
compile 'io.atomix.copycat:copycat-client:1.2.8'
compile 'io.atomix.copycat:copycat-server:1.2.8'
compile 'io.atomix.catalyst:catalyst-netty:1.2.1'
// OkHTTP: Simple HTTP library. // OkHTTP: Simple HTTP library.
compile "com.squareup.okhttp3:okhttp:$okhttp_version" compile "com.squareup.okhttp3:okhttp:$okhttp_version"
// BFT-SMaRt
compile 'commons-codec:commons-codec:1.10'
compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87'
// Apache Shiro: authentication, authorization and session management. // Apache Shiro: authentication, authorization and session management.
compile "org.apache.shiro:shiro-core:${shiro_version}" compile "org.apache.shiro:shiro-core:${shiro_version}"

View File

@ -52,7 +52,7 @@ class DistributedServiceTests : IntegrationTest() {
invokeRpc(CordaRPCOps::stateMachinesFeed)) invokeRpc(CordaRPCOps::stateMachinesFeed))
) )
driver(DriverParameters( driver(DriverParameters(
extraCordappPackagesToScan = listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), extraCordappPackagesToScan = listOf("net.corda.finance.contracts", "net.corda.finance.schemas", "net.corda.notary.raft"),
notarySpecs = listOf( notarySpecs = listOf(
NotarySpec( NotarySpec(
DUMMY_NOTARY_NAME, DUMMY_NOTARY_NAME,

View File

@ -16,7 +16,6 @@ import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
@ -30,6 +29,8 @@ import net.corda.testing.node.internal.MOCK_VERSION_INFO
import net.corda.testing.node.internal.makeInternalTestDataSourceProperties import net.corda.testing.node.internal.makeInternalTestDataSourceProperties
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import net.corda.testing.internal.TestingNamedCacheFactory
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After

View File

@ -5,7 +5,6 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.DEV_ROOT_CA import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
@ -14,6 +13,7 @@ import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.node.internal.makeTestDatabaseProperties import net.corda.testing.node.internal.makeTestDatabaseProperties
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.assertj.core.api.Assertions.assertThatIllegalArgumentException

View File

@ -48,8 +48,9 @@ class P2PMessagingTest : IntegrationTest() {
private fun startDriverWithDistributedService(dsl: DriverDSL.(List<InProcess>) -> Unit) { private fun startDriverWithDistributedService(dsl: DriverDSL.(List<InProcess>) -> Unit) {
driver(DriverParameters( driver(DriverParameters(
startNodesInProcess = true, startNodesInProcess = true,
notarySpecs = listOf(NotarySpec(DISTRIBUTED_SERVICE_NAME, cluster = ClusterSpec.Raft(clusterSize = 2))) extraCordappPackagesToScan = listOf("net.corda.notary.raft"),
notarySpecs = listOf(NotarySpec(DISTRIBUTED_SERVICE_NAME, cluster = ClusterSpec.Raft(clusterSize = 2)))
)) { )) {
dsl(defaultNotaryHandle.nodeHandles.getOrThrow().map { (it as InProcess) }) dsl(defaultNotaryHandle.nodeHandles.getOrThrow().map { (it as InProcess) })
} }

View File

@ -35,9 +35,7 @@ import net.corda.node.CordaClock
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider import net.corda.node.internal.cordapp.*
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy
@ -118,7 +116,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val platformClock: CordaClock, val platformClock: CordaClock,
cacheFactoryPrototype: NamedCacheFactory, cacheFactoryPrototype: NamedCacheFactory,
protected val versionInfo: VersionInfo, protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor, protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
protected val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { protected val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
@ -144,7 +141,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
} }
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null).tokenize() protected val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas).tokenize()
val identityService = PersistentIdentityService(cacheFactory).tokenize() val identityService = PersistentIdentityService(cacheFactory).tokenize()
val database: CordaPersistence = createCordaPersistence( val database: CordaPersistence = createCordaPersistence(
configuration.database, configuration.database,
@ -515,6 +513,24 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
) )
} }
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
val generatedCordapps = mutableListOf(VirtualCordapp.generateCoreCordapp(versionInfo))
if (isRunningSimpleNotaryService(configuration)) {
// For backwards compatibility purposes the single node notary implementation is built-in: a virtual
// CorDapp will be generated.
generatedCordapps += VirtualCordapp.generateSimpleNotaryCordapp(versionInfo)
}
return JarScanningCordappLoader.fromDirectories(
configuration.cordappDirectories,
versionInfo,
extraCordapps = generatedCordapps
)
}
private fun isRunningSimpleNotaryService(configuration: NodeConfiguration): Boolean {
return configuration.notary != null && configuration.notary?.className == SimpleNotaryService::class.java.name
}
private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause) private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause)
private fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) { private fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) {
@ -810,17 +826,32 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
private fun makeNotaryService(myNotaryIdentity: PartyAndCertificate?): NotaryService? { private fun makeNotaryService(myNotaryIdentity: PartyAndCertificate?): NotaryService? {
return configuration.notary?.let { return configuration.notary?.let { notaryConfig ->
makeCoreNotaryService(it, myNotaryIdentity).also { val serviceClass = getNotaryServiceClass(notaryConfig.className)
it.tokenize() log.info("Starting notary service: $serviceClass")
runOnStop += it::stop
installCoreFlow(NotaryFlow.Client::class, it::createServiceFlow) val notaryKey = myNotaryIdentity?.owningKey
log.info("Running core notary: ${it.javaClass.name}") ?: throw IllegalArgumentException("Unable to start notary service $serviceClass: notary identity not found")
it.start() val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java).apply { isAccessible = true }
val service = constructor.newInstance(services, notaryKey) as NotaryService
service.run {
tokenize()
runOnStop += ::stop
installCoreFlow(NotaryFlow.Client::class, ::createServiceFlow)
start()
} }
return service
} }
} }
private fun getNotaryServiceClass(className: String): Class<out NotaryService> {
val loadedImplementations = cordappLoader.cordapps.mapNotNull { it.notaryService }
log.debug("Notary service implementations found: ${loadedImplementations.joinToString(", ")}")
return loadedImplementations.firstOrNull { it.name == className }
?: throw IllegalArgumentException("The notary service implementation specified in the configuration: $className is not found. Available implementations: ${loadedImplementations.joinToString(", ")}}")
}
protected open fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal { protected open fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
@ -828,35 +859,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return PersistentKeyManagementService(cacheFactory, identityService, database) return PersistentKeyManagementService(cacheFactory, identityService, database)
} }
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, myNotaryIdentity: PartyAndCertificate?): NotaryService {
val notaryKey = myNotaryIdentity?.owningKey
?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
return notaryConfig.run {
when {
raft != null -> {
val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, cacheFactory, raft)
(if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider)
}
bftSMaRt != null -> {
if (validating) throw IllegalArgumentException("Validating BFTSMaRt notary not supported")
BFTNonValidatingNotaryService(services, notaryKey, bftSMaRt, makeBFTCluster(notaryKey, bftSMaRt))
}
mysql != null -> {
(if (validating) ::MySQLValidatingNotaryService else ::MySQLNonValidatingNotaryService)(services, notaryKey, mysql, configuration.devMode)
}
else -> (if (validating) ::ValidatingNotaryService else ::SimpleNotaryService)(services, notaryKey)
}
}
}
protected open fun makeBFTCluster(notaryKey: PublicKey, bftSMaRtConfig: BFTSMaRtConfiguration): BFTSMaRt.Cluster {
return object : BFTSMaRt.Cluster {
override fun waitUntilAllReplicasHaveInitialized() {
log.warn("A BFT replica may still be initializing, in which case the upcoming consensus change may cause it to spin.")
}
}
}
open fun stop() { open fun stop() {
// TODO: We need a good way of handling "nice to have" shutdown events, especially those that deal with the // TODO: We need a good way of handling "nice to have" shutdown events, especially those that deal with the
// network, including unsubscribing from updates from remote services. Possibly some sort of parameter to stop() // network, including unsubscribing from updates from remote services. Possibly some sort of parameter to stop()

View File

@ -28,10 +28,8 @@ import net.corda.core.utilities.contextLogger
import net.corda.node.CordaClock import net.corda.node.CordaClock
import net.corda.node.SimpleClock import net.corda.node.SimpleClock
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.artemis.ArtemisBroker import net.corda.node.internal.artemis.ArtemisBroker
import net.corda.node.internal.artemis.BrokerAddresses import net.corda.node.internal.artemis.BrokerAddresses
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.security.RPCSecurityManager import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
@ -88,14 +86,12 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) {
open class Node(configuration: NodeConfiguration, open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo, versionInfo: VersionInfo,
private val initialiseSerialization: Boolean = true, private val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo),
cacheFactoryPrototype: NamedCacheFactory = DefaultNamedCacheFactory() cacheFactoryPrototype: NamedCacheFactory = DefaultNamedCacheFactory()
) : AbstractNode<NodeInfo>( ) : AbstractNode<NodeInfo>(
configuration, configuration,
createClock(configuration), createClock(configuration),
cacheFactoryPrototype, cacheFactoryPrototype,
versionInfo, versionInfo,
cordappLoader,
// Under normal (non-test execution) it will always be "1" // Under normal (non-test execution) it will always be "1"
AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1) AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1)
) { ) {
@ -133,10 +129,6 @@ open class Node(configuration: NodeConfiguration,
private val sameVmNodeCounter = AtomicInteger() private val sameVmNodeCounter = AtomicInteger()
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo)
}
// TODO: make this configurable. // TODO: make this configurable.
const val MAX_RPC_MESSAGE_SIZE = 10485760 const val MAX_RPC_MESSAGE_SIZE = 10485760

View File

@ -22,7 +22,6 @@ import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationException import net.corda.node.utilities.registration.NodeRegistrationException
@ -259,7 +258,8 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
} }
val nodeInfo = node.start() val nodeInfo = node.start()
logLoadedCorDapps(node.services.cordappProvider.cordapps) val loadedCodapps = node.services.cordappProvider.cordapps.filter { it.isLoaded }
logLoadedCorDapps(loadedCodapps)
node.nodeReadyFuture.thenMatch({ node.nodeReadyFuture.thenMatch({
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits. // Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
@ -426,6 +426,16 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
SerialFilter.install(filter) SerialFilter.install(filter)
} }
/** This filter is required for BFT-Smart to work as it only supports Java serialization. */
// TODO: move this filter out of the node, allow Cordapps to specify filters.
private fun bftSMaRtSerialFilter(clazz: Class<*>): Boolean = clazz.name.let {
it.startsWith("bftsmart.")
|| it.startsWith("java.security.")
|| it.startsWith("java.util.")
|| it.startsWith("java.lang.")
|| it.startsWith("java.net.")
}
protected open fun getVersionInfo(): VersionInfo { protected open fun getVersionInfo(): VersionInfo {
return VersionInfo( return VersionInfo(
PLATFORM_VERSION, PLATFORM_VERSION,

View File

@ -9,6 +9,9 @@ import net.corda.core.flows.*
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappInfoResolver import net.corda.core.internal.cordapp.CordappInfoResolver
import net.corda.core.internal.notary.AsyncCFTNotaryService
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
@ -36,9 +39,12 @@ import kotlin.streams.toList
* @property cordappJarPaths The classpath of cordapp JARs * @property cordappJarPaths The classpath of cordapp JARs
*/ */
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>, class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() { private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl>) : CordappLoaderTemplate() {
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + coreCordapp } override val cordapps: List<CordappImpl> by lazy {
loadCordapps() + extraCordapps
}
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
@ -58,9 +64,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
* *
* @param corDappDirectories Directories used to scan for CorDapp JARs. * @param corDappDirectories Directories used to scan for CorDapp JARs.
*/ */
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader { fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo) val paths = corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps)
} }
/** /**
@ -68,11 +75,12 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
* *
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/ */
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader { fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo) val paths = scanJars.map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps)
} }
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
private fun jarUrlsInDirectory(directory: Path): List<URL> { private fun jarUrlsInDirectory(directory: Path): List<URL> {
@ -85,32 +93,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
} }
} }
/** A list of the core RPC flows present in Corda */
private val coreRPCFlows = listOf(
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(),
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
private fun loadCordapps(): List<CordappImpl> { private fun loadCordapps(): List<CordappImpl> {
val cordapps = cordappJarPaths val cordapps = cordappJarPaths
.map { scanCordapp(it).toCordapp(it) } .map { scanCordapp(it).toCordapp(it) }
@ -126,7 +109,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) } cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
return cordapps return cordapps
} }
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl { private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: CordappImpl.Info.UNKNOWN } val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: CordappImpl.Info.UNKNOWN }
return CordappImpl( return CordappImpl(
@ -142,10 +124,22 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
findAllFlows(this), findAllFlows(this),
url.url, url.url,
info, info,
getJarHash(url.url) getJarHash(url.url),
findNotaryService(this)
) )
} }
private fun findNotaryService(scanResult: RestrictedScanResult): Class<out NotaryService>? {
// Note: we search for implementations of both NotaryService and TrustedAuthorityNotaryService as
// the scanner won't find subclasses deeper down the hierarchy if any intermediate class is not
// present in the CorDapp.
val result = scanResult.getClassesWithSuperclass(NotaryService::class) +
scanResult.getClassesWithSuperclass(TrustedAuthorityNotaryService::class) +
scanResult.getClassesWithSuperclass(AsyncCFTNotaryService::class)
logger.info("Found notary service CorDapp implementations: " + result.joinToString(", "))
return result.firstOrNull()
}
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256() private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> { private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
@ -202,7 +196,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> { private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet() return scanResult.getClassesWithSuperclass(MappedSchema::class).instances().toSet()
} }
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000) private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
@ -243,18 +237,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
val qualifiedNamePrefix: String get() = rootPackageName?.let { "$it." } ?: "" val qualifiedNamePrefix: String get() = rootPackageName?.let { "$it." } ?: ""
} }
private fun <T : Any> List<Class<out T>>.instances(): List<T> {
return map { it.kotlin.objectOrNewInstance() }
}
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) { private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) {
fun getNamesOfClassesImplementing(type: KClass<*>): List<String> { fun getNamesOfClassesImplementing(type: KClass<*>): List<String> {
return scanResult.getNamesOfClassesImplementing(type.java) return scanResult.getNamesOfClassesImplementing(type.java)
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
} }
fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<T> { fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<Class<out T>> {
return scanResult.getNamesOfSubclassesOf(type.java) return scanResult.getNamesOfSubclassesOf(type.java)
.filter { it.startsWith(qualifiedNamePrefix) } .filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) } .mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() }
} }
fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> { fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> {

View File

@ -0,0 +1,60 @@
package net.corda.node.internal.cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.location
import net.corda.node.VersionInfo
import net.corda.node.services.transactions.NodeNotarySchemaV1
import net.corda.node.services.transactions.SimpleNotaryService
internal object VirtualCordapp {
/** A list of the core RPC flows present in Corda */
private val coreRpcFlows = listOf(
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java
)
/** A Cordapp representing the core package which is not scanned automatically. */
fun generateCoreCordapp(versionInfo: VersionInfo): CordappImpl {
return 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, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash,
notaryService = null,
isLoaded = false
)
}
/** A Cordapp for the built-in notary service implementation. */
fun generateSimpleNotaryCordapp(versionInfo: VersionInfo): CordappImpl {
return CordappImpl(
contractClassNames = listOf(),
initiatedFlows = listOf(),
rpcFlows = listOf(),
serviceFlows = listOf(),
schedulableFlows = listOf(),
services = listOf(),
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(NodeNotarySchemaV1),
info = CordappImpl.Info("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = SimpleNotaryService::class.java.location,
jarHash = SecureHash.allOnesHash,
notaryService = SimpleNotaryService::class.java,
isLoaded = false
)
}
}

View File

@ -137,7 +137,8 @@ data class NotaryConfig(val validating: Boolean,
val bftSMaRt: BFTSMaRtConfiguration? = null, val bftSMaRt: BFTSMaRtConfiguration? = null,
val custom: Boolean = false, val custom: Boolean = false,
val mysql: MySQLConfiguration? = null, val mysql: MySQLConfiguration? = null,
val serviceLegalName: CordaX500Name? = null val serviceLegalName: CordaX500Name? = null,
val className: String = "net.corda.node.services.transactions.SimpleNotaryService"
) { ) {
init { init {
require(raft == null || bftSMaRt == null || !custom || mysql == null) { require(raft == null || bftSMaRt == null || !custom || mysql == null) {

View File

@ -14,12 +14,10 @@ import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.P2PMessageDeduplicator import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.RunOnceService
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.RunOnceService
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.RaftUniquenessProvider
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
@ -30,7 +28,7 @@ import net.corda.node.services.vault.VaultSchemaV1
* TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState]. * TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState].
* TODO: create whitelisted tables when a CorDapp is first installed * TODO: create whitelisted tables when a CorDapp is first installed
*/ */
class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet(), includeNotarySchemas: Boolean = false) : SchemaService, SingletonSerializeAsToken() { class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()) : SchemaService, SingletonSerializeAsToken() {
// Core Entities used by a Node // Core Entities used by a Node
object NodeCore object NodeCore
@ -49,26 +47,12 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
override val migrationResource = "node-core.changelog-master" override val migrationResource = "node-core.changelog-master"
} }
// Entities used by a Notary
object NodeNotary
object NodeNotaryV1 : MappedSchema(schemaFamily = NodeNotary.javaClass, version = 1,
mappedTypes = listOf(PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
PersistentUniquenessProvider.CommittedState::class.java,
RaftUniquenessProvider.CommittedState::class.java,
BFTNonValidatingNotaryService.CommittedState::class.java
)) {
override val migrationResource = "node-notary.changelog-master"
}
// Required schemas are those used by internal Corda services // Required schemas are those used by internal Corda services
private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> = private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> =
mapOf(Pair(CommonSchemaV1, SchemaOptions()), mapOf(Pair(CommonSchemaV1, SchemaOptions()),
Pair(VaultSchemaV1, SchemaOptions()), Pair(VaultSchemaV1, SchemaOptions()),
Pair(NodeInfoSchemaV1, SchemaOptions()), Pair(NodeInfoSchemaV1, SchemaOptions()),
Pair(NodeCoreV1, SchemaOptions())) + Pair(NodeCoreV1, SchemaOptions()))
if (includeNotarySchemas) mapOf(Pair(NodeNotaryV1, SchemaOptions())) else emptyMap()
fun internalSchemas() = requiredSchemas.keys + extraSchemas.filter { schema -> // when mapped schemas from the finance module are present, they are considered as internal ones fun internalSchemas() = requiredSchemas.keys + extraSchemas.filter { schema -> // when mapped schemas from the finance module are present, they are considered as internal ones
schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1" } schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1" }

View File

@ -1,45 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.AsyncCFTNotaryService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.MySQLConfiguration
import java.security.PublicKey
/** Notary service backed by a replicated MySQL database. */
abstract class MySQLNotaryService(
final override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey,
configuration: MySQLConfiguration,
/** Database table will be automatically created in dev mode */
val devMode: Boolean) : AsyncCFTNotaryService() {
override val asyncUniquenessProvider = MySQLUniquenessProvider(
services.monitoringService.metrics,
services.clock,
configuration
)
override fun start() {
if (devMode) asyncUniquenessProvider.createTable()
}
override fun stop() {
asyncUniquenessProvider.stop()
}
}
class MySQLNonValidatingNotaryService(services: ServiceHubInternal,
notaryIdentityKey: PublicKey,
configuration: MySQLConfiguration,
devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, configuration, devMode) {
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = NonValidatingNotaryFlow(otherPartySession, this)
}
class MySQLValidatingNotaryService(services: ServiceHubInternal,
notaryIdentityKey: PublicKey,
configuration: MySQLConfiguration,
devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, configuration, devMode) {
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ValidatingNotaryFlow(otherPartySession, this)
}

View File

@ -1,26 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import java.security.PublicKey
/** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftNonValidatingNotaryService(
override val services: ServiceHub,
override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() {
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return NonValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
uniquenessProvider.start()
}
override fun stop() {
uniquenessProvider.stop()
}
}

View File

@ -1,26 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import java.security.PublicKey
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftValidatingNotaryService(
override val services: ServiceHub,
override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() {
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return ValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
uniquenessProvider.start()
}
override fun stop() {
uniquenessProvider.stop()
}
}

View File

@ -3,15 +3,38 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.schemas.MappedSchema
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey import java.security.PublicKey
/** A simple Notary service that does not perform transaction validation */ /** An embedded notary service that uses the node's database to store committed states. */
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
private val notaryConfig = services.configuration.notary
?: throw IllegalArgumentException("Failed to register ${this::class.java}: notary configuration not present")
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory) override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return if (notaryConfig.validating) {
log.info("Starting in validating mode")
ValidatingNotaryFlow(otherPartySession, this)
} else {
log.info("Starting in non-validating mode")
NonValidatingNotaryFlow(otherPartySession, this)
}
}
override fun start() {} override fun start() {}
override fun stop() {} override fun stop() {}
} }
// Entities used by a Notary
object NodeNotarySchema
object NodeNotarySchemaV1 : MappedSchema(schemaFamily = NodeNotarySchema.javaClass, version = 1,
mappedTypes = listOf(PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
PersistentUniquenessProvider.CommittedState::class.java
)) {
override val migrationResource = "node-notary.changelog-master"
}

View File

@ -1,17 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = ValidatingNotaryFlow(otherPartySession, this)
override fun start() {}
override fun stop() {}
}

View File

@ -27,18 +27,6 @@
<column name="consuming_transaction_id" type="NVARCHAR(64)"/> <column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable> </createTable>
</changeSet> </changeSet>
<changeSet author="R3.Corda" id="1511451595465-18">
<createTable tableName="node_raft_committed_states">
<column name="transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="output_index" type="INT">
<constraints nullable="false"/>
</column>
<column name="raft_log_index" type="BIGINT"/>
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-17"> <changeSet author="R3.Corda" id="1521131680317-17">
<createTable tableName="node_notary_request_log"> <createTable tableName="node_notary_request_log">
<column name="id" type="INT"> <column name="id" type="INT">
@ -58,18 +46,11 @@
</column> </column>
</createTable> </createTable>
</changeSet> </changeSet>
<changeSet author="R3.Corda" id="1511451595465-31">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_bft_states_pkey"
tableName="node_bft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-41"> <changeSet author="R3.Corda" id="1511451595465-41">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_notary_states_pkey" <addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_notary_states_pkey"
tableName="node_notary_committed_states"/> tableName="node_notary_committed_states"/>
</changeSet> </changeSet>
<changeSet author="R3.Corda" id="1511451595465-43">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_raft_state_pkey"
tableName="node_raft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-48"> <changeSet author="R3.Corda" id="1521131680317-48">
<addPrimaryKey columnNames="id" constraintName="node_notary_request_log_pkey" <addPrimaryKey columnNames="id" constraintName="node_notary_request_log_pkey"
tableName="node_notary_request_log"/> tableName="node_notary_request_log"/>

View File

@ -13,9 +13,4 @@
<addPrimaryKey tableName="node_bft_committed_states" columnNames="output_index, transaction_id" <addPrimaryKey tableName="node_bft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_bft_states_pkey" clustered="false"/> constraintName="node_bft_states_pkey" clustered="false"/>
</changeSet> </changeSet>
<changeSet id="non-clustered_pk-raft_state" author="R3.Corda" onValidationFail="MARK_RAN">
<dropPrimaryKey tableName="node_raft_committed_states" constraintName="node_raft_state_pkey"/>
<addPrimaryKey tableName="node_raft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_raft_state_pkey" clustered="false"/>
</changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -7,10 +7,6 @@
<changeSet author="R3.Corda" id="nullability"> <changeSet author="R3.Corda" id="nullability">
<addNotNullConstraint tableName="node_bft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/> <addNotNullConstraint tableName="node_bft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
<addNotNullConstraint tableName="node_notary_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/> <addNotNullConstraint tableName="node_notary_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="raft_log_index" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -47,7 +47,7 @@ class JarScanningCordappLoaderTest {
fun `classes that aren't in cordapps aren't loaded`() { fun `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 // 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("."))) val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get(".")))
assertThat(loader.cordapps).containsOnly(loader.coreCordapp) assertThat(loader.cordapps).isEmpty()
} }
@Test @Test
@ -55,9 +55,9 @@ class JarScanningCordappLoaderTest {
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!! val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
assertThat(loader.cordapps).hasSize(2) assertThat(loader.cordapps).hasSize(1)
val actualCordapp = loader.cordapps.single { it != loader.coreCordapp } val actualCordapp = loader.cordapps.single()
assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId)) assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId))
assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor") assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor")
assertThat(actualCordapp.rpcFlows).isEmpty() assertThat(actualCordapp.rpcFlows).isEmpty()
@ -73,8 +73,8 @@ class JarScanningCordappLoaderTest {
val loader = cordappLoaderForPackages(listOf(testScanPackage)) val loader = cordappLoaderForPackages(listOf(testScanPackage))
val actual = loader.cordapps.toTypedArray() val actual = loader.cordapps.toTypedArray()
// One core cordapp, one cordapp from this source tree. In gradle it will also pick up the node jar. // One cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(actual.size == 2 || actual.size == 3).isTrue() assertThat(actual.size == 0 || actual.size == 1).isTrue()
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() } val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() }
assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java) assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java)
@ -111,7 +111,7 @@ class JarScanningCordappLoaderTest {
fun `cordapp classloader sets target and min version to 1 if not specified`() { fun `cordapp classloader sets target and min version to 1 if not specified`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
loader.cordapps.filter { it.info.shortName != "corda-core" }.forEach { loader.cordapps.forEach {
assertThat(it.info.targetPlatformVersion).isEqualTo(1) assertThat(it.info.targetPlatformVersion).isEqualTo(1)
assertThat(it.info.minimumPlatformVersion).isEqualTo(1) assertThat(it.info.minimumPlatformVersion).isEqualTo(1)
} }
@ -123,8 +123,7 @@ class JarScanningCordappLoaderTest {
// make sure classloader extracts correct values // make sure classloader extracts correct values
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
// exclude the core cordapp val cordapp = loader.cordapps.first()
val cordapp = loader.cordapps.single { it.cordappClasses.contains("net.corda.core.internal.cordapp.CordappImpl") }
assertThat(cordapp.info.targetPlatformVersion).isEqualTo(3) assertThat(cordapp.info.targetPlatformVersion).isEqualTo(3)
assertThat(cordapp.info.minimumPlatformVersion).isEqualTo(2) assertThat(cordapp.info.minimumPlatformVersion).isEqualTo(2)
} }
@ -144,24 +143,21 @@ class JarScanningCordappLoaderTest {
fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() { fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
// exclude the core cordapp assertThat(loader.cordapps).hasSize(0)
assertThat(loader.cordapps).hasSize(1)
} }
@Test @Test
fun `cordapp classloader does load apps when their min platform version is less than the platform version`() { fun `cordapp classloader does load apps when their min platform version is less than the platform version`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
// exclude the core cordapp assertThat(loader.cordapps).hasSize(1)
assertThat(loader.cordapps).hasSize(2)
} }
@Test @Test
fun `cordapp classloader does load apps when their min platform version is equal to the platform version`() { fun `cordapp classloader does load apps when their min platform version is equal to the platform version`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2)) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
// exclude the core cordapp assertThat(loader.cordapps).hasSize(1)
assertThat(loader.cordapps).hasSize(2)
} }
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader { private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {

View File

@ -313,20 +313,11 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// of gets and puts. // of gets and puts.
private fun makeNodeWithTracking(name: CordaX500Name): TestStartedNode { private fun makeNodeWithTracking(name: CordaX500Name): TestStartedNode {
// Create a node in the mock network ... // Create a node in the mock network ...
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, cordappLoader -> return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args ->
if (cordappLoader != null) { object : InternalMockNetwork.MockNode(args) {
object : InternalMockNetwork.MockNode(args, cordappLoader) { // That constructs a recording tx storage
// That constructs a recording tx storage override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage(transactionCacheSizeBytes))
return RecordingTransactionStorage(database, super.makeTransactionStorage(transactionCacheSizeBytes))
}
}
} else {
object : InternalMockNetwork.MockNode(args) {
// That constructs a recording tx storage
override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
return RecordingTransactionStorage(database, super.makeTransactionStorage(transactionCacheSizeBytes))
}
} }
} }
}) })
@ -611,7 +602,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
fillUpForBuyer(bobError, issuer, bob, notary).second fillUpForBuyer(bobError, issuer, bob, notary).second
} }
val alicesFakePaper = aliceNode.database.transaction { val alicesFakePaper = aliceNode.database.transaction {
fillUpForSeller(aliceError, issuer, alice,1200.DOLLARS `issued by` issuer, null, notary).second fillUpForSeller(aliceError, issuer, alice, 1200.DOLLARS `issued by` issuer, null, notary).second
} }
insertFakeTransactions(bobsBadCash, bobNode, bob, notaryNode, bankNode) insertFakeTransactions(bobsBadCash, bobNode, bob, notaryNode, bankNode)

View File

@ -22,6 +22,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.FlowTimeoutConfiguration import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
@ -60,12 +61,13 @@ class TimedFlowTestRule(val clusterSize: Int) : ExternalResource() {
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("Custom Notary", "Zurich", "CH")) CordaX500Name("Custom Notary", "Zurich", "CH"))
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryIdentity, true)))) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryIdentity, true))))
val notaryConfig = mock<NotaryConfig> { val notaryConfig = mock<NotaryConfig> {
whenever(it.custom).thenReturn(true) whenever(it.custom).thenReturn(true)
whenever(it.isClusterConfig).thenReturn(true) whenever(it.isClusterConfig).thenReturn(true)
whenever(it.validating).thenReturn(true) whenever(it.validating).thenReturn(true)
} whenever(it.className).thenReturn(TimedFlowTests.TestNotaryService::class.java.name)
}
val notaryNodes = (0 until clusterSize).map { val notaryNodes = (0 until clusterSize).map {
mockNet.createUnstartedNode(InternalMockNodeParameters(configOverrides = { mockNet.createUnstartedNode(InternalMockNodeParameters(configOverrides = {
@ -191,8 +193,7 @@ class TimedFlowTests {
}.bufferUntilSubscribed().toBlocking().toFuture() }.bufferUntilSubscribed().toBlocking().toFuture()
} }
@CordaService class TestNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
private class TestNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = mock<UniquenessProvider>() override val uniquenessProvider = mock<UniquenessProvider>()
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = TestNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = TestNotaryFlow(otherPartySession, this)
override fun start() {} override fun start() {}

View File

@ -8,7 +8,7 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates

View File

@ -5,7 +5,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.After import org.junit.After

View File

@ -9,7 +9,7 @@ import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.* import net.corda.testing.core.*
@ -154,7 +154,8 @@ class DBTransactionStorageTests {
} }
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) { private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) {
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride ?: 1024)) transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
?: 1024))
} }
private fun assertTransactionIsRetrievable(transaction: SignedTransaction) { private fun assertTransactionIsRetrievable(transaction: SignedTransaction) {

View File

@ -9,7 +9,7 @@ import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.core.BOC_NAME import net.corda.testing.core.BOC_NAME
import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork

View File

@ -37,11 +37,11 @@ import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.schema.PersistentStateService import net.corda.node.services.schema.PersistentStateService
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.DummyDealStateSchemaV1 import net.corda.testing.internal.vault.DummyDealStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import net.corda.testing.internal.vault.DummyLinearStateSchemaV1

View File

@ -15,7 +15,7 @@ import net.corda.core.node.services.vault.Sort
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.LogHelper import net.corda.testing.internal.LogHelper

View File

@ -10,20 +10,18 @@ import net.corda.core.schemas.PersistentState
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.schema.NodeSchemaService.NodeCoreV1 import net.corda.node.services.schema.NodeSchemaService.NodeCoreV1
import net.corda.node.services.schema.NodeSchemaService.NodeNotaryV1
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.InProcessImpl import net.corda.testing.driver.internal.InProcessImpl
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import org.hibernate.annotations.Cascade import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType import org.hibernate.annotations.CascadeType
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import javax.persistence.* import javax.persistence.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class NodeSchemaServiceTest { class NodeSchemaServiceTest {
@ -47,7 +45,6 @@ class NodeSchemaServiceTest {
// check against NodeCore schemas // check against NodeCore schemas
assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1)) assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1))
assertFalse(schemaService.schemaOptions.containsKey(NodeNotaryV1))
mockNet.stopNodes() mockNet.stopNodes()
} }
@ -57,9 +54,8 @@ class NodeSchemaServiceTest {
val mockNotaryNode = mockNet.notaryNodes.first() val mockNotaryNode = mockNet.notaryNodes.first()
val schemaService = mockNotaryNode.services.schemaService val schemaService = mockNotaryNode.services.schemaService
// check against NodeCore + NodeNotary Schemas // check against NodeCore Schema
assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1)) assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1))
assertTrue(schemaService.schemaOptions.containsKey(NodeNotaryV1))
mockNet.stopNodes() mockNet.stopNodes()
} }
@ -97,7 +93,6 @@ class NodeSchemaServiceTest {
val mappedSchemas = result.returnValue.getOrThrow() val mappedSchemas = result.returnValue.getOrThrow()
// check against NodeCore schemas // check against NodeCore schemas
assertTrue(mappedSchemas.contains(NodeCoreV1.name)) assertTrue(mappedSchemas.contains(NodeCoreV1.name))
assertFalse(mappedSchemas.contains(NodeNotaryV1.name)) // still gets loaded due TODO restriction
} }
} }
@ -107,9 +102,8 @@ class NodeSchemaServiceTest {
driver(DriverParameters(startNodesInProcess = true)) { driver(DriverParameters(startNodesInProcess = true)) {
val notary = defaultNotaryNode.getOrThrow() val notary = defaultNotaryNode.getOrThrow()
val mappedSchemas = notary.rpc.startFlow(::MappedSchemasFlow).returnValue.getOrThrow() val mappedSchemas = notary.rpc.startFlow(::MappedSchemasFlow).returnValue.getOrThrow()
// check against NodeCore + NodeNotary Schemas // check against NodeCore Schema
assertTrue(mappedSchemas.contains(NodeCoreV1.name)) assertTrue(mappedSchemas.contains(NodeCoreV1.name))
assertTrue(mappedSchemas.contains(NodeNotaryV1.name))
} }
} }

View File

@ -37,7 +37,7 @@ class MaxTransactionSizeTests {
@Before @Before
fun setup() { fun setup() {
mockNet = MockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), networkParameters = testNetworkParameters(maxTransactionSize = 3_000_000)) mockNet = MockNetwork(listOf("net.corda.testing.contracts"), networkParameters = testNetworkParameters(maxTransactionSize = 3_000_000))
aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
notaryNode = mockNet.defaultNotaryNode notaryNode = mockNet.defaultNotaryNode

View File

@ -10,7 +10,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.notary.NotaryInternalException import net.corda.core.internal.notary.NotaryInternalException
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
@ -39,7 +39,7 @@ class PersistentUniquenessProviderTests {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(includeNotarySchemas = true)) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(extraSchemas = setOf(NodeNotarySchemaV1)))
} }
@After @After

View File

@ -82,7 +82,7 @@ class VaultSoftLockManagerTest {
private val mockVault = rigorousMock<VaultServiceInternal>().also { private val mockVault = rigorousMock<VaultServiceInternal>().also {
doNothing().whenever(it).softLockRelease(any(), anyOrNull()) doNothing().whenever(it).softLockRelease(any(), anyOrNull())
} }
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args, _ -> private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, database: CordaPersistence): VaultServiceInternal { override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, database: CordaPersistence): VaultServiceInternal {
val node = this val node = this

29
notary/mysql/build.gradle Normal file
View File

@ -0,0 +1,29 @@
apply plugin: 'kotlin'
apply plugin: 'idea'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.quasar-utils'
dependencies {
cordaCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// Corda integration dependencies
cordaCompile project(':node')
// For the MySQLUniquenessProvider
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
testCompile "junit:junit:$junit_version"
testCompile project(':node-driver')
}
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
}
publish {
name 'corda-notary-mysql'
}

View File

@ -0,0 +1,46 @@
package net.corda.notary.mysql
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.AsyncCFTNotaryService
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.NonValidatingNotaryFlow
import net.corda.node.services.transactions.ValidatingNotaryFlow
import java.security.PublicKey
/** Notary service backed by a replicated MySQL database. */
class MySQLNotaryService(
override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey) : AsyncCFTNotaryService() {
/** Database table will be automatically created in dev mode */
private val devMode = services.configuration.devMode
private val notaryConfig = services.configuration.notary
?: throw IllegalArgumentException("Failed to register ${this::class.java}: notary configuration not present")
override val asyncUniquenessProvider = with(services) {
val mysqlConfig = notaryConfig.mysql
?: throw IllegalArgumentException("Failed to register ${this::class.java}: raft configuration not present")
MySQLUniquenessProvider(
services.monitoringService.metrics,
services.clock,
mysqlConfig
)
}
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return if (notaryConfig.validating) {
ValidatingNotaryFlow(otherPartySession, this)
} else NonValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
if (devMode) asyncUniquenessProvider.createTable()
}
override fun stop() {
asyncUniquenessProvider.stop()
}
}

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions package net.corda.notary.mysql
import com.codahale.metrics.Gauge import com.codahale.metrics.Gauge
import com.codahale.metrics.MetricRegistry import com.codahale.metrics.MetricRegistry

View File

@ -1,4 +1,4 @@
package net.corda.node.services package net.corda.notary.mysql
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
@ -24,7 +24,6 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.config.MySQLConfiguration import net.corda.node.services.config.MySQLConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.MySQLNotaryService
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -64,9 +63,9 @@ class MySQLNotaryServiceTests : IntegrationTest() {
@Before @Before
fun before() { fun before() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true) mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.notary.mysql"), threadPerNode = true)
notaryParty = DevIdentityGenerator.generateDistributedNotarySingularIdentity(listOf(mockNet.baseDirectory(mockNet.nextNodeId)), notaryName) notaryParty = DevIdentityGenerator.generateDistributedNotarySingularIdentity(listOf(mockNet.baseDirectory(mockNet.nextNodeId)), notaryName)
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false)))) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, true))))
val notaryNodeUnstarted = createNotaryNode() val notaryNodeUnstarted = createNotaryNode()
val nodeUnstarted = mockNet.createUnstartedNode() val nodeUnstarted = mockNet.createUnstartedNode()
val startedNodes = listOf(notaryNodeUnstarted, nodeUnstarted).map { n -> val startedNodes = listOf(notaryNodeUnstarted, nodeUnstarted).map { n ->
@ -256,8 +255,9 @@ class MySQLNotaryServiceTests : IntegrationTest() {
entropyRoot = BigInteger.valueOf(60L), entropyRoot = BigInteger.valueOf(60L),
configOverrides = { configOverrides = {
val notaryConfig = NotaryConfig( val notaryConfig = NotaryConfig(
validating = false, validating = true,
mysql = MySQLConfiguration(dataStoreProperties, maxBatchSize = 10, maxBatchInputStates = 100) mysql = MySQLConfiguration(dataStoreProperties, maxBatchSize = 10, maxBatchInputStates = 100),
className = MySQLNotaryService::class.java.name
) )
doReturn(notaryConfig).whenever(it).notary doReturn(notaryConfig).whenever(it).notary
} }

View File

@ -327,7 +327,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
private fun makeNodeWithTracking( private fun makeNodeWithTracking(
name: CordaX500Name): TestStartedNode { name: CordaX500Name): TestStartedNode {
// Create a node in the mock network ... // Create a node in the mock network ...
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, _ -> return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
// That constructs a recording tx storage // That constructs a recording tx storage
override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage { override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {

View File

@ -4,10 +4,8 @@ apply plugin: 'java'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish'
configurations { configurations {
integrationTestCompile.extendsFrom testCompile integrationTestCompile.extendsFrom testCompile
@ -16,7 +14,6 @@ configurations {
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testCompile "junit:junit:$junit_version"
// Corda integration dependencies // Corda integration dependencies
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts') cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
@ -24,26 +21,11 @@ dependencies {
cordaCompile project(':core') cordaCompile project(':core')
cordaCompile project(':client:jfx') cordaCompile project(':client:jfx')
cordaCompile project(':client:rpc') cordaCompile project(':client:rpc')
cordaCompile project(':node-driver') cordaCompile project(':test-utils')
}
idea { // Notary implementations
module { cordapp project(':experimental:notary-raft')
downloadJavadoc = true // defaults to false cordapp project(':experimental:notary-bft-smart')
downloadSources = true
}
}
publishing {
publications {
jarAndSources(MavenPublication) {
from components.java
artifactId 'notarydemo'
artifact sourceJar
artifact javadocJar
}
}
} }
task deployNodes(dependsOn: ['deployNodesSingle', 'deployNodesRaft', 'deployNodesBFT', 'deployNodesCustom']) task deployNodes(dependsOn: ['deployNodesSingle', 'deployNodesRaft', 'deployNodesBFT', 'deployNodesCustom'])
@ -99,9 +81,11 @@ task deployNodesCustom(type: Cordform, dependsOn: 'jar') {
} }
task deployNodesRaft(type: Cordform, dependsOn: 'jar') { task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
def className = "net.corda.notary.raft.RaftNotaryService"
directory file("$buildDir/nodes/nodesRaft") directory file("$buildDir/nodes/nodesRaft")
nodeDefaults { nodeDefaults {
extraConfig = [h2Settings: [address: "localhost:0"]] extraConfig = [h2Settings: [address: "localhost:0"]]
cordapp project(':experimental:notary-raft')
} }
node { node {
name "O=Alice Corp,L=Madrid,C=ES" name "O=Alice Corp,L=Madrid,C=ES"
@ -124,7 +108,8 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
serviceLegalName: "O=Raft,L=Zurich,C=CH", serviceLegalName: "O=Raft,L=Zurich,C=CH",
raft: [ raft: [
nodeAddress: "localhost:10008" nodeAddress: "localhost:10008"
] ],
className: className
] ]
} }
node { node {
@ -140,7 +125,8 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
raft: [ raft: [
nodeAddress: "localhost:10012", nodeAddress: "localhost:10012",
clusterAddresses: ["localhost:10008"] clusterAddresses: ["localhost:10008"]
] ],
className: className
] ]
} }
node { node {
@ -156,16 +142,19 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
raft: [ raft: [
nodeAddress: "localhost:10016", nodeAddress: "localhost:10016",
clusterAddresses: ["localhost:10008"] clusterAddresses: ["localhost:10008"]
] ],
className: className
] ]
} }
} }
task deployNodesBFT(type: Cordform, dependsOn: 'jar') { task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
def clusterAddresses = ["localhost:11000", "localhost:11010", "localhost:11020", "localhost:11030"] def clusterAddresses = ["localhost:11000", "localhost:11010", "localhost:11020", "localhost:11030"]
def className = "net.corda.notary.bftsmart.BftSmartNotaryService"
directory file("$buildDir/nodes/nodesBFT") directory file("$buildDir/nodes/nodesBFT")
nodeDefaults { nodeDefaults {
extraConfig = [h2Settings: [address: "localhost:0"]] extraConfig = [h2Settings: [address: "localhost:0"]]
cordapp project(':experimental:notary-bft-smart')
} }
node { node {
name "O=Alice Corp,L=Madrid,C=ES" name "O=Alice Corp,L=Madrid,C=ES"
@ -189,7 +178,8 @@ task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
bftSMaRt: [ bftSMaRt: [
replicaId: 0, replicaId: 0,
clusterAddresses: clusterAddresses clusterAddresses: clusterAddresses
] ],
className: className
] ]
} }
node { node {
@ -203,9 +193,10 @@ task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
validating: false, validating: false,
serviceLegalName: "O=BFT,L=Zurich,C=CH", serviceLegalName: "O=BFT,L=Zurich,C=CH",
bftSMaRt: [ bftSMaRt: [
replicaId: 0, replicaId: 1,
clusterAddresses: clusterAddresses clusterAddresses: clusterAddresses
] ],
className: className
] ]
} }
node { node {
@ -219,9 +210,10 @@ task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
validating: false, validating: false,
serviceLegalName: "O=BFT,L=Zurich,C=CH", serviceLegalName: "O=BFT,L=Zurich,C=CH",
bftSMaRt: [ bftSMaRt: [
replicaId: 0, replicaId: 2,
clusterAddresses: clusterAddresses clusterAddresses: clusterAddresses
] ],
className: className
] ]
} }
node { node {
@ -235,9 +227,10 @@ task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
validating: false, validating: false,
serviceLegalName: "O=BFT,L=Zurich,C=CH", serviceLegalName: "O=BFT,L=Zurich,C=CH",
bftSMaRt: [ bftSMaRt: [
replicaId: 0, replicaId: 3,
clusterAddresses: clusterAddresses clusterAddresses: clusterAddresses
] ],
className: className
] ]
} }
} }

View File

@ -30,6 +30,8 @@ include 'experimental:flow-worker'
include 'experimental:ha-testing' include 'experimental:ha-testing'
include 'experimental:corda-utils' include 'experimental:corda-utils'
include 'experimental:rpc-worker' include 'experimental:rpc-worker'
include 'experimental:notary-raft'
include 'experimental:notary-bft-smart'
include 'jdk8u-deterministic' include 'jdk8u-deterministic'
include 'test-common' include 'test-common'
include 'test-cli' include 'test-cli'
@ -79,6 +81,7 @@ include 'node:dist'
include 'tools:notary-healthcheck:contract' include 'tools:notary-healthcheck:contract'
include 'tools:notary-healthcheck:cordapp' include 'tools:notary-healthcheck:cordapp'
include 'tools:notary-healthcheck:client' include 'tools:notary-healthcheck:client'
include 'notary:mysql'
apply from: 'buildCacheSettings.gradle' apply from: 'buildCacheSettings.gradle'

View File

@ -25,6 +25,8 @@ sourceSets {
} }
dependencies { dependencies {
// Bundling in the Raft notary service for tests involving distributed notaries
compile project(':experimental:notary-raft')
compile project(':test-utils') compile project(':test-utils')
// Integration test helpers // Integration test helpers

View File

@ -497,6 +497,7 @@ class DriverDSLImpl(
val config = NotaryConfig( val config = NotaryConfig(
validating = spec.validating, validating = spec.validating,
serviceLegalName = spec.name, serviceLegalName = spec.name,
className = "net.corda.notary.raft.RaftNotaryService",
raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses))
return config.toConfigMap() return config.toConfigMap()
} }

View File

@ -28,10 +28,8 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.hours import net.corda.core.utilities.hours
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.AbstractNode import net.corda.node.internal.AbstractNode
import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.api.StartedNodeServices
@ -43,9 +41,6 @@ import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.node.utilities.DefaultNamedCacheFactory import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
@ -69,7 +64,6 @@ import java.math.BigInteger
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock import java.time.Clock
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -149,7 +143,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs, val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
val networkParameters: NetworkParameters = testNetworkParameters(), val networkParameters: NetworkParameters = testNetworkParameters(),
val defaultFactory: (MockNodeArgs, CordappLoader?) -> MockNode = { args, cordappLoader -> cordappLoader?.let { MockNode(args, it) } ?: MockNode(args) }, val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) },
val cordappsForAllNodes: Set<TestCorDapp> = emptySet(), val cordappsForAllNodes: Set<TestCorDapp> = emptySet(),
val autoVisibleNodes: Boolean = true) : AutoCloseable { val autoVisibleNodes: Boolean = true) : AutoCloseable {
init { init {
@ -276,12 +270,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
} }
} }
open class MockNode(args: MockNodeArgs, cordappLoader: CordappLoader = JarScanningCordappLoader.fromDirectories(args.config.cordappDirectories, args.version)) : AbstractNode<TestStartedNode>( open class MockNode(args: MockNodeArgs) : AbstractNode<TestStartedNode>(
args.config, args.config,
TestClock(Clock.systemUTC()), TestClock(Clock.systemUTC()),
DefaultNamedCacheFactory(), DefaultNamedCacheFactory(),
args.version, args.version,
cordappLoader,
args.network.getServerThread(args.id), args.network.getServerThread(args.id),
args.network.busyLatch args.network.busyLatch
) { ) {
@ -424,27 +417,13 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
var acceptableLiveFiberCountOnStop: Int = 0 var acceptableLiveFiberCountOnStop: Int = 0
override fun acceptableLiveFiberCountOnStop(): Int = acceptableLiveFiberCountOnStop override fun acceptableLiveFiberCountOnStop(): Int = acceptableLiveFiberCountOnStop
override fun makeBFTCluster(notaryKey: PublicKey, bftSMaRtConfig: BFTSMaRtConfiguration): BFTSMaRt.Cluster {
return object : BFTSMaRt.Cluster {
override fun waitUntilAllReplicasHaveInitialized() {
val clusterNodes = mockNet.nodes.map { it.started!! }.filter { notaryKey in it.info.legalIdentities.map { it.owningKey } }
if (clusterNodes.size != bftSMaRtConfig.clusterAddresses.size) {
throw IllegalStateException("Unable to enumerate all nodes in BFT cluster.")
}
clusterNodes.forEach {
(it.notaryService as BFTNonValidatingNotaryService).waitUntilReplicaHasInitialized()
}
}
}
}
} }
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode { fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode {
return createUnstartedNode(parameters, defaultFactory) return createUnstartedNode(parameters, defaultFactory)
} }
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode): MockNode { fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> MockNode): MockNode {
return createNodeImpl(parameters, nodeFactory, false) return createNodeImpl(parameters, nodeFactory, false)
} }
@ -453,11 +432,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
} }
/** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */ /** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */
fun createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode): TestStartedNode { fun createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> MockNode): TestStartedNode {
return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!!
} }
private fun createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode, start: Boolean): MockNode { private fun createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs) -> MockNode, start: Boolean): MockNode {
val id = parameters.forcedID ?: nextNodeId++ val id = parameters.forcedID ?: nextNodeId++
val baseDirectory = baseDirectory(id) val baseDirectory = baseDirectory(id)
val certificatesDirectory = baseDirectory / "certificates" val certificatesDirectory = baseDirectory / "certificates"
@ -475,7 +454,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps) val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps)
doReturn(cordappDirectories).whenever(config).cordappDirectories doReturn(cordappDirectories).whenever(config).cordappDirectories
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version), JarScanningCordappLoader.fromDirectories(cordappDirectories, parameters.version)) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version))
_nodes += node _nodes += node
if (start) { if (start) {
node.start() node.start()
@ -483,7 +462,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
return node return node
} }
fun restartNode(node: TestStartedNode, nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode): TestStartedNode { fun restartNode(node: TestStartedNode, nodeFactory: (MockNodeArgs) -> MockNode): TestStartedNode {
node.internals.disableDBCloseOnStop() node.internals.disableDBCloseOnStop()
node.dispose() node.dispose()
return createNode( return createNode(

View File

@ -1,4 +1,4 @@
package net.corda.node.utilities package net.corda.testing.internal
import com.codahale.metrics.MetricRegistry import com.codahale.metrics.MetricRegistry
import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Cache
@ -9,6 +9,7 @@ import net.corda.core.internal.buildNamed
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.services.config.MB import net.corda.node.services.config.MB
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.NamedCacheFactory
class TestingNamedCacheFactory private constructor(private val sizeOverride: Long, private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() { class TestingNamedCacheFactory private constructor(private val sizeOverride: Long, private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() {
constructor(sizeOverride: Long = 1024) : this(sizeOverride, null, null) constructor(sizeOverride: Long = 1024) : this(sizeOverride, null, null)

View File

@ -153,7 +153,7 @@ private class DbManagementTool : CordaCliWrapper("database-manager", "The Corda
val nodeConfig = ConfigHelper.loadConfig(baseDirectory, config).parseAs<NodeConfigurationImpl>(UnknownConfigKeysPolicy.IGNORE::handle) val nodeConfig = ConfigHelper.loadConfig(baseDirectory, config).parseAs<NodeConfigurationImpl>(UnknownConfigKeysPolicy.IGNORE::handle)
val cordappLoader = JarScanningCordappLoader.fromDirectories(setOf(baseDirectory, baseDirectory / "cordapps")) val cordappLoader = JarScanningCordappLoader.fromDirectories(setOf(baseDirectory, baseDirectory / "cordapps"))
val schemaService = NodeSchemaService(extraSchemas = cordappLoader.cordappSchemas, includeNotarySchemas = nodeConfig.notary != null) val schemaService = NodeSchemaService(extraSchemas = cordappLoader.cordappSchemas)
handleCommand(baseDirectory, config, cmdLineOptions.mode, cordappLoader.appClassLoader, schemaService.schemaOptions.keys) handleCommand(baseDirectory, config, cmdLineOptions.mode, cordappLoader.appClassLoader, schemaService.schemaOptions.keys)
} }

View File

@ -15,6 +15,7 @@ dependencies {
cordaCompile project(':core') cordaCompile project(':core')
cordaCompile project(':client:rpc') cordaCompile project(':client:rpc')
cordaCompile project(':node-driver') cordaCompile project(':node-driver')
compile project(':notary:mysql')
compile project(':client:mock') compile project(':client:mock')
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
compile group: 'io.dropwizard.metrics', name: 'metrics-graphite', version: '3.2.5' compile group: 'io.dropwizard.metrics', name: 'metrics-graphite', version: '3.2.5'

View File

@ -13,9 +13,9 @@ import net.corda.core.node.services.CordaService
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.MySQLConfiguration import net.corda.node.services.config.MySQLConfiguration
import net.corda.node.services.transactions.MySQLUniquenessProvider
import net.corda.node.services.transactions.NonValidatingNotaryFlow import net.corda.node.services.transactions.NonValidatingNotaryFlow
import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.parseAs
import net.corda.notary.mysql.MySQLUniquenessProvider
import net.corda.notarytest.flows.AsyncLoadTestFlow import net.corda.notarytest.flows.AsyncLoadTestFlow
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress