mirror of
https://github.com/corda/corda.git
synced 2024-12-24 15:16:45 +00:00
Fixed AbstractNode to load custom notary services properly (#1720)
* Fixed AbstractNode to load custom notary services properly. Added a custom notary sample. * Prevent multiple custom notaries from being loaded * Throw if more than once custom notary service is loaded
This commit is contained in:
parent
544b761682
commit
7b10e92819
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.node.services
|
package net.corda.core.node.services
|
||||||
|
|
||||||
|
import com.google.common.primitives.Booleans
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
@ -15,12 +16,13 @@ import java.security.PublicKey
|
|||||||
abstract class NotaryService : SingletonSerializeAsToken() {
|
abstract class NotaryService : SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
const val ID_PREFIX = "corda.notary."
|
const val ID_PREFIX = "corda.notary."
|
||||||
fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false): String {
|
fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false, custom: Boolean = false): String {
|
||||||
require(!raft || !bft)
|
require(Booleans.countTrue(raft, bft, custom) <= 1) { "At most one of raft, bft or custom may be true" }
|
||||||
return StringBuffer(ID_PREFIX).apply {
|
return StringBuffer(ID_PREFIX).apply {
|
||||||
append(if (validating) "validating" else "simple")
|
append(if (validating) "validating" else "simple")
|
||||||
if (raft) append(".raft")
|
if (raft) append(".raft")
|
||||||
if (bft) append(".bft")
|
if (bft) append(".bft")
|
||||||
|
if (custom) append(".custom")
|
||||||
}.toString()
|
}.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ path to the node's base directory.
|
|||||||
.. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field
|
.. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field
|
||||||
is present the web server will start.
|
is present the web server will start.
|
||||||
|
|
||||||
:notary: Optional config object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
|
:notary: Optional configuration object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
|
||||||
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
|
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
|
||||||
|
|
||||||
:validating: Boolean to determine whether the notary is a validating or non-validating one.
|
:validating: Boolean to determine whether the notary is a validating or non-validating one.
|
||||||
@ -108,11 +108,15 @@ path to the node's base directory.
|
|||||||
members must be active and be able to communicate with the cluster leader for joining. If empty, a new
|
members must be active and be able to communicate with the cluster leader for joining. If empty, a new
|
||||||
cluster will be bootstrapped.
|
cluster will be bootstrapped.
|
||||||
|
|
||||||
:bftSMaRt: If part of a distributed BFT SMaRt cluster specify this config object, with the following settings:
|
:bftSMaRt: If part of a distributed BFT-SMaRt cluster specify this config object, with the following settings:
|
||||||
|
|
||||||
:replicaId:
|
:replicaId: The zero-based index of the current replica. All replicas must specify a unique replica id.
|
||||||
|
|
||||||
:clusterAddresses:
|
:clusterAddresses: List of all BFT-SMaRt cluster member addresses.
|
||||||
|
|
||||||
|
:custom: If `true`, will load and install a notary service from a CorDapp. See :doc:`tutorial-custom-notary`.
|
||||||
|
|
||||||
|
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
|
||||||
|
|
||||||
:networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is
|
:networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is
|
||||||
a config object with the details of the network map service:
|
a config object with the details of the network map service:
|
||||||
|
@ -112,8 +112,11 @@ Notary demo
|
|||||||
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
|
This demo shows a party getting transactions notarised by either a single-node or a distributed notary service.
|
||||||
All versions of the demo start two counterparty nodes.
|
All versions of the demo start two counterparty nodes.
|
||||||
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
|
One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation.
|
||||||
The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
|
|
||||||
The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
|
* The `Raft <https://raft.github.io/>`_ version of the demo will start three distributed notary nodes.
|
||||||
|
* The `BFT SMaRt <https://bft-smart.github.io/library/>`_ version of the demo will start four distributed notary nodes.
|
||||||
|
* The Single version of the demo will start a single-node validating notary service.
|
||||||
|
* The Custom version of the demo will load and start a custom single-node notary service that is defined the demo CorDapp.
|
||||||
|
|
||||||
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
|
The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary,
|
||||||
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
|
every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement.
|
||||||
@ -122,9 +125,9 @@ You will notice that successive transactions get signed by different members of
|
|||||||
|
|
||||||
To run the Raft version of the demo from the command line in Unix:
|
To run the Raft version of the demo from the command line in Unix:
|
||||||
|
|
||||||
1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories
|
1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create node directories for all versions of the demo,
|
||||||
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and
|
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
|
||||||
Single notaries).
|
BFT, Single and Custom notaries respectively).
|
||||||
2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||||
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||||
@ -133,8 +136,8 @@ To run the Raft version of the demo from the command line in Unix:
|
|||||||
To run from the command line in Windows:
|
To run from the command line in Windows:
|
||||||
|
|
||||||
1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories
|
1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories
|
||||||
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and
|
with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for
|
||||||
Single notaries).
|
BFT, Single and Custom notaries respectively).
|
||||||
2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs.
|
2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||||
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
Wait until a "Node started up and registered in ..." message appears on each of the terminals
|
||||||
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests
|
||||||
@ -142,6 +145,7 @@ To run from the command line in Windows:
|
|||||||
|
|
||||||
To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes
|
To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes
|
||||||
trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``.
|
trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``.
|
||||||
|
For the custom notary service use ``nodesCustom`.
|
||||||
|
|
||||||
Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
|
Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
|
||||||
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores
|
You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
.. highlight:: kotlin
|
.. highlight:: kotlin
|
||||||
|
|
||||||
Writing a custom notary service
|
Writing a custom notary service (experimental)
|
||||||
===============================
|
==============================================
|
||||||
|
|
||||||
.. warning:: Customising a notary service is an advanced feature and not recommended for most use-cases. Currently,
|
.. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. Currently,
|
||||||
customising Raft or BFT notaries is not yet fully supported. If you want to write your own Raft notary you will have to
|
customising Raft or BFT notaries is not yet 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.
|
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
|
Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it
|
||||||
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The only requirement
|
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary
|
||||||
is that the class provide a constructor with a single parameter of type ``AppServiceHub``.
|
service class should provide a constructor with two parameters of types ``AppServiceHub`` and ``PublicKey``.
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt
|
.. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
|
||||||
:language: kotlin
|
:language: kotlin
|
||||||
:start-after: START 1
|
:start-after: START 1
|
||||||
:end-before: END 1
|
:end-before: END 1
|
||||||
@ -20,7 +20,16 @@ The next step is to write a notary service flow. You are free to copy and modify
|
|||||||
as ``ValidatingNotaryFlow``, ``NonValidatingNotaryFlow``, or implement your own from scratch (following the
|
as ``ValidatingNotaryFlow``, ``NonValidatingNotaryFlow``, or implement your own from scratch (following the
|
||||||
``NotaryFlow.Service`` template). Below is an example of a custom flow for a *validating* notary service:
|
``NotaryFlow.Service`` template). Below is an example of a custom flow for a *validating* notary service:
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt
|
.. literalinclude:: ../../samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
|
||||||
:language: kotlin
|
:language: kotlin
|
||||||
:start-after: START 2
|
:start-after: START 2
|
||||||
:end-before: END 2
|
:end-before: END 2
|
||||||
|
|
||||||
|
To enable the service, add the following to the node configuration:
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
notary : {
|
||||||
|
validating : true # Set to false if your service is non-validating
|
||||||
|
custom : true
|
||||||
|
}
|
@ -16,6 +16,7 @@ Tutorials
|
|||||||
flow-testing
|
flow-testing
|
||||||
running-a-notary
|
running-a-notary
|
||||||
oracles
|
oracles
|
||||||
|
tutorial-custom-notary
|
||||||
tutorial-tear-offs
|
tutorial-tear-offs
|
||||||
tutorial-attachments
|
tutorial-attachments
|
||||||
event-scheduling
|
event-scheduling
|
@ -19,14 +19,11 @@ import net.corda.core.internal.concurrent.flatMap
|
|||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.core.messaging.RPCOps
|
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
|
||||||
import net.corda.core.node.*
|
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.node.AppServiceHub
|
import net.corda.core.node.AppServiceHub
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.node.StateLoader
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
@ -257,7 +254,8 @@ abstract class AbstractNode(config: NodeConfiguration,
|
|||||||
private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause)
|
private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause)
|
||||||
|
|
||||||
private fun installCordaServices() {
|
private fun installCordaServices() {
|
||||||
cordappProvider.cordapps.flatMap { it.services }.forEach {
|
val loadedServices = cordappProvider.cordapps.flatMap { it.services }
|
||||||
|
filterServicesToInstall(loadedServices).forEach {
|
||||||
try {
|
try {
|
||||||
installCordaService(it)
|
installCordaService(it)
|
||||||
} catch (e: NoSuchMethodException) {
|
} catch (e: NoSuchMethodException) {
|
||||||
@ -271,6 +269,25 @@ abstract class AbstractNode(config: 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 enable 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
|
||||||
*/
|
*/
|
||||||
@ -321,14 +338,15 @@ abstract class AbstractNode(config: NodeConfiguration,
|
|||||||
fun <T : SerializeAsToken> installCordaService(serviceClass: Class<T>): T {
|
fun <T : SerializeAsToken> installCordaService(serviceClass: Class<T>): T {
|
||||||
serviceClass.requireAnnotation<CordaService>()
|
serviceClass.requireAnnotation<CordaService>()
|
||||||
val service = try {
|
val service = try {
|
||||||
if (NotaryService::class.java.isAssignableFrom(serviceClass)) {
|
val serviceContext = AppServiceHubImpl<T>(services)
|
||||||
|
if (isNotaryService(serviceClass)) {
|
||||||
check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" }
|
check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" }
|
||||||
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
|
val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
|
||||||
constructor.newInstance(services, myNotaryIdentity!!.owningKey)
|
serviceContext.serviceInstance = constructor.newInstance(serviceContext, myNotaryIdentity!!.owningKey)
|
||||||
|
serviceContext.serviceInstance
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
|
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
|
||||||
val serviceContext = AppServiceHubImpl<T>(services)
|
|
||||||
serviceContext.serviceInstance = extendedServiceConstructor.newInstance(serviceContext)
|
serviceContext.serviceInstance = extendedServiceConstructor.newInstance(serviceContext)
|
||||||
serviceContext.serviceInstance
|
serviceContext.serviceInstance
|
||||||
} catch (ex: NoSuchMethodException) {
|
} catch (ex: NoSuchMethodException) {
|
||||||
@ -688,7 +706,9 @@ abstract class AbstractNode(config: NodeConfiguration,
|
|||||||
// Node's main identity
|
// Node's main identity
|
||||||
Pair("identity", myLegalName)
|
Pair("identity", myLegalName)
|
||||||
} else {
|
} else {
|
||||||
val notaryId = notaryConfig.run { NotaryService.constructId(validating, raft != null, bftSMaRt != null) }
|
val notaryId = notaryConfig.run {
|
||||||
|
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
||||||
|
}
|
||||||
if (notaryConfig.bftSMaRt == null && notaryConfig.raft == null) {
|
if (notaryConfig.bftSMaRt == null && notaryConfig.raft == null) {
|
||||||
// Node's notary identity
|
// Node's notary identity
|
||||||
Pair(notaryId, myLegalName.copy(commonName = notaryId))
|
Pair(notaryId, myLegalName.copy(commonName = notaryId))
|
||||||
|
@ -36,9 +36,15 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
val additionalNodeInfoPollingFrequencyMsec: Long
|
val additionalNodeInfoPollingFrequencyMsec: Long
|
||||||
}
|
}
|
||||||
|
|
||||||
data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null) {
|
data class NotaryConfig(val validating: Boolean,
|
||||||
|
val raft: RaftConfig? = null,
|
||||||
|
val bftSMaRt: BFTSMaRtConfiguration? = null,
|
||||||
|
val custom: Boolean = false
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(raft == null || bftSMaRt == null) { "raft and bftSMaRt configs cannot be specified together" }
|
require(raft == null || bftSMaRt == null || !custom) {
|
||||||
|
"raft, bftSMaRt, and custom configs cannot be specified together"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +54,8 @@ data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses:
|
|||||||
data class BFTSMaRtConfiguration constructor(val replicaId: Int,
|
data class BFTSMaRtConfiguration constructor(val replicaId: Int,
|
||||||
val clusterAddresses: List<NetworkHostAndPort>,
|
val clusterAddresses: List<NetworkHostAndPort>,
|
||||||
val debug: Boolean = false,
|
val debug: Boolean = false,
|
||||||
val exposeRaces: Boolean = false) {
|
val exposeRaces: Boolean = false
|
||||||
|
) {
|
||||||
init {
|
init {
|
||||||
require(replicaId >= 0) { "replicaId cannot be negative" }
|
require(replicaId >= 0) { "replicaId cannot be negative" }
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ private class BankOfCordaDriver {
|
|||||||
val bigCorpUser = User(BIGCORP_USERNAME, "test",
|
val bigCorpUser = User(BIGCORP_USERNAME, "test",
|
||||||
permissions = setOf(
|
permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>()))
|
startFlowPermission<CashPaymentFlow>()))
|
||||||
startNotaryNode(DUMMY_NOTARY.name, validating = false)
|
startNotaryNode(DUMMY_NOTARY.name, validating = true)
|
||||||
val bankOfCorda = startNode(
|
val bankOfCorda = startNode(
|
||||||
providedName = BOC.name,
|
providedName = BOC.name,
|
||||||
rpcUsers = listOf(bankUser))
|
rpcUsers = listOf(bankUser))
|
||||||
|
@ -46,13 +46,18 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task deployNodes(dependsOn: ['deployNodesSingle', 'deployNodesRaft', 'deployNodesBFT'])
|
task deployNodes(dependsOn: ['deployNodesSingle', 'deployNodesRaft', 'deployNodesBFT', 'deployNodesCustom'])
|
||||||
|
|
||||||
task deployNodesSingle(type: Cordform, dependsOn: 'jar') {
|
task deployNodesSingle(type: Cordform, dependsOn: 'jar') {
|
||||||
directory "./build/nodes/nodesSingle"
|
directory "./build/nodes/nodesSingle"
|
||||||
definitionClass = 'net.corda.notarydemo.SingleNotaryCordform'
|
definitionClass = 'net.corda.notarydemo.SingleNotaryCordform'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task deployNodesCustom(type: Cordform, dependsOn: 'jar') {
|
||||||
|
directory "./build/nodes/nodesCustom"
|
||||||
|
definitionClass = 'net.corda.notarydemo.CustomNotaryCordform'
|
||||||
|
}
|
||||||
|
|
||||||
task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
|
task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
|
||||||
directory "./build/nodes/nodesRaft"
|
directory "./build/nodes/nodesRaft"
|
||||||
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
|
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package net.corda.notarydemo
|
||||||
|
|
||||||
|
import net.corda.cordform.CordformContext
|
||||||
|
import net.corda.cordform.CordformDefinition
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.node.services.config.NotaryConfig
|
||||||
|
import net.corda.testing.ALICE
|
||||||
|
import net.corda.testing.BOB
|
||||||
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
|
import net.corda.testing.internal.demorun.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) = CustomNotaryCordform.runNodes()
|
||||||
|
|
||||||
|
object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||||
|
init {
|
||||||
|
node {
|
||||||
|
name(ALICE.name)
|
||||||
|
p2pPort(10002)
|
||||||
|
rpcPort(10003)
|
||||||
|
rpcUsers(notaryDemoUser)
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name(BOB.name)
|
||||||
|
p2pPort(10005)
|
||||||
|
rpcPort(10006)
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name(DUMMY_NOTARY.name)
|
||||||
|
p2pPort(10009)
|
||||||
|
rpcPort(10010)
|
||||||
|
notary(NotaryConfig(validating = true, custom = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setup(context: CordformContext) {}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.docs
|
package net.corda.notarydemo
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
@ -8,11 +8,17 @@ import net.corda.core.node.AppServiceHub
|
|||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.node.services.TimeWindowChecker
|
import net.corda.core.node.services.TimeWindowChecker
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.TransactionWithSignatures
|
import net.corda.core.transactions.TransactionWithSignatures
|
||||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||||
import java.security.PublicKey
|
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].
|
||||||
|
*
|
||||||
|
* Note that at present only a single-node notary service can be customised.
|
||||||
|
*/
|
||||||
// START 1
|
// START 1
|
||||||
@CordaService
|
@CordaService
|
||||||
class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
||||||
@ -26,6 +32,7 @@ class MyCustomValidatingNotaryService(override val services: AppServiceHub, over
|
|||||||
}
|
}
|
||||||
// END 1
|
// END 1
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
// START 2
|
// START 2
|
||||||
class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) {
|
class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) {
|
||||||
/**
|
/**
|
||||||
@ -38,11 +45,15 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
|
|||||||
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
|
||||||
val notary = stx.notary
|
val notary = stx.notary
|
||||||
checkNotary(notary)
|
checkNotary(notary)
|
||||||
val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction())
|
var timeWindow: TimeWindow? = null
|
||||||
null
|
val transactionWithSignatures = if (stx.isNotaryChangeTransaction()) {
|
||||||
else
|
stx.resolveNotaryChangeTransaction(serviceHub)
|
||||||
stx.tx.timeWindow
|
} else {
|
||||||
val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub)
|
val wtx = stx.tx
|
||||||
|
customVerify(wtx.toLedgerTransaction(serviceHub))
|
||||||
|
timeWindow = wtx.timeWindow
|
||||||
|
stx
|
||||||
|
}
|
||||||
checkSignatures(transactionWithSignatures)
|
checkSignatures(transactionWithSignatures)
|
||||||
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -54,6 +65,10 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun customVerify(transaction: LedgerTransaction) {
|
||||||
|
// Add custom verification logic
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkSignatures(tx: TransactionWithSignatures) {
|
private fun checkSignatures(tx: TransactionWithSignatures) {
|
||||||
try {
|
try {
|
||||||
tx.verifySignaturesExcept(service.notaryIdentityKey)
|
tx.verifySignaturesExcept(service.notaryIdentityKey)
|
Loading…
Reference in New Issue
Block a user