CORDA-535: Extract notary implementations into CorDapps (#3978)

* Move Raft and BFT notaries into separate modules

* Move schemas

* Fix tests & demos

* Modified logic for creating notary services:

Added a new field 'className' to the notary configuration. The node now
loads the specified implementation via reflection. The default className
value points to the simple notary implementation for backwards compatibility.
Relevant schemas are loaded in a similar fashion.

For backwards compatibility purposes the default SimpleNotaryService will
remain built-in to node, but its cordapp will be generated on startup – so
the loading of notary services is streamlined.

* Move test namedcache factory to test utils
This commit is contained in:
Andrius Dagys 2018-10-10 10:04:22 +01:00 committed by GitHub
parent b6f2532ce6
commit 9ebeac1ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 637 additions and 362 deletions

6
.idea/compiler.xml generated
View File

@ -157,8 +157,12 @@
<module name="node_main" target="1.8" />
<module name="node_smokeTest" 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_test" target="1.8" />
<module name="notary-raft_main" target="1.8" />
<module name="notary-raft_test" target="1.8" />
<module name="publish-utils_main" target="1.8" />
<module name="publish-utils_test" target="1.8" />
<module name="quasar-hook_main" target="1.8" />
@ -235,4 +239,4 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>
</project>

View File

@ -361,7 +361,9 @@ bintrayConfig {
'corda-tools-blob-inspector',
'corda-tools-explorer',
'corda-tools-network-bootstrapper',
'corda-tools-cliutils'
'corda-tools-cliutils',
'corda-notary-raft',
'corda-notary-bft-smart'
]
license {
name = 'Apache-2.0'

View File

@ -4,6 +4,7 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.notary.NotaryService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
@ -48,4 +49,5 @@ interface Cordapp {
val jarPath: URL
val cordappClasses: List<String>
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.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.toPath
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
@ -25,7 +26,10 @@ data class CordappImpl(
override val allFlows: List<Class<out FlowLogic<*>>>,
override val jarPath: URL,
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)
companion object {
@ -37,7 +41,10 @@ data class CordappImpl(
*
* 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?
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 }
}
}
}

View File

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

View File

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

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.client.netty.NettyClientServerCommunicationSystemClientSide
@ -34,10 +34,11 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.BFTSMaRt.Client
import net.corda.node.services.transactions.BFTSMaRt.Replica
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AppendOnlyPersistentMap
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.security.PublicKey
import java.util.*
@ -78,7 +79,7 @@ object BFTSMaRt {
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 {
private val log = contextLogger()
}
@ -178,7 +179,7 @@ object BFTSMaRt {
abstract class Replica(config: BFTSMaRtConfig,
replicaId: Int,
createMap: () -> AppendOnlyPersistentMap<StateRef, SecureHash,
BFTNonValidatingNotaryService.CommittedState, PersistentStateRef>,
BftSmartNotaryService.CommittedState, PersistentStateRef>,
protected val services: ServiceHubInternal,
protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() {
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.writer
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.transactions.PathManager
import java.io.PrintWriter
import java.net.InetAddress
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 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.NotaryService
import net.corda.core.internal.notary.verifySignature
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize
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.node.services.api.ServiceHubInternal
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
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.
*/
class BFTNonValidatingNotaryService(
class BftSmartNotaryService(
override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey,
private val bftSMaRtConfig: BFTSMaRtConfiguration,
cluster: BFTSMaRt.Cluster
override val notaryIdentityKey: PublicKey
) : NotaryService() {
companion object {
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 replicaHolder = SettableFuture.create<Replica>()
@ -71,7 +87,7 @@ class BFTNonValidatingNotaryService(
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
override fun call(): Void? {
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.whenever
@ -23,8 +23,6 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.services.config.BFTSMaRtConfiguration
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.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
@ -84,7 +82,11 @@ class BFTNotaryServiceTests {
val nodes = replicaIds.map { replicaId ->
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
}))
} + 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.node.services.transactions.BFTSMaRtConfig.Companion.portIsClaimedFormat
import net.corda.notary.bftsmart.BFTSMaRtConfig.Companion.portIsClaimedFormat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
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.BufferOutput
@ -27,6 +27,7 @@ import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence
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(
consumingTxHash = commitCommand.txId.toString(),
partyName = commitCommand.requestingParty,
@ -192,8 +193,8 @@ class RaftTransactionCommitLog<E, EK>(
registerAbstract(SecureHash::class.java, CordaKryoSerializer::class.java)
registerAbstract(TimeWindow::class.java, CordaKryoSerializer::class.java)
registerAbstract(NotaryError::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.CommitTransaction::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.Get::class.java, CordaKryoSerializer::class.java)
register(Commands.CommitTransaction::class.java, CordaKryoSerializer::class.java)
register(Commands.Get::class.java, CordaKryoSerializer::class.java)
register(StateRef::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.MetricRegistry
@ -26,12 +26,12 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
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.NamedCacheFactory
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.notary.raft.RaftTransactionCommitLog.Commands.CommitTransaction
import java.nio.file.Path
import java.time.Clock
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.StateRef

View File

@ -1,4 +1,4 @@
package net.corda.node.services.transactions
package net.corda.notary.raft
import io.atomix.catalyst.transport.Address
import io.atomix.copycat.client.ConnectionStrategies
@ -16,7 +16,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
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.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
@ -154,7 +154,7 @@ class RaftTransactionCommitLogTests {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(extraSchemas = setOf(RaftNotarySchemaV1)))
databases.add(database)
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) }

View File

@ -153,18 +153,9 @@ dependencies {
// We only need this dependency to compile our Caplet against.
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.
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.
compile "org.apache.shiro:shiro-core:${shiro_version}"

View File

@ -42,7 +42,7 @@ class DistributedServiceTests {
invokeRpc(CordaRPCOps::stateMachinesFeed))
)
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(
NotarySpec(
DUMMY_NOTARY_NAME,

View File

@ -14,7 +14,6 @@ import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider
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.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
@ -26,6 +25,7 @@ import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.internal.MOCK_VERSION_INFO
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.assertThatThrownBy

View File

@ -5,11 +5,11 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase
import net.corda.node.internal.schemas.NodeInfoSchemaV1
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.persistence.DatabaseConfig
import net.corda.testing.core.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.internal.TestingNamedCacheFactory
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.junit.After

View File

@ -40,6 +40,7 @@ class P2PMessagingTest {
private fun startDriverWithDistributedService(dsl: DriverDSL.(List<InProcess>) -> Unit) {
driver(DriverParameters(
startNodesInProcess = true,
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) })

View File

@ -34,9 +34,7 @@ import net.corda.node.CordaClock
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.cordapp.*
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy
@ -115,7 +113,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val platformClock: CordaClock,
cacheFactoryPrototype: NamedCacheFactory,
protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
@ -141,7 +138,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 database: CordaPersistence = createCordaPersistence(
configuration.database,
@ -498,6 +496,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 fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) {
@ -780,17 +796,32 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
private fun makeNotaryService(myNotaryIdentity: PartyAndCertificate?): NotaryService? {
return configuration.notary?.let {
makeCoreNotaryService(it, myNotaryIdentity).also {
it.tokenize()
runOnStop += it::stop
installCoreFlow(NotaryFlow.Client::class, it::createServiceFlow)
log.info("Running core notary: ${it.javaClass.name}")
it.start()
return configuration.notary?.let { notaryConfig ->
val serviceClass = getNotaryServiceClass(notaryConfig.className)
log.info("Starting notary service: $serviceClass")
val notaryKey = myNotaryIdentity?.owningKey
?: throw IllegalArgumentException("Unable to start notary service $serviceClass: notary identity not found")
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 {
// 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
@ -798,32 +829,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
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))
}
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() {
// 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()

View File

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

View File

@ -21,7 +21,6 @@ import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.shouldStartLocalShell
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.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationException
@ -256,7 +255,8 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
}
val nodeInfo = node.start()
logLoadedCorDapps(node.services.cordappProvider.cordapps)
val loadedCodapps = node.services.cordappProvider.cordapps.filter { it.isLoaded }
logLoadedCorDapps(loadedCodapps)
node.nodeReadyFuture.thenMatch({
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
@ -411,6 +411,16 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
}
/** 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 {
return VersionInfo(
PLATFORM_VERSION,

View File

@ -9,6 +9,8 @@ import net.corda.core.flows.*
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappInfoResolver
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.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
@ -36,9 +38,12 @@ import kotlin.streams.toList
* @property cordappJarPaths The classpath of cordapp JARs
*/
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)
@ -58,9 +63,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param corDappDirectories Directories used to scan for CorDapp JARs.
*/
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
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 +74,12 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList()): JarScanningCordappLoader {
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> {
@ -85,32 +92,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> {
val cordapps = cordappJarPaths
.map { scanCordapp(it).toCordapp(it) }
@ -126,7 +108,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
return cordapps
}
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 }
return CordappImpl(
@ -142,10 +123,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
findAllFlows(this),
url.url,
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)
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 findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
@ -202,7 +194,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
}
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)
@ -243,18 +235,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
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) {
fun getNamesOfClassesImplementing(type: KClass<*>): List<String> {
return scanResult.getNamesOfClassesImplementing(type.java)
.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)
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() }
}
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

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

View File

@ -16,9 +16,7 @@ import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
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.vault.VaultSchemaV1
@ -29,7 +27,7 @@ import net.corda.node.services.vault.VaultSchemaV1
* 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
*/
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
object NodeCore
@ -47,26 +45,12 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
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
private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> =
mapOf(Pair(CommonSchemaV1, SchemaOptions()),
Pair(VaultSchemaV1, SchemaOptions()),
Pair(NodeInfoSchemaV1, SchemaOptions()),
Pair(NodeCoreV1, SchemaOptions())) +
if (includeNotarySchemas) mapOf(Pair(NodeNotaryV1, SchemaOptions())) else emptyMap()
Pair(NodeCoreV1, SchemaOptions()))
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" }

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.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.schemas.MappedSchema
import net.corda.node.services.api.ServiceHubInternal
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() {
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 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 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)"/>
</createTable>
</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">
<createTable tableName="node_notary_request_log">
<column name="id" type="INT">
@ -58,18 +46,11 @@
</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="1511451595465-41">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_notary_states_pkey"
tableName="node_notary_committed_states"/>
</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"/>

View File

@ -13,9 +13,4 @@
<addPrimaryKey tableName="node_bft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_bft_states_pkey" clustered="false"/>
</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>

View File

@ -7,10 +7,6 @@
<changeSet author="R3.Corda" id="nullability">
<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_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

@ -47,7 +47,7 @@ class JarScanningCordappLoaderTest {
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
val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get(".")))
assertThat(loader.cordapps).containsOnly(loader.coreCordapp)
assertThat(loader.cordapps).isEmpty()
}
@Test
@ -55,9 +55,9 @@ class JarScanningCordappLoaderTest {
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
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.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor")
assertThat(actualCordapp.rpcFlows).isEmpty()
@ -73,8 +73,8 @@ class JarScanningCordappLoaderTest {
val loader = cordappLoaderForPackages(listOf(testScanPackage))
val actual = loader.cordapps.toTypedArray()
// One core cordapp, one cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(actual.size == 2 || actual.size == 3).isTrue()
// One cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(actual.size == 0 || actual.size == 1).isTrue()
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() }
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`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!
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.minimumPlatformVersion).isEqualTo(1)
}
@ -123,8 +123,7 @@ class JarScanningCordappLoaderTest {
// make sure classloader extracts correct values
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
// exclude the core cordapp
val cordapp = loader.cordapps.single { it.cordappClasses.contains("net.corda.core.internal.cordapp.CordappImpl") }
val cordapp = loader.cordapps.first()
assertThat(cordapp.info.targetPlatformVersion).isEqualTo(3)
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`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1))
// exclude the core cordapp
assertThat(loader.cordapps).hasSize(1)
assertThat(loader.cordapps).hasSize(0)
}
@Test
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1000))
// exclude the core cordapp
assertThat(loader.cordapps).hasSize(2)
assertThat(loader.cordapps).hasSize(1)
}
@Test
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
// exclude the core cordapp
assertThat(loader.cordapps).hasSize(2)
assertThat(loader.cordapps).hasSize(1)
}
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {

View File

@ -313,20 +313,11 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// of gets and puts.
private fun makeNodeWithTracking(name: CordaX500Name): TestStartedNode {
// Create a node in the mock network ...
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, cordappLoader ->
if (cordappLoader != null) {
object : InternalMockNetwork.MockNode(args, cordappLoader) {
// That constructs a recording tx storage
override fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
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))
}
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args ->
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
}
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)

View File

@ -22,6 +22,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
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.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
@ -90,6 +91,7 @@ class TimedFlowTests {
whenever(it.custom).thenReturn(true)
whenever(it.isClusterConfig).thenReturn(true)
whenever(it.validating).thenReturn(true)
whenever(it.className).thenReturn(TestNotaryService::class.java.name)
}
val notaryNodes = (0 until CLUSTER_SIZE).map {
@ -176,8 +178,7 @@ class TimedFlowTests {
}.bufferUntilSubscribed().toBlocking().toFuture()
}
@CordaService
private class TestNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
private class TestNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = mock<UniquenessProvider>()
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = TestNotaryFlow(otherPartySession, this)
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.node.services.UnknownAnonymousPartyException
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.X509Utilities
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.services.schema.NodeSchemaService
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.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.After

View File

@ -9,7 +9,7 @@ import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.configureDatabase
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.DatabaseConfig
import net.corda.testing.core.*
@ -154,7 +154,8 @@ class DBTransactionStorageTests {
}
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) {
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride ?: 1024))
transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride
?: 1024))
}
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.node.services.identity.PersistentIdentityService
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.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockNetwork

View File

@ -15,7 +15,7 @@ import net.corda.core.node.services.vault.Sort
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
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.DatabaseConfig
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.node.services.api.ServiceHubInternal
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.driver
import net.corda.testing.driver.internal.InProcessImpl
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.cordappsForPackages
import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
import org.junit.Ignore
import org.junit.Test
import javax.persistence.*
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NodeSchemaServiceTest {
@ -47,7 +45,6 @@ class NodeSchemaServiceTest {
// check against NodeCore schemas
assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1))
assertFalse(schemaService.schemaOptions.containsKey(NodeNotaryV1))
mockNet.stopNodes()
}
@ -57,9 +54,8 @@ class NodeSchemaServiceTest {
val mockNotaryNode = mockNet.notaryNodes.first()
val schemaService = mockNotaryNode.services.schemaService
// check against NodeCore + NodeNotary Schemas
// check against NodeCore Schema
assertTrue(schemaService.schemaOptions.containsKey(NodeCoreV1))
assertTrue(schemaService.schemaOptions.containsKey(NodeNotaryV1))
mockNet.stopNodes()
}
@ -97,7 +93,6 @@ class NodeSchemaServiceTest {
val mappedSchemas = result.returnValue.getOrThrow()
// check against NodeCore schemas
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)) {
val notary = defaultNotaryNode.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(NodeNotaryV1.name))
}
}

View File

@ -37,7 +37,7 @@ class MaxTransactionSizeTests {
@Before
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))
bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
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.node.internal.configureDatabase
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.DatabaseConfig
import net.corda.testing.core.SerializationEnvironmentRule
@ -39,7 +39,7 @@ class PersistentUniquenessProviderTests {
@Before
fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(extraSchemas = setOf(NodeNotarySchemaV1)))
}
@After

View File

@ -82,7 +82,7 @@ class VaultSoftLockManagerTest {
private val mockVault = rigorousMock<VaultServiceInternal>().also {
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) {
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, database: CordaPersistence): VaultServiceInternal {
val node = this

View File

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

View File

@ -23,6 +23,8 @@ include 'experimental:behave'
include 'experimental:quasar-hook'
include 'experimental:kryo-hook'
include 'experimental:corda-utils'
include 'experimental:notary-raft'
include 'experimental:notary-bft-smart'
include 'jdk8u-deterministic'
include 'test-common'
include 'test-cli'

View File

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

View File

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

View File

@ -28,14 +28,15 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.hours
import net.corda.core.utilities.seconds
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.AbstractNode
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.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.*
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.VerifierType
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
@ -43,8 +44,6 @@ import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.persistence.NodeAttachmentService
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.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.nodeapi.internal.DevIdentityGenerator
@ -69,7 +68,6 @@ import java.math.BigInteger
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
@ -149,7 +147,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
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 autoVisibleNodes: Boolean = true) : AutoCloseable {
init {
@ -276,12 +274,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,
TestClock(Clock.systemUTC()),
DefaultNamedCacheFactory(),
args.version,
cordappLoader,
args.network.getServerThread(args.id),
args.network.busyLatch
) {
@ -424,27 +421,13 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
var acceptableLiveFiberCountOnStop: Int = 0
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 {
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)
}
@ -453,11 +436,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
}
/** 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)!!
}
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 baseDirectory = baseDirectory(id)
val certificatesDirectory = baseDirectory / "certificates"
@ -474,7 +457,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps)
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
if (start) {
node.start()
@ -482,7 +465,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
return node
}
fun restartNode(node: TestStartedNode, nodeFactory: (MockNodeArgs, CordappLoader?) -> MockNode): TestStartedNode {
fun restartNode(node: TestStartedNode, nodeFactory: (MockNodeArgs) -> MockNode): TestStartedNode {
node.internals.disableDBCloseOnStop()
node.dispose()
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.github.benmanes.caffeine.cache.Cache
@ -9,6 +9,7 @@ import net.corda.core.internal.buildNamed
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.services.config.MB
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() {
constructor(sizeOverride: Long = 1024) : this(sizeOverride, null, null)