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:
Andrius Dagys 2017-10-13 10:36:25 +01:00 committed by GitHub
parent 544b761682
commit 7b10e92819
11 changed files with 148 additions and 45 deletions

View File

@ -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()
} }
} }

View File

@ -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:

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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))

View File

@ -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" }
} }

View File

@ -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))

View File

@ -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'

View File

@ -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) {}
}

View File

@ -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)