CORDA-2190: Improve notary service flow exception handling (#4200)

* CORDA-2190: Improve notary service flow exception handling

- Refactored notary flows to reduce validation code duplication
- Improved notarisation error handling to provide more helpful responses to the client
This commit is contained in:
Andrius Dagys
2018-11-09 14:00:40 +00:00
committed by GitHub
parent 2f644039b5
commit 336185de23
9 changed files with 123 additions and 141 deletions

View File

@ -49,6 +49,7 @@ sealed class NotaryError {
} }
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */ /** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
@Deprecated("Deprecated since platform version 4. This object is no longer used, [TransactionInvalid] will be reported in case of notary mismatch")
object WrongNotary : NotaryError() object WrongNotary : NotaryError()
/** Occurs when the notarisation request signature does not verify for the provided transaction. */ /** Occurs when the notarisation request signature does not verify for the provided transaction. */

View File

@ -23,61 +23,84 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
private const val maxAllowedInputsAndReferences = 10_000 private const val maxAllowedInputsAndReferences = 10_000
} }
private var transactionId: SecureHash? = null
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network" "We are not a notary on the network"
} }
val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it } val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
var txId: SecureHash? = null
try { try {
val parts = validateRequest(requestPayload) val tx: TransactionParts = validateRequest(requestPayload)
txId = parts.id val request = NotarisationRequest(tx.inputs, tx.id)
checkNotary(parts.notary) validateRequestSignature(request, requestPayload.requestSignature)
verifyTransaction(requestPayload)
service.commitInputStates( service.commitInputStates(
parts.inputs, tx.inputs,
txId, tx.id,
otherSideSession.counterparty, otherSideSession.counterparty,
requestPayload.requestSignature, requestPayload.requestSignature,
parts.timestamp, tx.timeWindow,
parts.references tx.references)
)
signTransactionAndSendResponse(txId)
} catch (e: NotaryInternalException) { } catch (e: NotaryInternalException) {
throw NotaryException(e.error, txId) logError(e.error)
// Any exception that's not a NotaryInternalException is assumed to be an unexpected internal error
// that is not relayed back to the client.
throw NotaryException(e.error, transactionId)
} }
signTransactionAndSendResponse(transactionId!!)
return null return null
} }
/** Checks whether the number of input states is too large. */ private fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
protected fun checkInputs(inputs: List<StateRef>) { try {
if (inputs.size > maxAllowedInputsAndReferences) { val transaction = extractParts(requestPayload)
val error = NotaryError.TransactionInvalid( transactionId = transaction.id
IllegalArgumentException("A transaction cannot have more than $maxAllowedInputsAndReferences " + checkNotary(transaction.notary)
"inputs or references, received: ${inputs.size}") checkInputs(transaction.inputs + transaction.references)
) return transaction
} catch (e: Exception) {
val error = NotaryError.TransactionInvalid(e)
throw NotaryInternalException(error) throw NotaryInternalException(error)
} }
} }
/** /** Extract the common transaction components required for notarisation. */
* Implement custom logic to perform transaction verification based on validity and privacy requirements. protected abstract fun extractParts(requestPayload: NotarisationPayload): TransactionParts
*/
/** Check if transaction is intended to be signed by this notary. */
@Suspendable @Suspendable
protected abstract fun validateRequest(requestPayload: NotarisationPayload): TransactionParts private fun checkNotary(notary: Party?) {
require(notary?.owningKey == service.notaryIdentityKey) {
"The notary specified on the transaction: [$notary] does not match the notary service's identity: [${service.notaryIdentityKey}] "
}
}
/** Checks whether the number of input states is too large. */
private fun checkInputs(inputs: List<StateRef>) {
require(inputs.size < maxAllowedInputsAndReferences) {
"A transaction cannot have more than $maxAllowedInputsAndReferences " +
"inputs or references, received: ${inputs.size}"
}
}
/** Verifies that the correct notarisation request was signed by the counterparty. */ /** Verifies that the correct notarisation request was signed by the counterparty. */
protected fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) { private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty val requestingParty = otherSideSession.counterparty
request.verifySignature(signature, requestingParty) request.verifySignature(signature, requestingParty)
} }
/** Check if transaction is intended to be signed by this notary. */ /**
* Override to implement custom logic to perform transaction verification based on validity and privacy requirements.
*/
@Suspendable @Suspendable
protected fun checkNotary(notary: Party?) { protected open fun verifyTransaction(requestPayload: NotarisationPayload) {
if (notary?.owningKey != service.notaryIdentityKey) {
throw NotaryInternalException(NotaryError.WrongNotary)
}
} }
@Suspendable @Suspendable
@ -90,15 +113,23 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
* The minimum amount of information needed to notarise a transaction. Note that this does not include * The minimum amount of information needed to notarise a transaction. Note that this does not include
* any sensitive transaction details. * any sensitive transaction details.
*/ */
protected data class TransactionParts @JvmOverloads constructor( protected data class TransactionParts(
val id: SecureHash, val id: SecureHash,
val inputs: List<StateRef>, val inputs: List<StateRef>,
val timestamp: TimeWindow?, val timeWindow: TimeWindow?,
val notary: Party?, val notary: Party?,
val references: List<StateRef> = emptyList() val references: List<StateRef> = emptyList()
) { )
fun copy(id: SecureHash, inputs: List<StateRef>, timestamp: TimeWindow?, notary: Party?): TransactionParts {
return TransactionParts(id, inputs, timestamp, notary, references) private fun logError(error: NotaryError) {
val errorCause = when (error) {
is NotaryError.RequestSignatureInvalid -> error.cause
is NotaryError.TransactionInvalid -> error.cause
is NotaryError.General -> error.cause
else -> null
}
if (errorCause != null) {
logger.error("Error notarising transaction $transactionId", errorCause)
} }
} }
} }

View File

@ -2,42 +2,31 @@ package net.corda.core.internal.notary
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.DigitalSignature
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import java.security.InvalidKeyException import net.corda.core.utilities.toBase58String
import java.security.SignatureException
import java.time.Instant import java.time.Instant
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */ /** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) { fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != signature.by) {
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
throw NotaryInternalException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
}
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
// available.
val expectedSignedBytes = this.serialize().bytes
verifyCorrectBytesSigned(signature, expectedSignedBytes)
}
private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) {
try { try {
signature.verify(bytes) val signature = requestSignature.digitalSignature
} catch (e: Exception) { require(intendedSigner.owningKey == signature.by) {
when (e) { "Expected a signature by ${intendedSigner.owningKey.toBase58String()}, but received by ${signature.by.toBase58String()}}"
is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryInternalException(error)
}
else -> throw e
} }
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
// available.
val expectedSignedBytes = this.serialize().bytes
signature.verify(expectedSignedBytes)
} catch (e: Exception) {
val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryInternalException(error)
} }
} }

View File

@ -53,6 +53,7 @@ abstract class SinglePartyNotaryService : NotaryService() {
references references
) )
) )
if (result is UniquenessProvider.Result.Failure) { if (result is UniquenessProvider.Result.Failure) {
throw NotaryInternalException(result.error) throw NotaryInternalException(result.error)
} }

View File

@ -265,8 +265,11 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
override fun toString(): String = "${javaClass.simpleName}(id=$id)" override fun toString(): String = "${javaClass.simpleName}(id=$id)"
private companion object { private companion object {
private fun missingSignatureMsg(missing: Set<PublicKey>, descriptions: List<String>, id: SecureHash): String = private fun missingSignatureMsg(missing: Set<PublicKey>, descriptions: List<String>, id: SecureHash): String {
"Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}" return "Missing signatures on transaction ${id.prefixChars()} for " +
"keys: ${missing.joinToString { it.toStringShort() }}, " +
"by signers: ${descriptions.joinToString()} "
}
} }
@KeepForDJVM @KeepForDJVM

View File

@ -1,38 +1,25 @@
package net.corda.node.services.transactions package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ComponentGroupEnum import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.internal.notary.NotaryServiceFlow import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.transactions.ContractUpgradeFilteredTransaction import net.corda.core.transactions.ContractUpgradeFilteredTransaction
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
/**
* The received transaction is not checked for contract-validity, as that would require fully
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
* history chain.
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out).
*/
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) { class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) {
/** override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
* The received transaction is not checked for contract-validity, as that would require fully val tx = requestPayload.coreTransaction
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
* history chain.
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out).
*/
@Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
val transaction = requestPayload.coreTransaction
checkInputs(transaction.inputs + transaction.references)
val request = NotarisationRequest(transaction.inputs, transaction.id)
validateRequestSignature(request, requestPayload.requestSignature)
val parts = extractParts(transaction)
checkNotary(parts.notary)
return parts
}
private fun extractParts(tx: CoreTransaction): TransactionParts {
return when (tx) { return when (tx) {
is FilteredTransaction -> { is FilteredTransaction -> {
tx.apply { tx.apply {
@ -43,7 +30,7 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePart
} }
TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references) TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references)
} }
is ContractUpgradeFilteredTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary) is ContractUpgradeFilteredTransaction,
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary) is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
else -> { else -> {
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," + throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +

View File

@ -2,19 +2,16 @@ package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
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.SinglePartyNotaryService
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
import java.security.SignatureException
/** /**
* A notary commit flow that makes sure a given transaction is valid before committing it. This does mean that the calling * A notary commit flow that makes sure a given transaction is valid before committing it. This does mean that the calling
@ -22,29 +19,25 @@ import java.security.SignatureException
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was * has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
* indeed valid. * indeed valid.
*/ */
class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) { open class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) {
override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
val stx = requestPayload.signedTransaction
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
return TransactionParts(stx.id, stx.inputs, timeWindow, stx.notary, stx.references)
}
/** /**
* Fully resolves the received transaction and its dependencies, runs contract verification logic and checks that * Fully resolves the received transaction and its dependencies, runs contract verification logic and checks that
* the transaction in question has all required signatures apart from the notary's. * the transaction in question has all required signatures apart from the notary's.
*/ */
@Suspendable @Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts { override fun verifyTransaction(requestPayload: NotarisationPayload) {
try { try {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
checkInputs(stx.inputs + stx.references)
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)
resolveAndContractVerify(stx) resolveAndContractVerify(stx)
verifySignatures(stx) verifySignatures(stx)
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!, stx.references)
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw NotaryInternalException(NotaryError.TransactionInvalid(e))
is TransactionVerificationException,
is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e
}
} }
} }
@ -60,10 +53,6 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNo
} }
private fun checkSignatures(tx: TransactionWithSignatures) { private fun checkSignatures(tx: TransactionWithSignatures) {
try { tx.verifySignaturesExcept(service.notaryIdentityKey)
tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
} }
} }

View File

@ -13,7 +13,6 @@ import net.corda.core.internal.FlowIORequest
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.internal.notary.UniquenessProvider import net.corda.core.internal.notary.UniquenessProvider
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
@ -22,6 +21,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.ValidatingNotaryFlow
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -174,20 +174,21 @@ class TimedFlowTests {
override val uniquenessProvider = object : UniquenessProvider { override val uniquenessProvider = object : UniquenessProvider {
/** A dummy commit method that immediately returns a success message. */ /** A dummy commit method that immediately returns a success message. */
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<UniquenessProvider.Result> { override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<UniquenessProvider.Result> {
return openFuture<UniquenessProvider.Result>(). apply { return openFuture<UniquenessProvider.Result>().apply {
set(UniquenessProvider.Result.Success) set(UniquenessProvider.Result.Success)
} }
} }
} }
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = TestNotaryFlow(otherPartySession, this) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = TestNotaryFlow(otherPartySession, this)
override fun start() {} override fun start() {}
override fun stop() {} override fun stop() {}
} }
/** A notary flow that will yield without returning a response on the very first received request. */ /** A notary flow that will yield without returning a response on the very first received request. */
private class TestNotaryFlow(otherSide: FlowSession, service: TestNotaryService) : NotaryServiceFlow(otherSide, service) { private class TestNotaryFlow(otherSide: FlowSession, service: TestNotaryService) : ValidatingNotaryFlow(otherSide, service) {
@Suspendable @Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts { override fun verifyTransaction(requestPayload: NotarisationPayload) {
val myIdentity = serviceHub.myInfo.legalIdentities.first() val myIdentity = serviceHub.myInfo.legalIdentities.first()
MDC.put("name", myIdentity.name.toString()) MDC.put("name", myIdentity.name.toString())
logger.info("Received a request from ${otherSideSession.counterparty.name}") logger.info("Received a request from ${otherSideSession.counterparty.name}")
@ -201,7 +202,6 @@ class TimedFlowTests {
} else { } else {
logger.info("Processing") logger.info("Processing")
} }
return TransactionParts(stx.id, stx.inputs, stx.tx.timeWindow, stx.notary)
} }
} }
} }

View File

@ -1,20 +1,19 @@
package net.corda.notarydemo package net.corda.notarydemo
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TimeWindow import net.corda.core.flows.FlowLogic
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.FlowSession
import net.corda.core.flows.* import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotaryError
import net.corda.core.internal.ResolveTransactionsFlow 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.SinglePartyNotaryService import net.corda.core.internal.notary.SinglePartyNotaryService
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.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.ValidatingNotaryFlow
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException
/** /**
* A custom notary service should provide a constructor that accepts two parameters of types [ServiceHubInternal] and [PublicKey]. * A custom notary service should provide a constructor that accepts two parameters of types [ServiceHubInternal] and [PublicKey].
@ -35,28 +34,15 @@ class MyCustomValidatingNotaryService(override val services: ServiceHubInternal,
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
// START 2 // START 2
class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryServiceFlow(otherSide, service) { class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : ValidatingNotaryFlow(otherSide, service) {
/** override fun verifyTransaction(requestPayload: NotarisationPayload) {
* The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
* transaction dependency chain.
*/
@Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
try { try {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)
verifySignatures(stx)
resolveAndContractVerify(stx) resolveAndContractVerify(stx)
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null verifySignatures(stx)
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!, stx.references) customVerify(stx)
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw NotaryInternalException(NotaryError.TransactionInvalid(e))
is TransactionVerificationException,
is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e
}
} }
} }
@ -64,7 +50,6 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
private fun resolveAndContractVerify(stx: SignedTransaction) { private fun resolveAndContractVerify(stx: SignedTransaction) {
subFlow(ResolveTransactionsFlow(stx, otherSideSession)) subFlow(ResolveTransactionsFlow(stx, otherSideSession))
stx.verify(serviceHub, false) stx.verify(serviceHub, false)
customVerify(stx)
} }
private fun verifySignatures(stx: SignedTransaction) { private fun verifySignatures(stx: SignedTransaction) {
@ -73,11 +58,7 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
} }
private fun checkSignatures(tx: TransactionWithSignatures) { private fun checkSignatures(tx: TransactionWithSignatures) {
try { tx.verifySignaturesExcept(service.notaryIdentityKey)
tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
} }
private fun customVerify(stx: SignedTransaction) { private fun customVerify(stx: SignedTransaction) {