CORDA-535: Remove the old mechanism for loading custom notary service implementations.

All notary service implementations are now assumed to be loaded from CorDapps.
This commit is contained in:
Andrius Dagys
2018-10-10 13:31:29 +01:00
parent 554b1fa371
commit b8b2cc772d
6 changed files with 32 additions and 72 deletions

View File

@ -4,13 +4,12 @@ Writing a custom notary service (experimental)
============================================== ==============================================
.. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. The APIs .. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. The APIs
for writing a custom notary may change in the future. Additionally, customising Raft or BFT notaries is not yet for writing a custom notary may change in the future.
fully supported. If you want to write your own Raft notary you will have to implement a custom database connector
(or use a separate database for the notary), and use a custom configuration file.
Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it The first step is to create a service class in your CorDapp that extends the ``NotaryService`` abstract class.
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary This will ensure that it is recognised as a notary service.
service class should provide a constructor with two parameters of types ``AppServiceHub`` and ``PublicKey``. The custom notary service class should provide a constructor with two parameters of types ``ServiceHubInternal`` and ``PublicKey``.
Note that ``ServiceHubInternal`` does not provide any API stability guarantees.
.. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt .. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
:language: kotlin :language: kotlin
@ -32,5 +31,5 @@ To enable the service, add the following to the node configuration:
notary : { notary : {
validating : true # Set to false if your service is non-validating validating : true # Set to false if your service is non-validating
custom : true className : "net.corda.notarydemo.MyCustomValidatingNotaryService" # The fully qualified name of your service class
} }

View File

@ -42,9 +42,12 @@ import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.* import net.corda.node.services.api.*
import net.corda.node.services.config.* import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.config.rpc.NodeRpcOptions import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.toShellConfig import net.corda.node.services.config.shell.toShellConfig
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
@ -59,7 +62,8 @@ import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.* import net.corda.node.services.persistence.*
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.* import net.corda.node.services.statemachine.*
import net.corda.node.services.transactions.* import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.* import net.corda.node.utilities.*
@ -518,9 +522,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) { private fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) {
val loadedServices = cordappLoader.cordapps.flatMap { it.services } val loadedServices = cordappLoader.cordapps.flatMap { it.services }
filterServicesToInstall(loadedServices).forEach { loadedServices.forEach {
try { try {
installCordaService(flowStarter, it, myNotaryIdentity) installCordaService(flowStarter, it)
} catch (e: NoSuchMethodException) { } catch (e: NoSuchMethodException) {
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name) ServiceHub::class.java.name)
@ -532,24 +536,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
} }
private fun filterServicesToInstall(loadedServices: List<Class<out SerializeAsToken>>): List<Class<out SerializeAsToken>> {
val customNotaryServiceList = loadedServices.filter { isNotaryService(it) }
if (customNotaryServiceList.isNotEmpty()) {
if (configuration.notary?.custom == true) {
require(customNotaryServiceList.size == 1) {
"Attempting to install more than one notary service: ${customNotaryServiceList.joinToString()}"
}
} else return loadedServices - customNotaryServiceList
}
return loadedServices
}
/**
* If the [serviceClass] is a notary service, it will only be enabled if the "custom" flag is set in
* the notary configuration.
*/
private fun isNotaryService(serviceClass: Class<*>) = NotaryService::class.java.isAssignableFrom(serviceClass)
/** /**
* This customizes the ServiceHub for each CordaService that is initiating flows. * This customizes the ServiceHub for each CordaService that is initiating flows.
*/ */
@ -590,53 +576,30 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance)
} }
private fun <T : SerializeAsToken> installCordaService(flowStarter: FlowStarter, serviceClass: Class<T>, myNotaryIdentity: PartyAndCertificate?) { private fun <T : SerializeAsToken> installCordaService(flowStarter: FlowStarter, serviceClass: Class<T>) {
serviceClass.requireAnnotation<CordaService>() serviceClass.requireAnnotation<CordaService>()
val service = try { val service = try {
if (isNotaryService(serviceClass)) { val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
myNotaryIdentity ?: throw IllegalStateException("Trying to install a notary service but no notary identity specified") val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
try { val service = extendedServiceConstructor.newInstance(serviceContext)
val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java).apply { isAccessible = true } serviceContext.serviceInstance = service
constructor.newInstance(services, myNotaryIdentity.owningKey ) service
} catch (ex: NoSuchMethodException) { } catch (ex: NoSuchMethodException) {
val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true } val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
val serviceContext = AppServiceHubImpl<T>(services, flowStarter) log.warn("${serviceClass.name} is using legacy CordaService constructor with ServiceHub parameter. " +
val service = constructor.newInstance(serviceContext, myNotaryIdentity.owningKey) "Upgrade to an AppServiceHub parameter to enable updated API features.")
serviceContext.serviceInstance = service constructor.newInstance(services)
service
}
} else {
try {
val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
val service = extendedServiceConstructor.newInstance(serviceContext)
serviceContext.serviceInstance = service
service
} catch (ex: NoSuchMethodException) {
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
log.warn("${serviceClass.name} is using legacy CordaService constructor with ServiceHub parameter. " +
"Upgrade to an AppServiceHub parameter to enable updated API features.")
constructor.newInstance(services)
}
}
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
throw ServiceInstantiationException(e.cause) throw ServiceInstantiationException(e.cause)
} }
cordappServices.putInstance(serviceClass, service) cordappServices.putInstance(serviceClass, service)
if (service is NotaryService) handleCustomNotaryService(service)
service.tokenize() service.tokenize()
log.info("Installed ${serviceClass.name} Corda service") log.info("Installed ${serviceClass.name} Corda service")
} }
private fun handleCustomNotaryService(service: NotaryService) {
runOnStop += service::stop
installCoreFlow(NotaryFlow.Client::class, service::createServiceFlow)
service.start()
}
private fun registerCordappFlows() { private fun registerCordappFlows() {
cordappLoader.cordapps.flatMap { it.initiatedFlows } cordappLoader.cordapps.flatMap { it.initiatedFlows }
.forEach { .forEach {

View File

@ -122,13 +122,12 @@ fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || should
data class NotaryConfig(val validating: Boolean, data class NotaryConfig(val validating: Boolean,
val raft: RaftConfig? = null, val raft: RaftConfig? = null,
val bftSMaRt: BFTSMaRtConfiguration? = 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" val className: String = "net.corda.node.services.transactions.SimpleNotaryService"
) { ) {
init { init {
require(raft == null || bftSMaRt == null || !custom) { require(raft == null || bftSMaRt == null) {
"raft, bftSMaRt, and custom configs cannot be specified together" "raft and bftSMaRt configs cannot be specified together"
} }
} }

View File

@ -88,7 +88,6 @@ class TimedFlowTests {
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.isClusterConfig).thenReturn(true) whenever(it.isClusterConfig).thenReturn(true)
whenever(it.validating).thenReturn(true) whenever(it.validating).thenReturn(true)
whenever(it.className).thenReturn(TestNotaryService::class.java.name) whenever(it.className).thenReturn(TestNotaryService::class.java.name)

View File

@ -76,7 +76,10 @@ task deployNodesCustom(type: Cordform, dependsOn: 'jar') {
address "localhost:10010" address "localhost:10010"
adminAddress "localhost:10110" adminAddress "localhost:10110"
} }
notary = [validating: true, "custom": true] notary = [
validating: true,
className: "net.corda.notarydemo.MyCustomValidatingNotaryService"
]
} }
} }

View File

@ -8,8 +8,6 @@ import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.notary.NotaryInternalException import net.corda.core.internal.notary.NotaryInternalException
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.node.AppServiceHub
import net.corda.core.node.services.CordaService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.TransactionWithSignatures
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -19,13 +17,12 @@ import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
/** /**
* A custom notary service should provide a constructor that accepts two parameters of types [AppServiceHub] and [PublicKey]. * A custom notary service should provide a constructor that accepts two parameters of types [ServiceHubInternal] and [PublicKey].
* *
* Note that the support for custom notaries is still experimental at present only a single-node notary service can be customised. * Note that the support for custom notaries is still experimental at present only a single-node notary service can be customised.
* The notary-related APIs might change in the future. * The notary-related APIs might change in the future.
*/ */
// START 1 // START 1
@CordaService
class MyCustomValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { class MyCustomValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory) override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)