From 9739b92e35af9abf3547f7f10d598c465ab0af4b Mon Sep 17 00:00:00 2001
From: Joseph Zuniga-Daly <joseph.zunigadaly@r3.com>
Date: Thu, 14 Dec 2023 09:33:51 +0000
Subject: [PATCH 1/7] CORE-15813: Close DB connection in error path (#5259)

---
 .../processors/db/internal/reconcile/db/DbReconcilerReader.kt    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/processors/db-processor/src/main/kotlin/net/corda/processors/db/internal/reconcile/db/DbReconcilerReader.kt b/processors/db-processor/src/main/kotlin/net/corda/processors/db/internal/reconcile/db/DbReconcilerReader.kt
index 647b4966d3b..4d0da6a1084 100644
--- a/processors/db-processor/src/main/kotlin/net/corda/processors/db/internal/reconcile/db/DbReconcilerReader.kt
+++ b/processors/db-processor/src/main/kotlin/net/corda/processors/db/internal/reconcile/db/DbReconcilerReader.kt
@@ -85,6 +85,7 @@ class DbReconcilerReader<K : Any, V : Any>(
                 }
             } catch (e: Exception) {
                 logger.warn("Error while retrieving DB records for reconciliation for ${context.prettyPrint()}", e)
+                context.close()
                 Stream.empty()
             }
         }

From 0916dfa116066d52807248e081879a922f7af61e Mon Sep 17 00:00:00 2001
From: Jenny Yang <135036209+jennyang-r3@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:02:27 +0000
Subject: [PATCH 2/7] CORE-17816: contract verifying notary implementation
 (#5186)

Contract verifying notary implementation according to design (https://github.com/corda/platform-eng-design/blob/main/core/corda-5/corda-5.2/notary/contract-verifying-notary/index.md#proposed-solution)
---
 .../factory/ComponentGroupFilterParameters.kt |  26 +-
 .../factory/FilteredTransactionFactoryImpl.kt |  30 +-
 .../FilteredTransactionImplIntegrationTest.kt |  54 +-
 .../FilteredTransactionFactoryImplTest.kt     |  35 +-
 .../utxo/flow/impl/UtxoLedgerServiceImpl.kt   |   5 +
 .../transaction/UtxoSignedTransactionImpl.kt  |  69 +--
 .../factory/UtxoLedgerTransactionFactory.kt   |   7 +
 .../impl/UtxoLedgerTransactionFactoryImpl.kt  |  14 +
 .../impl/UtxoSignedTransactionFactoryImpl.kt  |   7 +-
 .../UtxoFilteredTransactionBuilderImpl.kt     |  22 +-
 .../UtxoFilteredTransactionFactoryImpl.kt     |  29 +-
 .../amqp/UtxoSignedTransactionSerializer.kt   |   6 +-
 .../UtxoSignedTransactionKryoSerializer.kt    |   6 +-
 .../NotarySignatureVerificationServiceImpl.kt |  87 +++
 ...arySignatureVerificationServiceInternal.kt |  15 +
 .../UtxoLedgerPersistenceServiceImplTest.kt   |   3 +
 .../UtxoSignedTransactionImplTest.kt          |  20 -
 .../UtxoFilteredTransactionBuilderImplTest.kt | 121 +++--
 .../UtxoFilteredTransactionImplTest.kt        | 103 ++--
 .../UtxoFilteredTransactionTestBase.kt        |  28 +-
 .../UtxoFilteredTransactionFactoryImplTest.kt |  22 +-
 ...arySignatureVerificationServiceImplTest.kt | 160 ++++++
 gradle.properties                             |   2 +-
 .../plugin/common/CommonNotaryExceptions.kt   |  28 +
 .../common/NotaryTransactionDetails.kt}       |   6 +-
 .../build.gradle                              |  34 ++
 .../ContractVerifyingNotarizationPayload.kt   |  10 +
 .../api/FilteredTransactionAndSignatures.kt   |  11 +
 .../build.gradle                              |  43 ++
 .../ContractVerifyingNotaryClientFlowImpl.kt  | 111 ++++
 ...ntractVerifyingNotaryClientFlowImplTest.kt | 145 +++++
 .../build.gradle                              |  39 ++
 .../ContractVerifyingNotaryServerFlowImpl.kt  | 217 ++++++++
 ...ntractVerifyingNotaryServerFlowImplTest.kt | 504 ++++++++++++++++++
 .../NonValidatingNotaryServerFlowImpl.kt      |  11 +-
 settings.gradle                               |   3 +
 .../corda/e2etest/utilities/ClusterBuilder.kt |  11 +-
 .../e2etest/utilities/MembershipUtils.kt      |  29 +-
 .../corda/ledger/utxo/test/UtxoLedgerTest.kt  |  12 +-
 .../testkit/UtxoSignedTransactionExample.kt   |   3 +
 40 files changed, 1841 insertions(+), 247 deletions(-)
 create mode 100644 components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImpl.kt
 create mode 100644 components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceInternal.kt
 create mode 100644 components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImplTest.kt
 rename notary-plugins/{notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryTransactionDetails.kt => notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/NotaryTransactionDetails.kt} (90%)
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/build.gradle
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/ContractVerifyingNotarizationPayload.kt
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/FilteredTransactionAndSignatures.kt
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImpl.kt
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImplTest.kt
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImpl.kt
 create mode 100644 notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImplTest.kt

diff --git a/components/ledger/ledger-common-flow-api/src/main/kotlin/net/corda/ledger/common/flow/transaction/filtered/factory/ComponentGroupFilterParameters.kt b/components/ledger/ledger-common-flow-api/src/main/kotlin/net/corda/ledger/common/flow/transaction/filtered/factory/ComponentGroupFilterParameters.kt
index 3bc7958ef89..28d5c787193 100644
--- a/components/ledger/ledger-common-flow-api/src/main/kotlin/net/corda/ledger/common/flow/transaction/filtered/factory/ComponentGroupFilterParameters.kt
+++ b/components/ledger/ledger-common-flow-api/src/main/kotlin/net/corda/ledger/common/flow/transaction/filtered/factory/ComponentGroupFilterParameters.kt
@@ -34,9 +34,33 @@ sealed interface ComponentGroupFilterParameters {
     data class AuditProof<T : Any>(
         override val componentGroupIndex: Int,
         val deserializedClass: Class<T>,
-        val predicate: Predicate<T>
+        val predicate: AuditProofPredicate<T>
     ) : ComponentGroupFilterParameters {
         override val merkleProofType = MerkleProofType.AUDIT
+
+        sealed interface AuditProofPredicate<T> {
+            /**
+             *  [Content] include components in a [FilteredTransaction] where the components meet predicate.
+             *
+             *  @property predicate Filtering function that is applied to each deserialized component with the group
+             */
+            class Content<T>(private val predicate: Predicate<T>) : AuditProofPredicate<T>, Predicate<T> {
+                override fun test(t: T): Boolean {
+                    return predicate.test(t)
+                }
+            }
+
+            /**
+             *  [Index] indexes of components to include in a [FilteredTransaction]
+             *
+             *  @property indexes component indexes to include.
+             */
+            class Index<T>(private val indexes: List<Int>) : AuditProofPredicate<T>, Predicate<Int> {
+                override fun test(t: Int): Boolean {
+                    return t in indexes
+                }
+            }
+        }
     }
 
     /**
diff --git a/components/ledger/ledger-common-flow/src/main/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImpl.kt b/components/ledger/ledger-common-flow/src/main/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImpl.kt
index 59079479e0a..01063345e80 100644
--- a/components/ledger/ledger-common-flow/src/main/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImpl.kt
+++ b/components/ledger/ledger-common-flow/src/main/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImpl.kt
@@ -6,6 +6,7 @@ import net.corda.ledger.common.flow.impl.transaction.filtered.FilteredTransactio
 import net.corda.ledger.common.flow.transaction.filtered.FilteredComponentGroup
 import net.corda.ledger.common.flow.transaction.filtered.FilteredTransaction
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof
 import net.corda.ledger.common.flow.transaction.filtered.factory.FilteredTransactionFactory
 import net.corda.sandbox.type.SandboxConstants.CORDA_SYSTEM_SERVICE
 import net.corda.sandbox.type.UsedByFlow
@@ -19,7 +20,6 @@ import org.osgi.service.component.annotations.Component
 import org.osgi.service.component.annotations.Reference
 import org.osgi.service.component.annotations.ReferenceScope.PROTOTYPE_REQUIRED
 import org.osgi.service.component.annotations.ServiceScope.PROTOTYPE
-import java.util.function.Predicate
 
 @Component(
     service = [ FilteredTransactionFactory::class, SingletonSerializeAsToken::class, UsedByFlow::class ],
@@ -83,20 +83,30 @@ class FilteredTransactionFactoryImpl @Activate constructor(
             }
 
         val merkleProof = when (parameters) {
-            is ComponentGroupFilterParameters.AuditProof<*> -> {
+            is AuditProof<*> -> {
                 val skipFiltering = componentGroupIndex == 0
 
                 val filteredComponents = componentGroup
                     .mapIndexed { index, component -> index to component }
-                    .filter { (_, component) ->
-                        skipFiltering || (parameters.predicate as Predicate<Any>).test(
-                            serializationService.deserialize(
-                                component,
-                                parameters.deserializedClass
-                            )
-                        )
+                    .filter { (index, component) ->
+                        if (skipFiltering) {
+                            true
+                        } else {
+                            when (val predicate = parameters.predicate) {
+                                is AuditProof.AuditProofPredicate.Content -> {
+                                    (predicate as AuditProof.AuditProofPredicate.Content<Any>).test(
+                                        serializationService.deserialize(
+                                            component,
+                                            parameters.deserializedClass
+                                        )
+                                    )
+                                }
+                                is AuditProof.AuditProofPredicate.Index -> {
+                                    predicate.test(index)
+                                }
+                            }
+                        }
                     }
-
                 wireTransaction.componentMerkleTrees[componentGroupIndex]!!.let { merkleTree ->
                     if (filteredComponents.isEmpty()) {
                         if (componentGroup.isEmpty()) {
diff --git a/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/FilteredTransactionImplIntegrationTest.kt b/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/FilteredTransactionImplIntegrationTest.kt
index ead3672a037..daf5f546149 100644
--- a/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/FilteredTransactionImplIntegrationTest.kt
+++ b/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/FilteredTransactionImplIntegrationTest.kt
@@ -11,6 +11,7 @@ import net.corda.ledger.common.data.transaction.WireTransaction
 import net.corda.ledger.common.flow.impl.transaction.filtered.factory.FilteredTransactionFactoryImpl
 import net.corda.ledger.common.flow.transaction.filtered.FilteredTransaction
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.common.testkit.getWireTransactionExample
 import net.corda.v5.application.serialization.SerializationService
 import net.corda.v5.base.annotations.CordaSerializable
@@ -91,13 +92,33 @@ class FilteredTransactionImplIntegrationTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(
+                    0,
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(1),
-                ComponentGroupFilterParameters.AuditProof(2, Any::class.java) { it is MyClassC },
-                ComponentGroupFilterParameters.AuditProof(3, Any::class.java) { it is MyClassC },
+                ComponentGroupFilterParameters.AuditProof(
+                    2,
+                    Any::class.java,
+                    AuditProofPredicate.Content { it is MyClassC }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    3,
+                    Any::class.java,
+                    AuditProofPredicate.Content { it is MyClassC }
+                ),
                 ComponentGroupFilterParameters.SizeProof(4),
-                ComponentGroupFilterParameters.AuditProof(5, Any::class.java) { it is MyClassC },
-                ComponentGroupFilterParameters.AuditProof(6, Any::class.java) { it is MyClassC },
+                ComponentGroupFilterParameters.AuditProof(
+                    5,
+                    Any::class.java,
+                    AuditProofPredicate.Content { it is MyClassC }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    6,
+                    Any::class.java,
+                    AuditProofPredicate.Content { it is MyClassC }
+                ),
                 ComponentGroupFilterParameters.SizeProof(9),
             )
         )
@@ -124,9 +145,14 @@ class FilteredTransactionImplIntegrationTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(2, Any::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(
+                    0,
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+
+                ),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { true }),
+                ComponentGroupFilterParameters.AuditProof(2, Any::class.java, AuditProofPredicate.Content { true }),
             )
         )
 
@@ -163,9 +189,9 @@ class FilteredTransactionImplIntegrationTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { it is MyClassA || it is MyClassC },
-                ComponentGroupFilterParameters.AuditProof(2, Any::class.java) { it is MyClassA || it is MyClassC },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java, AuditProofPredicate.Content { true }),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { it is MyClassA || it is MyClassC }),
+                ComponentGroupFilterParameters.AuditProof(2, Any::class.java, AuditProofPredicate.Content { it is MyClassA || it is MyClassC }),
             )
         )
 
@@ -202,8 +228,8 @@ class FilteredTransactionImplIntegrationTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { it is MyClassA || it is MyClassC },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java, AuditProofPredicate.Content { true }),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { it is MyClassA || it is MyClassC }),
             )
         )
 
@@ -234,7 +260,7 @@ class FilteredTransactionImplIntegrationTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadataImpl::class.java, AuditProofPredicate.Content { true }),
                 ComponentGroupFilterParameters.SizeProof(1),
                 ComponentGroupFilterParameters.SizeProof(2),
             )
diff --git a/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImplTest.kt b/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImplTest.kt
index e587a585eae..d6167c8518c 100644
--- a/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImplTest.kt
+++ b/components/ledger/ledger-common-flow/src/test/kotlin/net/corda/ledger/common/flow/impl/transaction/filtered/factory/FilteredTransactionFactoryImplTest.kt
@@ -10,6 +10,7 @@ import net.corda.ledger.common.data.transaction.TransactionMetadataImpl
 import net.corda.ledger.common.data.transaction.WireTransaction
 import net.corda.ledger.common.flow.transaction.filtered.FilteredTransaction
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.common.testkit.getWireTransactionExample
 import net.corda.v5.application.serialization.SerializationService
 import net.corda.v5.base.annotations.CordaSerializable
@@ -66,9 +67,9 @@ class FilteredTransactionFactoryImplTest {
             filteredTransaction = filteredTransactionFactory.create(
                 wireTransaction,
                 componentGroupFilterParameters = listOf(
-                    ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
-                    ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { true },
-                    ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { true },
+                    ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { true }),
+                    ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { true }),
+                    ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { true }),
                 )
             )
         }.hasMessageContaining("Unique component group indexes are required when filtering a transaction")
@@ -85,8 +86,8 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { false },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { false },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { false }),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { false }),
             )
         )
 
@@ -108,8 +109,8 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { true }),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { true }),
             )
         )
 
@@ -133,8 +134,16 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { it is MyClassA || it is MyClassB },
+                ComponentGroupFilterParameters.AuditProof(
+                    0,
+                    TransactionMetadata::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    1,
+                    Any::class.java,
+                    AuditProofPredicate.Content { it is MyClassA || it is MyClassB }
+                ),
             )
         )
 
@@ -167,8 +176,8 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
-                ComponentGroupFilterParameters.AuditProof(1, Any::class.java) { false },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { true }),
+                ComponentGroupFilterParameters.AuditProof(1, Any::class.java, AuditProofPredicate.Content { false }),
             )
         )
 
@@ -199,7 +208,7 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { true }),
                 ComponentGroupFilterParameters.SizeProof(1),
             )
         )
@@ -229,7 +238,7 @@ class FilteredTransactionFactoryImplTest {
         filteredTransaction = filteredTransactionFactory.create(
             wireTransaction,
             componentGroupFilterParameters = listOf(
-                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(0, TransactionMetadata::class.java, AuditProofPredicate.Content { true }),
                 ComponentGroupFilterParameters.SizeProof(1),
             )
         )
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt
index 0ddc3e11c81..cec3d829a52 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/UtxoLedgerServiceImpl.kt
@@ -80,6 +80,11 @@ class UtxoLedgerServiceImpl @Activate constructor(
     override fun createTransactionBuilder() =
         UtxoTransactionBuilderImpl(utxoSignedTransactionFactory, notaryLookup)
 
+    @Suspendable
+    override fun verify(ledgerTransaction: UtxoLedgerTransaction) {
+        transactionVerificationService.verify(ledgerTransaction)
+    }
+
     @Suppress("UNCHECKED_CAST")
     @Suspendable
     override fun <T : ContractState> resolve(stateRefs: Iterable<StateRef>): List<StateAndRef<T>> {
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImpl.kt
index 1b90f18e80e..672232b9180 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImpl.kt
@@ -6,11 +6,11 @@ import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInter
 import net.corda.ledger.utxo.data.transaction.WrappedUtxoWireTransaction
 import net.corda.ledger.utxo.data.transaction.verifier.verifyMetadata
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
 import net.corda.v5.application.serialization.SerializationService
 import net.corda.v5.base.annotations.Suspendable
 import net.corda.v5.base.types.MemberX500Name
-import net.corda.v5.crypto.CompositeKey
 import net.corda.v5.crypto.KeyUtils
 import net.corda.v5.crypto.SecureHash
 import net.corda.v5.ledger.common.transaction.TransactionMetadata
@@ -28,6 +28,7 @@ import java.util.Objects
 data class UtxoSignedTransactionImpl(
     private val serializationService: SerializationService,
     private val transactionSignatureServiceInternal: TransactionSignatureServiceInternal,
+    private val notarySignatureVerificationService: NotarySignatureVerificationServiceInternal,
     private val utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory,
     override val wireTransaction: WireTransaction,
     private val signatures: List<DigitalSignatureAndMetadata>
@@ -91,6 +92,7 @@ data class UtxoSignedTransactionImpl(
         UtxoSignedTransactionImpl(
             serializationService,
             transactionSignatureServiceInternal,
+            notarySignatureVerificationService,
             utxoLedgerTransactionFactory,
             wireTransaction,
             signatures + signature
@@ -107,6 +109,7 @@ data class UtxoSignedTransactionImpl(
             UtxoSignedTransactionImpl(
                 serializationService,
                 transactionSignatureServiceInternal,
+                notarySignatureVerificationService,
                 utxoLedgerTransactionFactory,
                 wireTransaction,
                 signatures + newSignatures
@@ -119,7 +122,7 @@ data class UtxoSignedTransactionImpl(
         val keyIdToPublicKey = keyIdToSignatories.getOrPut(keyId.algorithm) {
             // Prepare keyIds for all public keys related to signatories for the relevant algorithm
             signatories.flatMap { signatory ->
-                getKeyOrLeafKeys(signatory).map {
+                notarySignatureVerificationService.getKeyOrLeafKeys(signatory).map {
                     transactionSignatureServiceInternal.getIdOfPublicKey(
                         it, keyId.algorithm
                     ) to it
@@ -179,51 +182,12 @@ data class UtxoSignedTransactionImpl(
             .toMap()
     }
 
-    private fun getNotaryPublicKeyByKeyId(keyId: SecureHash): PublicKey? {
-        val keyIdToPublicKey = keyIdToNotaryKeys.getOrPut(keyId.algorithm) {
-            // Prepare keyIds for all public keys related to the notary for the relevant algorithm
-            getKeyOrLeafKeys(notaryKey).associateBy {
-                transactionSignatureServiceInternal.getIdOfPublicKey(
-                    it,
-                    keyId.algorithm
-                )
-            }
-        }
-        return keyIdToPublicKey[keyId]
-    }
-
     override fun verifyAttachedNotarySignature() {
-        val notaryPublicKeysWithValidSignatures = signatures.mapNotNull {
-            val publicKey = getNotaryPublicKeyByKeyId(it.by)
-            if (publicKey != null) {
-                try {
-                    transactionSignatureServiceInternal.verifySignature(this, it, publicKey)
-                    publicKey
-                } catch (e: Exception) {
-                    throw TransactionSignatureException(
-                        id,
-                        "Failed to verify signature of ${it.signature} for transaction $id. Message: ${e.message}",
-                        e
-                    )
-                }
-            } else {
-                null
-            }
-        }.toSet()
-        // If the notary service key (composite key) is provided we need to make sure it contains the key the
-        // transaction was signed with. This means it was signed with one of the notary VNodes (worker).
-        if (!KeyUtils.isKeyFulfilledBy(notaryKey, notaryPublicKeysWithValidSignatures)) {
-            throw TransactionSignatureException(
-                id,
-                "Notary signing keys $notaryPublicKeysWithValidSignatures did not fulfil " +
-                    "requirements of notary service key $notaryKey",
-                null
-            )
-        }
+        notarySignatureVerificationService.verifyNotarySignatures(wireTransaction, notaryKey, signatures, keyIdToNotaryKeys)
     }
 
     override fun verifyNotarySignature(signature: DigitalSignatureAndMetadata) {
-        val publicKey = getNotaryPublicKeyByKeyId(signature.by)
+        val publicKey = notarySignatureVerificationService.getNotaryPublicKeyByKeyId(signature.by, notaryKey, keyIdToNotaryKeys)
             ?: throw TransactionSignatureException(
                 id,
                 "Notary signature has not been created by the notary for this transaction. " +
@@ -263,6 +227,18 @@ data class UtxoSignedTransactionImpl(
         return utxoLedgerTransactionFactory.create(wireTransaction)
     }
 
+    @Suspendable
+    override fun toLedgerTransaction(
+        inputStateAndRefs: List<StateAndRef<*>>,
+        referenceStateAndRefs: List<StateAndRef<*>>
+    ): UtxoLedgerTransaction {
+        return utxoLedgerTransactionFactory.createWithStateAndRefs(
+            wireTransaction,
+            inputStateAndRefs,
+            referenceStateAndRefs
+        )
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is UtxoSignedTransactionImpl) return false
@@ -279,11 +255,4 @@ data class UtxoSignedTransactionImpl(
     override fun toString(): String {
         return "UtxoSignedTransactionImpl(id=$id, signatures=$signatures, wireTransaction=$wireTransaction)"
     }
-
-    private fun getKeyOrLeafKeys(publicKey: PublicKey): List<PublicKey> {
-        return when (publicKey) {
-            is CompositeKey -> publicKey.leafKeys.toList()
-            else -> listOf(publicKey)
-        }
-    }
 }
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/UtxoLedgerTransactionFactory.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/UtxoLedgerTransactionFactory.kt
index d49fb001faa..4582cdab3a3 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/UtxoLedgerTransactionFactory.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/UtxoLedgerTransactionFactory.kt
@@ -4,6 +4,7 @@ import net.corda.ledger.common.data.transaction.WireTransaction
 import net.corda.ledger.utxo.data.transaction.UtxoLedgerTransactionInternal
 import net.corda.ledger.utxo.data.transaction.UtxoVisibleTransactionOutputDto
 import net.corda.v5.base.annotations.Suspendable
+import net.corda.v5.ledger.utxo.StateAndRef
 import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction
 
 /**
@@ -27,4 +28,10 @@ interface UtxoLedgerTransactionFactory {
         inputStateAndRefs: List<UtxoVisibleTransactionOutputDto>,
         referenceStateAndRefs: List<UtxoVisibleTransactionOutputDto>
     ): UtxoLedgerTransactionInternal
+
+    fun createWithStateAndRefs(
+        wireTransaction: WireTransaction,
+        inputStateAndRefs: List<StateAndRef<*>>,
+        referenceStateAndRefs: List<StateAndRef<*>>
+    ): UtxoLedgerTransactionInternal
 }
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoLedgerTransactionFactoryImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoLedgerTransactionFactoryImpl.kt
index 46c3d15b7a8..677f7ff874e 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoLedgerTransactionFactoryImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoLedgerTransactionFactoryImpl.kt
@@ -16,6 +16,7 @@ import net.corda.v5.application.serialization.SerializationService
 import net.corda.v5.base.annotations.Suspendable
 import net.corda.v5.base.exceptions.CordaRuntimeException
 import net.corda.v5.ledger.utxo.ContractState
+import net.corda.v5.ledger.utxo.StateAndRef
 import net.corda.v5.membership.GroupParameters
 import net.corda.v5.serialization.SingletonSerializeAsToken
 import org.osgi.service.component.annotations.Activate
@@ -79,6 +80,19 @@ class UtxoLedgerTransactionFactoryImpl @Activate constructor(
         )
     }
 
+    override fun createWithStateAndRefs(
+        wireTransaction: WireTransaction,
+        inputStateAndRefs: List<StateAndRef<*>>,
+        referenceStateAndRefs: List<StateAndRef<*>>
+    ): UtxoLedgerTransactionInternal {
+        return UtxoLedgerTransactionImpl(
+            WrappedUtxoWireTransaction(wireTransaction, serializationService),
+            inputStateAndRefs,
+            referenceStateAndRefs,
+            getGroupParameters(wireTransaction)
+        )
+    }
+
     private fun getGroupParameters(wireTransaction: WireTransaction): GroupParameters {
         val membershipGroupParametersHashString =
             requireNotNull((wireTransaction.metadata as TransactionMetadataInternal).getMembershipGroupParametersHash()) {
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoSignedTransactionFactoryImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoSignedTransactionFactoryImpl.kt
index 89d2a25d949..70c5d36d895 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoSignedTransactionFactoryImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/factory/impl/UtxoSignedTransactionFactoryImpl.kt
@@ -20,6 +20,7 @@ import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoTransactionBuilderInternal
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoSignedTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.ledger.utxo.flow.impl.transaction.verifier.UtxoLedgerTransactionVerificationService
 import net.corda.membership.lib.SignedGroupParameters
 import net.corda.sandbox.type.UsedByFlow
@@ -65,7 +66,9 @@ class UtxoSignedTransactionFactoryImpl @Activate constructor(
     @Reference(service = GroupParametersLookupInternal::class)
     private val groupParametersLookup: GroupParametersLookupInternal,
     @Reference(service = SignedGroupParametersVerifier::class)
-    private val signedGroupParametersVerifier: SignedGroupParametersVerifier
+    private val signedGroupParametersVerifier: SignedGroupParametersVerifier,
+    @Reference(service = NotarySignatureVerificationServiceInternal::class)
+    private val notarySignatureVerificationService: NotarySignatureVerificationServiceInternal
 ) : UtxoSignedTransactionFactory, UsedByFlow, SingletonSerializeAsToken {
 
     @Suspendable
@@ -92,6 +95,7 @@ class UtxoSignedTransactionFactoryImpl @Activate constructor(
         return UtxoSignedTransactionImpl(
             serializationService,
             transactionSignatureService,
+            notarySignatureVerificationService,
             utxoLedgerTransactionFactory,
             wireTransaction,
             signaturesWithMetadata
@@ -104,6 +108,7 @@ class UtxoSignedTransactionFactoryImpl @Activate constructor(
     ): UtxoSignedTransactionInternal = UtxoSignedTransactionImpl(
         serializationService,
         transactionSignatureService,
+        notarySignatureVerificationService,
         utxoLedgerTransactionFactory,
         wireTransaction,
         signaturesWithMetaData
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImpl.kt
index b320068fdda..5e6ea6b8a1a 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImpl.kt
@@ -1,6 +1,7 @@
 package net.corda.ledger.utxo.flow.impl.transaction.filtered
 
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.filtered.factory.UtxoFilteredTransactionFactory
@@ -52,7 +53,7 @@ data class UtxoFilteredTransactionBuilderImpl(
             signatories = ComponentGroupFilterParameters.AuditProof(
                 UtxoComponentGroup.SIGNATORIES.ordinal,
                 PublicKey::class.java,
-                predicate
+                AuditProofPredicate.Content(predicate)
             )
         )
     }
@@ -73,7 +74,7 @@ data class UtxoFilteredTransactionBuilderImpl(
             inputStates = ComponentGroupFilterParameters.AuditProof(
                 UtxoComponentGroup.INPUTS.ordinal,
                 StateRef::class.java,
-                predicate
+                AuditProofPredicate.Content(predicate)
             )
         )
     }
@@ -94,7 +95,7 @@ data class UtxoFilteredTransactionBuilderImpl(
             referenceStates = ComponentGroupFilterParameters.AuditProof(
                 UtxoComponentGroup.REFERENCES.ordinal,
                 StateRef::class.java,
-                predicate
+                AuditProofPredicate.Content(predicate)
             )
         )
     }
@@ -115,7 +116,18 @@ data class UtxoFilteredTransactionBuilderImpl(
             outputStates = ComponentGroupFilterParameters.AuditProof(
                 UtxoComponentGroup.OUTPUTS.ordinal,
                 ContractState::class.java,
-                predicate
+                AuditProofPredicate.Content(predicate)
+            )
+        )
+    }
+
+    @Suspendable
+    override fun withOutputStates(indexes: List<Int>): UtxoFilteredTransactionBuilderInternal {
+        return copy(
+            outputStates = ComponentGroupFilterParameters.AuditProof(
+                UtxoComponentGroup.OUTPUTS.ordinal,
+                ContractState::class.java,
+                AuditProofPredicate.Index(indexes)
             )
         )
     }
@@ -136,7 +148,7 @@ data class UtxoFilteredTransactionBuilderImpl(
             commands = ComponentGroupFilterParameters.AuditProof(
                 UtxoComponentGroup.COMMANDS.ordinal,
                 Command::class.java,
-                predicate
+                AuditProofPredicate.Content(predicate)
             )
         )
     }
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImpl.kt
index 35d237b8b53..69a29ad0a0d 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImpl.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImpl.kt
@@ -1,6 +1,7 @@
 package net.corda.ledger.utxo.flow.impl.transaction.filtered.factory
 
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.common.flow.transaction.filtered.factory.FilteredTransactionFactory
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup.METADATA
@@ -41,10 +42,14 @@ class UtxoFilteredTransactionFactoryImpl @Activate constructor(
         filteredTransactionBuilder: UtxoFilteredTransactionBuilderInternal
     ): UtxoFilteredTransaction {
         val notaryAndTimeWindow = if (filteredTransactionBuilder.notary || filteredTransactionBuilder.timeWindow) {
-            ComponentGroupFilterParameters.AuditProof(NOTARY.ordinal, Any::class.java) {
-                filteredTransactionBuilder.notary && (it is MemberX500Name || it is PublicKey) || // notary components
-                    filteredTransactionBuilder.timeWindow && it is TimeWindow // time window
-            }
+            ComponentGroupFilterParameters.AuditProof(
+                NOTARY.ordinal,
+                Any::class.java,
+                AuditProofPredicate.Content {
+                    filteredTransactionBuilder.notary && (it is MemberX500Name || it is PublicKey) || // notary components
+                        filteredTransactionBuilder.timeWindow && it is TimeWindow // time window
+                }
+            )
         } else {
             null
         }
@@ -53,7 +58,11 @@ class UtxoFilteredTransactionFactoryImpl @Activate constructor(
             filteredTransactionFactory.create(
                 signedTransaction.wireTransaction,
                 listOfNotNull(
-                    ComponentGroupFilterParameters.AuditProof(METADATA.ordinal, TransactionMetadata::class.java) { true },
+                    ComponentGroupFilterParameters.AuditProof(
+                        METADATA.ordinal,
+                        TransactionMetadata::class.java,
+                        AuditProofPredicate.Content { true }
+                    ),
                     notaryAndTimeWindow,
                     filteredTransactionBuilder.signatories,
                     filteredTransactionBuilder.inputStates,
@@ -61,15 +70,17 @@ class UtxoFilteredTransactionFactoryImpl @Activate constructor(
                     (filteredTransactionBuilder.outputStates as? ComponentGroupFilterParameters.AuditProof<*>)?.let { _ ->
                         ComponentGroupFilterParameters.AuditProof(
                             UtxoComponentGroup.OUTPUTS_INFO.ordinal,
-                            UtxoOutputInfoComponent::class.java
-                        ) { true }
+                            UtxoOutputInfoComponent::class.java,
+                            AuditProofPredicate.Content { true }
+                        )
                     },
                     filteredTransactionBuilder.outputStates,
                     (filteredTransactionBuilder.commands as? ComponentGroupFilterParameters.AuditProof<*>)?.let { _ ->
                         ComponentGroupFilterParameters.AuditProof(
                             UtxoComponentGroup.COMMANDS_INFO.ordinal,
-                            List::class.java
-                        ) { true }
+                            List::class.java,
+                            AuditProofPredicate.Content { true }
+                        )
                     },
                     filteredTransactionBuilder.commands
                 )
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/amqp/UtxoSignedTransactionSerializer.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/amqp/UtxoSignedTransactionSerializer.kt
index 546b693a1ec..c6a52645a80 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/amqp/UtxoSignedTransactionSerializer.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/amqp/UtxoSignedTransactionSerializer.kt
@@ -5,6 +5,7 @@ import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInter
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionImpl
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.sandbox.type.SandboxConstants.CORDA_UNINJECTABLE_SERVICE
 import net.corda.sandbox.type.UsedByFlow
 import net.corda.serialization.BaseProxySerializer
@@ -28,7 +29,9 @@ class UtxoSignedTransactionSerializer @Activate constructor(
     @Reference(service = TransactionSignatureServiceInternal::class)
     private val transactionSignatureService: TransactionSignatureServiceInternal,
     @Reference(service = UtxoLedgerTransactionFactory::class)
-    private val utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory
+    private val utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory,
+    @Reference(service = NotarySignatureVerificationServiceInternal::class)
+    private val notarySignatureVerificationService: NotarySignatureVerificationServiceInternal
 ) : BaseProxySerializer<UtxoSignedTransactionInternal, UtxoSignedTransactionProxy>(), UsedByFlow {
     private companion object {
         private const val VERSION_1 = 1
@@ -58,6 +61,7 @@ class UtxoSignedTransactionSerializer @Activate constructor(
                 UtxoSignedTransactionImpl(
                     serializationService,
                     transactionSignatureService,
+                    notarySignatureVerificationService,
                     utxoLedgerTransactionFactory,
                     proxy.wireTransaction,
                     proxy.signatures
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/kryo/UtxoSignedTransactionKryoSerializer.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/kryo/UtxoSignedTransactionKryoSerializer.kt
index 6f597229a4d..8c6bc5aaa18 100644
--- a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/kryo/UtxoSignedTransactionKryoSerializer.kt
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/serializer/kryo/UtxoSignedTransactionKryoSerializer.kt
@@ -5,6 +5,7 @@ import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInter
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionImpl
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.sandbox.type.SandboxConstants.CORDA_UNINJECTABLE_SERVICE
 import net.corda.sandbox.type.UsedByFlow
 import net.corda.serialization.checkpoint.CheckpointInput
@@ -28,7 +29,9 @@ class UtxoSignedTransactionKryoSerializer @Activate constructor(
     @Reference(service = TransactionSignatureServiceInternal::class)
     private val transactionSignatureService: TransactionSignatureServiceInternal,
     @Reference(service = UtxoLedgerTransactionFactory::class)
-    private val utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory
+    private val utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory,
+    @Reference(service = NotarySignatureVerificationServiceInternal::class)
+    private val notarySignatureVerificationService: NotarySignatureVerificationServiceInternal
 ) : CheckpointInternalCustomSerializer<UtxoSignedTransactionInternal>, UsedByFlow {
     override val type: Class<UtxoSignedTransactionInternal> get() = UtxoSignedTransactionInternal::class.java
 
@@ -45,6 +48,7 @@ class UtxoSignedTransactionKryoSerializer @Activate constructor(
         return UtxoSignedTransactionImpl(
             serialisationService,
             transactionSignatureService,
+            notarySignatureVerificationService,
             utxoLedgerTransactionFactory,
             wireTransaction,
             signatures
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImpl.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImpl.kt
new file mode 100644
index 00000000000..fa40fb94d0b
--- /dev/null
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImpl.kt
@@ -0,0 +1,87 @@
+package net.corda.ledger.utxo.flow.impl.transaction.verifier
+
+import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInternal
+import net.corda.sandbox.type.UsedByFlow
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.crypto.CompositeKey
+import net.corda.v5.crypto.KeyUtils
+import net.corda.v5.crypto.SecureHash
+import net.corda.v5.ledger.common.transaction.TransactionSignatureException
+import net.corda.v5.ledger.common.transaction.TransactionSignatureService
+import net.corda.v5.ledger.common.transaction.TransactionWithMetadata
+import net.corda.v5.ledger.utxo.NotarySignatureVerificationService
+import net.corda.v5.serialization.SingletonSerializeAsToken
+import org.osgi.service.component.annotations.Activate
+import org.osgi.service.component.annotations.Component
+import org.osgi.service.component.annotations.Reference
+import org.osgi.service.component.annotations.ServiceScope.PROTOTYPE
+import java.security.PublicKey
+
+@Component(
+    service = [NotarySignatureVerificationService::class, NotarySignatureVerificationServiceInternal::class, UsedByFlow::class],
+    scope = PROTOTYPE
+)
+class NotarySignatureVerificationServiceImpl @Activate constructor(
+    @Reference(service = TransactionSignatureService::class)
+    private val transactionSignatureService: TransactionSignatureService
+) : NotarySignatureVerificationService, NotarySignatureVerificationServiceInternal, UsedByFlow, SingletonSerializeAsToken {
+    override fun verifyNotarySignatures(
+        transaction: TransactionWithMetadata,
+        notaryKey: PublicKey,
+        signatures: List<DigitalSignatureAndMetadata>,
+        keyIdToNotaryKeys: MutableMap<String, Map<SecureHash, PublicKey>>
+    ) {
+        val notaryPublicKeysWithValidSignatures = signatures.mapNotNull {
+            val publicKey =
+                getNotaryPublicKeyByKeyId(it.by, notaryKey, keyIdToNotaryKeys)
+            if (publicKey != null) {
+                try {
+                    (transactionSignatureService as TransactionSignatureServiceInternal).verifySignature(transaction, it, publicKey)
+                    publicKey
+                } catch (e: Exception) {
+                    throw TransactionSignatureException(
+                        transaction.id,
+                        "Failed to verify signature of ${it.signature} for transaction $transaction. Message: ${e.message}",
+                        e
+                    )
+                }
+            } else {
+                null
+            }
+        }.toSet()
+        // If the notary service key (composite key) is provided we need to make sure it contains the key the
+        // transaction was signed with. This means it was signed with one of the notary VNodes (worker).
+        if (!KeyUtils.isKeyFulfilledBy(notaryKey, notaryPublicKeysWithValidSignatures)) {
+            throw TransactionSignatureException(
+                transaction.id,
+                "Notary signing keys $notaryPublicKeysWithValidSignatures did not fulfil " +
+                    "requirements of notary service key $notaryKey",
+                null
+            )
+        }
+    }
+
+    override fun getNotaryPublicKeyByKeyId(
+        keyId: SecureHash,
+        notaryKey: PublicKey,
+        keyIdToNotaryKeys: MutableMap<String, Map<SecureHash, PublicKey>>
+    ): PublicKey? {
+        val keyIdToPublicKey = keyIdToNotaryKeys.getOrPut(keyId.algorithm) {
+            // Prepare keyIds for all public keys related to the notary for the relevant algorithm
+            getKeyOrLeafKeys(notaryKey).associateBy {
+                (transactionSignatureService as TransactionSignatureServiceInternal).getIdOfPublicKey(
+                    it,
+                    keyId.algorithm
+                )
+            }
+        }
+        return keyIdToPublicKey[keyId]
+    }
+
+    override fun getKeyOrLeafKeys(publicKey: PublicKey): List<PublicKey> {
+        return when (publicKey) {
+            is CompositeKey -> publicKey.leafKeys.toList()
+            else -> listOf(publicKey)
+        }
+    }
+}
diff --git a/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceInternal.kt b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceInternal.kt
new file mode 100644
index 00000000000..df7d8d5ae94
--- /dev/null
+++ b/components/ledger/ledger-utxo-flow/src/main/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceInternal.kt
@@ -0,0 +1,15 @@
+package net.corda.ledger.utxo.flow.impl.transaction.verifier
+
+import net.corda.v5.crypto.SecureHash
+import net.corda.v5.ledger.utxo.NotarySignatureVerificationService
+import java.security.PublicKey
+
+interface NotarySignatureVerificationServiceInternal : NotarySignatureVerificationService {
+    fun getNotaryPublicKeyByKeyId(
+        keyId: SecureHash,
+        notaryKey: PublicKey,
+        keyIdToNotaryKeys: MutableMap<String, Map<SecureHash, PublicKey>>
+    ): PublicKey?
+
+    fun getKeyOrLeafKeys(publicKey: PublicKey): List<PublicKey>
+}
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerPersistenceServiceImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerPersistenceServiceImplTest.kt
index 97ed144d4af..1615dffc1b4 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerPersistenceServiceImplTest.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/persistence/UtxoLedgerPersistenceServiceImplTest.kt
@@ -26,6 +26,7 @@ import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionImpl
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoSignedTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.sandboxgroupcontext.CurrentSandboxGroupContext
 import net.corda.sandboxgroupcontext.SandboxGroupContext
 import net.corda.sandboxgroupcontext.VirtualNodeContext
@@ -57,6 +58,7 @@ class UtxoLedgerPersistenceServiceImplTest {
     private val externalEventExecutor = mock<ExternalEventExecutor>()
     private val serializationService = mock<SerializationService>()
     private val transactionSignatureService = mock<TransactionSignatureServiceInternal>()
+    private val notarySignatureVerificationService = mock<NotarySignatureVerificationServiceInternal>()
     private val utxoSignedTransactionFactory = mock<UtxoSignedTransactionFactory>()
     private val utxoLedgerTransactionFactory = mock<UtxoLedgerTransactionFactory>()
     private val sandbox = mock<SandboxGroupContext>()
@@ -174,6 +176,7 @@ class UtxoLedgerPersistenceServiceImplTest {
         val expectedObj = UtxoSignedTransactionImpl(
             serializationService,
             transactionSignatureService,
+            notarySignatureVerificationService,
             mock<UtxoLedgerTransactionFactory>(),
             wireTransaction,
             signatures
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImplTest.kt
index b04ddc7c220..2b5f8e4c78f 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImplTest.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/UtxoSignedTransactionImplTest.kt
@@ -9,12 +9,9 @@ import net.corda.ledger.utxo.testkit.getUtxoStateExample
 import net.corda.ledger.utxo.testkit.utxoTimeWindowExample
 import net.corda.v5.base.exceptions.CordaRuntimeException
 import net.corda.v5.base.types.MemberX500Name
-import net.corda.v5.ledger.common.transaction.TransactionSignatureException
 import net.corda.v5.membership.NotaryInfo
-import org.assertj.core.api.Assertions
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertDoesNotThrow
 import org.junit.jupiter.api.assertThrows
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
@@ -51,23 +48,6 @@ internal class UtxoSignedTransactionImplTest : UtxoLedgerTest() {
             .toSignedTransaction() as UtxoSignedTransactionInternal
     }
 
-    @Test
-    fun `verifyAttachedNotarySignature throws on unnotarized transaction`() {
-        Assertions.assertThatThrownBy { signedTransaction.verifyAttachedNotarySignature() }.isInstanceOf(
-            TransactionSignatureException::class.java
-        )
-            .hasMessageContaining("did not fulfil requirements of notary service key")
-    }
-
-    @Test
-    fun `verifyAttachedNotarySignature does not throw on notarized transaction`() {
-        val sig = getSignatureWithMetadataExample(notaryNode1PublicKey)
-        signedTransaction = signedTransaction.addSignature(sig)
-        assertDoesNotThrow {
-            signedTransaction.verifyAttachedNotarySignature()
-        }
-    }
-
     @Test
     fun `receiving notary signature with key id not matching notary key throws`() {
         val notExistingNotaryKey = kpg.generateKeyPair().public
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImplTest.kt
index d9ccf23562c..4e193793d6f 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImplTest.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionBuilderImplTest.kt
@@ -1,6 +1,8 @@
 package net.corda.ledger.utxo.flow.impl.transaction.filtered
 
-import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.SizeProof
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
 import net.corda.v5.ledger.utxo.Command
 import net.corda.v5.ledger.utxo.ContractState
@@ -28,126 +30,157 @@ class UtxoFilteredTransactionBuilderImplTest {
     @Test
     fun withSignatoriesSize() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withSignatoriesSize().signatories
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(SizeProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.SIGNATORIES.ordinal)
     }
 
     @Test
     fun withSignatories() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withSignatories().signatories
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.SIGNATORIES.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<PublicKey>).predicate.test(mock()))
-            .isEqualTo(true)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<PublicKey>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(true)
     }
 
     @Test
     fun `withSignatories predicate`() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withSignatories { false }.signatories
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.SIGNATORIES.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<PublicKey>).predicate.test(mock()))
-            .isEqualTo(false)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<PublicKey>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(false)
     }
 
     @Test
     fun withInputStatesSize() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withInputStatesSize().inputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(SizeProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.INPUTS.ordinal)
     }
 
     @Test
     fun withInputStates() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withInputStates().inputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.INPUTS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<StateRef>).predicate.test(mock()))
-            .isEqualTo(true)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<StateRef>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(true)
     }
 
     @Test
     fun `withInputStates predicate`() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withInputStates { false }.inputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.INPUTS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<StateRef>).predicate.test(mock()))
-            .isEqualTo(false)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<StateRef>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(false)
     }
 
     @Test
     fun withReferenceStatesSize() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withReferenceStatesSize().referenceStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(SizeProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.REFERENCES.ordinal)
     }
 
     @Test
     fun withReferenceStates() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withReferenceStates().referenceStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.REFERENCES.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<StateRef>).predicate.test(mock()))
-            .isEqualTo(true)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<StateRef>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(true)
     }
 
     @Test
     fun `withReferenceStates predicate`() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withReferenceStates { false }.referenceStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.REFERENCES.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<StateRef>).predicate.test(mock()))
-            .isEqualTo(false)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<StateRef>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(false)
     }
 
     @Test
     fun withOutputStatesSize() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withOutputStatesSize().outputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(SizeProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.OUTPUTS.ordinal)
     }
 
     @Test
     fun withOutputStates() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withOutputStates().outputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.OUTPUTS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<ContractState>).predicate.test(mock()))
-            .isEqualTo(true)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<ContractState>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(true)
     }
 
     @Test
     fun `withOutputStates predicate`() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withOutputStates { false }.outputStates
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.OUTPUTS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<ContractState>).predicate.test(mock()))
-            .isEqualTo(false)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<ContractState>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(false)
+    }
+
+    @Test
+    fun `withOutputStates indexes`() {
+        val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withOutputStates(listOf(0)).outputStates
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
+        assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.OUTPUTS.ordinal)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<ContractState>).predicate as AuditProofPredicate.Index)
+                .test(0)
+        ).isEqualTo(true)
     }
 
     @Test
     fun withCommandsSize() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withCommandsSize().commands
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(SizeProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.COMMANDS.ordinal)
     }
 
     @Test
     fun withCommands() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withCommands().commands
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.COMMANDS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<Command>).predicate.test(mock()))
-            .isEqualTo(true)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<Command>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(true)
     }
 
     @Test
     fun `withCommands predicate`() {
         val componentGroupFilterParameters = utxoFilteredTransactionBuilder.withCommands { false }.commands
-        assertThat(componentGroupFilterParameters).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(componentGroupFilterParameters).isInstanceOf(AuditProof::class.java)
         assertThat((componentGroupFilterParameters!!).componentGroupIndex).isEqualTo(UtxoComponentGroup.COMMANDS.ordinal)
-        assertThat((componentGroupFilterParameters as ComponentGroupFilterParameters.AuditProof<Command>).predicate.test(mock()))
-            .isEqualTo(false)
+        assertThat(
+            ((componentGroupFilterParameters as AuditProof<Command>).predicate as AuditProofPredicate.Content)
+                .test(mock())
+        ).isEqualTo(false)
     }
 
     @Test
@@ -173,11 +206,11 @@ class UtxoFilteredTransactionBuilderImplTest {
             .withCommands() as UtxoFilteredTransactionBuilderInternal
         assertThat(builder.notary).isTrue
         assertThat(builder.timeWindow).isTrue
-        assertThat(builder.signatories).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
-        assertThat(builder.inputStates).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
-        assertThat(builder.referenceStates).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
-        assertThat(builder.outputStates).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
-        assertThat(builder.commands).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(builder.signatories).isInstanceOf(SizeProof::class.java)
+        assertThat(builder.inputStates).isInstanceOf(AuditProof::class.java)
+        assertThat(builder.referenceStates).isInstanceOf(SizeProof::class.java)
+        assertThat(builder.outputStates).isInstanceOf(SizeProof::class.java)
+        assertThat(builder.commands).isInstanceOf(AuditProof::class.java)
     }
 
     @Test
@@ -189,9 +222,9 @@ class UtxoFilteredTransactionBuilderImplTest {
         assertThat(builder.notary).isFalse
         assertThat(builder.timeWindow).isFalse
         assertThat(builder.signatories).isNull()
-        assertThat(builder.inputStates).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
-        assertThat(builder.referenceStates).isInstanceOf(ComponentGroupFilterParameters.SizeProof::class.java)
+        assertThat(builder.inputStates).isInstanceOf(AuditProof::class.java)
+        assertThat(builder.referenceStates).isInstanceOf(SizeProof::class.java)
         assertThat(builder.outputStates).isNull()
-        assertThat(builder.commands).isInstanceOf(ComponentGroupFilterParameters.AuditProof::class.java)
+        assertThat(builder.commands).isInstanceOf(AuditProof::class.java)
     }
 }
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionImplTest.kt
index c482637f429..87029607b9e 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionImplTest.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionImplTest.kt
@@ -2,6 +2,7 @@ package net.corda.ledger.utxo.flow.impl.transaction.filtered
 
 import net.corda.ledger.common.data.transaction.TransactionMetadataImpl
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.common.testkit.publicKeyExample
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
 import net.corda.ledger.utxo.data.transaction.UtxoOutputInfoComponent
@@ -24,8 +25,9 @@ class UtxoFilteredTransactionImplTest : UtxoFilteredTransactionTestBase() {
             componentGroupFilterParameters = listOf(
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.METADATA.ordinal,
-                    TransactionMetadataImpl::class.java
-                ) { true }
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                )
             )
         )
 
@@ -78,26 +80,35 @@ class UtxoFilteredTransactionImplTest : UtxoFilteredTransactionTestBase() {
             componentGroupFilterParameters = listOf(
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.METADATA.ordinal,
-                    TransactionMetadataImpl::class.java
-                ) { true },
-                ComponentGroupFilterParameters.AuditProof(UtxoComponentGroup.NOTARY.ordinal, Any::class.java) { true },
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    UtxoComponentGroup.NOTARY.ordinal,
+                    Any::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS_INFO.ordinal,
-                    UtxoOutputInfoComponent::class.java
-                ) { true },
+                    UtxoOutputInfoComponent::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS_INFO.ordinal),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.INPUTS.ordinal,
-                    StateRef::class.java
-                ) { it.index != 0 },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { it.index != 0 }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.REFERENCES.ordinal,
-                    StateRef::class.java
-                ) { it.index != 0 },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { it.index != 0 }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS.ordinal,
-                    ContractState::class.java
-                ) { true },
+                    ContractState::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS.ordinal),
             )
         )
@@ -135,32 +146,41 @@ class UtxoFilteredTransactionImplTest : UtxoFilteredTransactionTestBase() {
             componentGroupFilterParameters = listOf(
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.METADATA.ordinal,
-                    TransactionMetadataImpl::class.java
-                ) { true },
-                ComponentGroupFilterParameters.AuditProof(UtxoComponentGroup.NOTARY.ordinal, Any::class.java) {
-                    when (it) {
-                        is MemberX500Name -> false
-                        is PublicKey -> false
-                        else -> true
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    UtxoComponentGroup.NOTARY.ordinal,
+                    Any::class.java,
+                    AuditProofPredicate.Content {
+                        when (it) {
+                            is MemberX500Name -> false
+                            is PublicKey -> false
+                            else -> true
+                        }
                     }
-                },
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS_INFO.ordinal,
-                    UtxoOutputInfoComponent::class.java
-                ) { true },
+                    UtxoOutputInfoComponent::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS_INFO.ordinal),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.INPUTS.ordinal,
-                    StateRef::class.java
-                ) { true },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.REFERENCES.ordinal,
-                    StateRef::class.java
-                ) { true },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS.ordinal,
-                    ContractState::class.java
-                ) { true },
+                    ContractState::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS.ordinal),
             )
         )
@@ -179,25 +199,30 @@ class UtxoFilteredTransactionImplTest : UtxoFilteredTransactionTestBase() {
             componentGroupFilterParameters = listOf(
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.METADATA.ordinal,
-                    TransactionMetadataImpl::class.java
-                ) { true },
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS_INFO.ordinal,
-                    UtxoOutputInfoComponent::class.java
-                ) { true },
+                    UtxoOutputInfoComponent::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS_INFO.ordinal),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.INPUTS.ordinal,
-                    StateRef::class.java
-                ) { true },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.REFERENCES.ordinal,
-                    StateRef::class.java
-                ) { true },
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS.ordinal,
-                    ContractState::class.java
-                ) { true },
+                    ContractState::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS.ordinal),
             )
         )
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionTestBase.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionTestBase.kt
index 3d586707ab6..7607184a5a0 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionTestBase.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/UtxoFilteredTransactionTestBase.kt
@@ -12,6 +12,7 @@ import net.corda.ledger.common.data.transaction.WireTransaction
 import net.corda.ledger.common.flow.impl.transaction.filtered.factory.FilteredTransactionFactoryImpl
 import net.corda.ledger.common.flow.transaction.filtered.FilteredTransaction
 import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters
+import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupFilterParameters.AuditProof.AuditProofPredicate
 import net.corda.ledger.common.testkit.getWireTransactionExample
 import net.corda.ledger.common.testkit.publicKeyExample
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
@@ -119,19 +120,30 @@ open class UtxoFilteredTransactionTestBase {
             componentGroupFilterParameters = listOf(
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.METADATA.ordinal,
-                    TransactionMetadataImpl::class.java
-                ) { true },
-                ComponentGroupFilterParameters.AuditProof(UtxoComponentGroup.NOTARY.ordinal, Any::class.java) { true },
+                    TransactionMetadataImpl::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
+                ComponentGroupFilterParameters.AuditProof(
+                    UtxoComponentGroup.NOTARY.ordinal,
+                    Any::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS_INFO.ordinal,
-                    UtxoOutputInfoComponent::class.java
-                ) { true },
+                    UtxoOutputInfoComponent::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS_INFO.ordinal),
-                ComponentGroupFilterParameters.AuditProof(UtxoComponentGroup.INPUTS.ordinal, StateRef::class.java) { true },
+                ComponentGroupFilterParameters.AuditProof(
+                    UtxoComponentGroup.INPUTS.ordinal,
+                    StateRef::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.AuditProof(
                     UtxoComponentGroup.OUTPUTS.ordinal,
-                    ContractState::class.java
-                ) { true },
+                    ContractState::class.java,
+                    AuditProofPredicate.Content { true }
+                ),
                 ComponentGroupFilterParameters.SizeProof(UtxoComponentGroup.COMMANDS.ordinal),
             )
         )
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImplTest.kt
index b1eb40f07a0..35a8e9938d0 100644
--- a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImplTest.kt
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/filtered/factory/UtxoFilteredTransactionFactoryImplTest.kt
@@ -4,12 +4,14 @@ import net.corda.ledger.common.flow.transaction.filtered.factory.ComponentGroupF
 import net.corda.ledger.common.flow.transaction.filtered.factory.FilteredTransactionFactory
 import net.corda.ledger.common.testkit.publicKeyExample
 import net.corda.ledger.utxo.data.transaction.UtxoComponentGroup
+import net.corda.ledger.utxo.flow.impl.timewindow.TimeWindowBetweenImpl
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.filtered.UtxoFilteredTransactionBuilderImpl
 import net.corda.ledger.utxo.flow.impl.transaction.filtered.UtxoFilteredTransactionBuilderInternal
 import net.corda.ledger.utxo.test.UtxoLedgerTest
 import net.corda.ledger.utxo.testkit.notaryX500Name
 import net.corda.ledger.utxo.testkit.utxoTimeWindowExample
+import net.corda.v5.base.types.MemberX500Name
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
@@ -18,6 +20,8 @@ import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
+import java.security.PublicKey
+import java.util.function.Predicate
 
 class UtxoFilteredTransactionFactoryImplTest : UtxoLedgerTest() {
 
@@ -75,9 +79,9 @@ class UtxoFilteredTransactionFactoryImplTest : UtxoLedgerTest() {
         assertThat(componentGroups).containsExactly(UtxoComponentGroup.METADATA.ordinal, UtxoComponentGroup.NOTARY.ordinal)
 
         val predicate = componentGroupFilterParameters[1].let { (it as ComponentGroupFilterParameters.AuditProof<Any>).predicate }
-        assertThat(predicate.test(notaryX500Name)).isTrue
-        assertThat(predicate.test(publicKeyExample)).isTrue
-        assertThat(predicate.test(utxoTimeWindowExample)).isFalse
+        assertThat((predicate as Predicate<MemberX500Name>).test(notaryX500Name)).isTrue
+        assertThat((predicate as Predicate<PublicKey>).test(publicKeyExample)).isTrue
+        assertThat((predicate as Predicate<TimeWindowBetweenImpl>).test(utxoTimeWindowExample)).isFalse
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -93,9 +97,9 @@ class UtxoFilteredTransactionFactoryImplTest : UtxoLedgerTest() {
         assertThat(componentGroups).containsExactly(UtxoComponentGroup.METADATA.ordinal, UtxoComponentGroup.NOTARY.ordinal)
 
         val predicate = componentGroupFilterParameters[1].let { (it as ComponentGroupFilterParameters.AuditProof<Any>).predicate }
-        assertThat(predicate.test(notaryX500Name)).isFalse
-        assertThat(predicate.test(publicKeyExample)).isFalse
-        assertThat(predicate.test(utxoTimeWindowExample)).isTrue
+        assertThat((predicate as Predicate<MemberX500Name>).test(notaryX500Name)).isFalse
+        assertThat((predicate as Predicate<PublicKey>).test(publicKeyExample)).isFalse
+        assertThat((predicate as Predicate<TimeWindowBetweenImpl>).test(utxoTimeWindowExample)).isTrue
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -112,9 +116,9 @@ class UtxoFilteredTransactionFactoryImplTest : UtxoLedgerTest() {
         assertThat(componentGroups).containsExactly(UtxoComponentGroup.METADATA.ordinal, UtxoComponentGroup.NOTARY.ordinal)
 
         val predicate = componentGroupFilterParameters[1].let { (it as ComponentGroupFilterParameters.AuditProof<Any>).predicate }
-        assertThat(predicate.test(notaryX500Name)).isTrue
-        assertThat(predicate.test(publicKeyExample)).isTrue
-        assertThat(predicate.test(utxoTimeWindowExample)).isTrue
+        assertThat((predicate as Predicate<MemberX500Name>).test(notaryX500Name)).isTrue
+        assertThat((predicate as Predicate<PublicKey>).test(publicKeyExample)).isTrue
+        assertThat((predicate as Predicate<TimeWindowBetweenImpl>).test(utxoTimeWindowExample)).isTrue
     }
 
     @Test
diff --git a/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImplTest.kt b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImplTest.kt
new file mode 100644
index 00000000000..a2dd65738bb
--- /dev/null
+++ b/components/ledger/ledger-utxo-flow/src/test/kotlin/net/corda/ledger/utxo/flow/impl/transaction/verifier/NotarySignatureVerificationServiceImplTest.kt
@@ -0,0 +1,160 @@
+package net.corda.ledger.utxo.flow.impl.transaction.verifier
+
+import net.corda.crypto.cipher.suite.SignatureSpecImpl
+import net.corda.crypto.core.DigitalSignatureWithKeyId
+import net.corda.crypto.core.SecureHashImpl
+import net.corda.crypto.core.fullIdHash
+import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInternal
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.application.crypto.DigitalSignatureMetadata
+import net.corda.v5.crypto.CompositeKey
+import net.corda.v5.crypto.SecureHash
+import net.corda.v5.ledger.common.transaction.TransactionSignatureException
+import net.corda.v5.ledger.common.transaction.TransactionWithMetadata
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.security.MessageDigest
+import java.security.PublicKey
+import java.time.Instant
+import kotlin.test.assertEquals
+
+class NotarySignatureVerificationServiceImplTest {
+    private lateinit var notarySignatureVerificationService: NotarySignatureVerificationServiceImpl
+    private val transactionSignatureServiceInternal = mock<TransactionSignatureServiceInternal>()
+
+    // notarykeys
+    private val notaryVNodeAliceKey = mock<PublicKey>().also { whenever(it.encoded).thenReturn(byteArrayOf(0x01)) }
+    private val notaryVNodeBobKey = mock<PublicKey>().also { whenever(it.encoded).thenReturn(byteArrayOf(0x02)) }
+    private val notaryServiceCompositeKey = mock<CompositeKey> {
+        on { leafKeys } doReturn setOf(notaryVNodeAliceKey)
+        on { isFulfilledBy(notaryVNodeAliceKey) } doReturn true
+        on { isFulfilledBy(setOf(notaryVNodeAliceKey)) } doReturn true
+    }
+
+    // transactionId
+    private val transaction = mock<TransactionWithMetadata>().also {
+        whenever(it.id).thenReturn(transactionId)
+    }
+    private val transactionId = mock<SecureHash>()
+
+    // signatures
+    private val signatureAlice = digitalSignatureAndMetadata(notaryVNodeAliceKey, byteArrayOf(1, 2, 0))
+    private val signatureBob = digitalSignatureAndMetadata(notaryVNodeBobKey, byteArrayOf(1, 2, 3))
+
+    private val keyIdToNotaryKeysMap: MutableMap<String, Map<SecureHash, PublicKey>> = mutableMapOf()
+
+    // keyIds
+    private val keyIdOfAlice = SecureHashImpl(
+        signatureAlice.by.algorithm,
+        MessageDigest.getInstance(signatureAlice.by.algorithm).digest(notaryVNodeAliceKey.encoded)
+    )
+    private val keyIdOfBob = SecureHashImpl(
+        signatureBob.by.algorithm,
+        MessageDigest.getInstance(signatureBob.by.algorithm).digest(notaryVNodeBobKey.encoded)
+    )
+
+    @BeforeEach
+    fun setup() {
+        notarySignatureVerificationService = NotarySignatureVerificationServiceImpl(
+            transactionSignatureServiceInternal
+        )
+    }
+
+    @Test
+    fun `notary signature verification with a valid public key`() {
+        whenever(
+            transactionSignatureServiceInternal.getIdOfPublicKey(
+                notaryVNodeAliceKey,
+                signatureAlice.by.algorithm
+            )
+        ).thenReturn(keyIdOfAlice)
+        assertDoesNotThrow {
+            notarySignatureVerificationService.verifyNotarySignatures(
+                transaction,
+                notaryVNodeAliceKey,
+                listOf(signatureAlice),
+                keyIdToNotaryKeysMap
+            )
+        }
+    }
+
+    @Test
+    fun `notary signature verification with a valid composite key`() {
+        whenever(
+            transactionSignatureServiceInternal.getIdOfPublicKey(
+                notaryVNodeAliceKey,
+                signatureAlice.by.algorithm
+            )
+        ).thenReturn(keyIdOfAlice)
+        assertDoesNotThrow {
+            notarySignatureVerificationService.verifyNotarySignatures(
+                transaction,
+                notaryServiceCompositeKey,
+                listOf(signatureAlice),
+                keyIdToNotaryKeysMap
+            )
+        }
+    }
+
+    @Test
+    fun `notary signature verification with an invalid notary key`() {
+        whenever(
+            transactionSignatureServiceInternal.getIdOfPublicKey(
+                notaryVNodeBobKey,
+                signatureBob.by.algorithm
+            )
+        ).thenReturn(keyIdOfBob)
+
+        val exception = assertThrows<TransactionSignatureException> {
+            notarySignatureVerificationService.verifyNotarySignatures(
+                transaction,
+                notaryServiceCompositeKey,
+                listOf(signatureBob),
+                keyIdToNotaryKeysMap
+            )
+        }
+
+        assertEquals(
+            "Notary signing keys [] did not fulfil requirements of notary service key $notaryServiceCompositeKey",
+            exception.message
+        )
+    }
+
+    @Test
+    fun `notary signature verification with failed signature verification`() {
+        whenever(
+            transactionSignatureServiceInternal.getIdOfPublicKey(
+                notaryVNodeBobKey,
+                signatureBob.by.algorithm
+            )
+        ).thenReturn(keyIdOfBob)
+        whenever(transactionSignatureServiceInternal.verifySignature(transaction, signatureBob, notaryVNodeBobKey))
+            .thenThrow(TransactionSignatureException(transactionId, "", null))
+
+        val exception = assertThrows<TransactionSignatureException> {
+            notarySignatureVerificationService.verifyNotarySignatures(
+                transaction,
+                notaryVNodeBobKey,
+                listOf(signatureBob),
+                keyIdToNotaryKeysMap
+            )
+        }
+
+        assertEquals(
+            "Failed to verify signature of ${signatureBob.signature} for transaction $transaction. Message: ",
+            exception.message
+        )
+    }
+
+    private fun digitalSignatureAndMetadata(publicKey: PublicKey, byteArray: ByteArray): DigitalSignatureAndMetadata {
+        return DigitalSignatureAndMetadata(
+            DigitalSignatureWithKeyId(publicKey.fullIdHash(), byteArray),
+            DigitalSignatureMetadata(Instant.now(), SignatureSpecImpl("dummySignatureName"), emptyMap())
+        )
+    }
+}
diff --git a/gradle.properties b/gradle.properties
index b7727b0c042..18c34e8644e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -46,7 +46,7 @@ commonsTextVersion = 1.10.0
 bouncycastleVersion=1.76
 # Corda API libs revision (change in 4th digit indicates a breaking change)
 # Change to 5.2.0.xx-SNAPSHOT to pick up maven local published copy
-cordaApiVersion=5.2.0.12-beta+
+cordaApiVersion=5.2.0.13-beta+
 
 disruptorVersion=3.4.4
 felixConfigAdminVersion=1.9.26
diff --git a/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/CommonNotaryExceptions.kt b/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/CommonNotaryExceptions.kt
index 5620006eb5c..d6e7b8c3f89 100644
--- a/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/CommonNotaryExceptions.kt
+++ b/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/CommonNotaryExceptions.kt
@@ -102,6 +102,34 @@ class NotaryExceptionMalformedRequest(
     txId
 )
 
+/**
+ * Occurs when a valid signature is not found in received signatures.
+ *
+ * @property errorText The error text produced by signature verification
+ */
+@CordaSerializable
+class NotaryExceptionInvalidSignature(
+    val errorText: String?,
+    txId: SecureHash? = null
+) : NotaryExceptionFatal(
+    "Signature invalid Error: $errorText",
+    txId
+)
+
+/**
+ * Occurs when a received transaction is invalid.
+ *
+ * @property errorText The error text produced by transaction verification
+ */
+@CordaSerializable
+class NotaryExceptionTransactionVerificationFailure(
+    val errorText: String?,
+    txId: SecureHash? = null
+) : NotaryExceptionFatal(
+    "Transaction verification failure Error: $errorText",
+    txId
+)
+
 /**
  * Error type used for scenarios that were unexpected, or couldn't be mapped.
  *
diff --git a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryTransactionDetails.kt b/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/NotaryTransactionDetails.kt
similarity index 90%
rename from notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryTransactionDetails.kt
rename to notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/NotaryTransactionDetails.kt
index 1ba11a314ba..452b11fa700 100644
--- a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryTransactionDetails.kt
+++ b/notary-plugins/notary-plugin-common/src/main/kotlin/com/r3/corda/notary/plugin/common/NotaryTransactionDetails.kt
@@ -1,4 +1,4 @@
-package com.r3.corda.notary.plugin.nonvalidating.server
+package com.r3.corda.notary.plugin.common
 
 import net.corda.v5.base.types.MemberX500Name
 import net.corda.v5.crypto.SecureHash
@@ -12,7 +12,7 @@ import java.security.PublicKey
  * A representation of a transaction (non-validating). It is easier to perform operations on this representation than
  * on the actual transaction object (e.g. FilteredTransaction).
  */
-data class NonValidatingNotaryTransactionDetails(
+data class NotaryTransactionDetails(
     private val id: SecureHash,
     private val metadata: TransactionMetadata,
     val numOutputs: Int,
@@ -31,4 +31,4 @@ data class NonValidatingNotaryTransactionDetails(
     override fun getMetadata(): TransactionMetadata {
         return metadata
     }
-}
+}
\ No newline at end of file
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/build.gradle b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/build.gradle
new file mode 100644
index 00000000000..bd1d554ecc2
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/build.gradle
@@ -0,0 +1,34 @@
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'corda.common-publishing'
+    id 'net.corda.plugins.cordapp-cpk2'
+    id 'corda.javadoc-generation'
+}
+
+ext {
+    releasable = true
+}
+
+description 'Corda Contract Verifying Notary Plugin API'
+
+group 'com.r3.corda.notary.plugin.contractverifying'
+
+cordapp {
+    targetPlatformVersion platformVersion.toInteger()
+    workflow {
+        name "Corda Contract Verifying Notary - API"
+        versionId 1
+        vendor "R3"
+    }
+}
+
+dependencies {
+    cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
+
+    cordaProvided 'net.corda:corda-application'
+    cordaProvided 'net.corda:corda-notary-plugin'
+    cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
+    cordaProvided 'org.slf4j:slf4j-api'
+
+    cordapp project(":notary-plugins:notary-plugin-common")
+}
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/ContractVerifyingNotarizationPayload.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/ContractVerifyingNotarizationPayload.kt
new file mode 100644
index 00000000000..f6d55308f9f
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/ContractVerifyingNotarizationPayload.kt
@@ -0,0 +1,10 @@
+package com.r3.corda.notary.plugin.contractverifying.api
+
+import net.corda.v5.base.annotations.CordaSerializable
+import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction
+
+@CordaSerializable
+data class ContractVerifyingNotarizationPayload(
+    val initialTransaction: UtxoSignedTransaction,
+    val filteredTransactionsAndSignatures: List<FilteredTransactionAndSignatures>,
+)
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/FilteredTransactionAndSignatures.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/FilteredTransactionAndSignatures.kt
new file mode 100644
index 00000000000..244ae07f4b8
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-api/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/api/FilteredTransactionAndSignatures.kt
@@ -0,0 +1,11 @@
+package com.r3.corda.notary.plugin.contractverifying.api
+
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.base.annotations.CordaSerializable
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredTransaction
+
+@CordaSerializable
+data class FilteredTransactionAndSignatures(
+    val filteredTransaction: UtxoFilteredTransaction,
+    val signatures: List<DigitalSignatureAndMetadata>
+)
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
new file mode 100644
index 00000000000..0c9dbe0a872
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'corda.common-publishing'
+    id 'net.corda.plugins.cordapp-cpk2'
+    id 'corda.javadoc-generation'
+}
+
+ext {
+    releasable = true
+}
+
+description 'Corda Contract Verifying Notary Plugin Client'
+
+group 'com.r3.corda.notary.plugin.contractverifying'
+
+cordapp {
+    targetPlatformVersion platformVersion.toInteger()
+    workflow {
+        name "Corda Contract Verifying Notary - Client"
+        versionId 1
+        vendor "R3"
+    }
+}
+
+dependencies {
+    cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
+
+    cordaProvided 'net.corda:corda-application'
+    cordaProvided 'net.corda:corda-notary-plugin'
+    cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
+    cordaProvided 'org.slf4j:slf4j-api'
+
+    // Common package pulled in as transitive dependency through API
+    cordapp project(":notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-api")
+
+    testImplementation "org.assertj:assertj-core:$assertjVersion"
+    testImplementation "org.mockito:mockito-core:$mockitoVersion"
+    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+
+    testImplementation project(':libs:serialization:serialization-amqp')
+    testImplementation project(':libs:crypto:cipher-suite')
+    testImplementation project(":testing:crypto-testkit")
+}
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImpl.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImpl.kt
new file mode 100644
index 00000000000..2d2ac3af046
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImpl.kt
@@ -0,0 +1,111 @@
+package com.r3.corda.notary.plugin.contractverifying.client
+
+import com.r3.corda.notary.plugin.common.NotarizationResponse
+import com.r3.corda.notary.plugin.contractverifying.api.ContractVerifyingNotarizationPayload
+import com.r3.corda.notary.plugin.contractverifying.api.FilteredTransactionAndSignatures
+import net.corda.v5.application.crypto.DigestService
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.application.flows.CordaInject
+import net.corda.v5.application.flows.InitiatingFlow
+import net.corda.v5.application.messaging.FlowMessaging
+import net.corda.v5.base.annotations.Suspendable
+import net.corda.v5.base.annotations.VisibleForTesting
+import net.corda.v5.base.types.MemberX500Name
+import net.corda.v5.crypto.DigestAlgorithmName
+import net.corda.v5.ledger.notary.plugin.api.PluggableNotaryClientFlow
+import net.corda.v5.ledger.utxo.UtxoLedgerService
+import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction
+import org.slf4j.LoggerFactory
+
+@InitiatingFlow(protocol = "com.r3.corda.notary.plugin.contractverifying", version = [1])
+class ContractVerifyingNotaryClientFlowImpl(
+    private val signedTransaction: UtxoSignedTransaction,
+    private val notaryRepresentative: MemberX500Name
+) : PluggableNotaryClientFlow {
+    private companion object {
+        private val logger = LoggerFactory.getLogger(this::class.java.enclosingClass)
+    }
+
+    @CordaInject
+    private lateinit var flowMessaging: FlowMessaging
+
+    @CordaInject
+    private lateinit var digestService: DigestService
+
+    @CordaInject
+    private lateinit var utxoLedgerService: UtxoLedgerService
+
+    /**
+     * Constructor used for testing to initialize the necessary services
+     */
+    @VisibleForTesting
+    internal constructor(
+        stx: UtxoSignedTransaction,
+        notary: MemberX500Name,
+        flowMessaging: FlowMessaging,
+        utxoLedgerService: UtxoLedgerService,
+        digestService: DigestService
+    ): this(stx, notary) {
+        this.flowMessaging = flowMessaging
+        this.utxoLedgerService = utxoLedgerService
+        this.digestService = digestService
+    }
+
+    @Suspendable
+    override fun call(): List<DigitalSignatureAndMetadata> {
+        if (logger.isTraceEnabled) {
+            logger.trace("Notarizing transaction {} with notary {}", signedTransaction.id, notaryRepresentative)
+        }
+
+        val session = flowMessaging.initiateFlow(notaryRepresentative)
+        val payload = createPayload()
+        val notarizationResponse = session.sendAndReceive(
+            NotarizationResponse::class.java,
+            payload
+        )
+
+        return when (val error = notarizationResponse.error) {
+            null -> {
+                if (logger.isTraceEnabled) {
+                    logger.trace(
+                        "Received notarization response from notary service {} for transaction {}",
+                        signedTransaction.notaryName, signedTransaction.id
+                    )
+                }
+                notarizationResponse.signatures
+            }
+            else -> {
+                if (logger.isTraceEnabled) {
+                    logger.trace(
+                        "Received notarization error from notary service {}. Error: {}",
+                        signedTransaction.notaryName, error
+                    )
+                }
+                throw error
+            }
+        }
+    }
+
+    @Suspendable
+    internal fun createPayload(): ContractVerifyingNotarizationPayload {
+        val hashedNotaryKey = digestService.hash(signedTransaction.notaryKey.encoded, DigestAlgorithmName.SHA2_256)
+
+        val filteredDependenciesAndSignatures = signedTransaction.let { it.inputStateRefs + it.referenceStateRefs }
+            .groupBy { stateRef -> stateRef.transactionId }
+            .mapValues { (_, stateRefs) -> stateRefs.map { stateRef -> stateRef.index } }
+            .map { (transactionId, indexes) ->
+                val dependency = requireNotNull(utxoLedgerService.findSignedTransaction(transactionId)) {
+                    "Dependent transaction $transactionId does not exist"
+                }
+                FilteredTransactionAndSignatures(
+                    utxoLedgerService.filterSignedTransaction(dependency)
+                        .withOutputStates(indexes)
+                        .withNotary()
+                        .withTimeWindow()
+                        .build(),
+                    dependency.signatures.filter { hashedNotaryKey == it.by }
+                )
+            }
+        return ContractVerifyingNotarizationPayload(signedTransaction, filteredDependenciesAndSignatures)
+    }
+}
\ No newline at end of file
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImplTest.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImplTest.kt
new file mode 100644
index 00000000000..0a1d24a793e
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/client/ContractVerifyingNotaryClientFlowImplTest.kt
@@ -0,0 +1,145 @@
+package com.r3.corda.notary.plugin.contractverifying.client
+
+import com.r3.corda.notary.plugin.common.NotarizationResponse
+import com.r3.corda.notary.plugin.common.NotaryExceptionReferenceStateUnknown
+import net.corda.crypto.cipher.suite.SignatureSpecImpl
+import net.corda.crypto.testkit.SecureHashUtils
+import net.corda.v5.application.crypto.DigestService
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.application.crypto.DigitalSignatureMetadata
+import net.corda.v5.application.messaging.FlowMessaging
+import net.corda.v5.application.messaging.FlowSession
+import net.corda.v5.base.types.MemberX500Name
+import net.corda.v5.crypto.DigestAlgorithmName
+import net.corda.v5.ledger.utxo.StateRef
+import net.corda.v5.ledger.utxo.UtxoLedgerService
+import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredTransaction
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredTransactionBuilder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.assertAll
+import org.junit.jupiter.api.assertThrows
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import java.security.PublicKey
+import java.time.Instant
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class ContractVerifyingNotaryClientFlowImplTest {
+
+    private companion object {
+        val txId = SecureHashUtils.randomSecureHash()
+        val hashedNotaryKey = SecureHashUtils.randomSecureHash()
+    }
+
+    /* State Refs */
+    private val mockStateRef = mock<StateRef> {
+        on { transactionId } doReturn txId
+    }
+
+    /* notary public keys */
+    private val encodedNotary = byteArrayOf(0x01)
+    private val notaryPublicKey = mock<PublicKey> {
+        on { encoded } doReturn encodedNotary
+    }
+
+    /* Transactions */
+    private val mockFilteredTx = mock<UtxoFilteredTransaction>()
+    private val mockSignedTx = mock<UtxoSignedTransaction> {
+        on { notaryKey } doReturn notaryPublicKey
+        on {inputStateRefs} doReturn listOf(mockStateRef)
+        on { referenceStateRefs } doReturn emptyList()
+    }
+
+    /* Signature */
+    private val dummyUniquenessSignature = DigitalSignatureAndMetadata(
+        mock(),
+        DigitalSignatureMetadata(Instant.now(), SignatureSpecImpl("dummySignatureName"), emptyMap())
+    )
+    private val mockDependency = mock<UtxoSignedTransaction> {
+        on {signatures} doReturn listOf(dummyUniquenessSignature)
+    }
+
+    @Test
+    fun `Contract verifying notary plugin client creates payload properly`() {
+        val client = createClient(mock())
+
+        val payload = client.createPayload()
+
+        assertAll({
+            assertThat(payload).isNotNull
+            assertThat(payload.initialTransaction).isEqualTo(mockSignedTx)
+        })
+    }
+
+    @Test
+    fun `Contract verifying notary plugin client returns signature on successful notarization`() {
+        val mockSession = mock<FlowSession> {
+            on { sendAndReceive(eq(NotarizationResponse::class.java), any()) } doReturn NotarizationResponse(
+                listOf(dummyUniquenessSignature),
+                null
+            )
+        }
+        val mockFlowMessaging = mock<FlowMessaging> {
+            on { initiateFlow(any()) } doReturn mockSession
+        }
+
+        val client = createClient(mockFlowMessaging)
+
+        val signatures = client.call()
+
+        assertThat(signatures).containsExactly(dummyUniquenessSignature)
+    }
+
+    @Test
+    fun `Contract verifying notary plugin client throws error on failed notarization`() {
+        val mockSession = mock<FlowSession> {
+            on { sendAndReceive(eq(NotarizationResponse::class.java), any()) } doReturn NotarizationResponse(
+                emptyList(),
+                NotaryExceptionReferenceStateUnknown(emptyList(), txId)
+            )
+        }
+        val mockFlowMessaging = mock<FlowMessaging> {
+            on { initiateFlow(any()) } doReturn mockSession
+        }
+
+        val client = createClient(mockFlowMessaging)
+
+        val ex = assertThrows<NotaryExceptionReferenceStateUnknown> {
+            client.call()
+        }
+
+        assertThat(ex.txId).isEqualTo(txId)
+    }
+
+    private fun createClient(flowMessaging: FlowMessaging): ContractVerifyingNotaryClientFlowImpl {
+
+        val mockBuilder = mock<UtxoFilteredTransactionBuilder> {
+            on { withOutputStates(listOf(0)) } doReturn this.mock
+            on { withNotary() } doReturn this.mock
+            on { withTimeWindow() } doReturn this.mock
+            on { build() } doReturn mockFilteredTx
+        }
+
+        val utxoLedgerService = mock<UtxoLedgerService> {
+            on { filterSignedTransaction(any()) } doReturn mockBuilder
+            on { findSignedTransaction(any()) } doReturn mockDependency
+        }
+
+        val digestService = mock<DigestService> {
+            on { hash( mockSignedTx.notaryKey.encoded, DigestAlgorithmName.SHA2_256) } doReturn hashedNotaryKey
+        }
+
+        return ContractVerifyingNotaryClientFlowImpl(
+            mockSignedTx,
+            MemberX500Name("Alice", "Alice Corp", "LDN", "GB"),
+            flowMessaging,
+            utxoLedgerService,
+            digestService
+        )
+    }
+}
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
new file mode 100644
index 00000000000..e7bda386545
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'corda.common-publishing'
+    id 'net.corda.plugins.cordapp-cpk2'
+}
+
+description 'Corda Contract Verifying Notary Plugin Server'
+
+group 'com.r3.corda.notary.plugin.contractverifying'
+
+cordapp {
+    targetPlatformVersion platformVersion.toInteger()
+    workflow {
+        name "Corda Cotract Verifying Notary - Server"
+        versionId 1
+        vendor "R3"
+    }
+}
+
+dependencies {
+    cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
+
+    cordaProvided 'net.corda:corda-application'
+    cordaProvided 'net.corda:corda-notary-plugin'
+    cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
+    cordaProvided 'org.slf4j:slf4j-api'
+
+    // Common package pulled in as transitive dependency through API
+    cordapp project(":notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-api")
+
+    testImplementation "org.assertj:assertj-core:$assertjVersion"
+    testImplementation "org.mockito:mockito-core:$mockitoVersion"
+    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+
+    testImplementation project(":libs:crypto:crypto-core")
+    testImplementation project(":libs:uniqueness:common")
+    testImplementation project(":testing:crypto-testkit")
+    testImplementation project(':testing:ledger:ledger-common-testkit')
+}
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImpl.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImpl.kt
new file mode 100644
index 00000000000..4fa71518d2d
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/main/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImpl.kt
@@ -0,0 +1,217 @@
+package com.r3.corda.notary.plugin.contractverifying.server
+
+import com.r3.corda.notary.plugin.common.NotarizationResponse
+import com.r3.corda.notary.plugin.common.NotaryExceptionGeneral
+import com.r3.corda.notary.plugin.common.NotaryExceptionInvalidSignature
+import com.r3.corda.notary.plugin.common.NotaryExceptionTransactionVerificationFailure
+import com.r3.corda.notary.plugin.common.NotaryTransactionDetails
+import com.r3.corda.notary.plugin.common.toNotarizationResponse
+import com.r3.corda.notary.plugin.contractverifying.api.ContractVerifyingNotarizationPayload
+import com.r3.corda.notary.plugin.contractverifying.api.FilteredTransactionAndSignatures
+import net.corda.v5.application.flows.CordaInject
+import net.corda.v5.application.flows.InitiatedBy
+import net.corda.v5.application.flows.ResponderFlow
+import net.corda.v5.application.membership.MemberLookup
+import net.corda.v5.application.messaging.FlowSession
+import net.corda.v5.application.uniqueness.model.UniquenessCheckResultSuccess
+import net.corda.v5.base.annotations.Suspendable
+import net.corda.v5.base.annotations.VisibleForTesting
+import net.corda.v5.base.types.MemberX500Name
+import net.corda.v5.crypto.SecureHash
+import net.corda.v5.ledger.common.transaction.TransactionSignatureService
+import net.corda.v5.ledger.notary.plugin.core.NotaryException
+import net.corda.v5.ledger.utxo.NotarySignatureVerificationService
+import net.corda.v5.ledger.utxo.StateAndRef
+import net.corda.v5.ledger.utxo.UtxoLedgerService
+import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredData
+import net.corda.v5.ledger.utxo.uniqueness.client.LedgerUniquenessCheckerClientService
+import org.slf4j.LoggerFactory
+import java.security.PublicKey
+
+@InitiatedBy(protocol = "com.r3.corda.notary.plugin.contractverifying", version = [1])
+class ContractVerifyingNotaryServerFlowImpl() : ResponderFlow {
+    private companion object {
+        private val logger = LoggerFactory.getLogger(this::class.java.enclosingClass)
+        const val NOTARY_SERVICE_NAME = "corda.notary.service.name"
+    }
+
+    @CordaInject
+    private lateinit var clientService: LedgerUniquenessCheckerClientService
+
+    @CordaInject
+    private lateinit var transactionSignatureService: TransactionSignatureService
+
+    @CordaInject
+    private lateinit var utxoLedgerService: UtxoLedgerService
+
+    @CordaInject
+    private lateinit var memberLookup: MemberLookup
+
+    @CordaInject
+    private lateinit var notarySignatureVerificationService: NotarySignatureVerificationService
+
+    /**
+     * Constructor used for testing to initialize the necessary services
+     */
+    @VisibleForTesting
+    internal constructor(
+        clientService: LedgerUniquenessCheckerClientService,
+        transactionSignatureService: TransactionSignatureService,
+        utxoLedgerService: UtxoLedgerService,
+        memberLookup: MemberLookup,
+        notarySignatureVerificationService: NotarySignatureVerificationService
+    ) : this() {
+        this.clientService = clientService
+        this.transactionSignatureService = transactionSignatureService
+        this.utxoLedgerService = utxoLedgerService
+        this.memberLookup = memberLookup
+        this.notarySignatureVerificationService = notarySignatureVerificationService
+    }
+
+    @Suspendable
+    override fun call(session: FlowSession) {
+        try {
+            val (initialTransaction, filteredTransactionsAndSignatures) = session.receive(ContractVerifyingNotarizationPayload::class.java)
+            if (logger.isTraceEnabled) {
+                logger.trace("Received notarization request for transaction {}", initialTransaction.id)
+            }
+
+            val initialTransactionDetails = getInitialTransactionDetail(initialTransaction)
+
+            validateTransactionNotaryAgainstCurrentNotary(initialTransactionDetails)
+
+            verifySignatures(initialTransaction.notaryKey, filteredTransactionsAndSignatures)
+
+            verifyTransaction(initialTransaction, filteredTransactionsAndSignatures)
+
+            if (logger.isTraceEnabled) {
+                logger.trace("Requesting uniqueness check for transaction {}", initialTransactionDetails.id)
+            }
+
+            val uniquenessResult = clientService.requestUniquenessCheck(
+                initialTransaction.id.toString(),
+                session.counterparty.toString(),
+                initialTransaction.inputStateRefs.map { it.toString() },
+                initialTransaction.referenceStateRefs.map { it.toString() },
+                initialTransaction.outputStateAndRefs.count(),
+                initialTransaction.timeWindow.from,
+                initialTransaction.timeWindow.until
+            )
+
+            if (logger.isDebugEnabled) {
+                logger.debug(
+                    "Uniqueness check completed for transaction {}, result is: {}. Sending response to {}",
+                    initialTransaction.id, uniquenessResult, session.counterparty
+                )
+            }
+
+            val signature = if (uniquenessResult is UniquenessCheckResultSuccess) {
+                transactionSignatureService.signBatch(
+                    listOf(initialTransactionDetails),
+                    listOf(initialTransaction.notaryKey)
+                ).first().first()
+            } else null
+
+            session.send(uniquenessResult.toNotarizationResponse(initialTransactionDetails.id, signature))
+        } catch (e: Exception) {
+            logger.warn("Error while processing request from client. Cause: $e ${e.stackTraceToString()}")
+            val exception =
+                if (e is NotaryException) e else NotaryExceptionGeneral("Error during notarization. Cause: ${e.message}")
+            session.send(
+                NotarizationResponse(
+                    emptyList(),
+                    exception
+                )
+            )
+        }
+    }
+
+    /**
+     * A helper function that constructs an instance of [NotaryTransactionDetails] from the given transaction.
+     */
+    @Suspendable
+    private fun getInitialTransactionDetail(initialTransaction: UtxoSignedTransaction): NotaryTransactionDetails {
+        return NotaryTransactionDetails(
+            initialTransaction.id,
+            initialTransaction.metadata,
+            initialTransaction.outputStateAndRefs.count(),
+            initialTransaction.timeWindow,
+            initialTransaction.inputStateRefs,
+            initialTransaction.referenceStateRefs,
+            initialTransaction.notaryName,
+            initialTransaction.notaryKey
+        )
+    }
+
+    @Suspendable
+    private fun validateTransactionNotaryAgainstCurrentNotary(txDetails: NotaryTransactionDetails) {
+        val currentNotaryContext = memberLookup
+            .myInfo()
+            .memberProvidedContext
+        val currentNotaryServiceName = currentNotaryContext
+            .parse(NOTARY_SERVICE_NAME, MemberX500Name::class.java)
+
+        require(currentNotaryServiceName == txDetails.notaryName) {
+            "Notary service on the transaction ${txDetails.notaryName} does not match the notary service represented" +
+                    " by this notary virtual node (${currentNotaryServiceName})"
+        }
+    }
+
+    /**
+     * A function that will verify the signatures of the given [filteredTransactionsAndSignatures].
+     */
+    @Suspendable
+    fun verifySignatures(
+        notaryKey: PublicKey,
+        filteredTransactionsAndSignatures: List<FilteredTransactionAndSignatures>
+    ) {
+        val keyIdToNotaryKeys: MutableMap<String, MutableMap<SecureHash, PublicKey>> = mutableMapOf()
+
+        filteredTransactionsAndSignatures.forEach { (filteredTransaction, signatures) ->
+            filteredTransaction.verify()
+            try {
+                notarySignatureVerificationService.verifyNotarySignatures(
+                    filteredTransaction,
+                    notaryKey,
+                    signatures,
+                    keyIdToNotaryKeys
+                )
+            } catch (e: Exception) {
+                throw NotaryExceptionInvalidSignature(
+                    "A valid notary signature is not found with error message: ${e.message}."
+                )
+            }
+        }
+    }
+
+    @Suspendable
+    private fun verifyTransaction(
+        initialTransaction: UtxoSignedTransaction,
+        filteredTransactionsAndSignatures: List<FilteredTransactionAndSignatures>
+    ) {
+        try {
+            val dependentStateAndRefs = filteredTransactionsAndSignatures.flatMap { (filteredTransaction, _) ->
+                (filteredTransaction.outputStateAndRefs as UtxoFilteredData.Audit<StateAndRef<*>>).values.values
+            }.associateBy { it.ref }
+
+            val inputStateAndRefs = initialTransaction.inputStateRefs.map { stateRef ->
+                requireNotNull(dependentStateAndRefs[stateRef]) {
+                    "Missing input state and ref from the filtered transaction"
+                }
+            }
+
+            val referenceStateAndRefs = initialTransaction.referenceStateRefs.map { stateRef ->
+                requireNotNull(dependentStateAndRefs[stateRef]) {
+                    "Missing reference state and ref from the filtered transaction"
+                }
+            }
+            utxoLedgerService.verify(initialTransaction.toLedgerTransaction(inputStateAndRefs, referenceStateAndRefs))
+        } catch (e: Exception) {
+            throw NotaryExceptionTransactionVerificationFailure(
+                "Transaction failed to verify with error message: ${e.message}.",
+                initialTransaction.id
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImplTest.kt b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImplTest.kt
new file mode 100644
index 00000000000..19a29aa3566
--- /dev/null
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/src/test/kotlin/com/r3/corda/notary/plugin/contractverifying/server/ContractVerifyingNotaryServerFlowImplTest.kt
@@ -0,0 +1,504 @@
+package com.r3.corda.notary.plugin.contractverifying.server
+
+import com.r3.corda.notary.plugin.common.NotarizationResponse
+import com.r3.corda.notary.plugin.common.NotaryExceptionGeneral
+import com.r3.corda.notary.plugin.common.NotaryExceptionInvalidSignature
+import com.r3.corda.notary.plugin.common.NotaryExceptionReferenceStateUnknown
+import com.r3.corda.notary.plugin.common.NotaryExceptionTransactionVerificationFailure
+import com.r3.corda.notary.plugin.contractverifying.api.ContractVerifyingNotarizationPayload
+import com.r3.corda.notary.plugin.contractverifying.api.FilteredTransactionAndSignatures
+import net.corda.crypto.core.fullIdHash
+import net.corda.crypto.testkit.SecureHashUtils
+import net.corda.ledger.common.flow.transaction.TransactionSignatureServiceInternal
+import net.corda.ledger.common.testkit.getSignatureWithMetadataExample
+import net.corda.uniqueness.datamodel.impl.UniquenessCheckErrorReferenceStateUnknownImpl
+import net.corda.uniqueness.datamodel.impl.UniquenessCheckErrorUnhandledExceptionImpl
+import net.corda.uniqueness.datamodel.impl.UniquenessCheckResultFailureImpl
+import net.corda.uniqueness.datamodel.impl.UniquenessCheckResultSuccessImpl
+import net.corda.uniqueness.datamodel.impl.UniquenessCheckStateRefImpl
+import net.corda.v5.application.crypto.DigitalSignatureAndMetadata
+import net.corda.v5.application.membership.MemberLookup
+import net.corda.v5.application.messaging.FlowSession
+import net.corda.v5.application.uniqueness.model.UniquenessCheckError
+import net.corda.v5.application.uniqueness.model.UniquenessCheckResult
+import net.corda.v5.base.types.MemberX500Name
+import net.corda.v5.crypto.CompositeKey
+import net.corda.v5.crypto.DigestAlgorithmName
+import net.corda.v5.ledger.common.transaction.TransactionMetadata
+import net.corda.v5.ledger.common.transaction.TransactionNoAvailableKeysException
+import net.corda.v5.ledger.utxo.NotarySignatureVerificationService
+import net.corda.v5.ledger.utxo.StateAndRef
+import net.corda.v5.ledger.utxo.StateRef
+import net.corda.v5.ledger.utxo.TimeWindow
+import net.corda.v5.ledger.utxo.UtxoLedgerService
+import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredData
+import net.corda.v5.ledger.utxo.transaction.filtered.UtxoFilteredTransaction
+import net.corda.v5.ledger.utxo.uniqueness.client.LedgerUniquenessCheckerClientService
+import net.corda.v5.membership.MemberContext
+import net.corda.v5.membership.MemberInfo
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.security.PublicKey
+import java.time.Instant
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class ContractVerifyingNotaryServerFlowImplTest {
+
+    private companion object {
+        const val NOTARY_SERVICE_NAME = "corda.notary.service.name"
+        const val NOTARY_SERVICE_BACKCHAIN_REQUIRED = "corda.notary.service.backchain.required"
+    }
+
+    // Cache for storing response from server
+    private val responseFromServer = mutableListOf<NotarizationResponse>()
+
+    // Notary vnodes
+    private val notaryVNodeAliceKey = mock<PublicKey>().also { whenever(it.encoded).thenReturn(byteArrayOf(0x01)) }
+    private val notaryVNodeBobKey = mock<PublicKey>().also { whenever(it.encoded).thenReturn(byteArrayOf(0x02)) }
+    private val invalidVnodeKey = mock<PublicKey>().also { whenever(it.encoded).thenReturn(byteArrayOf(0x03)) }
+
+    private val signatureAlice = getSignatureWithMetadataExample(notaryVNodeAliceKey)
+    private val signatureBob = getSignatureWithMetadataExample(notaryVNodeBobKey)
+    private val signatureInvalid = getSignatureWithMetadataExample(invalidVnodeKey)
+
+    // Notary Service and key
+    private val notaryServiceCompositeKey = mock<CompositeKey> {
+        on { leafKeys } doReturn setOf(notaryVNodeAliceKey, notaryVNodeBobKey)
+    }
+    private val notaryServiceName = MemberX500Name.parse("O=MyNotaryService, L=London, C=GB")
+
+    // The client that initiated the session with the notary server
+    private val memberCharlieName = MemberX500Name.parse("O=MemberCharlie, L=London, C=GB")
+
+    // mock services
+    private val mockTransactionSignatureService = mock<TransactionSignatureServiceInternal>()
+    private val mockLedgerService = mock<UtxoLedgerService>()
+
+    // mock for notary member lookup
+    private val notaryInfo = mock<MemberInfo>()
+    private val memberProvidedContext = mock<MemberContext>()
+    private val mockMemberLookup = mock<MemberLookup>()
+
+    // mock fields for signed transaction
+    private val txId = SecureHashUtils.randomSecureHash()
+    private val transactionMetadata = mock<TransactionMetadata>()
+    private val mockTimeWindow = mock<TimeWindow> {
+        on { from } doReturn Instant.now()
+        on { until } doReturn Instant.now().plusMillis(100000)
+    }
+    private  val mockInputRef = mock<StateRef>()
+    private val mockOutputStateAndRef = mock<StateAndRef<*>>()
+
+    // mock signed transaction
+    private val signedTx = mock<UtxoSignedTransaction> {
+        on { id } doReturn txId
+        on { inputStateRefs } doReturn listOf(mockInputRef)
+        on { referenceStateRefs } doReturn emptyList()
+        on { outputStateAndRefs } doReturn listOf(mockOutputStateAndRef)
+        on { timeWindow } doReturn mockTimeWindow
+        on { notaryKey } doReturn notaryServiceCompositeKey
+        on { notaryName } doReturn notaryServiceName
+        on { metadata } doReturn transactionMetadata
+    }
+
+    @BeforeEach
+    fun clearCache() {
+        responseFromServer.clear()
+        whenever(mockTransactionSignatureService.signBatch(any(), any())).doAnswer { inv ->
+            List((inv.arguments.first() as List<*>).size) {
+                @Suppress("unchecked_cast")
+                (inv.arguments[1] as List<PublicKey>).map { publicKey ->
+                    getSignatureWithMetadataExample(
+                        publicKey
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `Contract verifying notary should respond with error if no keys found for signing`() {
+        // We sign with a key that is not part of the notary composite key
+        whenever(mockTransactionSignatureService.signBatch(any(), any())).thenThrow(
+            TransactionNoAvailableKeysException("The publicKeys do not have any private counterparts available.", null)
+        )
+        createAndCallServer(mockSuccessfulUniquenessClientService(), notarySignature = signatureInvalid)
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseFromServer.first().signatures).isEmpty()
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText)
+            .contains("The publicKeys do not have any private counterparts available.")
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if request signature is invalid`() {
+        whenever(mockTransactionSignatureService.signBatch(any(), any())).thenReturn(
+            listOf(listOf(signatureAlice))
+        )
+
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+            filteredTxContents = mapOf("notaryKey" to invalidVnodeKey),
+            signatureVerificationLogic = ::throwVerify
+
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionInvalidSignature::class.java)
+        assertThat((responseError as NotaryExceptionInvalidSignature).errorText)
+            .contains("A valid notary signature is not found with error message: DUMMY ERROR")
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if the uniqueness check fails`() {
+        val unknownStateRef = UniquenessCheckStateRefImpl(SecureHashUtils.randomSecureHash(), 0)
+
+        createAndCallServer(
+            mockErrorUniquenessClientService(
+                UniquenessCheckErrorReferenceStateUnknownImpl(listOf(unknownStateRef))
+            )
+        )
+
+        assertThat(responseFromServer).hasSize(1)
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseFromServer.first().signatures).isEmpty()
+        assertThat(responseError).isInstanceOf(NotaryExceptionReferenceStateUnknown::class.java)
+        assertThat((responseError as NotaryExceptionReferenceStateUnknown).unknownStates).containsExactly(
+            unknownStateRef
+        )
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if an error encountered during uniqueness check`() {
+        createAndCallServer(mockThrowErrorUniquenessCheckClientService())
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText)
+            .contains("Error during notarization. Cause: Uniqueness checker cannot be reached")
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with signatures if alice key is in composite key`() {
+        whenever(mockTransactionSignatureService.signBatch(any(), any())).thenReturn(
+            listOf(listOf(signatureAlice))
+        )
+
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val response = responseFromServer.first()
+        assertThat(response.error).isNull()
+        assertThat(response.signatures).hasSize(1)
+        assertThat(response.signatures.first().by).isEqualTo(notaryVNodeAliceKey.fullIdHash())
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with signatures if bob key is in composite key`() {
+        whenever(mockTransactionSignatureService.signBatch(any(), any())).thenReturn(
+            listOf(listOf(signatureBob))
+        )
+
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val response = responseFromServer.first()
+        assertThat(response.error).isNull()
+        assertThat(response.signatures).hasSize(1)
+        assertThat(response.signatures.first().by).isEqualTo(notaryVNodeBobKey.fullIdHash())
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if time window not present on filtered tx`() {
+        createAndCallServer(mockSuccessfulUniquenessClientService(), filteredTxContents = mapOf("timeWindow" to null))
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText).contains(
+            "Error during notarization."
+        )
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if notary name not present on filtered tx`() {
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+            filteredTxContents = mapOf("notaryName" to null)
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText).contains(
+            "Error during notarization."
+        )
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if notary key not present on filtered tx`() {
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+            filteredTxContents = mapOf("notaryKey" to null)
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText).contains(
+            "Error during notarization."
+        )
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if output states are not audit type in the filtered tx`() {
+        val mockOutputStateProof = mock<UtxoFilteredData.SizeOnly<StateRef>>()
+
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+            filteredTxContents = mapOf("outputStateRefs" to mockOutputStateProof)
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionTransactionVerificationFailure::class.java)
+        assertThat((responseError as NotaryExceptionTransactionVerificationFailure).errorText).contains(
+            "Transaction failed to verify"
+        )
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if transaction verification fails`() {
+        createAndCallServer(mockSuccessfulUniquenessClientService(), txVerificationLogic = ::throwVerify)
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText)
+            .contains("Error during notarization. Cause: DUMMY ERROR")
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should throw general error when unhandled exception in uniqueness checker`() {
+        createAndCallServer(
+            mockErrorUniquenessClientService(
+                UniquenessCheckErrorUnhandledExceptionImpl(
+                    IllegalArgumentException::class.java.name,
+                    "Unhandled error!"
+                )
+            )
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText)
+            .contains(
+                "Unhandled exception of type java.lang.IllegalArgumentException encountered during " +
+                        "uniqueness checking with message: Unhandled error!"
+            )
+
+    }
+
+    @Test
+    fun `Contract verifying notary plugin server should respond with error if notary identity invalid`() {
+        createAndCallServer(
+            mockSuccessfulUniquenessClientService(),
+            // What party we pass in here does not matter, it just needs to be different from the notary server party
+            filteredTxContents = mapOf("notaryName" to MemberX500Name.parse("C=GB,L=London,O=Bob"))
+        )
+        assertThat(responseFromServer).hasSize(1)
+
+        val responseError = responseFromServer.first().error
+        assertThat(responseError).isNotNull
+        assertThat(responseError).isInstanceOf(NotaryExceptionGeneral::class.java)
+        assertThat((responseError as NotaryExceptionGeneral).errorText)
+            .contains("Error during notarization.")
+
+    }
+
+
+    /**
+     *  This function mocks up data such as filtered tx, signed tx and signatures to test verifying notary server.
+     *
+     *  @param clientService {@link LedgerUniquenessCheckerClientService} mock with UniquenessCheckResult that will be either
+     *  - UniquenessCheckResultFailureImpl using [mockErrorUniquenessClientService]
+     *  - UniquenessCheckResultSuccessImpl using [mockSuccessfulUniquenessClientService]
+     *  @param notaryServiceKey PublicKey of notary. CompositeKey by default.
+     *  @param notarySignature DigitalSignatureAndMetadata of notary
+     *  @param filteredTxContents Map of content of FilteredTransaction to mock up with.
+     *  It will be mocked up with default values if it's empty.
+     *  filtered contests that can be in a map:
+     *  - outputStateRefs
+     *  - notaryName
+     *  - notaryKey
+     *  - timeWindow
+     *  @param txVerificationLogic lambda function to execute on UtxoFilteredTransaction.verify() call
+     *  @param signatureVerificationLogic lambda function to execute on
+     *  UtxoFilteredTransaction.verifyAttachedNotarySignature() call
+     * */
+    @Suppress("LongParameterList")
+    private fun createAndCallServer(
+        clientService: LedgerUniquenessCheckerClientService,
+        notaryServiceKey: PublicKey = notaryServiceCompositeKey,
+        notarySignature: DigitalSignatureAndMetadata = signatureAlice,
+        filteredTxContents: Map<String, Any?> = emptyMap(),
+        txVerificationLogic: () -> Unit = {},
+        signatureVerificationLogic: () -> Unit = {}
+    ) {
+        // Get current notary and parse its data
+        whenever(mockMemberLookup.myInfo()).thenReturn(notaryInfo)
+        whenever(notaryInfo.memberProvidedContext).thenReturn(memberProvidedContext)
+        whenever(memberProvidedContext.parse(NOTARY_SERVICE_NAME, MemberX500Name::class.java)).thenReturn(
+            notaryServiceName
+        )
+        whenever(memberProvidedContext.parse(NOTARY_SERVICE_BACKCHAIN_REQUIRED, Boolean::class.java)).thenReturn(true)
+        whenever(
+            mockTransactionSignatureService.getIdOfPublicKey(
+                signedTx.notaryKey,
+                DigestAlgorithmName.SHA2_256.name
+            )
+        ).thenReturn(notarySignature.by)
+
+        // Mock Filtered Transaction
+        val mockDependencyOutputStateAndRef = mock<StateAndRef<*>> {
+            on { ref } doReturn mockInputRef
+        }
+
+        val mockOutputStateRefUtxoFilteredData = mock<UtxoFilteredData.Audit<StateAndRef<*>>> {
+            on { values } doReturn mapOf(0 to mockDependencyOutputStateAndRef)
+        }
+        val filteredTx = mock<UtxoFilteredTransaction> {
+            on { outputStateAndRefs } doAnswer {
+                @Suppress("unchecked_cast")
+                filteredTxContents["outputStateRefs"] as? UtxoFilteredData<StateAndRef<*>>
+                    ?: mockOutputStateRefUtxoFilteredData
+            }
+            on { notaryName } doAnswer {
+                if (filteredTxContents.containsKey("notaryName")) {
+                    filteredTxContents["notaryName"] as MemberX500Name?
+                } else {
+                    notaryServiceName
+                }
+            }
+            on { notaryKey } doAnswer {
+                if (filteredTxContents.containsKey("notaryKey")) {
+                    filteredTxContents["notaryKey"] as PublicKey?
+                } else {
+                    notaryServiceKey
+                }
+            }
+            on { timeWindow } doAnswer {
+                if (filteredTxContents.containsKey("timeWindow")) {
+                    filteredTxContents["timeWindow"] as TimeWindow?
+                } else {
+                    mockTimeWindow
+                }
+            }
+
+            on { verify() } doAnswer {
+                txVerificationLogic()
+            }
+            on { id } doReturn txId
+        }
+
+        // Prepare filteredTransactionAndSignature data
+        val filteredTxAndSignature = FilteredTransactionAndSignatures(
+            filteredTx,
+            listOf(notarySignature)
+        )
+        val filteredTxsAndSignatures = listOf(
+            filteredTxAndSignature
+        )
+
+        val mockNotarySignatureVerificationService = mock<NotarySignatureVerificationService> {
+            on { verifyNotarySignatures(any(), any(), any(), any()) } doAnswer { signatureVerificationLogic() }
+        }
+
+        // Mock the receive and send from the counterparty session, unless it is overwritten
+        val paramOrDefaultSession = mock<FlowSession> {
+            on { receive(ContractVerifyingNotarizationPayload::class.java) } doReturn ContractVerifyingNotarizationPayload(
+                signedTx,
+                filteredTxsAndSignatures
+            )
+            on { send(any()) } doAnswer {
+                responseFromServer.add(it.arguments.first() as NotarizationResponse)
+                Unit
+            }
+            on { counterparty } doReturn memberCharlieName
+        }
+
+        val server = ContractVerifyingNotaryServerFlowImpl(
+            clientService,
+            mockTransactionSignatureService,
+            mockLedgerService,
+            mockMemberLookup,
+            mockNotarySignatureVerificationService
+        )
+
+        server.call(paramOrDefaultSession)
+    }
+
+    private fun throwVerify() {
+        throw IllegalArgumentException("DUMMY ERROR")
+    }
+
+    private fun mockSuccessfulUniquenessClientService(): LedgerUniquenessCheckerClientService {
+        return mockUniquenessClientService(UniquenessCheckResultSuccessImpl(Instant.now()))
+    }
+
+    private fun mockErrorUniquenessClientService(
+        errorType: UniquenessCheckError
+    ): LedgerUniquenessCheckerClientService {
+        return mockUniquenessClientService(
+            UniquenessCheckResultFailureImpl(
+                Instant.now(),
+                errorType
+            )
+        )
+    }
+
+    private fun mockThrowErrorUniquenessCheckClientService() = mock<LedgerUniquenessCheckerClientService> {
+        on { requestUniquenessCheck(any(), any(), any(), any(), any(), any(), any()) } doThrow
+                IllegalArgumentException("Uniqueness checker cannot be reached")
+    }
+
+    private fun mockUniquenessClientService(response: UniquenessCheckResult) =
+        mock<LedgerUniquenessCheckerClientService> {
+            on { requestUniquenessCheck(any(), any(), any(), any(), any(), any(), any()) } doReturn response
+        }
+}
\ No newline at end of file
diff --git a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryServerFlowImpl.kt b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryServerFlowImpl.kt
index 0cf15f89708..02ac88cbc56 100644
--- a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryServerFlowImpl.kt
+++ b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/src/main/kotlin/com/r3/corda/notary/plugin/nonvalidating/server/NonValidatingNotaryServerFlowImpl.kt
@@ -1,5 +1,6 @@
 package com.r3.corda.notary.plugin.nonvalidating.server
 
+import com.r3.corda.notary.plugin.common.NotaryTransactionDetails
 import com.r3.corda.notary.plugin.common.NotarizationResponse
 import com.r3.corda.notary.plugin.common.NotaryExceptionGeneral
 import com.r3.corda.notary.plugin.common.toNotarizationResponse
@@ -127,7 +128,7 @@ class NonValidatingNotaryServerFlowImpl() : ResponderFlow {
      * This function will validate selected notary is valid notary to notarize.
      * */
     @Suspendable
-    private fun validateTransactionNotaryAgainstCurrentNotary(txDetails: NonValidatingNotaryTransactionDetails) {
+    private fun validateTransactionNotaryAgainstCurrentNotary(txDetails: NotaryTransactionDetails) {
         val currentNotaryContext = memberLookup
             .myInfo()
             .memberProvidedContext
@@ -153,7 +154,7 @@ class NonValidatingNotaryServerFlowImpl() : ResponderFlow {
      */
     @Suspendable
     @Suppress("TooGenericExceptionCaught")
-    private fun validateRequest(requestPayload: NonValidatingNotarizationPayload): NonValidatingNotaryTransactionDetails {
+    private fun validateRequest(requestPayload: NonValidatingNotarizationPayload): NotaryTransactionDetails {
         val transactionParts = try {
             extractParts(requestPayload)
         } catch (e: Exception) {
@@ -167,10 +168,10 @@ class NonValidatingNotaryServerFlowImpl() : ResponderFlow {
     }
 
     /**
-     * A helper function that constructs an instance of [NonValidatingNotaryTransactionDetails] from the given transaction.
+     * A helper function that constructs an instance of [NotaryTransactionDetails] from the given transaction.
      */
     @Suspendable
-    private fun extractParts(requestPayload: NonValidatingNotarizationPayload): NonValidatingNotaryTransactionDetails {
+    private fun extractParts(requestPayload: NonValidatingNotarizationPayload): NotaryTransactionDetails {
         val filteredTx = requestPayload.transaction as UtxoFilteredTransaction
 
         // The notary component is not needed by us but we validate that it is present just in case
@@ -202,7 +203,7 @@ class NonValidatingNotaryServerFlowImpl() : ResponderFlow {
             "Could not fetch output states from the filtered transaction"
         }
 
-        return NonValidatingNotaryTransactionDetails(
+        return NotaryTransactionDetails(
             filteredTx.id,
             filteredTx.metadata,
             outputStates.size,
diff --git a/settings.gradle b/settings.gradle
index 69fc96c2395..fdf5408d957 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -375,6 +375,9 @@ include 'notary-plugins:notary-plugin-common'
 include 'notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-api'
 include 'notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-client'
 include 'notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-server'
+include 'notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-api'
+include 'notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-client'
+include 'notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-server'
 include 'osgi-framework-api'
 include 'osgi-framework-bootstrap'
 include 'processors:crypto-processor'
diff --git a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/ClusterBuilder.kt b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/ClusterBuilder.kt
index d581a0134e9..f3293f6bb40 100644
--- a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/ClusterBuilder.kt
+++ b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/ClusterBuilder.kt
@@ -298,13 +298,14 @@ class ClusterBuilder {
     private fun registerNotaryBody(
         notaryServiceName: String,
         customMetadata: Map<String, String>,
-        isBackchainRequiredNotary: Boolean = true
+        isBackchainRequiredNotary: Boolean = true,
+        notaryPlugin: String = "nonvalidating"
     ): String {
         val context = (mapOf(
             "corda.key.scheme" to "CORDA.ECDSA.SECP256R1",
             "corda.roles.0" to "notary",
             "corda.notary.service.name" to "$notaryServiceName",
-            "corda.notary.service.flow.protocol.name" to "com.r3.corda.notary.plugin.nonvalidating",
+            "corda.notary.service.flow.protocol.name" to "com.r3.corda.notary.plugin.$notaryPlugin",
             "corda.notary.service.flow.protocol.version.0" to "1",
             "corda.notary.service.backchain.required" to "$isBackchainRequiredNotary"
         ) + customMetadata)
@@ -448,13 +449,15 @@ class ClusterBuilder {
         holdingIdShortHash: String,
         notaryServiceName: String? = null,
         customMetadata: Map<String, String> = emptyMap(),
-        isBackchainRequiredNotary: Boolean = true
+        isBackchainRequiredNotary: Boolean = true,
+        notaryPlugin: String = "nonvalidating"
     ) = register(
         holdingIdShortHash,
         if (notaryServiceName != null) registerNotaryBody(
             notaryServiceName,
             customMetadata,
-            isBackchainRequiredNotary
+            isBackchainRequiredNotary,
+            notaryPlugin
         ) else registerMemberBody(
             customMetadata
         )
diff --git a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
index 7781e61e19f..ba55c8ed0e4 100644
--- a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
+++ b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
@@ -156,7 +156,8 @@ fun ClusterInfo.onboardNotaryMember(
     getAdditionalContext: ((holdingId: String) -> Map<String, String>)? = null,
     tlsCertificateUploadedCallback: (String) -> Unit = {},
     notaryServiceName: String = DEFAULT_NOTARY_SERVICE,
-    isBackchainRequired: Boolean = true
+    isBackchainRequired: Boolean = true,
+    notaryPlugin: String = "nonvalidating"
 ) = onboardMember(
     resourceName,
     cpiName,
@@ -171,7 +172,7 @@ fun ClusterInfo.onboardNotaryMember(
             "corda.roles.0" to "notary",
             "corda.notary.service.name" to MemberX500Name.parse(notaryServiceName).toString(),
             "corda.notary.service.backchain.required" to "$isBackchainRequired",
-            "corda.notary.service.flow.protocol.name" to "com.r3.corda.notary.plugin.nonvalidating",
+            "corda.notary.service.flow.protocol.name" to "com.r3.corda.notary.plugin.$notaryPlugin",
             "corda.notary.service.flow.protocol.version.0" to "1",
             "corda.notary.keys.0.id" to notaryKeyId,
             "corda.notary.keys.0.signature.spec" to DEFAULT_SIGNATURE_SPEC
@@ -291,22 +292,38 @@ fun registerStaticMember(
     holdingIdentityShortHash: String,
     notaryServiceName: String? = null,
     customMetadata: Map<String, String> = emptyMap(),
-    isBackchainRequired: Boolean = true
-) = DEFAULT_CLUSTER.registerStaticMember(holdingIdentityShortHash, notaryServiceName, customMetadata, isBackchainRequired)
+    isBackchainRequired: Boolean = true,
+    notaryPlugin: String = "nonvalidating"
+) = DEFAULT_CLUSTER.registerStaticMember(
+    holdingIdentityShortHash,
+    notaryServiceName,
+    customMetadata,
+    isBackchainRequired,
+    notaryPlugin
+)
 
 val memberRegisterLock = ReentrantLock()
 fun ClusterInfo.registerStaticMember(
     holdingIdentityShortHash: String,
     notaryServiceName: String? = null,
     customMetadata: Map<String, String> = emptyMap(),
-    isBackchainRequired: Boolean = true
+    isBackchainRequired: Boolean = true,
+    notaryPlugin: String = "nonvalidating"
 ) {
     cluster {
         memberRegisterLock.withLock {
             assertWithRetry {
                 interval(1.seconds)
                 timeout(10.seconds)
-                command { registerStaticMember(holdingIdentityShortHash, notaryServiceName, customMetadata, isBackchainRequired) }
+                command {
+                    registerStaticMember(
+                        holdingIdentityShortHash,
+                        notaryServiceName,
+                        customMetadata,
+                        isBackchainRequired,
+                        notaryPlugin
+                    )
+                }
                 condition {
                     it.code == ResponseCode.OK.statusCode
                             && it.toJson()["registrationStatus"].textValue() == REGISTRATION_SUBMITTED
diff --git a/testing/ledger/ledger-utxo-base-test/src/main/kotlin/net/corda/ledger/utxo/test/UtxoLedgerTest.kt b/testing/ledger/ledger-utxo-base-test/src/main/kotlin/net/corda/ledger/utxo/test/UtxoLedgerTest.kt
index 8c680f3caf5..ae037309e78 100644
--- a/testing/ledger/ledger-utxo-base-test/src/main/kotlin/net/corda/ledger/utxo/test/UtxoLedgerTest.kt
+++ b/testing/ledger/ledger-utxo-base-test/src/main/kotlin/net/corda/ledger/utxo/test/UtxoLedgerTest.kt
@@ -23,6 +23,7 @@ import net.corda.ledger.utxo.testkit.anotherNotaryX500Name
 import net.corda.ledger.utxo.testkit.getUtxoSignedTransactionExample
 import net.corda.ledger.utxo.testkit.notaryX500Name
 import net.corda.ledger.utxo.flow.impl.groupparameters.verifier.SignedGroupParametersVerifier
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.sandboxgroupcontext.CurrentSandboxGroupContext
 import net.corda.v5.ledger.common.NotaryLookup
 import net.corda.v5.membership.NotaryInfo
@@ -35,6 +36,7 @@ abstract class UtxoLedgerTest : CommonLedgerTest() {
     private val mockUtxoLedgerGroupParametersPersistenceService = mock<UtxoLedgerGroupParametersPersistenceService>()
     private val mockGroupParametersLookup = mockGroupParametersLookup()
     private val mockSignedGroupParametersVerifier = mock<SignedGroupParametersVerifier>()
+    private val mockNotarySignatureVerificationService = mock<NotarySignatureVerificationServiceInternal>()
 
     val mockUtxoLedgerStateQueryService = mock<UtxoLedgerStateQueryService>()
     val mockCurrentSandboxGroupContext = mock<CurrentSandboxGroupContext>()
@@ -82,7 +84,8 @@ abstract class UtxoLedgerTest : CommonLedgerTest() {
         mockUtxoLedgerTransactionVerificationService,
         mockUtxoLedgerGroupParametersPersistenceService,
         mockGroupParametersLookup,
-        mockSignedGroupParametersVerifier
+        mockSignedGroupParametersVerifier,
+        mockNotarySignatureVerificationService
     )
     val utxoLedgerService = UtxoLedgerServiceImpl(
         utxoFilteredTransactionFactory,
@@ -99,13 +102,15 @@ abstract class UtxoLedgerTest : CommonLedgerTest() {
     val utxoSignedTransactionKryoSerializer = UtxoSignedTransactionKryoSerializer(
         serializationServiceWithWireTx,
         fakeTransactionSignatureService(),
-        utxoLedgerTransactionFactory
+        utxoLedgerTransactionFactory,
+        mockNotarySignatureVerificationService
     )
     val utxoSignedTransactionAMQPSerializer =
         UtxoSignedTransactionSerializer(
             serializationServiceNullCfg,
             fakeTransactionSignatureService(),
-            utxoLedgerTransactionFactory
+            utxoLedgerTransactionFactory,
+            mockNotarySignatureVerificationService
         )
     val utxoSignedTransactionExample = getUtxoSignedTransactionExample(
         digestService,
@@ -114,6 +119,7 @@ abstract class UtxoLedgerTest : CommonLedgerTest() {
         jsonMarshallingService,
         jsonValidator,
         fakeTransactionSignatureService(),
+        mockNotarySignatureVerificationService,
         utxoLedgerTransactionFactory
     )
 
diff --git a/testing/ledger/ledger-utxo-testkit/src/main/kotlin/net/corda/ledger/utxo/testkit/UtxoSignedTransactionExample.kt b/testing/ledger/ledger-utxo-testkit/src/main/kotlin/net/corda/ledger/utxo/testkit/UtxoSignedTransactionExample.kt
index c857e2e354c..27778534d62 100644
--- a/testing/ledger/ledger-utxo-testkit/src/main/kotlin/net/corda/ledger/utxo/testkit/UtxoSignedTransactionExample.kt
+++ b/testing/ledger/ledger-utxo-testkit/src/main/kotlin/net/corda/ledger/utxo/testkit/UtxoSignedTransactionExample.kt
@@ -14,6 +14,7 @@ import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionImpl
 import net.corda.ledger.utxo.flow.impl.transaction.UtxoSignedTransactionInternal
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoLedgerTransactionFactory
 import net.corda.ledger.utxo.flow.impl.transaction.factory.UtxoSignedTransactionFactory
+import net.corda.ledger.utxo.flow.impl.transaction.verifier.NotarySignatureVerificationServiceInternal
 import net.corda.v5.application.crypto.DigestService
 import net.corda.v5.application.marshalling.JsonMarshallingService
 import net.corda.v5.application.serialization.SerializationService
@@ -45,6 +46,7 @@ fun getUtxoSignedTransactionExample(
     jsonMarshallingService: JsonMarshallingService,
     jsonValidator: JsonValidator,
     transactionSignatureService: TransactionSignatureServiceInternal,
+    notarySignatureVerificationService: NotarySignatureVerificationServiceInternal,
     utxoLedgerTransactionFactory: UtxoLedgerTransactionFactory,
     cpkPackageSeed: String? = null
 ): UtxoSignedTransaction {
@@ -58,6 +60,7 @@ fun getUtxoSignedTransactionExample(
     return UtxoSignedTransactionImpl(
         serializationService,
         transactionSignatureService,
+        notarySignatureVerificationService,
         utxoLedgerTransactionFactory,
         wireTransaction,
         listOf(getSignatureWithMetadataExample())

From 5e73b0670f0f6ff071b2ca87283af632ebf27680 Mon Sep 17 00:00:00 2001
From: Yiftach Kaplan <67583323+yift-r3@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:48:30 +0000
Subject: [PATCH 3/7] CORE-18548: Prepare HTTP client in Gateway (#5235)

* CORE-18548: Prepare HTTP client in Gateway

* Fix detect

* Ack -> AuthenticatedDataMessage

* Apply review comments

* Add p2pLinkManager to combine worker
---
 .../workers/combined/CombinedWorker.kt        |   2 +-
 .../GatewayWorker.kt                          |  12 +-
 charts/corda/templates/workers.yaml           |   4 +-
 components/gateway/build.gradle               |   1 +
 .../p2p/gateway/GatewayIntegrationTest.kt     | 130 +++++++++++-------
 .../kotlin/net/corda/p2p/gateway/Gateway.kt   |   9 +-
 .../internal/InboundMessageHandler.kt         |  11 +-
 .../internal/LinkManagerRpcClient.kt          |  33 +++++
 .../internal/InboundMessageHandlerTest.kt     |   4 +-
 .../internal/LinkManagerRpcClientTest.kt      |  86 ++++++++++++
 components/link-manager/build.gradle          |   1 +
 .../net/corda/p2p/P2PLayerEndToEndTest.kt     |  19 ++-
 gradle.properties                             |   2 +-
 .../messaging/api/constants/WorkerRPCPaths.kt |   1 +
 processors/gateway-processor/build.gradle     |   1 +
 .../gateway/internal/GatewayProcessorImpl.kt  |  11 +-
 16 files changed, 265 insertions(+), 62 deletions(-)
 create mode 100644 components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClient.kt
 create mode 100644 components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClientTest.kt

diff --git a/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt b/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt
index 59e1e1f235c..680ddb0d7ad 100644
--- a/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt
+++ b/applications/workers/release/combined-worker/src/main/kotlin/net/corda/applications/workers/combined/CombinedWorker.kt
@@ -313,7 +313,7 @@ private class CombinedWorkerParams {
 
     @Option(names = ["--serviceEndpoint"], description = ["Internal REST endpoints for Corda workers"])
     val workerEndpoints: Map<String, String> =
-        listOf("crypto", "verification", "uniqueness", "persistence", "tokenSelection")
+        listOf("crypto", "verification", "uniqueness", "persistence", "tokenSelection", "p2pLinkManager")
             .associate { "endpoints.$it" to "localhost:7004" }
             .toMap()
 }
diff --git a/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt b/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt
index 543cc89dd8b..bb60438053f 100644
--- a/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt
+++ b/applications/workers/release/p2p-gateway-worker/src/main/kotlin/net.corda.applications.workers.p2p.gateway/GatewayWorker.kt
@@ -13,6 +13,7 @@ import net.corda.lifecycle.registry.LifecycleRegistry
 import net.corda.osgi.api.Application
 import net.corda.osgi.api.Shutdown
 import net.corda.processors.p2p.gateway.GatewayProcessor
+import net.corda.schema.configuration.BootConfig.BOOT_WORKER_SERVICE
 import net.corda.tracing.configureTracing
 import net.corda.tracing.shutdownTracing
 import net.corda.web.api.WebServer
@@ -21,6 +22,7 @@ import org.osgi.service.component.annotations.Component
 import org.osgi.service.component.annotations.Reference
 import org.slf4j.LoggerFactory
 import picocli.CommandLine
+import picocli.CommandLine.Option
 
 @Component
 @Suppress("LongParameterList")
@@ -63,7 +65,8 @@ class GatewayWorker @Activate constructor(
         val config = WorkerHelpers.getBootstrapConfig(
             secretsServiceFactoryResolver,
             params.defaultParams,
-            configurationValidatorFactory.createConfigValidator()
+            configurationValidatorFactory.createConfigValidator(),
+            listOf(WorkerHelpers.createConfigFromParams(BOOT_WORKER_SERVICE, params.workerEndpoints)),
         )
         webServer.start(params.defaultParams.workerServerPort)
         gatewayProcessor.start(config)
@@ -80,4 +83,11 @@ class GatewayWorker @Activate constructor(
 private class GatewayWorkerParams {
     @CommandLine.Mixin
     var defaultParams = DefaultWorkerParams()
+
+    @Option(
+        names = ["--serviceEndpoint"],
+        description = ["Internal REST endpoints for Corda workers"],
+        required = true,
+    )
+    val workerEndpoints: Map<String, String> = emptyMap()
 }
diff --git a/charts/corda/templates/workers.yaml b/charts/corda/templates/workers.yaml
index 85781457a3e..99291c37a76 100644
--- a/charts/corda/templates/workers.yaml
+++ b/charts/corda/templates/workers.yaml
@@ -13,8 +13,8 @@
 {{- include "corda.worker" ( list $ .Values.workers.verification "verification" ) }}
 {{- include "corda.worker" ( list $ .Values.workers.membership "membership" ) }}
 {{- include "corda.worker" ( list $ .Values.workers.p2pGateway "p2pGateway"
-  ( dict "httpPort" 8080 )
-) }}
+    ( dict "httpPort" 8080 "servicesAccessed" ( list "p2pLinkManager" ) )
+  ) }}
 {{- include "corda.worker" ( list $ .Values.workers.p2pLinkManager "p2pLinkManager" ) }}
 {{- include "corda.worker" ( list $ .Values.workers.persistence "persistence"
     ( dict "clusterDbAccess" true )
diff --git a/components/gateway/build.gradle b/components/gateway/build.gradle
index 027c89109c8..b7867499cd7 100644
--- a/components/gateway/build.gradle
+++ b/components/gateway/build.gradle
@@ -33,6 +33,7 @@ dependencies {
     implementation "com.github.ben-manes.caffeine:caffeine:$caffeineVersion"
     implementation project(':libs:schema-registry:schema-registry')
     implementation project(":libs:metrics")
+    implementation project(':libs:platform-info')
 
     testImplementation "org.mockito:mockito-core:$mockitoVersion"
     testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
diff --git a/components/gateway/src/integrationTest/kotlin/net/corda/p2p/gateway/GatewayIntegrationTest.kt b/components/gateway/src/integrationTest/kotlin/net/corda/p2p/gateway/GatewayIntegrationTest.kt
index 6513008b2f7..a73bd6cc263 100644
--- a/components/gateway/src/integrationTest/kotlin/net/corda/p2p/gateway/GatewayIntegrationTest.kt
+++ b/components/gateway/src/integrationTest/kotlin/net/corda/p2p/gateway/GatewayIntegrationTest.kt
@@ -25,6 +25,7 @@ import net.corda.data.p2p.gateway.GatewayMessage
 import net.corda.data.p2p.gateway.GatewayResponse
 import net.corda.data.p2p.mtls.gateway.ClientCertificateSubjects
 import net.corda.libs.configuration.SmartConfigImpl
+import net.corda.libs.platform.PlatformInfoProvider
 import net.corda.lifecycle.LifecycleStatus
 import net.corda.lifecycle.impl.LifecycleCoordinatorFactoryImpl
 import net.corda.lifecycle.impl.LifecycleCoordinatorSchedulerFactoryImpl
@@ -60,6 +61,7 @@ import net.corda.schema.Schemas.P2P.GATEWAY_TLS_TRUSTSTORES
 import net.corda.schema.Schemas.P2P.LINK_IN_TOPIC
 import net.corda.schema.Schemas.P2P.LINK_OUT_TOPIC
 import net.corda.schema.Schemas.P2P.SESSION_OUT_PARTITIONS
+import net.corda.schema.configuration.BootConfig
 import net.corda.schema.configuration.BootConfig.INSTANCE_ID
 import net.corda.schema.configuration.BootConfig.TOPIC_PREFIX
 import net.corda.schema.configuration.MessagingConfig.MAX_ALLOWED_MSG_SIZE
@@ -122,11 +124,24 @@ internal class GatewayIntegrationTest : TestBase() {
     private val sessionId = "session-1"
     private val instanceId = AtomicInteger(0)
 
-    private val messagingConfig = SmartConfigImpl.empty()
+    private val baseMessagingConfig = SmartConfigImpl.empty()
         .withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.getAndIncrement()))
         .withValue(MAX_ALLOWED_MSG_SIZE, ConfigValueFactory.fromAnyRef(10000000))
+    private fun messagingConfig() =
+        baseMessagingConfig
+            .withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet()))
 
     private val avroSchemaRegistry = AvroSchemaRegistryImpl()
+    private val platformInfoProvider = object : PlatformInfoProvider {
+        override val activePlatformVersion = 1
+        override val localWorkerPlatformVersion = 1
+        override val localWorkerSoftwareVersion = "5.2"
+    }
+    private val bootConfig = SmartConfigImpl.empty()
+        .withValue(
+            BootConfig.P2P_LINK_MANAGER_WORKER_REST_ENDPOINT,
+            ConfigValueFactory.fromAnyRef("localhost:${getOpenPort()}"),
+        )
 
     private inner class Node(private val name: String) {
         private val topicService = TopicServiceImpl()
@@ -136,7 +151,7 @@ internal class GatewayIntegrationTest : TestBase() {
             LifecycleCoordinatorFactoryImpl(LifecycleRegistryImpl(), LifecycleCoordinatorSchedulerFactoryImpl())
         val subscriptionFactory = InMemSubscriptionFactory(topicService, rpcTopicService, lifecycleCoordinatorFactory)
         val publisherFactory = CordaPublisherFactory(topicService, rpcTopicService, lifecycleCoordinatorFactory)
-        val publisher = publisherFactory.createPublisher(PublisherConfig("$name.id", false), messagingConfig)
+        val publisher = publisherFactory.createPublisher(PublisherConfig("$name.id", false), baseMessagingConfig)
         val cryptoOpsClient = TestCryptoOpsClient(lifecycleCoordinatorFactory)
 
         fun stop() {
@@ -165,8 +180,7 @@ internal class GatewayIntegrationTest : TestBase() {
                     override val keyClass = Any::class.java
                     override val valueClass = Any::class.java
                 },
-                messagingConfig = messagingConfig
-                    .withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet()))
+                messagingConfig = messagingConfig()
                     .withValue(TOPIC_PREFIX, ConfigValueFactory.fromAnyRef("")),
                 partitionAssignmentListener = null
             ).use {
@@ -272,9 +286,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -318,9 +334,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -386,9 +404,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -461,9 +481,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -528,9 +550,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
                 avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -633,9 +657,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -680,9 +706,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(ipKeyStore, aliceHoldingIdentity)
                 it.startAndWaitForStarted()
@@ -780,9 +808,11 @@ internal class GatewayIntegrationTest : TestBase() {
                     alice.subscriptionFactory,
                     alice.publisherFactory,
                     alice.lifecycleCoordinatorFactory,
-                    messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                     alice.cryptoOpsClient,
                     avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig,
+                    messagingConfig(),
                 ).usingLifecycle { gateway ->
                     gateway.start()
 
@@ -867,9 +897,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 it.startAndWaitForStarted()
                 (1..clientNumber).map { index ->
@@ -976,9 +1008,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle {
                 alice.publishKeyStoreCertificatesAndKeys(aliceKeyStore, aliceHoldingIdentity)
                 startTime = Instant.now().toEpochMilli()
@@ -1047,10 +1081,7 @@ internal class GatewayIntegrationTest : TestBase() {
                     override val keyClass = Any::class.java
                     override val valueClass = Any::class.java
                 },
-                messagingConfig = messagingConfig.withValue(
-                    INSTANCE_ID,
-                    ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())
-                ),
+                messagingConfig = messagingConfig(),
                 partitionAssignmentListener = null
             )
             bobSubscription.start()
@@ -1069,10 +1100,7 @@ internal class GatewayIntegrationTest : TestBase() {
                     override val keyClass = Any::class.java
                     override val valueClass = Any::class.java
                 },
-                messagingConfig = messagingConfig.withValue(
-                    INSTANCE_ID,
-                    ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())
-                ),
+                messagingConfig = messagingConfig(),
                 partitionAssignmentListener = null
             )
             aliceSubscription.start()
@@ -1098,9 +1126,11 @@ internal class GatewayIntegrationTest : TestBase() {
                     alice.subscriptionFactory,
                     alice.publisherFactory,
                     alice.lifecycleCoordinatorFactory,
-                    messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                     alice.cryptoOpsClient,
-                    avroSchemaRegistry
+                    avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig,
+                    messagingConfig(),
                 ),
                 Gateway(
                     createConfigurationServiceFor(
@@ -1121,9 +1151,11 @@ internal class GatewayIntegrationTest : TestBase() {
                     bob.subscriptionFactory,
                     bob.publisherFactory,
                     bob.lifecycleCoordinatorFactory,
-                    messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                     bob.cryptoOpsClient,
                     avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig,
+                    messagingConfig(),
                 )
             ).onEach {
                 it.startAndWaitForStarted()
@@ -1194,9 +1226,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 alice.subscriptionFactory,
                 alice.publisherFactory,
                 alice.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 alice.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle { gateway ->
                 val port = getOpenPort()
                 logger.info("Publishing good config")
@@ -1349,9 +1383,11 @@ internal class GatewayIntegrationTest : TestBase() {
                 server.subscriptionFactory,
                 server.publisherFactory,
                 server.lifecycleCoordinatorFactory,
-                messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                 server.cryptoOpsClient,
-                avroSchemaRegistry
+                avroSchemaRegistry,
+                platformInfoProvider,
+                bootConfig,
+                messagingConfig(),
             ).usingLifecycle { gateway ->
                 gateway.startAndWaitForStarted()
                 val firstCertificatesAuthority = CertificateAuthorityFactory
@@ -1526,10 +1562,7 @@ internal class GatewayIntegrationTest : TestBase() {
                     override val keyClass = Any::class.java
                     override val valueClass = Any::class.java
                 },
-                messagingConfig = messagingConfig.withValue(
-                    INSTANCE_ID,
-                    ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())
-                ),
+                messagingConfig = messagingConfig(),
                 partitionAssignmentListener = null
             )
             bobSubscription.start()
@@ -1548,10 +1581,7 @@ internal class GatewayIntegrationTest : TestBase() {
                     override val keyClass = Any::class.java
                     override val valueClass = Any::class.java
                 },
-                messagingConfig = messagingConfig.withValue(
-                    INSTANCE_ID,
-                    ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())
-                ),
+                messagingConfig = messagingConfig(),
                 partitionAssignmentListener = null
             )
             aliceSubscription.start()
@@ -1577,9 +1607,11 @@ internal class GatewayIntegrationTest : TestBase() {
                     alice.subscriptionFactory,
                     alice.publisherFactory,
                     alice.lifecycleCoordinatorFactory,
-                    messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                     alice.cryptoOpsClient,
-                    avroSchemaRegistry
+                    avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig,
+                    messagingConfig(),
                 ),
                 Gateway(
                     createConfigurationServiceFor(
@@ -1599,9 +1631,11 @@ internal class GatewayIntegrationTest : TestBase() {
                     bob.subscriptionFactory,
                     bob.publisherFactory,
                     bob.lifecycleCoordinatorFactory,
-                    messagingConfig.withValue(INSTANCE_ID, ConfigValueFactory.fromAnyRef(instanceId.incrementAndGet())),
                     bob.cryptoOpsClient,
                     avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig,
+                    messagingConfig(),
                 )
             ).onEach {
                 it.startAndWaitForStarted()
diff --git a/components/gateway/src/main/kotlin/net/corda/p2p/gateway/Gateway.kt b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/Gateway.kt
index 9613d9d50c1..74a811c6539 100644
--- a/components/gateway/src/main/kotlin/net/corda/p2p/gateway/Gateway.kt
+++ b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/Gateway.kt
@@ -3,6 +3,7 @@ package net.corda.p2p.gateway
 import net.corda.configuration.read.ConfigurationReadService
 import net.corda.crypto.client.CryptoOpsClient
 import net.corda.libs.configuration.SmartConfig
+import net.corda.libs.platform.PlatformInfoProvider
 import net.corda.lifecycle.LifecycleCoordinatorFactory
 import net.corda.lifecycle.domino.logic.ComplexDominoTile
 import net.corda.lifecycle.domino.logic.DominoTile
@@ -33,9 +34,11 @@ class Gateway(
     subscriptionFactory: SubscriptionFactory,
     publisherFactory: PublisherFactory,
     lifecycleCoordinatorFactory: LifecycleCoordinatorFactory,
-    messagingConfiguration: SmartConfig,
     cryptoOpsClient: CryptoOpsClient,
-    avroSchemaRegistry: AvroSchemaRegistry
+    avroSchemaRegistry: AvroSchemaRegistry,
+    platformInfoProvider: PlatformInfoProvider,
+    bootConfig: SmartConfig,
+    messagingConfiguration: SmartConfig,
 ) : LifecycleWithDominoTile {
 
     private val commonComponents = CommonComponents(
@@ -52,6 +55,8 @@ class Gateway(
         messagingConfiguration,
         commonComponents,
         avroSchemaRegistry,
+        platformInfoProvider,
+        bootConfig,
     )
     private val outboundMessageProcessor = OutboundMessageHandler(
         lifecycleCoordinatorFactory,
diff --git a/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandler.kt b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandler.kt
index bccb0a4c295..cf1400f5f60 100644
--- a/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandler.kt
+++ b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandler.kt
@@ -14,6 +14,7 @@ import net.corda.data.p2p.crypto.ResponderHelloMessage
 import net.corda.data.p2p.gateway.GatewayMessage
 import net.corda.data.p2p.gateway.GatewayResponse
 import net.corda.libs.configuration.SmartConfig
+import net.corda.libs.platform.PlatformInfoProvider
 import net.corda.lifecycle.LifecycleCoordinatorFactory
 import net.corda.lifecycle.domino.logic.ComplexDominoTile
 import net.corda.lifecycle.domino.logic.LifecycleWithDominoTile
@@ -51,7 +52,9 @@ internal class InboundMessageHandler(
     subscriptionFactory: SubscriptionFactory,
     messagingConfiguration: SmartConfig,
     commonComponents: CommonComponents,
-    private val avroSchemaRegistry: AvroSchemaRegistry
+    private val avroSchemaRegistry: AvroSchemaRegistry,
+    platformInfoProvider: PlatformInfoProvider,
+    bootConfig: SmartConfig,
 ) : RequestListener, LifecycleWithDominoTile {
 
     init {
@@ -89,6 +92,12 @@ internal class InboundMessageHandler(
         subscriptionFactory,
         messagingConfiguration
     )
+    private val linkManagerClient =
+        LinkManagerRpcClient(
+            publisherFactory,
+            platformInfoProvider,
+            bootConfig,
+        )
 
     private val server = ReconfigurableHttpServer(
         lifecycleCoordinatorFactory,
diff --git a/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClient.kt b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClient.kt
new file mode 100644
index 00000000000..7dbab949854
--- /dev/null
+++ b/components/gateway/src/main/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClient.kt
@@ -0,0 +1,33 @@
+package net.corda.p2p.gateway.messaging.internal
+
+import net.corda.data.p2p.LinkInMessage
+import net.corda.data.p2p.crypto.AuthenticatedDataMessage
+import net.corda.libs.configuration.SmartConfig
+import net.corda.libs.platform.PlatformInfoProvider
+import net.corda.messaging.api.constants.WorkerRPCPaths.P2P_LINK_MANAGER_PATH
+import net.corda.messaging.api.publisher.factory.PublisherFactory
+import net.corda.messaging.api.publisher.send
+import net.corda.schema.configuration.BootConfig.P2P_LINK_MANAGER_WORKER_REST_ENDPOINT
+import java.net.URI
+
+internal class LinkManagerRpcClient(
+    publisherFactory: PublisherFactory,
+    platformInfoProvider: PlatformInfoProvider,
+    bootConfig: SmartConfig,
+) {
+    private val client by lazy {
+        publisherFactory.createHttpRpcClient()
+    }
+
+    private val endpoint=
+        bootConfig.getString(P2P_LINK_MANAGER_WORKER_REST_ENDPOINT)
+
+    private val url by lazy {
+        val platformVersion = platformInfoProvider.localWorkerSoftwareShortVersion
+        URI.create("http://$endpoint/api/$platformVersion$P2P_LINK_MANAGER_PATH")
+    }
+
+    fun send(message: LinkInMessage): AuthenticatedDataMessage? {
+        return client.send<AuthenticatedDataMessage>(url, message)
+    }
+}
\ No newline at end of file
diff --git a/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandlerTest.kt b/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandlerTest.kt
index afb90b9be9c..1ce9e3ca01b 100644
--- a/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandlerTest.kt
+++ b/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/InboundMessageHandlerTest.kt
@@ -105,7 +105,9 @@ class InboundMessageHandlerTest {
         subscriptionFactory,
         SmartConfigImpl.empty(),
         mock(),
-        avroSchemaRegistry
+        avroSchemaRegistry,
+        mock(),
+        mock(),
     )
 
     @AfterEach
diff --git a/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClientTest.kt b/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClientTest.kt
new file mode 100644
index 00000000000..d47a0cc561c
--- /dev/null
+++ b/components/gateway/src/test/kotlin/net/corda/p2p/gateway/messaging/internal/LinkManagerRpcClientTest.kt
@@ -0,0 +1,86 @@
+package net.corda.p2p.gateway.messaging.internal
+
+import net.corda.data.p2p.LinkInMessage
+import net.corda.data.p2p.crypto.AuthenticatedDataMessage
+import net.corda.libs.configuration.SmartConfig
+import net.corda.libs.platform.PlatformInfoProvider
+import net.corda.messaging.api.constants.WorkerRPCPaths.P2P_LINK_MANAGER_PATH
+import net.corda.messaging.api.publisher.HttpRpcClient
+import net.corda.messaging.api.publisher.factory.PublisherFactory
+import net.corda.schema.configuration.BootConfig.P2P_LINK_MANAGER_WORKER_REST_ENDPOINT
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.net.URI
+
+class LinkManagerRpcClientTest {
+    private val mockClient = mock< HttpRpcClient>()
+    private val publisherFactory = mock<PublisherFactory> {
+        on { createHttpRpcClient() } doReturn mockClient
+    }
+    private val platformInfoProvider = mock<PlatformInfoProvider> {
+        on { localWorkerSoftwareShortVersion } doReturn "5.x"
+    }
+    private val bootConfig = mock<SmartConfig> {
+        on { getString(P2P_LINK_MANAGER_WORKER_REST_ENDPOINT) } doReturn "link-manager:2001"
+    }
+    private val client = LinkManagerRpcClient(
+        publisherFactory,
+        platformInfoProvider,
+        bootConfig,
+    )
+
+    @Test
+    fun `send sends the message to the correct URL`() {
+        val uri = argumentCaptor<URI>()
+        whenever(
+            mockClient.send(
+                uri.capture(),
+                any(),
+                eq(AuthenticatedDataMessage::class.java),
+            )
+        ).doReturn(null)
+
+        client.send(mock())
+
+        assertThat(uri.firstValue.toString())
+            .isEqualTo("http://link-manager:2001/api/5.x$P2P_LINK_MANAGER_PATH")
+    }
+
+    @Test
+    fun `send sends the correct object`() {
+        val sent = argumentCaptor<LinkInMessage>()
+        whenever(
+            mockClient.send(
+                any(),
+                sent.capture(),
+                eq(AuthenticatedDataMessage::class.java),
+            )
+        ).doReturn(null)
+        val message = LinkInMessage()
+
+        client.send(message)
+
+        assertThat(sent.firstValue)
+            .isSameAs(message)
+    }
+
+    @Test
+    fun `send return the correct response the correct object`() {
+        val message = AuthenticatedDataMessage()
+        whenever(
+            mockClient.send(
+                any(),
+                any(),
+                eq(AuthenticatedDataMessage::class.java),
+            )
+        ).doReturn(message)
+
+        assertThat(client.send(mock())).isSameAs(message)
+    }
+}
diff --git a/components/link-manager/build.gradle b/components/link-manager/build.gradle
index c8950c3747d..6d9fdb60ec9 100644
--- a/components/link-manager/build.gradle
+++ b/components/link-manager/build.gradle
@@ -88,6 +88,7 @@ dependencies {
     nonOsgiIntegrationTestImplementation project(":libs:messaging:db-message-bus-impl")
     nonOsgiIntegrationTestImplementation project(":components:crypto:crypto-client")
     nonOsgiIntegrationTestImplementation project(':libs:schema-registry:schema-registry')
+    nonOsgiIntegrationTestImplementation project(':libs:platform-info')
     nonOsgiIntegrationTestImplementation "net.corda:corda-avro-schema"
     nonOsgiIntegrationTestImplementation project(":libs:schema-registry:schema-registry-impl")
     nonOsgiIntegrationTestRuntimeOnly 'org.osgi:osgi.core'
diff --git a/components/link-manager/src/nonOsgiIntegrationTest/kotlin/net/corda/p2p/P2PLayerEndToEndTest.kt b/components/link-manager/src/nonOsgiIntegrationTest/kotlin/net/corda/p2p/P2PLayerEndToEndTest.kt
index d1c0b3c677e..b5d1e51863e 100644
--- a/components/link-manager/src/nonOsgiIntegrationTest/kotlin/net/corda/p2p/P2PLayerEndToEndTest.kt
+++ b/components/link-manager/src/nonOsgiIntegrationTest/kotlin/net/corda/p2p/P2PLayerEndToEndTest.kt
@@ -40,6 +40,7 @@ import net.corda.libs.configuration.schema.p2p.LinkManagerConfiguration.Companio
 import net.corda.libs.configuration.schema.p2p.LinkManagerConfiguration.Companion.SESSIONS_PER_PEER_KEY
 import net.corda.libs.configuration.schema.p2p.LinkManagerConfiguration.Companion.SESSION_REFRESH_THRESHOLD_KEY
 import net.corda.libs.configuration.schema.p2p.LinkManagerConfiguration.Companion.SESSION_TIMEOUT_KEY
+import net.corda.libs.platform.PlatformInfoProvider
 import net.corda.lifecycle.Lifecycle
 import net.corda.lifecycle.LifecycleCoordinatorName
 import net.corda.lifecycle.LifecycleStatus
@@ -78,6 +79,7 @@ import net.corda.schema.Schemas.P2P.P2P_IN_TOPIC
 import net.corda.schema.Schemas.P2P.P2P_OUT_MARKERS
 import net.corda.schema.Schemas.P2P.P2P_OUT_TOPIC
 import net.corda.schema.configuration.BootConfig.INSTANCE_ID
+import net.corda.schema.configuration.BootConfig.P2P_LINK_MANAGER_WORKER_REST_ENDPOINT
 import net.corda.schema.configuration.ConfigKeys
 import net.corda.schema.configuration.MessagingConfig
 import net.corda.schema.registry.impl.AvroSchemaRegistryImpl
@@ -665,6 +667,17 @@ class P2PLayerEndToEndTest {
                 mock(),
                 mock(),
             )
+        private val platformInfoProvider = object : PlatformInfoProvider {
+            override val activePlatformVersion = 1
+            override val localWorkerPlatformVersion = 1
+            override val localWorkerSoftwareVersion = "5.2"
+        }
+        private val bootConfig = SmartConfigFactory.createWithoutSecurityServices()
+            .create(ConfigFactory.empty())
+            .withValue(
+                P2P_LINK_MANAGER_WORKER_REST_ENDPOINT,
+                ConfigValueFactory.fromAnyRef("localhost:8080"),
+            )
 
         private val gateway =
             Gateway(
@@ -672,9 +685,11 @@ class P2PLayerEndToEndTest {
                 subscriptionFactory,
                 publisherFactory,
                 lifecycleCoordinatorFactory,
-                bootstrapConfig,
                 cryptoOpsClient,
-                AvroSchemaRegistryImpl()
+                AvroSchemaRegistryImpl(),
+                platformInfoProvider,
+                bootConfig,
+                bootstrapConfig,
             )
 
         private fun Publisher.publishConfig(key: String, config: Config) {
diff --git a/gradle.properties b/gradle.properties
index 18c34e8644e..bb81ddb510f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -46,7 +46,7 @@ commonsTextVersion = 1.10.0
 bouncycastleVersion=1.76
 # Corda API libs revision (change in 4th digit indicates a breaking change)
 # Change to 5.2.0.xx-SNAPSHOT to pick up maven local published copy
-cordaApiVersion=5.2.0.13-beta+
+cordaApiVersion=5.2.0.14-beta+
 
 disruptorVersion=3.4.4
 felixConfigAdminVersion=1.9.26
diff --git a/libs/messaging/messaging/src/main/kotlin/net/corda/messaging/api/constants/WorkerRPCPaths.kt b/libs/messaging/messaging/src/main/kotlin/net/corda/messaging/api/constants/WorkerRPCPaths.kt
index f2b82e48308..7faa586a8e9 100644
--- a/libs/messaging/messaging/src/main/kotlin/net/corda/messaging/api/constants/WorkerRPCPaths.kt
+++ b/libs/messaging/messaging/src/main/kotlin/net/corda/messaging/api/constants/WorkerRPCPaths.kt
@@ -12,4 +12,5 @@ object WorkerRPCPaths {
     const val UNIQUENESS_PATH = "/uniqueness-checker"
     const val VERIFICATION_PATH = "/verification"
     const val TOKEN_SELECTION_PATH = "/token-selection"
+    const val P2P_LINK_MANAGER_PATH = "/p2p-link-manager"
 }
diff --git a/processors/gateway-processor/build.gradle b/processors/gateway-processor/build.gradle
index 7d9e1fece40..10c0ba7b1b9 100644
--- a/processors/gateway-processor/build.gradle
+++ b/processors/gateway-processor/build.gradle
@@ -17,6 +17,7 @@ dependencies {
     implementation project(':libs:lifecycle:lifecycle')
     implementation project(':libs:messaging:messaging')
     implementation project(':libs:schema-registry:schema-registry')
+    implementation project(':libs:platform-info')
     implementation 'net.corda:corda-base'
 
     runtimeOnly project(':components:configuration:configuration-read-service-impl')
diff --git a/processors/gateway-processor/src/main/kotlin/net/corda/processors/p2p/gateway/internal/GatewayProcessorImpl.kt b/processors/gateway-processor/src/main/kotlin/net/corda/processors/p2p/gateway/internal/GatewayProcessorImpl.kt
index cfa4d27dafb..9f02c535dfa 100644
--- a/processors/gateway-processor/src/main/kotlin/net/corda/processors/p2p/gateway/internal/GatewayProcessorImpl.kt
+++ b/processors/gateway-processor/src/main/kotlin/net/corda/processors/p2p/gateway/internal/GatewayProcessorImpl.kt
@@ -4,6 +4,7 @@ import net.corda.configuration.read.ConfigurationReadService
 import net.corda.crypto.client.CryptoOpsClient
 import net.corda.libs.configuration.SmartConfig
 import net.corda.libs.configuration.merger.ConfigMerger
+import net.corda.libs.platform.PlatformInfoProvider
 import net.corda.lifecycle.LifecycleCoordinator
 import net.corda.lifecycle.LifecycleCoordinatorFactory
 import net.corda.lifecycle.LifecycleCoordinatorName
@@ -39,7 +40,9 @@ class GatewayProcessorImpl @Activate constructor(
     @Reference(service = CryptoOpsClient::class)
     private val cryptoOpsClient: CryptoOpsClient,
     @Reference(service = AvroSchemaRegistry::class)
-    private val avroSchemaRegistry: AvroSchemaRegistry
+    private val avroSchemaRegistry: AvroSchemaRegistry,
+    @Reference(service = PlatformInfoProvider::class)
+    private val platformInfoProvider: PlatformInfoProvider,
 ) : GatewayProcessor {
 
     private companion object {
@@ -79,9 +82,11 @@ class GatewayProcessorImpl @Activate constructor(
                     subscriptionFactory,
                     publisherFactory,
                     coordinatorFactory,
-                    configMerger.getMessagingConfig(event.config),
                     cryptoOpsClient,
-                    avroSchemaRegistry
+                    avroSchemaRegistry,
+                    platformInfoProvider,
+                    bootConfig = event.config,
+                    messagingConfiguration = configMerger.getMessagingConfig(event.config),
                 )
                 this.gateway = gateway
 

From 632cee8cf3f71383fe1583eb9977c4a68b536e8d Mon Sep 17 00:00:00 2001
From: Lorcan Wogan <69468264+LWogan@users.noreply.github.com>
Date: Thu, 14 Dec 2023 16:17:03 +0000
Subject: [PATCH 4/7] CORE-18936 mediator attempts never resets to 0.  (#5272)

Only increment once per retry
---
 .../corda/messaging/mediator/MultiSourceEventMediatorImpl.kt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt b/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
index e60852fbd9d..9675ed0b46e 100644
--- a/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
+++ b/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
@@ -112,10 +112,10 @@ class MultiSourceEventMediatorImpl<K : Any, S : Any, E : Any>(
                             consumer.subscribe()
                         }
                         pollAndProcessEvents(consumer)
+                        attempts = 0
                     } catch (exception: Exception) {
                         when (exception) {
                             is CordaMessageAPIIntermittentException -> {
-                                attempts++
                                 log.warn(
                                     "Multi-source event mediator ${config.name} failed to process records, " +
                                             "Retrying poll and process. Attempts: $attempts.")

From 89db8fe3592c4812cdacf797259726c8c419f400 Mon Sep 17 00:00:00 2001
From: Lorcan Wogan <69468264+LWogan@users.noreply.github.com>
Date: Thu, 14 Dec 2023 16:39:33 +0000
Subject: [PATCH 5/7] CORE-18935 handle completion exceptions thrown within the
 mediator properly (#5270)

Can be thrown by CompletableFuture join calls. this will wrap the thrown CordaMessageAPIIntermittentExceptions in CompletionExceptions. This can erroneously treat them as fatal
---
 .../mediator/MultiSourceEventMediatorImpl.kt       |  4 +++-
 .../mediator/MultiSourceEventMediatorImplTest.kt   | 14 +++++++-------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt b/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
index 9675ed0b46e..9ee3826b9af 100644
--- a/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
+++ b/libs/messaging/messaging-impl/src/main/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImpl.kt
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory
 import java.lang.Thread.sleep
 import java.time.Duration
 import java.util.UUID
+import java.util.concurrent.CompletionException
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicBoolean
@@ -114,7 +115,8 @@ class MultiSourceEventMediatorImpl<K : Any, S : Any, E : Any>(
                         pollAndProcessEvents(consumer)
                         attempts = 0
                     } catch (exception: Exception) {
-                        when (exception) {
+                        val cause = if (exception is CompletionException) exception.cause else exception
+                        when (cause) {
                             is CordaMessageAPIIntermittentException -> {
                                 log.warn(
                                     "Multi-source event mediator ${config.name} failed to process records, " +
diff --git a/libs/messaging/messaging-impl/src/test/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImplTest.kt b/libs/messaging/messaging-impl/src/test/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImplTest.kt
index b446a17f605..aecaf5831c4 100644
--- a/libs/messaging/messaging-impl/src/test/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImplTest.kt
+++ b/libs/messaging/messaging-impl/src/test/kotlin/net/corda/messaging/mediator/MultiSourceEventMediatorImplTest.kt
@@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicInteger
 
 class MultiSourceEventMediatorImplTest {
     companion object {
-        private const val TEST_TIMEOUT_SECONDS = 20L
+        private const val TEST_TIMEOUT_SECONDS = 2000L
         private const val TEST_PROCESSOR_RETRIES = 3
         const val KEY1 = "key1"
         const val KEY2 = "key2"
@@ -93,7 +93,7 @@ class MultiSourceEventMediatorImplTest {
         }
 
         val messageRouter = MessageRouter { _ ->
-            RoutingDestination.routeTo(messagingClient, "endpoint", RoutingDestination.Type.ASYNCHRONOUS)
+            RoutingDestination.routeTo(messagingClient, "endpoint", RoutingDestination.Type.SYNCHRONOUS)
         }
         whenever(messageRouterFactory.create(any<MessagingClientFinder>())).thenReturn(messageRouter)
 
@@ -176,8 +176,8 @@ class MultiSourceEventMediatorImplTest {
         verify(messagingClient, times(events.size)).send(any())
     }
 
-//    @Test
-    fun `mediator retries after intermittent exceptions`() {
+    @Test
+    fun `mediator retries after intermittent exceptions wrapped in completion exception`() {
         val event1 = cordaConsumerRecords(KEY1, "event1")
         val sendCount = AtomicInteger(0)
         val errorsCount = TEST_PROCESSOR_RETRIES + 1
@@ -202,9 +202,9 @@ class MultiSourceEventMediatorImplTest {
         waitWhile(Duration.ofSeconds(TEST_TIMEOUT_SECONDS)) { sendCount.get() < expectedProcessingCount }
         mediator.close()
 
-        verify(mediatorConsumerFactory, times(2)).create(any<MediatorConsumerConfig<Any, Any>>())
-        verify(messagingClientFactory, times(2)).create(any<MessagingClientConfig>())
-        verify(messageRouterFactory, times(2)).create(any<MessagingClientFinder>())
+        verify(mediatorConsumerFactory, times(1)).create(any<MediatorConsumerConfig<Any, Any>>())
+        verify(messagingClientFactory, times(1)).create(any<MessagingClientConfig>())
+        verify(messageRouterFactory, times(1)).create(any<MessagingClientFinder>())
         verify(messageProcessor, times(expectedProcessingCount)).onNext(anyOrNull(), any())
         verify(consumer, atLeast(expectedProcessingCount)).poll(any())
         verify(messagingClient, times(expectedProcessingCount)).send(any())

From 0c55559a44a7182d845b9e05a72542f8f4a9a6c6 Mon Sep 17 00:00:00 2001
From: nkovacsx <57627796+nkovacsx@users.noreply.github.com>
Date: Thu, 14 Dec 2023 16:40:31 +0000
Subject: [PATCH 6/7] CORE-18798 Only pass in backchain property if >5.1 and
 add upgrade logic (#5231)

- We should only pass in corda.notary.service.backchain.required when registering a notary if the version is >= 5.2.
- We should be able to update this flag during re-registration, added that logic too.
- I also added a logic when upgrading from 5.0/5.1 to 5.2+. In the "old" context we won't have the corda.notary.service.backchain.required property and we should only upgrade a transition of null->true because pre-5.2 notaries will always be backchain required.
- Added tests to verify.
---
 .../DynamicMemberRegistrationService.kt       |  17 ++-
 .../DynamicMemberRegistrationServiceTest.kt   | 113 +++++++++++++++++-
 gradle.properties                             |   2 +-
 .../e2etest/utilities/MembershipUtils.kt      |   9 +-
 4 files changed, 133 insertions(+), 8 deletions(-)

diff --git a/components/membership/registration-impl/src/main/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationService.kt b/components/membership/registration-impl/src/main/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationService.kt
index fa174cb135b..c28581b3976 100644
--- a/components/membership/registration-impl/src/main/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationService.kt
+++ b/components/membership/registration-impl/src/main/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationService.kt
@@ -61,6 +61,7 @@ import net.corda.membership.lib.MemberInfoExtension.Companion.MEMBER_CPI_SIGNER_
 import net.corda.membership.lib.MemberInfoExtension.Companion.MEMBER_CPI_VERSION
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_KEY_SPEC
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_ROLE
+import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_BACKCHAIN_REQUIRED
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_PROTOCOL_VERSIONS
 import net.corda.membership.lib.MemberInfoExtension.Companion.PARTY_NAME
 import net.corda.membership.lib.MemberInfoExtension.Companion.PARTY_SESSION_KEYS
@@ -471,11 +472,12 @@ class DynamicMemberRegistrationService @Activate constructor(
                     tlsSubject
 
             previousRegistrationContext?.let { previous ->
+                verifyBackchainFlagMovement(previousRegistrationContext, newRegistrationContext)
                 ((newRegistrationContext.entries - previous.entries) + (previous.entries - newRegistrationContext.entries)).filter {
                     it.key.startsWith(SESSION_KEYS) ||
                     it.key.startsWith(LEDGER_KEYS) ||
                     it.key.startsWith(ROLES_PREFIX) ||
-                    it.key.startsWith("corda.notary")
+                    (it.key.startsWith("corda.notary") && !it.key.endsWith("service.backchain.required"))
                 }.apply {
                     require(isEmpty()) {
                         throw InvalidMembershipRegistrationException(
@@ -812,4 +814,17 @@ class DynamicMemberRegistrationService @Activate constructor(
             )
         }
     }
+
+    private fun verifyBackchainFlagMovement(previousContext: Map<String, String>, newContext: Map<String, String>) {
+        // This property can only be null when upgrading from 5.0/5.1, and we should move it to `true`
+        // because pre-5.2 notaries do not support optional backchain
+        // Once the flag is set it should never change during re-registrations
+        // (i.e. no true->false or false->true change allowed)
+        val previousOptionalBackchainValue = previousContext[NOTARY_SERVICE_BACKCHAIN_REQUIRED]?.toBoolean()
+        val currentOptionalBackchainValue = newContext[NOTARY_SERVICE_BACKCHAIN_REQUIRED]?.toBoolean()
+        require((previousOptionalBackchainValue == null && currentOptionalBackchainValue == true)
+                || previousOptionalBackchainValue == currentOptionalBackchainValue) {
+            "Optional back-chain flag can only move from 'none' to 'true' during re-registration."
+        }
+    }
 }
diff --git a/components/membership/registration-impl/src/test/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationServiceTest.kt b/components/membership/registration-impl/src/test/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationServiceTest.kt
index 3f4fd6f6fd2..a7474ed6efd 100644
--- a/components/membership/registration-impl/src/test/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationServiceTest.kt
+++ b/components/membership/registration-impl/src/test/kotlin/net/corda/membership/impl/registration/dynamic/member/DynamicMemberRegistrationServiceTest.kt
@@ -57,9 +57,8 @@ import net.corda.membership.lib.MemberInfoExtension.Companion.LEDGER_KEY_SIGNATU
 import net.corda.membership.lib.MemberInfoExtension.Companion.MEMBER_CPI_NAME
 import net.corda.membership.lib.MemberInfoExtension.Companion.MEMBER_CPI_SIGNER_HASH
 import net.corda.membership.lib.MemberInfoExtension.Companion.MEMBER_CPI_VERSION
-import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_KEY_HASH
-import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_KEY_PEM
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_KEY_SPEC
+import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_BACKCHAIN_REQUIRED
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_NAME
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_PROTOCOL
 import net.corda.membership.lib.MemberInfoExtension.Companion.NOTARY_SERVICE_PROTOCOL_VERSIONS
@@ -155,13 +154,20 @@ class DynamicMemberRegistrationServiceTest {
         const val TEST_KEY_ID = "DDD123456789"
         const val PUBLISHER_CLIENT_ID = "dynamic-member-registration-service"
         const val GROUP_NAME = "dummy_group"
+        const val NOTARY_KEY_PEM = "1234"
+        const val NOTARY_KEY_HASH = "SHA-256:73A2AF8864FC500FA49048BF3003776C19938F360E56BD03663866FB3087884A"
+        const val NOTARY_KEY_SIG_SPEC = "SHA256withECDSA"
 
         val MEMBER_CONTEXT_BYTES = "2222".toByteArray()
         val REQUEST_BYTES = "3333".toByteArray()
         val UNAUTH_REQUEST_BYTES = "4444".toByteArray()
         const val SESSION_KEY_ID_KEY = "corda.session.keys.0.id"
         const val LEDGER_KEY_ID_KEY = "corda.ledger.keys.0.id"
+
         const val NOTARY_KEY_ID_KEY = "corda.notary.keys.0.id"
+        const val NOTARY_KEY_PEM_KEY = "corda.notary.keys.0.pem"
+        const val NOTARY_KEY_HASH_KEY = "corda.notary.keys.0.hash"
+        const val NOTARY_KEY_SIG_SPEC_KEY = "corda.notary.keys.0.signature.spec"
     }
 
     private val ecdhKey: PublicKey = mock()
@@ -418,6 +424,15 @@ class DynamicMemberRegistrationServiceTest {
         MEMBER_CPI_SIGNER_HASH to testCpiSignerSummaryHash.toString(),
     )
 
+    private val previousNotaryRegistrationContext = previousRegistrationContext.filter {
+        !it.key.contains(LEDGER_KEYS)
+    } + mapOf(
+        "$ROLES_PREFIX.0" to "notary",
+        NOTARY_SERVICE_PROTOCOL to "net.corda.notary.MyNotaryService",
+        NOTARY_SERVICE_NAME to "O=MyNotaryService, L=London, C=GB",
+        NOTARY_KEY_ID_KEY to NOTARY_KEY_ID
+    )
+
     @AfterEach
     fun cleanUp() {
         registrationContextCustomFieldsVerifier.close()
@@ -1457,6 +1472,96 @@ class DynamicMemberRegistrationServiceTest {
             registrationService.stop()
         }
 
+        @Test
+        fun `re-registration allows optional backchain flag to be set to true from null`() {
+            val notaryKeyConvertedFields = mapOf(
+                NOTARY_KEY_PEM_KEY to NOTARY_KEY_PEM,
+                NOTARY_KEY_HASH_KEY to NOTARY_KEY_HASH,
+                NOTARY_KEY_SIG_SPEC_KEY to NOTARY_KEY_SIG_SPEC
+            )
+
+            val previous = mock<MemberContext> {
+                on { entries } doReturn (previousNotaryRegistrationContext + notaryKeyConvertedFields).entries
+            }
+            val newContextEntries = (previousNotaryRegistrationContext).toMutableMap().apply {
+                put(SESSION_KEY_ID_KEY, SESSION_KEY_ID)
+                put(NOTARY_SERVICE_BACKCHAIN_REQUIRED, "true")
+            }.entries
+
+            val newContext = mock<MemberContext> {
+                on { entries } doReturn newContextEntries
+            }
+            whenever(memberInfo.memberProvidedContext).doReturn(previous)
+            whenever(groupReader.lookup(eq(memberName), any())).doReturn(memberInfo)
+
+            assertDoesNotThrow {
+                registrationService.register(registrationResultId, member, newContext.toMap())
+            }
+        }
+
+        @Test
+        fun `re-registration does not allow optional backchain flag to be set to false from null`() {
+            val notaryKeyConvertedFields = mapOf(
+                NOTARY_KEY_PEM_KEY to NOTARY_KEY_PEM,
+                NOTARY_KEY_HASH_KEY to NOTARY_KEY_HASH,
+                NOTARY_KEY_SIG_SPEC_KEY to NOTARY_KEY_SIG_SPEC
+            )
+
+            val previous = mock<MemberContext> {
+                on { entries } doReturn (previousNotaryRegistrationContext + notaryKeyConvertedFields).entries
+            }
+            val newContextEntries = (previousNotaryRegistrationContext).toMutableMap().apply {
+                put(SESSION_KEY_ID_KEY, SESSION_KEY_ID)
+                put(NOTARY_SERVICE_BACKCHAIN_REQUIRED, "false")
+            }.entries
+
+            val newContext = mock<MemberContext> {
+                on { entries } doReturn newContextEntries
+            }
+            whenever(memberInfo.memberProvidedContext).doReturn(previous)
+            whenever(groupReader.lookup(eq(memberName), any())).doReturn(memberInfo)
+
+            val registrationException = assertThrows<InvalidMembershipRegistrationException> {
+                registrationService.register(registrationResultId, member, newContext.toMap())
+            }
+
+            assertThat(registrationException)
+                .hasStackTraceContaining("Optional back-chain flag can only move from 'none' to 'true' during re-registration.")
+        }
+
+        @Test
+        fun `re-registration does not allow optional backchain flag to be set to false from true`() {
+            val notaryKeyConvertedFields = mapOf(
+                NOTARY_KEY_PEM_KEY to NOTARY_KEY_PEM,
+                NOTARY_KEY_HASH_KEY to NOTARY_KEY_HASH,
+                NOTARY_KEY_SIG_SPEC_KEY to NOTARY_KEY_SIG_SPEC
+            )
+
+            val previousNotaryRegistrationContextWithBackchainFlag = previousNotaryRegistrationContext +
+                    mapOf(NOTARY_SERVICE_BACKCHAIN_REQUIRED to "true")
+
+            val previous = mock<MemberContext> {
+                on { entries } doReturn (previousNotaryRegistrationContextWithBackchainFlag + notaryKeyConvertedFields).entries
+            }
+            val newContextEntries = (previousNotaryRegistrationContextWithBackchainFlag).toMutableMap().apply {
+                put(SESSION_KEY_ID_KEY, SESSION_KEY_ID)
+                put(NOTARY_SERVICE_BACKCHAIN_REQUIRED, "false")
+            }.entries
+
+            val newContext = mock<MemberContext> {
+                on { entries } doReturn newContextEntries
+            }
+            whenever(memberInfo.memberProvidedContext).doReturn(previous)
+            whenever(groupReader.lookup(eq(memberName), any())).doReturn(memberInfo)
+
+            val registrationException = assertThrows<InvalidMembershipRegistrationException> {
+                registrationService.register(registrationResultId, member, newContext.toMap())
+            }
+
+            assertThat(registrationException)
+                .hasStackTraceContaining("Optional back-chain flag can only move from 'none' to 'true' during re-registration.")
+        }
+
         @Test
         fun `registration fails when notary keys are numbered incorrectly`() {
             val testProperties =
@@ -1533,8 +1638,8 @@ class DynamicMemberRegistrationServiceTest {
                 .containsEntry(String.format(ROLES_PREFIX, 0), "notary")
                 .containsKey(NOTARY_SERVICE_NAME)
                 .containsEntry(NOTARY_KEY_ID_KEY, NOTARY_KEY_ID)
-                .containsEntry(String.format(NOTARY_KEY_PEM, 0), "1234")
-                .containsKey(String.format(NOTARY_KEY_HASH, 0))
+                .containsEntry(String.format(NOTARY_KEY_PEM_KEY, 0), "1234")
+                .containsKey(String.format(NOTARY_KEY_HASH_KEY, 0))
                 .containsEntry(String.format(NOTARY_KEY_SPEC, 0), SignatureSpecs.ECDSA_SHA256.signatureName)
         }
 
diff --git a/gradle.properties b/gradle.properties
index bb81ddb510f..e207c4bf69a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -46,7 +46,7 @@ commonsTextVersion = 1.10.0
 bouncycastleVersion=1.76
 # Corda API libs revision (change in 4th digit indicates a breaking change)
 # Change to 5.2.0.xx-SNAPSHOT to pick up maven local published copy
-cordaApiVersion=5.2.0.14-beta+
+cordaApiVersion=5.2.0.15-beta+
 
 disruptorVersion=3.4.4
 felixConfigAdminVersion=1.9.26
diff --git a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
index ba55c8ed0e4..4e2ee624dec 100644
--- a/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
+++ b/testing/e2e-test-utilities/src/main/kotlin/net/corda/e2etest/utilities/MembershipUtils.kt
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import net.corda.e2etest.utilities.types.NetworkOnboardingMetadata
 import net.corda.e2etest.utilities.types.jsonToMemberList
 import net.corda.rest.ResponseCode
+import net.corda.rest.annotations.RestApiVersion
 import net.corda.test.util.eventually
 import net.corda.utilities.minutes
 import net.corda.utilities.seconds
@@ -171,12 +172,16 @@ fun ClusterInfo.onboardNotaryMember(
         mapOf(
             "corda.roles.0" to "notary",
             "corda.notary.service.name" to MemberX500Name.parse(notaryServiceName).toString(),
-            "corda.notary.service.backchain.required" to "$isBackchainRequired",
             "corda.notary.service.flow.protocol.name" to "com.r3.corda.notary.plugin.$notaryPlugin",
             "corda.notary.service.flow.protocol.version.0" to "1",
             "corda.notary.keys.0.id" to notaryKeyId,
             "corda.notary.keys.0.signature.spec" to DEFAULT_SIGNATURE_SPEC
-        ) + (getAdditionalContext?.let { it(holdingId) } ?: emptyMap())
+        ) + (getAdditionalContext?.let { it(holdingId) } ?: emptyMap()) + (
+                // Add the optional backchain property if version is >= 5.2
+                if (restApiVersion != RestApiVersion.C5_0 && restApiVersion != RestApiVersion.C5_1)
+                    mapOf("corda.notary.service.backchain.required" to "$isBackchainRequired")
+                else emptyMap()
+        )
     },
     tlsCertificateUploadedCallback = tlsCertificateUploadedCallback,
     useLedgerKey = false

From e79670d36cb5fa2b5a7f0c86213107093c11ed2f Mon Sep 17 00:00:00 2001
From: Dries Samyn <dries.samyn@r3.com>
Date: Thu, 14 Dec 2023 17:16:00 +0000
Subject: [PATCH 7/7] CORE-18912 - introduce version catalogue (#5264)

Add Kotlin and test libs in version catalog.
---
 .../release/crypto-worker/build.gradle        |  2 --
 .../workers/release/db-worker/build.gradle    |  2 --
 .../release/persistence-worker/build.gradle   |  2 --
 .../token-selection-worker/build.gradle       |  2 --
 .../release/uniqueness-worker/build.gradle    |  2 --
 .../workers/worker-common/build.gradle        |  2 --
 .../workers/workers-smoketest/build.gradle    |  5 ++-
 build.gradle                                  | 24 +++++--------
 buildSrc/settings.gradle                      |  7 ++++
 .../src/main/groovy/corda.common-app.gradle   | 10 +++---
 .../main/groovy/corda.common-library.gradle   |  9 +++--
 .../groovy/corda.osgi-test-conventions.gradle |  5 ++-
 .../chunking/chunk-db-write-impl/build.gradle |  2 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../crypto-client-hsm-impl/build.gradle       |  2 --
 .../crypto/crypto-client-impl/build.gradle    |  2 --
 .../crypto-component-test-utils/build.gradle  |  4 +--
 .../crypto/crypto-hes-impl/build.gradle       |  5 ---
 .../crypto/crypto-persistence/build.gradle    |  6 ----
 components/crypto/crypto-rest/build.gradle    |  2 --
 .../crypto/crypto-service-impl/build.gradle   |  1 -
 .../crypto/crypto-softhsm-impl/build.gradle   |  1 -
 .../db-connection-manager-impl/build.gradle   |  2 --
 components/domino-logic/build.gradle          |  2 --
 .../external-messaging-services/build.gradle  |  4 ---
 components/flow/flow-mapper-impl/build.gradle |  3 --
 .../flow/flow-mapper-service/build.gradle     |  4 ---
 .../flow/flow-p2p-filter-service/build.gradle |  3 --
 .../build.gradle                              |  2 --
 components/flow/flow-service/build.gradle     |  3 --
 components/gateway/build.gradle               |  2 --
 .../ledger/ledger-common-flow/build.gradle    |  2 --
 .../ledger-consensual-flow/build.gradle       |  3 --
 .../ledger/ledger-persistence/build.gradle    |  3 +-
 .../ledger/ledger-utxo-flow/build.gradle      |  4 ---
 .../ledger-utxo-token-cache/build.gradle      |  3 --
 .../ledger/ledger-verification/build.gradle   |  4 +--
 .../notary-worker-selection-impl/build.gradle |  3 --
 .../notary-worker-selection/build.gradle      |  3 --
 components/link-manager/build.gradle          |  2 --
 .../certificates-client-impl/build.gradle     |  2 --
 .../certificates-service-impl/build.gradle    |  3 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  3 --
 .../membership/group-policy-impl/build.gradle |  2 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../membership-client-impl/build.gradle       |  3 --
 .../membership-group-read-impl/build.gradle   |  2 --
 .../membership-p2p-impl/build.gradle          |  3 --
 .../membership/membership-p2p/build.gradle    |  2 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  5 ---
 .../membership-rest-impl/build.gradle         |  3 --
 .../membership-service-impl/build.gradle      |  4 ---
 .../build.gradle                              |  2 --
 .../membership/registration-impl/build.gradle |  3 --
 .../synchronisation-impl/build.gradle         |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  4 +--
 .../persistence-service-common/build.gradle   |  3 --
 .../build.gradle                              |  3 --
 .../reconciliation-impl/build.gradle          |  4 ---
 components/rest-gateway-comp/build.gradle     |  3 --
 components/scheduler/build.gradle             |  4 ---
 .../backing-store-impl/build.gradle           |  3 --
 .../build.gradle                              |  4 ---
 .../uniqueness-checker-impl/build.gradle      |  4 ---
 .../cpi-info-read-service-impl/build.gradle   |  4 ---
 .../cpi-info-write-service-impl/build.gradle  |  3 --
 .../cpi-info-write-service/build.gradle       |  3 --
 .../cpi-upload-rest-service/build.gradle      |  2 --
 .../cpk-read-service-impl/build.gradle        |  3 --
 .../cpk-write-service-impl/build.gradle       |  3 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  3 --
 .../build.gradle                              |  3 --
 gradle.properties                             |  5 ---
 gradle/libs.versions.toml                     | 34 +++++++++++++++++++
 .../application/application-impl/build.gradle |  3 --
 libs/cache/cache-caffeine/build.gradle        |  3 --
 libs/chunking/chunking-core/build.gradle      |  3 --
 .../configuration-core/build.gradle           |  4 ---
 .../configuration-datamodel/build.gradle      |  3 --
 .../configuration-merger/build.gradle         |  3 --
 .../configuration-validation/build.gradle     |  3 --
 libs/crypto/cipher-suite-impl/build.gradle    |  5 ---
 libs/crypto/cipher-suite/build.gradle         |  5 ---
 libs/crypto/crypto-config-impl/build.gradle   |  4 ---
 libs/crypto/crypto-core/build.gradle          |  4 ---
 libs/crypto/crypto-flow/build.gradle          |  5 ---
 libs/crypto/crypto-impl/build.gradle          |  4 ---
 .../crypto-serialization-impl/build.gradle    |  5 ---
 libs/crypto/delegated-signing/build.gradle    |  2 --
 libs/crypto/merkle-impl/build.gradle          |  4 ---
 libs/db/db-admin-impl/build.gradle            |  3 --
 libs/db/db-core/build.gradle                  |  3 --
 libs/db/db-orm-impl/build.gradle              |  3 --
 libs/db/db-orm/build.gradle                   |  3 --
 libs/external-messaging/build.gradle          |  3 --
 .../build.gradle                              |  3 --
 libs/flows/session-manager-impl/build.gradle  |  3 --
 libs/kotlin-heap-fix/build.gradle             |  2 +-
 libs/kotlin-reflection/build.gradle           | 11 +++---
 libs/layered-property-map/build.gradle        |  1 -
 libs/ledger/ledger-common-data/build.gradle   |  5 ---
 libs/ledger/ledger-utxo-data/build.gradle     |  4 ---
 .../build.gradle                              |  3 --
 libs/lifecycle/lifecycle-impl/build.gradle    |  2 --
 .../lifecycle-test-impl/build.gradle          |  6 ++--
 libs/lifecycle/lifecycle/build.gradle         |  2 --
 libs/lifecycle/registry/build.gradle          |  2 --
 .../membership/membership-common/build.gradle |  2 --
 .../membership-datamodel/build.gradle         |  3 --
 libs/membership/membership-impl/build.gradle  |  4 ---
 libs/membership/network-info/build.gradle     |  3 --
 .../membership/schema-validation/build.gradle |  4 ---
 .../db-message-bus-impl/build.gradle          |  3 --
 .../db-topic-admin-impl/build.gradle          |  2 --
 .../kafka-message-bus-impl/build.gradle       |  3 --
 .../kafka-topic-admin-impl/build.gradle       |  3 --
 libs/messaging/messaging-impl/build.gradle    |  3 --
 libs/messaging/messaging/build.gradle         |  3 --
 libs/metrics/build.gradle                     |  1 -
 libs/p2p-crypto/build.gradle                  |  2 --
 libs/packaging/packaging-core/build.gradle    |  3 --
 libs/packaging/packaging-verify/build.gradle  |  2 --
 libs/packaging/packaging/build.gradle         |  3 --
 .../permission-cache-common/build.gradle      |  2 --
 .../permission-endpoint/build.gradle          |  3 --
 .../build.gradle                              |  2 --
 .../permission-manager-impl/build.gradle      |  2 --
 .../permission-password/build.gradle          |  1 -
 .../build.gradle                              |  2 --
 .../build.gradle                              |  2 --
 .../build.gradle                              |  2 --
 .../permission-validation-impl/build.gradle   |  3 --
 libs/platform-info/build.gradle               |  2 --
 libs/rest/json-serialization/build.gradle     |  2 --
 libs/rest/rbac-security-manager/build.gradle  |  2 --
 libs/rest/rest-client/build.gradle            |  4 ---
 libs/rest/rest-common/build.gradle            |  2 --
 libs/rest/rest-server-impl/build.gradle       |  7 ----
 libs/rest/rest-test-common/build.gradle       |  2 +-
 libs/rest/rest/build.gradle                   |  2 --
 libs/rest/ssl-cert-read-impl/build.gradle     |  3 --
 libs/sandbox-hooks/build.gradle               |  2 --
 libs/sandbox-internal/build.gradle            |  2 --
 .../scheduler-datamodel/build.gradle          |  2 --
 .../json-serializers/build.gradle             |  2 --
 .../serialization/json-validator/build.gradle |  2 --
 .../serialization-amqp/build.gradle           |  5 ---
 .../serialization-kryo/build.gradle           |  4 ---
 .../state-manager-db-impl/build.gradle        |  2 --
 libs/task-manager/build.gradle                |  4 ---
 libs/utilities/build.gradle                   |  3 --
 .../cpi-upload-manager-impl/build.gradle      |  2 --
 .../sandbox-group-context/build.gradle        |  3 --
 .../virtual-node-info/build.gradle            |  3 --
 libs/web/web-impl/build.gradle                |  3 --
 libs/web/web/build.gradle                     |  3 --
 .../build.gradle                              |  4 +--
 .../build.gradle                              |  4 +--
 .../build.gradle                              |  5 +--
 .../build.gradle                              |  5 +--
 osgi-framework-bootstrap/build.gradle         |  7 ++--
 processors/crypto-processor/build.gradle      |  3 --
 processors/db-processor/build.gradle          |  3 --
 processors/member-processor/build.gradle      |  2 --
 processors/persistence-processor/build.gradle |  3 --
 processors/rest-processor/build.gradle        |  2 --
 processors/scheduler-processor/build.gradle   |  3 --
 settings.gradle                               |  3 --
 testing/apps/test-app/build.gradle            |  2 --
 .../ledger-consensual-demo-app/build.gradle   |  3 +-
 .../cpbs/ledger-utxo-demo-app/build.gradle    |  3 +-
 .../ledger-utxo-demo-contract/build.gradle    |  2 --
 .../cpi-info-read-service-fake/build.gradle   |  2 --
 testing/db-message-bus-testkit/build.gradle   |  2 +-
 testing/e2e-test-utilities/build.gradle       |  4 +--
 testing/flow/external-events/build.gradle     |  5 ---
 .../kryo-serialization-testkit/build.gradle   |  2 +-
 .../ledger-common-base-test/build.gradle      |  2 +-
 .../ledger-consensual-base-test/build.gradle  |  2 +-
 .../ledger/ledger-utxo-base-test/build.gradle |  2 +-
 testing/message-patterns/build.gradle         |  2 --
 .../p2p/inmemory-messaging-impl/build.gradle  |  3 --
 testing/persistence-testkit/build.gradle      |  4 +--
 testing/sandboxes/build.gradle                |  2 +-
 .../security-manager-utilities/build.gradle   |  2 +-
 testing/test-serialization/build.gradle       |  4 +--
 testing/test-utilities/build.gradle           |  4 +--
 .../backing-store-fake/build.gradle           |  4 ---
 .../uniqueness-utilities/build.gradle         |  4 +--
 tools/plugins/db-config/build.gradle          |  3 +-
 tools/plugins/initial-config/build.gradle     |  1 +
 tools/plugins/network/build.gradle            |  2 +-
 tools/plugins/package/build.gradle            |  2 +-
 tools/plugins/preinstall/build.gradle         |  2 +-
 tools/plugins/secret-config/build.gradle      |  3 +-
 tools/plugins/topic-config/build.gradle       |  3 +-
 tools/plugins/virtual-node/build.gradle       |  4 +--
 213 files changed, 113 insertions(+), 594 deletions(-)
 create mode 100644 buildSrc/settings.gradle
 create mode 100644 gradle/libs.versions.toml

diff --git a/applications/workers/release/crypto-worker/build.gradle b/applications/workers/release/crypto-worker/build.gradle
index 550e154628d..cd9855df449 100644
--- a/applications/workers/release/crypto-worker/build.gradle
+++ b/applications/workers/release/crypto-worker/build.gradle
@@ -38,8 +38,6 @@ dependencies {
 
 
     testRuntimeOnly "org.apache.felix:org.apache.felix.framework:$felixVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     // This puts the jdbc driver into the docker image in the /opt/jdbc-driver folder
     // this folder can contain many jdbc drivers (and DataSourceFactory provider bundles).
diff --git a/applications/workers/release/db-worker/build.gradle b/applications/workers/release/db-worker/build.gradle
index 5b2477f22af..fb5a3266df8 100644
--- a/applications/workers/release/db-worker/build.gradle
+++ b/applications/workers/release/db-worker/build.gradle
@@ -44,8 +44,6 @@ dependencies {
     runtimeOnly project(':libs:tracing-impl')
 
     testImplementation 'org.osgi:osgi.core'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:application:addon')
     testImplementation project(':libs:application:banner')
     testImplementation project(":libs:lifecycle:lifecycle")
diff --git a/applications/workers/release/persistence-worker/build.gradle b/applications/workers/release/persistence-worker/build.gradle
index bb468b352b9..14c9317f68f 100644
--- a/applications/workers/release/persistence-worker/build.gradle
+++ b/applications/workers/release/persistence-worker/build.gradle
@@ -43,8 +43,6 @@ dependencies {
     runtimeOnly project(':libs:web:web-impl')
 
     testImplementation 'org.osgi:osgi.core'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:application:addon')
     testImplementation project(':libs:application:banner')
 
diff --git a/applications/workers/release/token-selection-worker/build.gradle b/applications/workers/release/token-selection-worker/build.gradle
index 46b60623fac..2ca5b84b86f 100644
--- a/applications/workers/release/token-selection-worker/build.gradle
+++ b/applications/workers/release/token-selection-worker/build.gradle
@@ -41,8 +41,6 @@ dependencies {
     runtimeOnly project(':libs:tracing-impl')
 
     testImplementation 'org.osgi:osgi.core'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:application:addon')
     testImplementation project(':libs:application:banner')
 
diff --git a/applications/workers/release/uniqueness-worker/build.gradle b/applications/workers/release/uniqueness-worker/build.gradle
index 1ea5e705a46..4379b10a53a 100644
--- a/applications/workers/release/uniqueness-worker/build.gradle
+++ b/applications/workers/release/uniqueness-worker/build.gradle
@@ -42,8 +42,6 @@ dependencies {
     runtimeOnly project(':libs:tracing-impl')
 
     testImplementation 'org.osgi:osgi.core'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:application:addon')
     testImplementation project(':libs:application:banner')
 
diff --git a/applications/workers/worker-common/build.gradle b/applications/workers/worker-common/build.gradle
index 3bc5a56d0fb..0f5510d1e12 100644
--- a/applications/workers/worker-common/build.gradle
+++ b/applications/workers/worker-common/build.gradle
@@ -53,8 +53,6 @@ dependencies {
     runtimeOnly project(':libs:web:web-impl')
     runtimeOnly "org.apache.commons:commons-lang3:$commonsLangVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:web:web-impl")
 
     testRuntimeOnly 'org.osgi:osgi.core'
diff --git a/applications/workers/workers-smoketest/build.gradle b/applications/workers/workers-smoketest/build.gradle
index 04f7843f3cb..aeb1968f468 100644
--- a/applications/workers/workers-smoketest/build.gradle
+++ b/applications/workers/workers-smoketest/build.gradle
@@ -53,8 +53,6 @@ kotlin {
 
 dependencies {
     // NO CORDA DEPENDENCIES!!
-    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-
     smokeTestImplementation "net.corda:corda-avro-schema:$cordaApiVersion"
     // Avoid having the schema names and keys scattered across projects
     smokeTestImplementation "net.corda:corda-config-schema:$cordaApiVersion"
@@ -82,7 +80,8 @@ dependencies {
     smokeTestImplementation project(":testing:packaging-test-utilities")
     smokeTestImplementation project(':testing:test-utilities')
     smokeTestImplementation project(":testing:uniqueness:uniqueness-utilities")
-    smokeTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
+    smokeTestImplementation libs.bundles.test
+    smokeTestRuntimeOnly libs.bundles.test.runtime
     smokeTestRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
 }
 
diff --git a/build.gradle b/build.gradle
index e38b9b556a8..7514a1dfd70 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,9 +14,9 @@ buildscript {
 }
 
 plugins {
-    id 'org.jetbrains.kotlin.jvm' apply false
-    id 'org.jetbrains.kotlin.plugin.allopen' apply false
-    id 'org.jetbrains.kotlin.plugin.jpa' apply false
+    alias libs.plugins.kotlin.jvm apply false
+    alias libs.plugins.kotlin.allopen apply false
+    alias libs.plugins.kotlin.jpa apply false
     id 'io.gitlab.arturbosch.detekt' apply false
     id 'biz.aQute.bnd.builder' apply false
     id 'com.adarshr.test-logger' version '3.2.0' apply false
@@ -278,14 +278,6 @@ allprojects {
 
         shouldRunAfter tasks.named('test', Test)
     }
-
-    dependencies {
-        // Test utilities
-        testImplementation "org.assertj:assertj-core:$assertjVersion"
-        testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
-        testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
-        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
-    }
 }
 
 tasks.register('createSBOMZip', Zip) {
@@ -326,11 +318,11 @@ if (project.hasProperty('generateSBOM')) {
 void configureKotlinForOSGi(Configuration configuration) {
     configuration.resolutionStrategy {
         dependencySubstitution {
-            substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk8') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-            substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk7') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-            substitute module('org.jetbrains.kotlin:kotlin-stdlib-common') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-            substitute module('org.jetbrains.kotlin:kotlin-stdlib') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-            substitute module('org.jetbrains.kotlin:kotlin-reflect') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
+            substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk8') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+            substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk7') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+            substitute module('org.jetbrains.kotlin:kotlin-stdlib-common') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+            substitute module('org.jetbrains.kotlin:kotlin-stdlib') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+            substitute module('org.jetbrains.kotlin:kotlin-reflect') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
         }
     }
 }
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 00000000000..3ef0465d8ca
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,7 @@
+dependencyResolutionManagement {
+    versionCatalogs {
+        create('libs', { from(files("../gradle/libs.versions.toml")) })
+    }
+}
+
+rootProject.name = 'buildSrc'
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/corda.common-app.gradle b/buildSrc/src/main/groovy/corda.common-app.gradle
index 7d5ac7245f1..5d99531e014 100644
--- a/buildSrc/src/main/groovy/corda.common-app.gradle
+++ b/buildSrc/src/main/groovy/corda.common-app.gradle
@@ -20,11 +20,11 @@ plugins {
 def applyDependencySubstitution = { Configuration conf ->
     conf.resolutionStrategy.dependencySubstitution {
         //Replace Kotlin stdlib
-        substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk8') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-        substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk7') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-        substitute module('org.jetbrains.kotlin:kotlin-stdlib-common') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-        substitute module('org.jetbrains.kotlin:kotlin-stdlib') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
-        substitute module('org.jetbrains.kotlin:kotlin-reflect') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion")
+        substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk8') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+        substitute module('org.jetbrains.kotlin:kotlin-stdlib-jdk7') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+        substitute module('org.jetbrains.kotlin:kotlin-stdlib-common') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+        substitute module('org.jetbrains.kotlin:kotlin-stdlib') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
+        substitute module('org.jetbrains.kotlin:kotlin-reflect') using module("org.jetbrains.kotlin:kotlin-osgi-bundle:${libs.versions.kotlinVersion.get()}")
     }
 }
 
diff --git a/buildSrc/src/main/groovy/corda.common-library.gradle b/buildSrc/src/main/groovy/corda.common-library.gradle
index 63eee4015d7..8db251a1e1b 100644
--- a/buildSrc/src/main/groovy/corda.common-library.gradle
+++ b/buildSrc/src/main/groovy/corda.common-library.gradle
@@ -2,6 +2,7 @@
 
 plugins {
     id 'java-library'
+    id 'org.jetbrains.kotlin.jvm'
     id 'biz.aQute.bnd.builder'
 }
 
@@ -39,12 +40,10 @@ configurations {
 
 dependencies {
     compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
-    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
-    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+    testImplementation libs.bundles.test
+    testRuntimeOnly libs.bundles.test.runtime
 
-    integrationTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
-    integrationTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+    integrationTestRuntimeOnly libs.bundles.test.runtime
 }
 
 tasks.named('jar', Jar) {
diff --git a/buildSrc/src/main/groovy/corda.osgi-test-conventions.gradle b/buildSrc/src/main/groovy/corda.osgi-test-conventions.gradle
index 5787930edc5..79f0be669cc 100644
--- a/buildSrc/src/main/groovy/corda.osgi-test-conventions.gradle
+++ b/buildSrc/src/main/groovy/corda.osgi-test-conventions.gradle
@@ -38,10 +38,9 @@ dependencies {
     }
     integrationTestRuntimeOnly files(configurations.quasarBundles)
 
-    integrationTestImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
+    integrationTestImplementation libs.bundles.test
     integrationTestImplementation "org.osgi:org.osgi.test.junit5:$osgiTestJunit5Version"
-    integrationTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
-    integrationTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
+    integrationTestRuntimeOnly libs.bundles.test.runtime
     integrationTestRuntimeOnly "org.apache.felix:org.apache.felix.framework:$felixVersion"
     integrationTestRuntimeOnly "org.apache.felix:org.apache.felix.scr:$felixScrVersion"
     integrationTestRuntimeOnly "org.osgi:org.osgi.service.component:$osgiServiceComponentVersion"
diff --git a/components/chunking/chunk-db-write-impl/build.gradle b/components/chunking/chunk-db-write-impl/build.gradle
index 380be875d9c..7b2d00a22fb 100644
--- a/components/chunking/chunk-db-write-impl/build.gradle
+++ b/components/chunking/chunk-db-write-impl/build.gradle
@@ -67,8 +67,6 @@ dependencies {
     integrationTestImplementation project(':libs:platform-info')
 
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
diff --git a/components/configuration/configuration-read-service-impl/build.gradle b/components/configuration/configuration-read-service-impl/build.gradle
index 8e8626bf9f8..7fdb859c389 100644
--- a/components/configuration/configuration-read-service-impl/build.gradle
+++ b/components/configuration/configuration-read-service-impl/build.gradle
@@ -31,8 +31,6 @@ dependencies {
 
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     integrationTestImplementation project(":testing:db-message-bus-testkit")
     integrationTestImplementation project(":testing:test-utilities")
     integrationTestImplementation project(":libs:lifecycle:registry")
diff --git a/components/configuration/configuration-rest-resource-service-impl/build.gradle b/components/configuration/configuration-rest-resource-service-impl/build.gradle
index abb2ff533bb..44e86714b7a 100644
--- a/components/configuration/configuration-rest-resource-service-impl/build.gradle
+++ b/components/configuration/configuration-rest-resource-service-impl/build.gradle
@@ -27,7 +27,4 @@ dependencies {
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/configuration/configuration-write-service-impl/build.gradle b/components/configuration/configuration-write-service-impl/build.gradle
index 2a8759eea34..10a999949a1 100644
--- a/components/configuration/configuration-write-service-impl/build.gradle
+++ b/components/configuration/configuration-write-service-impl/build.gradle
@@ -26,7 +26,4 @@ dependencies {
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation 'org.slf4j:slf4j-api'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/crypto/crypto-client-hsm-impl/build.gradle b/components/crypto/crypto-client-hsm-impl/build.gradle
index 156a3e2c6ae..0fd20255ed7 100644
--- a/components/crypto/crypto-client-hsm-impl/build.gradle
+++ b/components/crypto/crypto-client-hsm-impl/build.gradle
@@ -23,8 +23,6 @@ dependencies {
     implementation project(":libs:messaging:messaging")
     implementation project(":libs:utilities")
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     testImplementation project(":components:crypto:crypto-component-test-utils")
 
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
diff --git a/components/crypto/crypto-client-impl/build.gradle b/components/crypto/crypto-client-impl/build.gradle
index 718bf5e564d..419f53738bb 100644
--- a/components/crypto/crypto-client-impl/build.gradle
+++ b/components/crypto/crypto-client-impl/build.gradle
@@ -24,8 +24,6 @@ dependencies {
     implementation project(":libs:messaging:messaging")
     implementation project(":libs:utilities")
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     testImplementation project(":components:crypto:crypto-component-test-utils")
 
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
diff --git a/components/crypto/crypto-component-test-utils/build.gradle b/components/crypto/crypto-component-test-utils/build.gradle
index 354bf90fbfb..c7255602080 100644
--- a/components/crypto/crypto-component-test-utils/build.gradle
+++ b/components/crypto/crypto-component-test-utils/build.gradle
@@ -23,7 +23,7 @@ dependencies {
     api project(":libs:lifecycle:registry")
     api project(":testing:test-utilities")
 
-    api "org.assertj:assertj-core:$assertjVersion"
-    api "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    api libs.assertj.core
+    api libs.mockito.kotlin
 }
 
diff --git a/components/crypto/crypto-hes-impl/build.gradle b/components/crypto/crypto-hes-impl/build.gradle
index 11c2eb18455..15f723af2d9 100644
--- a/components/crypto/crypto-hes-impl/build.gradle
+++ b/components/crypto/crypto-hes-impl/build.gradle
@@ -20,11 +20,6 @@ dependencies {
     implementation project(":components:crypto:crypto-component-core-impl")
     implementation project(":libs:crypto:crypto-core")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testImplementation project(":components:crypto:crypto-component-test-utils")
     testImplementation project(":libs:crypto:cipher-suite-impl")
     testImplementation project(":testing:test-utilities")
diff --git a/components/crypto/crypto-persistence/build.gradle b/components/crypto/crypto-persistence/build.gradle
index 7286ebc2c68..41fd81cbd21 100644
--- a/components/crypto/crypto-persistence/build.gradle
+++ b/components/crypto/crypto-persistence/build.gradle
@@ -31,12 +31,6 @@ dependencies {
     implementation project(":libs:metrics")
     implementation project(':libs:virtual-node:virtual-node-info')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
-
 }
 
 
diff --git a/components/crypto/crypto-rest/build.gradle b/components/crypto/crypto-rest/build.gradle
index fefdf242394..07995e5f3b5 100644
--- a/components/crypto/crypto-rest/build.gradle
+++ b/components/crypto/crypto-rest/build.gradle
@@ -28,7 +28,5 @@ dependencies {
     implementation "net.corda:corda-topic-schema"
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:lifecycle:lifecycle-test-impl')
 }
diff --git a/components/crypto/crypto-service-impl/build.gradle b/components/crypto/crypto-service-impl/build.gradle
index 3024f485604..f79319b71d3 100644
--- a/components/crypto/crypto-service-impl/build.gradle
+++ b/components/crypto/crypto-service-impl/build.gradle
@@ -47,7 +47,6 @@ dependencies {
 
     testImplementation project(":libs:cache:cache-caffeine")
     testImplementation 'net.corda:corda-application'
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(":components:crypto:crypto-component-test-utils")
 
     testImplementation project(":components:crypto:crypto-persistence-model")
diff --git a/components/crypto/crypto-softhsm-impl/build.gradle b/components/crypto/crypto-softhsm-impl/build.gradle
index 66a87d330f3..4390555329a 100644
--- a/components/crypto/crypto-softhsm-impl/build.gradle
+++ b/components/crypto/crypto-softhsm-impl/build.gradle
@@ -39,7 +39,6 @@ dependencies {
 
     implementation platform("net.corda:corda-api:$cordaApiVersion")
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation "com.github.ben-manes.caffeine:caffeine:$caffeineVersion"
     testImplementation project(":components:crypto:crypto-component-test-utils")
     testImplementation project(":components:crypto:crypto-hes-impl")
diff --git a/components/db/db-connection-manager-impl/build.gradle b/components/db/db-connection-manager-impl/build.gradle
index 037cb6adf34..9fb66814dc1 100644
--- a/components/db/db-connection-manager-impl/build.gradle
+++ b/components/db/db-connection-manager-impl/build.gradle
@@ -26,8 +26,6 @@ dependencies {
     implementation "com.typesafe:config:$typeSafeConfigVersion"
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.osgi:osgi.core"
 
     integrationTestImplementation project(':libs:db:db-admin')
diff --git a/components/domino-logic/build.gradle b/components/domino-logic/build.gradle
index 8277109cf43..f3026b1fc47 100644
--- a/components/domino-logic/build.gradle
+++ b/components/domino-logic/build.gradle
@@ -17,8 +17,6 @@ dependencies {
 
     implementation "com.typesafe:config:$typeSafeConfigVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.apache.logging.log4j:log4j-core:$log4jVersion"
     testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
 }
diff --git a/components/external-messaging-services/build.gradle b/components/external-messaging-services/build.gradle
index 7f20874b325..1c1218d84bc 100644
--- a/components/external-messaging-services/build.gradle
+++ b/components/external-messaging-services/build.gradle
@@ -22,8 +22,4 @@ dependencies {
     implementation project(':libs:messaging:message-bus')
     implementation project(":libs:utilities")
     implementation project(":libs:virtual-node:virtual-node-info")
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/flow/flow-mapper-impl/build.gradle b/components/flow/flow-mapper-impl/build.gradle
index 7cc886d64a9..b89340bec45 100644
--- a/components/flow/flow-mapper-impl/build.gradle
+++ b/components/flow/flow-mapper-impl/build.gradle
@@ -26,9 +26,6 @@ dependencies {
     implementation "net.corda:corda-config-schema"
     implementation "net.corda:corda-topic-schema"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:flow:flow-utilities")
     testImplementation project(":libs:flows:flow-utils")
 }
diff --git a/components/flow/flow-mapper-service/build.gradle b/components/flow/flow-mapper-service/build.gradle
index c8d6a4d8f8e..49153c6867a 100644
--- a/components/flow/flow-mapper-service/build.gradle
+++ b/components/flow/flow-mapper-service/build.gradle
@@ -31,10 +31,6 @@ dependencies {
 
     runtimeOnly project(":libs:web:web-impl")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
     testImplementation project(":testing:test-utilities")
     testImplementation project(":testing:flow:flow-utilities")
diff --git a/components/flow/flow-p2p-filter-service/build.gradle b/components/flow/flow-p2p-filter-service/build.gradle
index 553f9a8e296..dfdd013005b 100644
--- a/components/flow/flow-p2p-filter-service/build.gradle
+++ b/components/flow/flow-p2p-filter-service/build.gradle
@@ -28,9 +28,6 @@ dependencies {
 
     runtimeOnly project(":libs:web:web-impl")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:flows:flow-utils")
 
     integrationTestImplementation project(":testing:db-message-bus-testkit")
diff --git a/components/flow/flow-rest-resource-service-impl/build.gradle b/components/flow/flow-rest-resource-service-impl/build.gradle
index 4174528f488..22a1963d902 100644
--- a/components/flow/flow-rest-resource-service-impl/build.gradle
+++ b/components/flow/flow-rest-resource-service-impl/build.gradle
@@ -39,8 +39,6 @@ dependencies {
     implementation "com.google.guava:guava:$guavaVersion"
     implementation "org.apache.commons:commons-lang3:$commonsLangVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
     testImplementation project(':testing:test-utilities')
 }
diff --git a/components/flow/flow-service/build.gradle b/components/flow/flow-service/build.gradle
index 6ea2f66a3e7..cd63cc0bf8b 100644
--- a/components/flow/flow-service/build.gradle
+++ b/components/flow/flow-service/build.gradle
@@ -65,9 +65,6 @@ dependencies {
     implementation "com.esotericsoftware:kryo:$kryoVersion"
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.apache.felix:org.apache.felix.framework:$felixVersion"
     testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
diff --git a/components/gateway/build.gradle b/components/gateway/build.gradle
index b7867499cd7..420871e0127 100644
--- a/components/gateway/build.gradle
+++ b/components/gateway/build.gradle
@@ -35,8 +35,6 @@ dependencies {
     implementation project(":libs:metrics")
     implementation project(':libs:platform-info')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:test-utilities")
     integrationTestRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
     integrationTestImplementation "org.apache.logging.log4j:log4j-core:$log4jVersion"
diff --git a/components/ledger/ledger-common-flow/build.gradle b/components/ledger/ledger-common-flow/build.gradle
index c37b01e8741..c5f2c5169f3 100644
--- a/components/ledger/ledger-common-flow/build.gradle
+++ b/components/ledger/ledger-common-flow/build.gradle
@@ -42,8 +42,6 @@ dependencies {
     testImplementation project(':testing:layered-property-map-testkit')
     testImplementation project(':testing:ledger:ledger-common-base-test')
     testImplementation project(':testing:test-serialization')
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestImplementation project(':testing:ledger:ledger-common-base-integration-test')
     integrationTestImplementation project(':testing:group-policy-test-common')
diff --git a/components/ledger/ledger-consensual-flow/build.gradle b/components/ledger/ledger-consensual-flow/build.gradle
index 477a6ab6e42..ec4a6789b6b 100644
--- a/components/ledger/ledger-consensual-flow/build.gradle
+++ b/components/ledger/ledger-consensual-flow/build.gradle
@@ -36,9 +36,6 @@ dependencies {
     implementation project(':libs:utilities')
     implementation project(':libs:virtual-node:sandbox-group-context')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(':testing:ledger:ledger-consensual-base-test')
     testImplementation project(':testing:kryo-serialization-testkit')
     testImplementation project(':libs:serialization:serialization-amqp')
diff --git a/components/ledger/ledger-persistence/build.gradle b/components/ledger/ledger-persistence/build.gradle
index 136ab89968a..60753ac1e2a 100644
--- a/components/ledger/ledger-persistence/build.gradle
+++ b/components/ledger/ledger-persistence/build.gradle
@@ -61,7 +61,6 @@ dependencies {
     testImplementation project(':testing:db-testkit')
     testImplementation project(':testing:persistence-testkit')
 
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
@@ -84,7 +83,7 @@ dependencies {
     // IMPORTANT:  do NOT attempt to use mockito-kotlin in the integration tests.
     // It's not an OSGi bundle, so you will get errors (despite Intellij appearing to allow you to use it).
 
-    integrationTestImplementation("org.mockito:mockito-core:$mockitoVersion")
+    integrationTestImplementation libs.mockito.core
     integrationTestImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
 
     integrationTestImplementation project(':libs:db:db-admin-impl')
diff --git a/components/ledger/ledger-utxo-flow/build.gradle b/components/ledger/ledger-utxo-flow/build.gradle
index 73741dc6328..a1ec25d5503 100644
--- a/components/ledger/ledger-utxo-flow/build.gradle
+++ b/components/ledger/ledger-utxo-flow/build.gradle
@@ -51,10 +51,6 @@ dependencies {
     implementation project(':notary-plugins:notary-plugin-common')
     implementation project(':libs:configuration:configuration-core')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     testImplementation project(':testing:test-serialization')
     testImplementation project(':testing:kryo-serialization-testkit')
     testImplementation project(':libs:serialization:serialization-internal')
diff --git a/components/ledger/ledger-utxo-token-cache/build.gradle b/components/ledger/ledger-utxo-token-cache/build.gradle
index 2493fceaefd..25a87d4c46c 100644
--- a/components/ledger/ledger-utxo-token-cache/build.gradle
+++ b/components/ledger/ledger-utxo-token-cache/build.gradle
@@ -43,9 +43,6 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.apache.felix:org.apache.felix.framework:$felixVersion"
     testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
diff --git a/components/ledger/ledger-verification/build.gradle b/components/ledger/ledger-verification/build.gradle
index ed5c584f0d6..3bdc95bcfd3 100644
--- a/components/ledger/ledger-verification/build.gradle
+++ b/components/ledger/ledger-verification/build.gradle
@@ -53,8 +53,6 @@ dependencies {
     runtimeOnly project(":libs:crypto:crypto-serialization-impl")
     runtimeOnly project(':libs:crypto:merkle-impl')
 
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testImplementation project(':libs:ledger:ledger-utxo-data')
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
     testImplementation project(":libs:lifecycle:lifecycle-impl")
@@ -71,7 +69,7 @@ dependencies {
     // IMPORTANT:  do NOT attempt to use mockito-kotlin in the integration tests.
     // It's not an OSGi bundle, so you will get errors (despite Intellij appearing to allow you to use it).
 
-    integrationTestImplementation("org.mockito:mockito-core:$mockitoVersion")
+    integrationTestImplementation libs.mockito.core
     integrationTestImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
 
     integrationTestImplementation project(':components:virtual-node:cpi-info-read-service')
diff --git a/components/ledger/notary-worker-selection-impl/build.gradle b/components/ledger/notary-worker-selection-impl/build.gradle
index b7fbeb348b3..ce4a99a081d 100644
--- a/components/ledger/notary-worker-selection-impl/build.gradle
+++ b/components/ledger/notary-worker-selection-impl/build.gradle
@@ -17,7 +17,4 @@ dependencies {
     implementation project(':components:ledger:notary-worker-selection')
     implementation project(':components:membership:membership-group-read')
     implementation project(':libs:sandbox-types')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/ledger/notary-worker-selection/build.gradle b/components/ledger/notary-worker-selection/build.gradle
index 6cfb04d0726..4d71acbf0ab 100644
--- a/components/ledger/notary-worker-selection/build.gradle
+++ b/components/ledger/notary-worker-selection/build.gradle
@@ -14,7 +14,4 @@ dependencies {
     api platform("net.corda:corda-api:$cordaApiVersion")
     api 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     api 'net.corda:corda-ledger-common'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/link-manager/build.gradle b/components/link-manager/build.gradle
index 6d9fdb60ec9..8772df9ea02 100644
--- a/components/link-manager/build.gradle
+++ b/components/link-manager/build.gradle
@@ -68,8 +68,6 @@ dependencies {
 
     testImplementation project(":testing:test-utilities")
     testImplementation project(":testing:p2p:certificates")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.apache.logging.log4j:log4j-core:$log4jVersion"
     testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
 
diff --git a/components/membership/certificates-client-impl/build.gradle b/components/membership/certificates-client-impl/build.gradle
index d959843852c..05aa4fba19a 100644
--- a/components/membership/certificates-client-impl/build.gradle
+++ b/components/membership/certificates-client-impl/build.gradle
@@ -27,7 +27,5 @@ dependencies {
     implementation project(':libs:membership:certificates-common')
     implementation project(":libs:rest:rest")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 }
diff --git a/components/membership/certificates-service-impl/build.gradle b/components/membership/certificates-service-impl/build.gradle
index 9cdb45b423c..d76dc9e90d7 100644
--- a/components/membership/certificates-service-impl/build.gradle
+++ b/components/membership/certificates-service-impl/build.gradle
@@ -25,9 +25,6 @@ dependencies {
     testImplementation project(':libs:db:db-orm-impl')
     testImplementation project(':testing:db-testkit')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 }
diff --git a/components/membership/group-params-writer-service-impl/build.gradle b/components/membership/group-params-writer-service-impl/build.gradle
index f13dcc7aac1..146406c4b1b 100644
--- a/components/membership/group-params-writer-service-impl/build.gradle
+++ b/components/membership/group-params-writer-service-impl/build.gradle
@@ -22,8 +22,6 @@ dependencies {
     implementation project(':libs:layered-property-map')
     implementation project(':libs:messaging:messaging')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:membership:membership-impl')
     testImplementation project(":testing:layered-property-map-testkit")
     testImplementation project(":testing:test-utilities")
diff --git a/components/membership/group-policy-configuration-validation-impl/build.gradle b/components/membership/group-policy-configuration-validation-impl/build.gradle
index 4fdf04f5422..9e7431708fc 100644
--- a/components/membership/group-policy-configuration-validation-impl/build.gradle
+++ b/components/membership/group-policy-configuration-validation-impl/build.gradle
@@ -16,7 +16,4 @@ dependencies {
     implementation project(':libs:membership:membership-common')
     implementation project(':components:membership:group-policy-configuration-validation')
     implementation project(':components:configuration:configuration-read-service')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/group-policy-impl/build.gradle b/components/membership/group-policy-impl/build.gradle
index 0bf4228d92b..8429c699821 100644
--- a/components/membership/group-policy-impl/build.gradle
+++ b/components/membership/group-policy-impl/build.gradle
@@ -29,8 +29,6 @@ dependencies {
 
     implementation "com.typesafe:config:$typeSafeConfigVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':components:membership:registration-impl')
     testImplementation project(':components:configuration:configuration-read-service')
     testImplementation project(':libs:messaging:messaging')
diff --git a/components/membership/locally-hosted-identities-service-impl/build.gradle b/components/membership/locally-hosted-identities-service-impl/build.gradle
index c5c4c121d76..3760bb1588e 100644
--- a/components/membership/locally-hosted-identities-service-impl/build.gradle
+++ b/components/membership/locally-hosted-identities-service-impl/build.gradle
@@ -22,7 +22,4 @@ dependencies {
     implementation 'net.corda:corda-config-schema'
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/members-client-certificate-publisher-service-impl/build.gradle b/components/membership/members-client-certificate-publisher-service-impl/build.gradle
index f43af5aad47..310bf145e11 100644
--- a/components/membership/members-client-certificate-publisher-service-impl/build.gradle
+++ b/components/membership/members-client-certificate-publisher-service-impl/build.gradle
@@ -20,7 +20,4 @@ dependencies {
     implementation 'net.corda:corda-config-schema'
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/membership-client-impl/build.gradle b/components/membership/membership-client-impl/build.gradle
index 5dcfa80efad..05abe2ac8b5 100644
--- a/components/membership/membership-client-impl/build.gradle
+++ b/components/membership/membership-client-impl/build.gradle
@@ -33,7 +33,4 @@ dependencies {
     testImplementation project(":testing:test-utilities")
 
     testImplementation "net.corda:corda-serialization"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/membership-group-read-impl/build.gradle b/components/membership/membership-group-read-impl/build.gradle
index b6c1f663dc1..d164fe4e738 100644
--- a/components/membership/membership-group-read-impl/build.gradle
+++ b/components/membership/membership-group-read-impl/build.gradle
@@ -55,7 +55,5 @@ dependencies {
     testImplementation project(":testing:test-utilities")
 
     testImplementation "net.corda:corda-serialization"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
diff --git a/components/membership/membership-p2p-impl/build.gradle b/components/membership/membership-p2p-impl/build.gradle
index 3b062690ff8..67632915487 100644
--- a/components/membership/membership-p2p-impl/build.gradle
+++ b/components/membership/membership-p2p-impl/build.gradle
@@ -28,9 +28,6 @@ dependencies {
     implementation project(':components:membership:membership-group-read')
     implementation project(':components:membership:membership-p2p')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation project(':testing:test-utilities')
 
 }
diff --git a/components/membership/membership-p2p/build.gradle b/components/membership/membership-p2p/build.gradle
index ab8687c0c39..f3864328a1e 100644
--- a/components/membership/membership-p2p/build.gradle
+++ b/components/membership/membership-p2p/build.gradle
@@ -28,6 +28,4 @@ dependencies {
     implementation project(":libs:serialization:serialization-avro")
 
     testImplementation project(':testing:test-utilities')
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/membership-persistence-client-impl/build.gradle b/components/membership/membership-persistence-client-impl/build.gradle
index 64f281e753c..b190cc3383c 100644
--- a/components/membership/membership-persistence-client-impl/build.gradle
+++ b/components/membership/membership-persistence-client-impl/build.gradle
@@ -25,9 +25,6 @@ dependencies {
     implementation project(':libs:messaging:messaging')
     implementation project(':libs:utilities')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(':testing:layered-property-map-testkit')
     testImplementation project(':testing:test-utilities')
 }
\ No newline at end of file
diff --git a/components/membership/membership-persistence-service-impl/build.gradle b/components/membership/membership-persistence-service-impl/build.gradle
index de5cb255ccf..f252434599d 100644
--- a/components/membership/membership-persistence-service-impl/build.gradle
+++ b/components/membership/membership-persistence-service-impl/build.gradle
@@ -38,13 +38,8 @@ dependencies {
     implementation project(':libs:utilities')
     implementation project(':libs:serialization:serialization-avro')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(':testing:test-utilities')
 
-    integrationTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     integrationTestImplementation 'net.corda:corda-crypto'
     integrationTestImplementation 'net.corda:corda-membership'
     integrationTestImplementation project(':testing:db-testkit')
diff --git a/components/membership/membership-rest-impl/build.gradle b/components/membership/membership-rest-impl/build.gradle
index 5bd808938a8..a727c1ed41c 100644
--- a/components/membership/membership-rest-impl/build.gradle
+++ b/components/membership/membership-rest-impl/build.gradle
@@ -44,7 +44,4 @@ dependencies {
     testImplementation 'net.corda:corda-config-schema'
 
     testImplementation "net.corda:corda-serialization"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/membership-service-impl/build.gradle b/components/membership/membership-service-impl/build.gradle
index 66ace83fd34..74f512103da 100644
--- a/components/membership/membership-service-impl/build.gradle
+++ b/components/membership/membership-service-impl/build.gradle
@@ -30,10 +30,6 @@ dependencies {
     implementation project(":components:crypto:crypto-client")
     implementation project(":libs:serialization:serialization-avro")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation project(":testing:test-utilities")
     testImplementation project(':libs:lifecycle:lifecycle-test-impl')
     testImplementation project(":libs:membership:membership-impl")
diff --git a/components/membership/mtls-mgm-allowed-list-reader-writer-impl/build.gradle b/components/membership/mtls-mgm-allowed-list-reader-writer-impl/build.gradle
index 8c5886e7d70..05b2d25cfcd 100644
--- a/components/membership/mtls-mgm-allowed-list-reader-writer-impl/build.gradle
+++ b/components/membership/mtls-mgm-allowed-list-reader-writer-impl/build.gradle
@@ -25,6 +25,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation 'org.slf4j:slf4j-api'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/registration-impl/build.gradle b/components/membership/registration-impl/build.gradle
index 8caee0be870..62535097196 100644
--- a/components/membership/registration-impl/build.gradle
+++ b/components/membership/registration-impl/build.gradle
@@ -70,7 +70,4 @@ dependencies {
     testImplementation project(":testing:test-utilities")
 
     testImplementation "org.apache.commons:commons-text:$commonsTextVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/membership/synchronisation-impl/build.gradle b/components/membership/synchronisation-impl/build.gradle
index 52588951f2d..f1aba7ee6d6 100644
--- a/components/membership/synchronisation-impl/build.gradle
+++ b/components/membership/synchronisation-impl/build.gradle
@@ -35,9 +35,6 @@ dependencies {
     implementation project(':libs:serialization:serialization-avro')
 
     testImplementation "org.apache.commons:commons-text:$commonsTextVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(':components:crypto:crypto-hes')
     testImplementation project(":libs:membership:membership-impl")
     testImplementation project(':testing:layered-property-map-testkit')
diff --git a/components/permissions/permission-management-cache-service/build.gradle b/components/permissions/permission-management-cache-service/build.gradle
index 95a34f32c56..09f79e8fc27 100644
--- a/components/permissions/permission-management-cache-service/build.gradle
+++ b/components/permissions/permission-management-cache-service/build.gradle
@@ -25,7 +25,4 @@ dependencies {
     implementation project(':libs:permissions:permission-storage-common')
 
     api project(':libs:permissions:permission-management-cache')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/components/permissions/permission-management-service/build.gradle b/components/permissions/permission-management-service/build.gradle
index 1a21e6d4627..8370ecda8e4 100644
--- a/components/permissions/permission-management-service/build.gradle
+++ b/components/permissions/permission-management-service/build.gradle
@@ -33,7 +33,4 @@ dependencies {
 
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/permissions/permission-rest-resource-impl/build.gradle b/components/permissions/permission-rest-resource-impl/build.gradle
index 44b5f8c3ddb..6c0e58b42d7 100644
--- a/components/permissions/permission-rest-resource-impl/build.gradle
+++ b/components/permissions/permission-rest-resource-impl/build.gradle
@@ -32,7 +32,4 @@ dependencies {
 
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/permissions/permission-storage-reader-service/build.gradle b/components/permissions/permission-storage-reader-service/build.gradle
index 00a06db95f2..498d81f7c89 100644
--- a/components/permissions/permission-storage-reader-service/build.gradle
+++ b/components/permissions/permission-storage-reader-service/build.gradle
@@ -35,7 +35,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "javax.persistence:javax.persistence-api"
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/permissions/permission-storage-writer-service/build.gradle b/components/permissions/permission-storage-writer-service/build.gradle
index ab030e3b5d4..a56c662fce3 100644
--- a/components/permissions/permission-storage-writer-service/build.gradle
+++ b/components/permissions/permission-storage-writer-service/build.gradle
@@ -34,7 +34,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation 'javax.persistence:javax.persistence-api'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/permissions/permission-validation-cache-service/build.gradle b/components/permissions/permission-validation-cache-service/build.gradle
index 85274890bff..97d554b694a 100644
--- a/components/permissions/permission-validation-cache-service/build.gradle
+++ b/components/permissions/permission-validation-cache-service/build.gradle
@@ -25,7 +25,4 @@ dependencies {
     implementation project(':libs:permissions:permission-storage-common')
 
     api project(':libs:permissions:permission-validation-cache')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/components/persistence/entity-processor-service-impl/build.gradle b/components/persistence/entity-processor-service-impl/build.gradle
index c333ff49c66..cbbaeb6bd51 100644
--- a/components/persistence/entity-processor-service-impl/build.gradle
+++ b/components/persistence/entity-processor-service-impl/build.gradle
@@ -51,8 +51,6 @@ dependencies {
     testImplementation project(':testing:db-testkit')
     testImplementation project(':testing:persistence-testkit')
 
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testRuntimeOnly "org.ops4j.pax.jdbc:pax-jdbc-hsqldb:$paxJdbcVersion"
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
@@ -68,7 +66,7 @@ dependencies {
     // IMPORTANT:  do NOT attempt to use mockito-kotlin in the integration tests.
     // It's not an OSGi bundle, so you will get errors (despite Intellij appearing to allow you to use it).
 
-    integrationTestImplementation("org.mockito:mockito-core:$mockitoVersion")
+    integrationTestImplementation libs.mockito.core
 
     integrationTestImplementation project(":testing:db-message-bus-testkit")
 
diff --git a/components/persistence/persistence-service-common/build.gradle b/components/persistence/persistence-service-common/build.gradle
index e543259f54e..7ad59e62212 100644
--- a/components/persistence/persistence-service-common/build.gradle
+++ b/components/persistence/persistence-service-common/build.gradle
@@ -32,7 +32,4 @@ dependencies {
     implementation "org.hibernate:hibernate-core:$hibernateVersion"
 
     runtimeOnly project(":libs:crypto:crypto-serialization-impl")
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/rbac-security-manager-service/build.gradle b/components/rbac-security-manager-service/build.gradle
index 5132f53f41d..3088f4e5fd4 100644
--- a/components/rbac-security-manager-service/build.gradle
+++ b/components/rbac-security-manager-service/build.gradle
@@ -20,7 +20,4 @@ dependencies {
 
     api project(":libs:lifecycle:lifecycle")
     api project(':libs:rest:rest-security-read')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/reconciliation/reconciliation-impl/build.gradle b/components/reconciliation/reconciliation-impl/build.gradle
index d9979755fc2..af2efa2e97e 100644
--- a/components/reconciliation/reconciliation-impl/build.gradle
+++ b/components/reconciliation/reconciliation-impl/build.gradle
@@ -19,8 +19,4 @@ dependencies {
     implementation project(':libs:metrics')
     implementation project(':libs:utilities')
     implementation project(':components:reconciliation:reconciliation')
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/rest-gateway-comp/build.gradle b/components/rest-gateway-comp/build.gradle
index e5a5a3571ea..a53c127a8f5 100644
--- a/components/rest-gateway-comp/build.gradle
+++ b/components/rest-gateway-comp/build.gradle
@@ -30,7 +30,4 @@ dependencies {
     implementation project(":libs:utilities")
 
     implementation project(":osgi-framework-api")
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/scheduler/build.gradle b/components/scheduler/build.gradle
index 8a11321a7a8..15bfe727396 100644
--- a/components/scheduler/build.gradle
+++ b/components/scheduler/build.gradle
@@ -27,9 +27,5 @@ dependencies {
     implementation project(':libs:scheduler:scheduler-datamodel')
     implementation project(':libs:utilities')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     runtimeOnly project(":libs:lifecycle:lifecycle-impl")
 }
diff --git a/components/uniqueness/backing-store-impl/build.gradle b/components/uniqueness/backing-store-impl/build.gradle
index d4777d0a8a9..ff208d1adc5 100644
--- a/components/uniqueness/backing-store-impl/build.gradle
+++ b/components/uniqueness/backing-store-impl/build.gradle
@@ -55,9 +55,6 @@ dependencies {
     implementation project(":libs:utilities")
 
     testImplementation "org.hibernate:hibernate-core:$hibernateVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:crypto-testkit")
     testImplementation project(":testing:test-utilities")
     testImplementation project(":testing:uniqueness:backing-store-fake")
diff --git a/components/uniqueness/uniqueness-checker-client-service-impl/build.gradle b/components/uniqueness/uniqueness-checker-client-service-impl/build.gradle
index 2be8dd03c18..365bae2561a 100644
--- a/components/uniqueness/uniqueness-checker-client-service-impl/build.gradle
+++ b/components/uniqueness/uniqueness-checker-client-service-impl/build.gradle
@@ -31,9 +31,5 @@ dependencies {
     implementation project(':libs:utilities')
     implementation project(':libs:virtual-node:virtual-node-info')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testImplementation project(":testing:crypto-testkit")
 }
diff --git a/components/uniqueness/uniqueness-checker-impl/build.gradle b/components/uniqueness/uniqueness-checker-impl/build.gradle
index aeafe32fcd6..e9d3b7e110f 100644
--- a/components/uniqueness/uniqueness-checker-impl/build.gradle
+++ b/components/uniqueness/uniqueness-checker-impl/build.gradle
@@ -31,10 +31,6 @@ dependencies {
     implementation project(":libs:utilities")
     implementation project(":libs:web:web")
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testImplementation project(":libs:virtual-node:virtual-node-info")
     testImplementation project(":testing:crypto-testkit")
     testImplementation project(":testing:test-utilities")
diff --git a/components/virtual-node/cpi-info-read-service-impl/build.gradle b/components/virtual-node/cpi-info-read-service-impl/build.gradle
index 23367fd5ce7..f0e45ad0872 100644
--- a/components/virtual-node/cpi-info-read-service-impl/build.gradle
+++ b/components/virtual-node/cpi-info-read-service-impl/build.gradle
@@ -33,8 +33,4 @@ dependencies {
 
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/cpi-info-write-service-impl/build.gradle b/components/virtual-node/cpi-info-write-service-impl/build.gradle
index 0b78b3ebd8a..6dab0312904 100644
--- a/components/virtual-node/cpi-info-write-service-impl/build.gradle
+++ b/components/virtual-node/cpi-info-write-service-impl/build.gradle
@@ -35,7 +35,4 @@ dependencies {
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/cpi-info-write-service/build.gradle b/components/virtual-node/cpi-info-write-service/build.gradle
index 93cd8de08da..d489aacc847 100644
--- a/components/virtual-node/cpi-info-write-service/build.gradle
+++ b/components/virtual-node/cpi-info-write-service/build.gradle
@@ -34,7 +34,4 @@ dependencies {
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/cpi-upload-rest-service/build.gradle b/components/virtual-node/cpi-upload-rest-service/build.gradle
index a7d12fa8ee5..a58c3b85929 100644
--- a/components/virtual-node/cpi-upload-rest-service/build.gradle
+++ b/components/virtual-node/cpi-upload-rest-service/build.gradle
@@ -32,7 +32,5 @@ dependencies {
     implementation project(':libs:utilities')
     implementation project(':libs:platform-info')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:virtual-node:cpi-upload-manager-impl')
 }
diff --git a/components/virtual-node/cpk-read-service-impl/build.gradle b/components/virtual-node/cpk-read-service-impl/build.gradle
index 702847b8dcb..408e00efff8 100644
--- a/components/virtual-node/cpk-read-service-impl/build.gradle
+++ b/components/virtual-node/cpk-read-service-impl/build.gradle
@@ -28,9 +28,6 @@ dependencies {
 
     api project(":components:virtual-node:cpk-read-service")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
diff --git a/components/virtual-node/cpk-write-service-impl/build.gradle b/components/virtual-node/cpk-write-service-impl/build.gradle
index dde6ca8b039..4cc11837ff7 100644
--- a/components/virtual-node/cpk-write-service-impl/build.gradle
+++ b/components/virtual-node/cpk-write-service-impl/build.gradle
@@ -33,9 +33,6 @@ dependencies {
 
     api project(":components:virtual-node:cpk-write-service")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
diff --git a/components/virtual-node/sandbox-group-context-service/build.gradle b/components/virtual-node/sandbox-group-context-service/build.gradle
index fb72f214396..da669f4d01c 100644
--- a/components/virtual-node/sandbox-group-context-service/build.gradle
+++ b/components/virtual-node/sandbox-group-context-service/build.gradle
@@ -46,9 +46,6 @@ dependencies {
 
     testImplementation 'net.corda:corda-serialization'
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 
     testCompileOnly 'org.osgi:osgi.core'
diff --git a/components/virtual-node/virtual-node-info-read-service-rest-extensions/build.gradle b/components/virtual-node/virtual-node-info-read-service-rest-extensions/build.gradle
index fab9bb9c21c..b7a3c6fdabd 100644
--- a/components/virtual-node/virtual-node-info-read-service-rest-extensions/build.gradle
+++ b/components/virtual-node/virtual-node-info-read-service-rest-extensions/build.gradle
@@ -18,6 +18,4 @@ dependencies {
     implementation project(":components:virtual-node:virtual-node-info-read-service")
 
     testImplementation project(":testing:test-utilities")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/virtual-node-info-read-service/build.gradle b/components/virtual-node/virtual-node-info-read-service/build.gradle
index 89c6d9fa780..008d3072c5c 100644
--- a/components/virtual-node/virtual-node-info-read-service/build.gradle
+++ b/components/virtual-node/virtual-node-info-read-service/build.gradle
@@ -38,8 +38,5 @@ dependencies {
     testImplementation project(":libs:lifecycle:lifecycle-impl")
     testImplementation project(":libs:lifecycle:registry")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 }
diff --git a/components/virtual-node/virtual-node-management-sender/build.gradle b/components/virtual-node/virtual-node-management-sender/build.gradle
index c1c72d5740b..62bf598b533 100644
--- a/components/virtual-node/virtual-node-management-sender/build.gradle
+++ b/components/virtual-node/virtual-node-management-sender/build.gradle
@@ -27,6 +27,4 @@ dependencies {
     implementation 'net.corda:corda-config-schema'
     implementation 'net.corda:corda-topic-schema'
     // Test
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/virtual-node-rest-maintenance-impl/build.gradle b/components/virtual-node/virtual-node-rest-maintenance-impl/build.gradle
index ee8a890511c..fbd79b34c4f 100644
--- a/components/virtual-node/virtual-node-rest-maintenance-impl/build.gradle
+++ b/components/virtual-node/virtual-node-rest-maintenance-impl/build.gradle
@@ -31,6 +31,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/components/virtual-node/virtual-node-rest-service-impl/build.gradle b/components/virtual-node/virtual-node-rest-service-impl/build.gradle
index eb1aae6c0f9..628c378a8df 100644
--- a/components/virtual-node/virtual-node-rest-service-impl/build.gradle
+++ b/components/virtual-node/virtual-node-rest-service-impl/build.gradle
@@ -36,7 +36,4 @@ dependencies {
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/components/virtual-node/virtual-node-write-service-impl/build.gradle b/components/virtual-node/virtual-node-write-service-impl/build.gradle
index b435ebea6c3..1ed87ea94be 100644
--- a/components/virtual-node/virtual-node-write-service-impl/build.gradle
+++ b/components/virtual-node/virtual-node-write-service-impl/build.gradle
@@ -56,9 +56,6 @@ dependencies {
     testImplementation project(':testing:db-testkit')
     testImplementation project(':testing:test-utilities')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
diff --git a/gradle.properties b/gradle.properties
index e207c4bf69a..4f99ffdb549 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,7 +2,6 @@
 artifactoryContextUrl=https://software.r3.com/artifactory
 publicArtifactURL = https://download.corda.net/maven
 kotlin.code.style=official
-kotlinVersion=1.8.21
 kotlin.stdlib.default.dependency=false
 kotlinMetadataVersion=0.7.0
 
@@ -105,14 +104,10 @@ osgiJdbcServiceVersion=1.1.0
 paxJdbcVersion=1.5.3
 
 # Test dependency versions
-assertjVersion=3.24.2
 dom4jOsgiVersion = 2.1.3_1
 hamcrestVersion=2.2
 hsqldbVersion=2.7.2
 jimfsVersion = 1.3.0
-junit5Version=5.10.0
-mockitoKotlinVersion=4.1.0
-mockitoVersion=5.5.0
 osgiTestJunit5Version=1.2.1
 postgresDriverVersion=42.6.0
 mssqlDriverVersion=11.2.3.jre17
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 00000000000..aa4ffa21f7c
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,34 @@
+[versions]
+kotlinVersion = "1.8.21"
+
+# Testing
+assertjVersion = "3.24.2"
+junitVersion = "5.10.0"
+mockitoVersion = "5.5.0"
+mockitoKotlinVersion = "4.1.0"
+
+[libraries]
+assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" }
+junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junitVersion" }
+junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" }
+junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junitVersion" }
+junit-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" }
+junit-platform = { group = "org.junit.platform", name = "junit-platform-launcher" }
+kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlinVersion" }
+kotlin-stdlib-common = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-common", version.ref = "kotlinVersion" }
+kotlin-stdlib-jdk7 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk7", version.ref = "kotlinVersion" }
+kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlinVersion" }
+kotlin-osgi-bundle = { group = "org.jetbrains.kotlin", name = "kotlin-osgi-bundle", version.ref = "kotlinVersion" }
+kotlin-reflect= { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlinVersion" }
+kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinVersion" }
+mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockitoVersion" }
+mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlinVersion" }
+
+[bundles]
+test = ["junit", "junit-api", "junit-params", "mockito-core", "mockito-kotlin", "assertj-core", "kotlin-test"]
+test-runtime = ["junit-engine", "junit-platform"]
+
+[plugins]
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlinVersion" }
+kotlin-allopen = { id = "org.jetbrains.kotlin.plugin.allopen", version.ref = "kotlinVersion" }
+kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlinVersion" }
\ No newline at end of file
diff --git a/libs/application/application-impl/build.gradle b/libs/application/application-impl/build.gradle
index 8439d3527d8..cbd6ded6b32 100644
--- a/libs/application/application-impl/build.gradle
+++ b/libs/application/application-impl/build.gradle
@@ -21,8 +21,5 @@ dependencies {
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
     implementation 'org.slf4j:slf4j-api'
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/cache/cache-caffeine/build.gradle b/libs/cache/cache-caffeine/build.gradle
index 7ad4e0b94b3..b4f97ecb550 100644
--- a/libs/cache/cache-caffeine/build.gradle
+++ b/libs/cache/cache-caffeine/build.gradle
@@ -18,8 +18,5 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation 'org.slf4j:slf4j-api'
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/chunking/chunking-core/build.gradle b/libs/chunking/chunking-core/build.gradle
index 01482e889b7..9304fe43406 100644
--- a/libs/chunking/chunking-core/build.gradle
+++ b/libs/chunking/chunking-core/build.gradle
@@ -21,9 +21,6 @@ dependencies {
     implementation project(':libs:crypto:crypto-core')
     implementation project(':libs:utilities')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
diff --git a/libs/configuration/configuration-core/build.gradle b/libs/configuration/configuration-core/build.gradle
index fd4a7467658..a0305b60331 100644
--- a/libs/configuration/configuration-core/build.gradle
+++ b/libs/configuration/configuration-core/build.gradle
@@ -20,8 +20,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
 
     implementation project(":libs:crypto:crypto-core")
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/configuration/configuration-datamodel/build.gradle b/libs/configuration/configuration-datamodel/build.gradle
index 3da45883147..0bb7252fb23 100644
--- a/libs/configuration/configuration-datamodel/build.gradle
+++ b/libs/configuration/configuration-datamodel/build.gradle
@@ -22,9 +22,6 @@ dependencies {
 
     integrationTestRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
     integrationTestRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 tasks.named('jar', Jar) {
diff --git a/libs/configuration/configuration-merger/build.gradle b/libs/configuration/configuration-merger/build.gradle
index a9a64ac22ca..14f294d23ee 100644
--- a/libs/configuration/configuration-merger/build.gradle
+++ b/libs/configuration/configuration-merger/build.gradle
@@ -20,7 +20,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation 'org.slf4j:slf4j-api'
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/configuration/configuration-validation/build.gradle b/libs/configuration/configuration-validation/build.gradle
index fb3514b493f..f5c119ec9e1 100644
--- a/libs/configuration/configuration-validation/build.gradle
+++ b/libs/configuration/configuration-validation/build.gradle
@@ -27,7 +27,4 @@ dependencies {
             because "Version bundled with current version of 'com.networknt:json-schema-validator' does not have OSGi manifest."
         }
     }
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/crypto/cipher-suite-impl/build.gradle b/libs/crypto/cipher-suite-impl/build.gradle
index 7190f2b0a8a..80e75ad92de 100644
--- a/libs/crypto/cipher-suite-impl/build.gradle
+++ b/libs/crypto/cipher-suite-impl/build.gradle
@@ -21,9 +21,4 @@ dependencies {
     api project(':libs:crypto:crypto-impl')
     api project(':libs:sandbox-types')
     implementation project(':libs:utilities')
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/crypto/cipher-suite/build.gradle b/libs/crypto/cipher-suite/build.gradle
index 980ccccad82..12f79974fb1 100644
--- a/libs/crypto/cipher-suite/build.gradle
+++ b/libs/crypto/cipher-suite/build.gradle
@@ -20,10 +20,5 @@ dependencies {
     api "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
     api "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
diff --git a/libs/crypto/crypto-config-impl/build.gradle b/libs/crypto/crypto-config-impl/build.gradle
index c06f3fb0fc5..00b58cf7a67 100644
--- a/libs/crypto/crypto-config-impl/build.gradle
+++ b/libs/crypto/crypto-config-impl/build.gradle
@@ -18,9 +18,5 @@ dependencies {
     implementation project(':libs:crypto:cipher-suite')
     implementation project(":libs:crypto:crypto-core")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:configuration:configuration-validation')
 }
diff --git a/libs/crypto/crypto-core/build.gradle b/libs/crypto/crypto-core/build.gradle
index 130b3de88d2..4118e4f4135 100644
--- a/libs/crypto/crypto-core/build.gradle
+++ b/libs/crypto/crypto-core/build.gradle
@@ -20,11 +20,7 @@ dependencies {
     api "net.corda:corda-crypto-extensions"
 
     testImplementation "javax.persistence:javax.persistence-api"
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "org.hibernate:hibernate-core:$hibernateVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testImplementation project(":testing:test-utilities")
 }
diff --git a/libs/crypto/crypto-flow/build.gradle b/libs/crypto/crypto-flow/build.gradle
index a82df05f80b..6057142ea67 100644
--- a/libs/crypto/crypto-flow/build.gradle
+++ b/libs/crypto/crypto-flow/build.gradle
@@ -17,11 +17,6 @@ dependencies {
     api project(':libs:crypto:cipher-suite')
 
     implementation project(":libs:crypto:cipher-suite-impl")
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 
diff --git a/libs/crypto/crypto-impl/build.gradle b/libs/crypto/crypto-impl/build.gradle
index 65dbfd6c855..0425caf7000 100644
--- a/libs/crypto/crypto-impl/build.gradle
+++ b/libs/crypto/crypto-impl/build.gradle
@@ -29,11 +29,7 @@ dependencies {
 
     testImplementation project(":testing:layered-property-map-testkit")
     testImplementation "javax.persistence:javax.persistence-api"
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "org.hibernate:hibernate-core:$hibernateVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 tasks.named('jar', Jar) {
diff --git a/libs/crypto/crypto-serialization-impl/build.gradle b/libs/crypto/crypto-serialization-impl/build.gradle
index 1c66ac697ba..66b1f5ca614 100644
--- a/libs/crypto/crypto-serialization-impl/build.gradle
+++ b/libs/crypto/crypto-serialization-impl/build.gradle
@@ -19,9 +19,4 @@ dependencies {
     implementation project(":libs:crypto:crypto-core")
     implementation project(':libs:serialization:serialization-internal')
     implementation project(":libs:utilities")
-
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/crypto/delegated-signing/build.gradle b/libs/crypto/delegated-signing/build.gradle
index 561accbd5f1..e426152d813 100644
--- a/libs/crypto/delegated-signing/build.gradle
+++ b/libs/crypto/delegated-signing/build.gradle
@@ -10,8 +10,6 @@ dependencies {
     implementation 'net.corda:corda-base'
     implementation "net.corda:corda-crypto"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 description 'Crypto Delegated Signing'
diff --git a/libs/crypto/merkle-impl/build.gradle b/libs/crypto/merkle-impl/build.gradle
index 4543500a94a..7ad5d50bdaa 100644
--- a/libs/crypto/merkle-impl/build.gradle
+++ b/libs/crypto/merkle-impl/build.gradle
@@ -23,10 +23,6 @@ dependencies {
     implementation project(":libs:crypto:crypto-core")
     implementation project(':libs:sandbox-types')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:crypto:cipher-suite-impl")
     testImplementation project(":testing:test-utilities")
 
diff --git a/libs/db/db-admin-impl/build.gradle b/libs/db/db-admin-impl/build.gradle
index 690e5138f88..3039b61a224 100644
--- a/libs/db/db-admin-impl/build.gradle
+++ b/libs/db/db-admin-impl/build.gradle
@@ -27,9 +27,6 @@ dependencies {
     runtimeOnly "commons-beanutils:commons-beanutils:$beanutilsVersion"
 
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 
     integrationTestRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
diff --git a/libs/db/db-core/build.gradle b/libs/db/db-core/build.gradle
index b5a333f839c..b496999e12a 100644
--- a/libs/db/db-core/build.gradle
+++ b/libs/db/db-core/build.gradle
@@ -18,9 +18,6 @@ dependencies {
 
     api "com.zaxxer:HikariCP:$hikariCpVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestRuntimeOnly 'org.osgi:osgi.core'
     integrationTestRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
diff --git a/libs/db/db-orm-impl/build.gradle b/libs/db/db-orm-impl/build.gradle
index 228942fa937..96d4ee0a510 100644
--- a/libs/db/db-orm-impl/build.gradle
+++ b/libs/db/db-orm-impl/build.gradle
@@ -33,9 +33,6 @@ dependencies {
     implementation project(":libs:db:db-orm")
     implementation project(":libs:utilities")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
diff --git a/libs/db/db-orm/build.gradle b/libs/db/db-orm/build.gradle
index 708a5df7e95..b71b7eb6796 100644
--- a/libs/db/db-orm/build.gradle
+++ b/libs/db/db-orm/build.gradle
@@ -14,7 +14,4 @@ dependencies {
     compileOnly "org.osgi:osgi.annotation"
     api "javax.persistence:javax.persistence-api"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/external-messaging/build.gradle b/libs/external-messaging/build.gradle
index 058908b89b9..f4ea285dba2 100644
--- a/libs/external-messaging/build.gradle
+++ b/libs/external-messaging/build.gradle
@@ -17,8 +17,5 @@ dependencies {
     implementation project(":libs:virtual-node:virtual-node-info")
     implementation project(":libs:configuration:configuration-core")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/flows/external-event-responses-impl/build.gradle b/libs/flows/external-event-responses-impl/build.gradle
index 27f4d8eb6ee..a4bc0571d29 100644
--- a/libs/flows/external-event-responses-impl/build.gradle
+++ b/libs/flows/external-event-responses-impl/build.gradle
@@ -17,9 +17,6 @@ dependencies {
     implementation project(":libs:messaging:messaging")
     implementation project(":libs:utilities")
     implementation project(":libs:serialization:serialization-avro")
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 description 'Flow External Events Library Implementation'
diff --git a/libs/flows/session-manager-impl/build.gradle b/libs/flows/session-manager-impl/build.gradle
index 010d47f9deb..6915c2b3444 100644
--- a/libs/flows/session-manager-impl/build.gradle
+++ b/libs/flows/session-manager-impl/build.gradle
@@ -23,9 +23,6 @@ dependencies {
     implementation "net.corda:corda-config-schema"
     implementation "net.corda:corda-topic-schema"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:flow:flow-utilities")
     testImplementation project(":libs:flows:flow-utils")
 }
diff --git a/libs/kotlin-heap-fix/build.gradle b/libs/kotlin-heap-fix/build.gradle
index 57773064111..c54a8ede249 100644
--- a/libs/kotlin-heap-fix/build.gradle
+++ b/libs/kotlin-heap-fix/build.gradle
@@ -14,7 +14,7 @@ dependencies {
 
 processResources {
     filesMatching('**/configuration.properties') {
-        expand([ 'kotlinVersion': kotlinVersion ])
+        expand([ 'kotlinVersion': libs.versions.kotlinVersion.get() ])
     }
 }
 
diff --git a/libs/kotlin-reflection/build.gradle b/libs/kotlin-reflection/build.gradle
index aea5cfdd89e..ad9593eb061 100644
--- a/libs/kotlin-reflection/build.gradle
+++ b/libs/kotlin-reflection/build.gradle
@@ -45,17 +45,18 @@ dependencies {
     implementation platform("net.corda:corda-api:$cordaApiVersion")
 
     testCompileOnly 'org.jetbrains:annotations'
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-params:$junit5Version"
+    testImplementation libs.assertj.core
+    testImplementation libs.junit.params
+    testRuntimeOnly libs.junit.engine
     testRuntimeOnly "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinMetadataVersion"
     testRuntimeOnly "org.ow2.asm:asm:$asmVersion"
 
     integrationTestImplementation project(':libs:kotlin-reflection:kotlin-reflection-test-example')
     integrationTestImplementation 'org.slf4j:slf4j-api'
     integrationTestImplementation "org.apache.felix:org.apache.felix.framework:$felixVersion"
-    integrationTestImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
-    integrationTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
-    integrationTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+    integrationTestImplementation libs.junit
+    integrationTestRuntimeOnly libs.junit.engine
+    integrationTestRuntimeOnly libs.junit.platform
     integrationTestRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
 
diff --git a/libs/layered-property-map/build.gradle b/libs/layered-property-map/build.gradle
index 066191384d9..6e1dd26671c 100644
--- a/libs/layered-property-map/build.gradle
+++ b/libs/layered-property-map/build.gradle
@@ -18,7 +18,6 @@ dependencies {
     implementation "net.corda:corda-serialization"
     implementation project(':libs:crypto:cipher-suite')
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation "net.corda:corda-crypto"
     testImplementation project(':libs:crypto:crypto-core')
     testImplementation project(":testing:test-utilities")
diff --git a/libs/ledger/ledger-common-data/build.gradle b/libs/ledger/ledger-common-data/build.gradle
index f44e7013a6b..b87724265ca 100644
--- a/libs/ledger/ledger-common-data/build.gradle
+++ b/libs/ledger/ledger-common-data/build.gradle
@@ -26,13 +26,8 @@ dependencies {
     implementation project(':libs:serialization:serialization-internal')
     implementation project(':libs:utilities')
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     testImplementation project(':testing:test-serialization')
     testImplementation project(':testing:kryo-serialization-testkit')
     testImplementation project(':testing:ledger:ledger-common-base-test')
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/ledger/ledger-utxo-data/build.gradle b/libs/ledger/ledger-utxo-data/build.gradle
index 18f85eb0107..a4672b5a5ce 100644
--- a/libs/ledger/ledger-utxo-data/build.gradle
+++ b/libs/ledger/ledger-utxo-data/build.gradle
@@ -22,10 +22,6 @@ dependencies {
     implementation project(':libs:serialization:serialization-internal')
     implementation project(':libs:utilities')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     testImplementation project(':testing:ledger:ledger-common-base-test')
     testImplementation project(':testing:ledger:ledger-utxo-testkit')
     testImplementation project(':testing:test-serialization')
diff --git a/libs/ledger/ledger-utxo-transaction-verifier/build.gradle b/libs/ledger/ledger-utxo-transaction-verifier/build.gradle
index 4b333a6e799..f9695f2b175 100644
--- a/libs/ledger/ledger-utxo-transaction-verifier/build.gradle
+++ b/libs/ledger/ledger-utxo-transaction-verifier/build.gradle
@@ -24,7 +24,4 @@ dependencies {
 
     testImplementation project(':testing:ledger:ledger-utxo-testkit')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/lifecycle/lifecycle-impl/build.gradle b/libs/lifecycle/lifecycle-impl/build.gradle
index 72593d0a081..b2cccd5dec7 100644
--- a/libs/lifecycle/lifecycle-impl/build.gradle
+++ b/libs/lifecycle/lifecycle-impl/build.gradle
@@ -20,6 +20,4 @@ dependencies {
 
     testImplementation project(":testing:test-utilities")
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/lifecycle/lifecycle-test-impl/build.gradle b/libs/lifecycle/lifecycle-test-impl/build.gradle
index bb5dd56fc7d..d4c91206f48 100644
--- a/libs/lifecycle/lifecycle-test-impl/build.gradle
+++ b/libs/lifecycle/lifecycle-test-impl/build.gradle
@@ -16,9 +16,9 @@ dependencies {
 
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-    implementation "org.assertj:assertj-core:$assertjVersion"
-    implementation "org.mockito:mockito-core:$mockitoVersion"
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.assertj.core
+    implementation libs.mockito.core
+    implementation libs.mockito.kotlin
     implementation 'net.corda:corda-base'
     implementation 'org.slf4j:slf4j-api'
 }
diff --git a/libs/lifecycle/lifecycle/build.gradle b/libs/lifecycle/lifecycle/build.gradle
index 08fbcd81348..66b8b0d480c 100644
--- a/libs/lifecycle/lifecycle/build.gradle
+++ b/libs/lifecycle/lifecycle/build.gradle
@@ -14,6 +14,4 @@ dependencies {
     implementation 'net.corda:corda-base'
     implementation 'org.slf4j:slf4j-api'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/lifecycle/registry/build.gradle b/libs/lifecycle/registry/build.gradle
index 85eef106120..bd27b877fb1 100644
--- a/libs/lifecycle/registry/build.gradle
+++ b/libs/lifecycle/registry/build.gradle
@@ -14,6 +14,4 @@ dependencies {
     implementation 'org.slf4j:slf4j-api'
     implementation project(":libs:lifecycle:lifecycle")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/membership/membership-common/build.gradle b/libs/membership/membership-common/build.gradle
index 73c7f5ab3c9..4295d8e35d1 100644
--- a/libs/membership/membership-common/build.gradle
+++ b/libs/membership/membership-common/build.gradle
@@ -23,7 +23,5 @@ dependencies {
     implementation project(':libs:configuration:configuration-core')
     implementation project(':libs:serialization:serialization-avro')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:test-utilities")
 }
diff --git a/libs/membership/membership-datamodel/build.gradle b/libs/membership/membership-datamodel/build.gradle
index 5940b37729c..df48b0dcb74 100644
--- a/libs/membership/membership-datamodel/build.gradle
+++ b/libs/membership/membership-datamodel/build.gradle
@@ -20,9 +20,6 @@ dependencies {
     implementation project(":libs:db:db-orm")
 
     testImplementation 'net.corda:corda-avro-schema'
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 // This is required for Hibernate Proxy generation. Without it OSGi will report:
diff --git a/libs/membership/membership-impl/build.gradle b/libs/membership/membership-impl/build.gradle
index 094eb05390e..e8ae94b9464 100644
--- a/libs/membership/membership-impl/build.gradle
+++ b/libs/membership/membership-impl/build.gradle
@@ -32,8 +32,4 @@ dependencies {
     testImplementation project(':testing:test-serialization')
     testImplementation project(':libs:serialization:serialization-amqp')
     testImplementation project(":testing:test-utilities")
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/membership/network-info/build.gradle b/libs/membership/network-info/build.gradle
index 61e1f83b15f..bd13cd082c1 100644
--- a/libs/membership/network-info/build.gradle
+++ b/libs/membership/network-info/build.gradle
@@ -24,7 +24,4 @@ dependencies {
 
     testImplementation project(":testing:test-utilities")
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/membership/schema-validation/build.gradle b/libs/membership/schema-validation/build.gradle
index c75d043610b..39e33af34ba 100644
--- a/libs/membership/schema-validation/build.gradle
+++ b/libs/membership/schema-validation/build.gradle
@@ -22,8 +22,4 @@ dependencies {
         }
     }
     implementation project(':libs:membership:membership-common')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/messaging/db-message-bus-impl/build.gradle b/libs/messaging/db-message-bus-impl/build.gradle
index 10e4b05b50c..d5b7507f3fc 100644
--- a/libs/messaging/db-message-bus-impl/build.gradle
+++ b/libs/messaging/db-message-bus-impl/build.gradle
@@ -36,9 +36,6 @@ dependencies {
     runtimeOnly "com.sun.activation:javax.activation:$activationVersion"
 
     testImplementation project(":testing:test-utilities")
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testRuntimeOnly 'org.osgi:osgi.core'
 
diff --git a/libs/messaging/db-topic-admin-impl/build.gradle b/libs/messaging/db-topic-admin-impl/build.gradle
index 7dcbad3cce7..f9691a5fdaf 100644
--- a/libs/messaging/db-topic-admin-impl/build.gradle
+++ b/libs/messaging/db-topic-admin-impl/build.gradle
@@ -26,8 +26,6 @@ dependencies {
     implementation project(":libs:messaging:db-message-bus-datamodel")
     implementation project(":libs:messaging:db-message-bus-impl")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestImplementation project(":libs:db:db-admin")
     integrationTestImplementation project(":libs:db:db-admin-impl")
diff --git a/libs/messaging/kafka-message-bus-impl/build.gradle b/libs/messaging/kafka-message-bus-impl/build.gradle
index 8acab0a36cd..fc5045f472f 100644
--- a/libs/messaging/kafka-message-bus-impl/build.gradle
+++ b/libs/messaging/kafka-message-bus-impl/build.gradle
@@ -34,9 +34,6 @@ dependencies {
 
     implementation "com.typesafe:config:$typeSafeConfigVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:test-utilities")
 
     testRuntimeOnly 'org.osgi:osgi.core'
diff --git a/libs/messaging/kafka-topic-admin-impl/build.gradle b/libs/messaging/kafka-topic-admin-impl/build.gradle
index 3aae8c6f098..490f4316097 100644
--- a/libs/messaging/kafka-topic-admin-impl/build.gradle
+++ b/libs/messaging/kafka-topic-admin-impl/build.gradle
@@ -21,9 +21,6 @@ dependencies {
     implementation project(":libs:messaging:topic-admin")
     implementation project(":libs:utilities")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     testRuntimeOnly 'org.osgi:osgi.core'
 }
 
diff --git a/libs/messaging/messaging-impl/build.gradle b/libs/messaging/messaging-impl/build.gradle
index c918d4c3701..836a5c999a0 100644
--- a/libs/messaging/messaging-impl/build.gradle
+++ b/libs/messaging/messaging-impl/build.gradle
@@ -37,9 +37,6 @@ dependencies {
     implementation "com.typesafe:config:$typeSafeConfigVersion"
     implementation "com.google.guava:guava:$guavaVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:test-utilities")
     testImplementation project(':libs:platform-info')
     testImplementation project(':libs:web:web-impl')
diff --git a/libs/messaging/messaging/build.gradle b/libs/messaging/messaging/build.gradle
index 31e52cedd13..8981a0c2248 100644
--- a/libs/messaging/messaging/build.gradle
+++ b/libs/messaging/messaging/build.gradle
@@ -29,9 +29,6 @@ dependencies {
     implementation project(":libs:messaging:message-bus")
     implementation project(":libs:configuration:configuration-core")
     implementation project(":libs:serialization:serialization-avro")
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
 description 'Messaging API'
diff --git a/libs/metrics/build.gradle b/libs/metrics/build.gradle
index 8bb00d715ec..2940fc7273f 100644
--- a/libs/metrics/build.gradle
+++ b/libs/metrics/build.gradle
@@ -20,6 +20,5 @@ dependencies {
         exclude group: 'org.latencyutils', module: 'LatencyUtils'
     }
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
diff --git a/libs/p2p-crypto/build.gradle b/libs/p2p-crypto/build.gradle
index 58155829ff3..9e04224e88b 100644
--- a/libs/p2p-crypto/build.gradle
+++ b/libs/p2p-crypto/build.gradle
@@ -20,8 +20,6 @@ dependencies {
     api "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
 
     testImplementation project(':libs:crypto:cipher-suite')
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestImplementation project(":testing:p2p:certificates")
 }
diff --git a/libs/packaging/packaging-core/build.gradle b/libs/packaging/packaging-core/build.gradle
index d671fec2d6c..f7999cf6d9b 100644
--- a/libs/packaging/packaging-core/build.gradle
+++ b/libs/packaging/packaging-core/build.gradle
@@ -16,7 +16,4 @@ dependencies {
 
     implementation project(':libs:crypto:crypto-core')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/packaging/packaging-verify/build.gradle b/libs/packaging/packaging-verify/build.gradle
index 474b334c840..ed84c018358 100644
--- a/libs/packaging/packaging-verify/build.gradle
+++ b/libs/packaging/packaging-verify/build.gradle
@@ -27,6 +27,4 @@ dependencies {
 
     testImplementation project(':testing:test-utilities')
     testImplementation project(':testing:packaging-test-utilities')
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/packaging/packaging/build.gradle b/libs/packaging/packaging/build.gradle
index 4b26a6a04df..590ffb0c91c 100644
--- a/libs/packaging/packaging/build.gradle
+++ b/libs/packaging/packaging/build.gradle
@@ -47,9 +47,6 @@ dependencies {
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
     testImplementation 'org.osgi:osgi.core'
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:crypto:cipher-suite')
     testImplementation project(":testing:test-utilities")
     testImplementation project(":testing:packaging-test-utilities")
diff --git a/libs/permissions/permission-cache-common/build.gradle b/libs/permissions/permission-cache-common/build.gradle
index 9c26b627941..f10a578b870 100644
--- a/libs/permissions/permission-cache-common/build.gradle
+++ b/libs/permissions/permission-cache-common/build.gradle
@@ -15,7 +15,5 @@ dependencies {
     implementation 'net.corda:corda-base'
     implementation project(":libs:messaging:messaging")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "net.corda:corda-avro-schema"
 }
diff --git a/libs/permissions/permission-endpoint/build.gradle b/libs/permissions/permission-endpoint/build.gradle
index d689d4cfca9..13cc353c288 100644
--- a/libs/permissions/permission-endpoint/build.gradle
+++ b/libs/permissions/permission-endpoint/build.gradle
@@ -19,7 +19,4 @@ dependencies {
     api project(":libs:rest:rest")
     implementation project(":libs:permissions:permission-manager")
     implementation project(":libs:lifecycle:lifecycle")
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/permissions/permission-management-cache-impl/build.gradle b/libs/permissions/permission-management-cache-impl/build.gradle
index 7b4ca8dac54..7f431a72069 100644
--- a/libs/permissions/permission-management-cache-impl/build.gradle
+++ b/libs/permissions/permission-management-cache-impl/build.gradle
@@ -20,6 +20,4 @@ dependencies {
     implementation project(":libs:permissions:permission-management-cache")
     implementation project(":libs:permissions:permission-cache-common")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/permissions/permission-manager-impl/build.gradle b/libs/permissions/permission-manager-impl/build.gradle
index 612a2280e0c..dca851025a5 100644
--- a/libs/permissions/permission-manager-impl/build.gradle
+++ b/libs/permissions/permission-manager-impl/build.gradle
@@ -28,6 +28,4 @@ dependencies {
     implementation project(":libs:cache:cache-caffeine")
     implementation project(":libs:crypto:crypto-core")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/permissions/permission-password/build.gradle b/libs/permissions/permission-password/build.gradle
index f291a96a9ea..fdea716b68b 100644
--- a/libs/permissions/permission-password/build.gradle
+++ b/libs/permissions/permission-password/build.gradle
@@ -18,6 +18,5 @@ dependencies {
     implementation project(":libs:crypto:crypto-core")
     implementation "org.apache.commons:commons-text:$commonsTextVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation project(":libs:crypto:cipher-suite-impl")
 }
diff --git a/libs/permissions/permission-storage-reader-impl/build.gradle b/libs/permissions/permission-storage-reader-impl/build.gradle
index 87d342e3685..cbd6af73794 100644
--- a/libs/permissions/permission-storage-reader-impl/build.gradle
+++ b/libs/permissions/permission-storage-reader-impl/build.gradle
@@ -29,6 +29,4 @@ dependencies {
 
     implementation "javax.persistence:javax.persistence-api"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/permissions/permission-storage-writer-impl/build.gradle b/libs/permissions/permission-storage-writer-impl/build.gradle
index 4a55f2d1afb..066a4e8732f 100644
--- a/libs/permissions/permission-storage-writer-impl/build.gradle
+++ b/libs/permissions/permission-storage-writer-impl/build.gradle
@@ -27,6 +27,4 @@ dependencies {
 
     implementation "javax.persistence:javax.persistence-api"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/permissions/permission-validation-cache-impl/build.gradle b/libs/permissions/permission-validation-cache-impl/build.gradle
index 0354424fb99..01054b47c75 100644
--- a/libs/permissions/permission-validation-cache-impl/build.gradle
+++ b/libs/permissions/permission-validation-cache-impl/build.gradle
@@ -20,6 +20,4 @@ dependencies {
     implementation project(":libs:permissions:permission-validation-cache")
     implementation project(":libs:permissions:permission-cache-common")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/permissions/permission-validation-impl/build.gradle b/libs/permissions/permission-validation-impl/build.gradle
index 84a88393279..71e8b39885f 100644
--- a/libs/permissions/permission-validation-impl/build.gradle
+++ b/libs/permissions/permission-validation-impl/build.gradle
@@ -26,7 +26,4 @@ dependencies {
     implementation "net.corda:corda-avro-schema"
     implementation "net.corda:corda-topic-schema"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
 }
diff --git a/libs/platform-info/build.gradle b/libs/platform-info/build.gradle
index c1670ff9c83..22963b03021 100644
--- a/libs/platform-info/build.gradle
+++ b/libs/platform-info/build.gradle
@@ -13,7 +13,5 @@ dependencies {
     implementation "net.corda:corda-base"
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.osgi:osgi.core"
 }
diff --git a/libs/rest/json-serialization/build.gradle b/libs/rest/json-serialization/build.gradle
index 9f0e47aa469..792e770c3c8 100644
--- a/libs/rest/json-serialization/build.gradle
+++ b/libs/rest/json-serialization/build.gradle
@@ -19,6 +19,4 @@ dependencies {
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
     implementation project(":libs:serialization:json-serializers")
     implementation project(':libs:rest:rest')
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
 }
diff --git a/libs/rest/rbac-security-manager/build.gradle b/libs/rest/rbac-security-manager/build.gradle
index 7755853fd1e..a18a1f8e990 100644
--- a/libs/rest/rbac-security-manager/build.gradle
+++ b/libs/rest/rbac-security-manager/build.gradle
@@ -18,6 +18,4 @@ dependencies {
     implementation project(":libs:permissions:permission-manager")
     implementation project(":libs:permissions:permission-validation")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/rest/rest-client/build.gradle b/libs/rest/rest-client/build.gradle
index 0f6373830b5..5ae43fa907d 100644
--- a/libs/rest/rest-client/build.gradle
+++ b/libs/rest/rest-client/build.gradle
@@ -29,10 +29,6 @@ dependencies {
     implementation "com.konghq:unirest-objectmapper-jackson:$unirestVersion"
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation project(":testing:test-utilities")
 
     testImplementation project(":libs:rest:rest-test-common")
diff --git a/libs/rest/rest-common/build.gradle b/libs/rest/rest-common/build.gradle
index 25d25612578..781f1b1c13b 100644
--- a/libs/rest/rest-common/build.gradle
+++ b/libs/rest/rest-common/build.gradle
@@ -18,6 +18,4 @@ dependencies {
     implementation project(':libs:messaging:messaging')
 
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
 }
diff --git a/libs/rest/rest-server-impl/build.gradle b/libs/rest/rest-server-impl/build.gradle
index 343705083ec..a8c84ee5973 100644
--- a/libs/rest/rest-server-impl/build.gradle
+++ b/libs/rest/rest-server-impl/build.gradle
@@ -48,8 +48,6 @@ dependencies {
     implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion"
 
     testImplementation project(":libs:rest:rest-test-common")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testRuntimeOnly 'org.osgi:osgi.core'
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
@@ -61,11 +59,6 @@ dependencies {
 
     integrationTestImplementation "com.konghq:unirest-java:$unirestVersion"
 
-    integrationTestImplementation "org.mockito:mockito-core:$mockitoVersion"
-    integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
-    integrationTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-
     integrationTestImplementation project(':libs:rest:ssl-cert-read-impl')
     integrationTestImplementation project(":testing:test-utilities")
 }
diff --git a/libs/rest/rest-test-common/build.gradle b/libs/rest/rest-test-common/build.gradle
index 08ac90e5ca7..6a3d5cbad3b 100644
--- a/libs/rest/rest-test-common/build.gradle
+++ b/libs/rest/rest-test-common/build.gradle
@@ -17,7 +17,7 @@ dependencies {
     implementation "com.nimbusds:oauth2-oidc-sdk:$nimbusVersion"
     implementation "com.konghq:unirest-java:$unirestVersion"
 
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.kotlin
 
     implementation project(":libs:rest:rest")
     implementation project(":libs:rest:rest-common")
diff --git a/libs/rest/rest/build.gradle b/libs/rest/rest/build.gradle
index 75544117911..2d2723b3aa4 100644
--- a/libs/rest/rest/build.gradle
+++ b/libs/rest/rest/build.gradle
@@ -12,6 +12,4 @@ dependencies {
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/rest/ssl-cert-read-impl/build.gradle b/libs/rest/ssl-cert-read-impl/build.gradle
index 24f3c809302..e8e9e63f898 100644
--- a/libs/rest/ssl-cert-read-impl/build.gradle
+++ b/libs/rest/ssl-cert-read-impl/build.gradle
@@ -19,7 +19,4 @@ dependencies {
     implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
 
     api project(':libs:rest:ssl-cert-read')
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/libs/sandbox-hooks/build.gradle b/libs/sandbox-hooks/build.gradle
index e4f1d223d46..87bba16f1e6 100644
--- a/libs/sandbox-hooks/build.gradle
+++ b/libs/sandbox-hooks/build.gradle
@@ -19,8 +19,6 @@ dependencies {
     implementation project(':libs:kotlin-heap-fix')
     implementation project(':libs:sandbox')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.apache.felix:org.apache.felix.framework:$felixVersion"
     testRuntimeOnly "org.apache.felix:org.apache.felix.scr:$felixScrVersion"
 }
diff --git a/libs/sandbox-internal/build.gradle b/libs/sandbox-internal/build.gradle
index 239b75b3fb1..5c6c0757d1d 100644
--- a/libs/sandbox-internal/build.gradle
+++ b/libs/sandbox-internal/build.gradle
@@ -31,8 +31,6 @@ dependencies {
     runtimeOnly project(':libs:sandbox-hooks')
 
     testCompileOnly 'org.osgi:osgi.core'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:crypto-testkit")
     testRuntimeOnly "org.apache.felix:org.apache.felix.framework:$felixVersion"
     testRuntimeOnly "org.apache.felix:org.apache.felix.scr:$felixScrVersion"
diff --git a/libs/scheduler/scheduler-datamodel/build.gradle b/libs/scheduler/scheduler-datamodel/build.gradle
index d262c7fc2a8..3a85eb7073f 100644
--- a/libs/scheduler/scheduler-datamodel/build.gradle
+++ b/libs/scheduler/scheduler-datamodel/build.gradle
@@ -14,8 +14,6 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation project(':libs:db:db-core')
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestImplementation project(':libs:db:db-admin')
     integrationTestImplementation project(':libs:db:db-admin-impl')
diff --git a/libs/serialization/json-serializers/build.gradle b/libs/serialization/json-serializers/build.gradle
index ec3b961c376..95f1b957bbb 100644
--- a/libs/serialization/json-serializers/build.gradle
+++ b/libs/serialization/json-serializers/build.gradle
@@ -12,6 +12,4 @@ dependencies {
     implementation "net.corda:corda-application"
     implementation 'net.corda:corda-base'
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/serialization/json-validator/build.gradle b/libs/serialization/json-validator/build.gradle
index d643a3c94b4..44a67f76cd0 100644
--- a/libs/serialization/json-validator/build.gradle
+++ b/libs/serialization/json-validator/build.gradle
@@ -30,8 +30,6 @@ dependencies {
         }
     }
     implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
 }
 
 def jar = tasks.named('jar', Jar) {
diff --git a/libs/serialization/serialization-amqp/build.gradle b/libs/serialization/serialization-amqp/build.gradle
index d76033cfa1c..85945c95336 100644
--- a/libs/serialization/serialization-amqp/build.gradle
+++ b/libs/serialization/serialization-amqp/build.gradle
@@ -45,13 +45,8 @@ dependencies {
     testImplementation project(':libs:crypto:crypto-serialization-impl')
     testImplementation project(':testing:test-serialization')
     testImplementation project(':libs:crypto:cipher-suite')
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
     testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
     testImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
     testRuntimeOnly 'org.osgi:osgi.core'
 
diff --git a/libs/serialization/serialization-kryo/build.gradle b/libs/serialization/serialization-kryo/build.gradle
index 14b85a83b97..054a9e82843 100644
--- a/libs/serialization/serialization-kryo/build.gradle
+++ b/libs/serialization/serialization-kryo/build.gradle
@@ -45,10 +45,6 @@ dependencies {
     cpbs project(path: 'cpks:serializable-cpk-two', configuration: 'cordaCPB')
 
     testCompileOnly 'org.osgi:osgi.core'
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testRuntimeOnly "org.apache.felix:org.apache.felix.framework:$felixVersion"
     testRuntimeOnly "co.paralleluniverse:quasar-core-osgi:$quasarVersion:framework-extension"
     testImplementation project(":testing:kryo-serialization-testkit")
diff --git a/libs/state-manager/state-manager-db-impl/build.gradle b/libs/state-manager/state-manager-db-impl/build.gradle
index 290146754fc..853992408ef 100644
--- a/libs/state-manager/state-manager-db-impl/build.gradle
+++ b/libs/state-manager/state-manager-db-impl/build.gradle
@@ -22,8 +22,6 @@ dependencies {
     implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     integrationTestImplementation project(':libs:db:db-admin')
     integrationTestImplementation project(':testing:db-testkit')
diff --git a/libs/task-manager/build.gradle b/libs/task-manager/build.gradle
index 92c44653b1f..e8eda79362a 100644
--- a/libs/task-manager/build.gradle
+++ b/libs/task-manager/build.gradle
@@ -13,8 +13,4 @@ dependencies {
     implementation project(":libs:utilities")
 
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
 }
diff --git a/libs/utilities/build.gradle b/libs/utilities/build.gradle
index 49f0b57a9cc..81376ad82dc 100644
--- a/libs/utilities/build.gradle
+++ b/libs/utilities/build.gradle
@@ -22,10 +22,7 @@ dependencies {
 
     implementation project(":libs:configuration:configuration-core")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
     testImplementation "com.google.jimfs:jimfs:$jimfsVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     testRuntimeOnly 'org.osgi:osgi.core'
 }
diff --git a/libs/virtual-node/cpi-upload-manager-impl/build.gradle b/libs/virtual-node/cpi-upload-manager-impl/build.gradle
index dc80f28bb69..00d8ef30163 100644
--- a/libs/virtual-node/cpi-upload-manager-impl/build.gradle
+++ b/libs/virtual-node/cpi-upload-manager-impl/build.gradle
@@ -22,6 +22,4 @@ dependencies {
     implementation "net.corda:corda-topic-schema"
     implementation 'net.corda:corda-crypto'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/virtual-node/sandbox-group-context/build.gradle b/libs/virtual-node/sandbox-group-context/build.gradle
index 5deea37d07b..ff05561f262 100644
--- a/libs/virtual-node/sandbox-group-context/build.gradle
+++ b/libs/virtual-node/sandbox-group-context/build.gradle
@@ -16,7 +16,4 @@ dependencies {
     api project(':libs:sandbox-types')
     api project(':libs:virtual-node:virtual-node-info')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/libs/virtual-node/virtual-node-info/build.gradle b/libs/virtual-node/virtual-node-info/build.gradle
index a39fc2e28f0..8fdd5b3a6c7 100644
--- a/libs/virtual-node/virtual-node-info/build.gradle
+++ b/libs/virtual-node/virtual-node-info/build.gradle
@@ -17,8 +17,5 @@ dependencies {
     api project(':libs:crypto:crypto-core')
     api project(':libs:packaging:packaging-core')
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 }
diff --git a/libs/web/web-impl/build.gradle b/libs/web/web-impl/build.gradle
index 85eacdd2d9a..ffee91948e8 100644
--- a/libs/web/web-impl/build.gradle
+++ b/libs/web/web-impl/build.gradle
@@ -32,8 +32,5 @@ dependencies {
 
     testImplementation 'org.osgi:osgi.core'
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
     integrationTestRuntimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
 }
\ No newline at end of file
diff --git a/libs/web/web/build.gradle b/libs/web/web/build.gradle
index d85df7e411a..228a5e741ba 100644
--- a/libs/web/web/build.gradle
+++ b/libs/web/web/build.gradle
@@ -16,7 +16,4 @@ dependencies {
     compileOnly 'org.osgi:org.osgi.service.component.annotations'
     compileOnly "org.osgi:osgi.annotation"
     compileOnly 'org.osgi:osgi.core'
-
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
\ No newline at end of file
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
index 0c9dbe0a872..47ad08154dd 100644
--- a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-client/build.gradle
@@ -33,9 +33,7 @@ dependencies {
     // Common package pulled in as transitive dependency through API
     cordapp project(":notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-api")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    testImplementation libs.bundles.test
 
     testImplementation project(':libs:serialization:serialization-amqp')
     testImplementation project(':libs:crypto:cipher-suite')
diff --git a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
index e7bda386545..cba43f37b41 100644
--- a/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
+++ b/notary-plugins/notary-plugin-contract-verifying/notary-plugin-contract-verifying-server/build.gradle
@@ -28,9 +28,7 @@ dependencies {
     // Common package pulled in as transitive dependency through API
     cordapp project(":notary-plugins:notary-plugin-contract-verifying:notary-plugin-contract-verifying-api")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    testImplementation libs.bundles.test
 
     testImplementation project(":libs:crypto:crypto-core")
     testImplementation project(":libs:uniqueness:common")
diff --git a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-client/build.gradle b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-client/build.gradle
index c0d1cee5413..6f0342e96c4 100644
--- a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-client/build.gradle
+++ b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-client/build.gradle
@@ -33,10 +33,7 @@ dependencies {
     // Common package pulled in as transitive dependency through API
     cordapp project(":notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-api")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
+    testImplementation libs.bundles.test
     testImplementation project(':libs:serialization:serialization-amqp')
     testImplementation project(':libs:crypto:cipher-suite')
     testImplementation project(":testing:crypto-testkit")
diff --git a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/build.gradle b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/build.gradle
index 42fa7472778..d8d1b6b9c27 100644
--- a/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/build.gradle
+++ b/notary-plugins/notary-plugin-non-validating/notary-plugin-non-validating-server/build.gradle
@@ -28,10 +28,7 @@ dependencies {
     // Common package pulled in as transitive dependency through API
     cordapp project(":notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-api")
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
-
+    testImplementation libs.bundles.test
     testImplementation project(":libs:crypto:crypto-core")
     testImplementation project(':libs:serialization:serialization-amqp')
     testImplementation project(":libs:uniqueness:common")
diff --git a/osgi-framework-bootstrap/build.gradle b/osgi-framework-bootstrap/build.gradle
index 8090f8bb65c..a4c8534ae82 100755
--- a/osgi-framework-bootstrap/build.gradle
+++ b/osgi-framework-bootstrap/build.gradle
@@ -29,10 +29,11 @@ dependencies {
     runtimeOnly "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
     runtimeOnly "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
+    testImplementation libs.mockito.core
     testImplementation "org.apache.sling:org.apache.sling.testing.osgi-mock.junit5:$slingVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
+    testImplementation libs.junit.api
+    testImplementation libs.assertj.core
+    testRuntimeOnly libs.junit.engine
 
     testBundles project(':testing:apps:test-app')
 }
diff --git a/processors/crypto-processor/build.gradle b/processors/crypto-processor/build.gradle
index ddfdd90fd8e..574e151a821 100644
--- a/processors/crypto-processor/build.gradle
+++ b/processors/crypto-processor/build.gradle
@@ -98,9 +98,6 @@ dependencies {
     integrationTestRuntimeOnly "org.ops4j.pax.jdbc:pax-jdbc-hsqldb:$paxJdbcVersion"
     integrationTestRuntimeOnly "org.hsqldb:hsqldb:$hsqldbVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':libs:lifecycle:lifecycle-test-impl')
     testRuntimeOnly project(':libs:lifecycle:lifecycle-impl')
 }
diff --git a/processors/db-processor/build.gradle b/processors/db-processor/build.gradle
index c4fb4634917..073934279b5 100644
--- a/processors/db-processor/build.gradle
+++ b/processors/db-processor/build.gradle
@@ -103,8 +103,5 @@ dependencies {
 
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 }
diff --git a/processors/member-processor/build.gradle b/processors/member-processor/build.gradle
index d5c0e94d25b..0f3fcab555a 100644
--- a/processors/member-processor/build.gradle
+++ b/processors/member-processor/build.gradle
@@ -65,8 +65,6 @@ dependencies {
     runtimeOnly project(':components:membership:members-client-certificate-publisher-service-impl')
     runtimeOnly project(":libs:web:web-impl")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":testing:test-utilities")
     testImplementation "net.corda:corda-topic-schema"
     testImplementation "net.corda:corda-config-schema"
diff --git a/processors/persistence-processor/build.gradle b/processors/persistence-processor/build.gradle
index d86e6b7b8ee..7a211e24dab 100644
--- a/processors/persistence-processor/build.gradle
+++ b/processors/persistence-processor/build.gradle
@@ -45,8 +45,5 @@ dependencies {
 
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 }
diff --git a/processors/rest-processor/build.gradle b/processors/rest-processor/build.gradle
index 6b48db2cccd..c242f54d7c3 100644
--- a/processors/rest-processor/build.gradle
+++ b/processors/rest-processor/build.gradle
@@ -72,8 +72,6 @@ dependencies {
     runtimeOnly project(':libs:virtual-node:cpi-upload-manager-impl')
     runtimeOnly project(':components:virtual-node:cpi-upload-rest-service')
 
-    integrationTestImplementation "org.assertj:assertj-core:$assertjVersion"
-    integrationTestImplementation "org.osgi:org.osgi.test.junit5:$osgiTestJunit5Version"
     integrationTestImplementation "io.swagger.core.v3:swagger-core:$swaggerVersion"
 
     integrationTestImplementation project(':libs:rest:rest')
diff --git a/processors/scheduler-processor/build.gradle b/processors/scheduler-processor/build.gradle
index 6d06f143f85..13ebc2143d9 100644
--- a/processors/scheduler-processor/build.gradle
+++ b/processors/scheduler-processor/build.gradle
@@ -37,9 +37,6 @@ dependencies {
 
     testRuntimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(':testing:test-utilities')
 
     integrationTestImplementation "net.corda:corda-avro-schema"
diff --git a/settings.gradle b/settings.gradle
index fdf5408d957..bff78a63320 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -40,9 +40,6 @@ pluginManagement {
         id 'com.gradle.enterprise' version gradleEnterpriseVersion
         id 'com.gradle.common-custom-user-data-gradle-plugin' version gradleDataPlugin
         id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
-        id 'org.jetbrains.kotlin.jvm' version kotlinVersion
-        id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
-        id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
         id 'net.corda.plugins.cordapp-cpk2' version cordaGradlePluginsVersion
         id 'net.corda.plugins.cordapp-cpb2' version cordaGradlePluginsVersion
         id 'io.gitlab.arturbosch.detekt' version detektPluginVersion
diff --git a/testing/apps/test-app/build.gradle b/testing/apps/test-app/build.gradle
index d7a1626d3c2..88cf1cea581 100644
--- a/testing/apps/test-app/build.gradle
+++ b/testing/apps/test-app/build.gradle
@@ -18,6 +18,4 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     compileOnly 'org.slf4j:slf4j-api'
     compileOnly project(':osgi-framework-api')
-
-    testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
 }
diff --git a/testing/cpbs/ledger-consensual-demo-app/build.gradle b/testing/cpbs/ledger-consensual-demo-app/build.gradle
index 068f5ccdbed..7b5ebd28a00 100644
--- a/testing/cpbs/ledger-consensual-demo-app/build.gradle
+++ b/testing/cpbs/ledger-consensual-demo-app/build.gradle
@@ -20,10 +20,9 @@ dependencies {
 
     cordapp project(':testing:cpbs:ledger-consensual-demo-contract')
 
+    testImplementation libs.bundles.test
     testImplementation project(":libs:application:application-impl")
     testImplementation project(':libs:crypto:crypto-core')
     testImplementation project(":libs:sandbox-types")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
 
diff --git a/testing/cpbs/ledger-utxo-demo-app/build.gradle b/testing/cpbs/ledger-utxo-demo-app/build.gradle
index eac5230bc5b..e35198a49ac 100644
--- a/testing/cpbs/ledger-utxo-demo-app/build.gradle
+++ b/testing/cpbs/ledger-utxo-demo-app/build.gradle
@@ -20,11 +20,10 @@ dependencies {
 
     cordapp project(':testing:cpbs:ledger-utxo-demo-contract')
 
+    testImplementation libs.bundles.test
     testImplementation project(":libs:application:application-impl")
     testImplementation project(':libs:crypto:crypto-core')
     testImplementation project(":libs:sandbox-types")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     // Common and API packages pulled in as transitive dependencies through client
     cordapp project(':notary-plugins:notary-plugin-non-validating:notary-plugin-non-validating-client')
diff --git a/testing/cpbs/ledger-utxo-demo-contract/build.gradle b/testing/cpbs/ledger-utxo-demo-contract/build.gradle
index 38fa47c50e6..75796159d0c 100644
--- a/testing/cpbs/ledger-utxo-demo-contract/build.gradle
+++ b/testing/cpbs/ledger-utxo-demo-contract/build.gradle
@@ -20,6 +20,4 @@ dependencies {
 
     testImplementation project(":libs:application:application-impl")
     testImplementation project(":libs:sandbox-types")
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/testing/cpi-info-read-service-fake/build.gradle b/testing/cpi-info-read-service-fake/build.gradle
index a45987c0a2f..8486e4d93b0 100644
--- a/testing/cpi-info-read-service-fake/build.gradle
+++ b/testing/cpi-info-read-service-fake/build.gradle
@@ -24,6 +24,4 @@ dependencies {
     testImplementation project(":libs:lifecycle:lifecycle-test-impl")
     testImplementation project(":libs:lifecycle:registry")
 
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/testing/db-message-bus-testkit/build.gradle b/testing/db-message-bus-testkit/build.gradle
index 8a886961cc4..7d7eaf53395 100644
--- a/testing/db-message-bus-testkit/build.gradle
+++ b/testing/db-message-bus-testkit/build.gradle
@@ -9,7 +9,7 @@ dependencies {
     compileOnly 'org.osgi:org.osgi.service.component.annotations'
 
     implementation "org.osgi:org.osgi.test.junit5:$osgiTestJunit5Version"
-    implementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
+    implementation libs.junit.api
 
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
diff --git a/testing/e2e-test-utilities/build.gradle b/testing/e2e-test-utilities/build.gradle
index 9a80cc782f0..20cb267734d 100644
--- a/testing/e2e-test-utilities/build.gradle
+++ b/testing/e2e-test-utilities/build.gradle
@@ -23,9 +23,9 @@ dependencies {
     implementation "com.konghq:unirest-java:$unirestVersion"
     implementation "com.typesafe:config:$typeSafeConfigVersion"
     implementation "org.apache.commons:commons-text:$commonsTextVersion"
-    implementation "org.assertj:assertj-core:$assertjVersion"
+    implementation libs.assertj.core
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$kotlinCoroutinesVersion"
-    implementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
+    implementation libs.junit.api
     implementation "org.slf4j:slf4j-api:$slf4jVersion"
 
     implementation project(':components:flow:flow-rest-resource-service')
diff --git a/testing/flow/external-events/build.gradle b/testing/flow/external-events/build.gradle
index 8f7a3ce04d5..dfbddccffdb 100644
--- a/testing/flow/external-events/build.gradle
+++ b/testing/flow/external-events/build.gradle
@@ -14,15 +14,10 @@ dependencies {
     implementation 'net.corda:corda-base'
     implementation 'net.corda:corda-topic-schema'
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-    implementation "org.assertj:assertj-core:$assertjVersion"
 
     implementation project(':libs:configuration:configuration-core')
     implementation project(':libs:flows:external-event-responses')
     implementation project(':libs:lifecycle:lifecycle')
     implementation project(":libs:messaging:messaging")
     implementation project(':testing:test-utilities')
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/testing/kryo-serialization-testkit/build.gradle b/testing/kryo-serialization-testkit/build.gradle
index 97d8844bae5..a8d21eeaf6c 100644
--- a/testing/kryo-serialization-testkit/build.gradle
+++ b/testing/kryo-serialization-testkit/build.gradle
@@ -9,7 +9,7 @@ dependencies {
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
     implementation "com.esotericsoftware:kryo:$kryoVersion"
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.kotlin
 
     implementation project(":libs:serialization:serialization-kryo")
     implementation project(":libs:serialization:serialization-checkpoint-api")
diff --git a/testing/ledger/ledger-common-base-test/build.gradle b/testing/ledger/ledger-common-base-test/build.gradle
index 3293ad61efa..6f2bae08963 100644
--- a/testing/ledger/ledger-common-base-test/build.gradle
+++ b/testing/ledger/ledger-common-base-test/build.gradle
@@ -23,5 +23,5 @@ dependencies {
     implementation project(':libs:serialization:serialization-amqp')
     implementation project(':libs:serialization:serialization-internal')
 
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.kotlin
 }
\ No newline at end of file
diff --git a/testing/ledger/ledger-consensual-base-test/build.gradle b/testing/ledger/ledger-consensual-base-test/build.gradle
index 102facc9a69..6b08b99f72b 100644
--- a/testing/ledger/ledger-consensual-base-test/build.gradle
+++ b/testing/ledger/ledger-consensual-base-test/build.gradle
@@ -12,5 +12,5 @@ dependencies {
 
     implementation project(':components:ledger:ledger-consensual-flow')
     implementation project(":libs:serialization:json-validator")
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.kotlin
 }
\ No newline at end of file
diff --git a/testing/ledger/ledger-utxo-base-test/build.gradle b/testing/ledger/ledger-utxo-base-test/build.gradle
index c107d3fed3e..38c40b4cd01 100644
--- a/testing/ledger/ledger-utxo-base-test/build.gradle
+++ b/testing/ledger/ledger-utxo-base-test/build.gradle
@@ -14,5 +14,5 @@ dependencies {
     implementation project(':components:ledger:ledger-utxo-flow')
     implementation project(':libs:membership:membership-common')
     implementation project(':libs:flows:flow-api')
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.kotlin
 }
diff --git a/testing/message-patterns/build.gradle b/testing/message-patterns/build.gradle
index eea227f312c..23e5ab87623 100644
--- a/testing/message-patterns/build.gradle
+++ b/testing/message-patterns/build.gradle
@@ -42,8 +42,6 @@ dependencies {
     integrationTestImplementation 'net.corda:corda-topic-schema'
 
     integrationTestImplementation "com.typesafe:config:$typeSafeConfigVersion"
-    integrationTestImplementation "org.assertj:assertj-core:$assertjVersion"
-    integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     integrationTestImplementation "org.osgi:org.osgi.test.junit5:$osgiTestJunit5Version"
     integrationTestImplementation "javax.persistence:javax.persistence-api"
     integrationTestImplementation "org.apache.servicemix.bundles:org.apache.servicemix.bundles.kafka-clients:$kafkaClientVersion"
diff --git a/testing/p2p/inmemory-messaging-impl/build.gradle b/testing/p2p/inmemory-messaging-impl/build.gradle
index c80d9b9aa5a..e15a9d8ec72 100644
--- a/testing/p2p/inmemory-messaging-impl/build.gradle
+++ b/testing/p2p/inmemory-messaging-impl/build.gradle
@@ -23,8 +23,5 @@ dependencies {
     implementation 'org.slf4j:slf4j-api'
 
     integrationTestApi project(":testing:test-utilities")
-    testImplementation "org.assertj:assertj-core:$assertjVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation project(":libs:lifecycle:lifecycle-impl")
 }
diff --git a/testing/persistence-testkit/build.gradle b/testing/persistence-testkit/build.gradle
index 42176cf0e61..7ac8eb3cabf 100644
--- a/testing/persistence-testkit/build.gradle
+++ b/testing/persistence-testkit/build.gradle
@@ -23,6 +23,6 @@ dependencies {
     implementation 'net.corda:corda-application'
     implementation 'net.corda:corda-db-schema'
 
-    implementation("org.mockito:mockito-core:$mockitoVersion")
-    implementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion")
+    implementation libs.mockito.core
+    implementation libs.mockito.kotlin
 }
diff --git a/testing/sandboxes/build.gradle b/testing/sandboxes/build.gradle
index c84e028ad34..4d7c32616af 100644
--- a/testing/sandboxes/build.gradle
+++ b/testing/sandboxes/build.gradle
@@ -16,7 +16,7 @@ dependencies {
     api project(":libs:packaging:packaging")
     api project(':libs:packaging:packaging-core')
     api project(':libs:virtual-node:virtual-node-info')
-    api "org.junit.jupiter:junit-jupiter-api:$junit5Version"
+    api libs.junit.api
 
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
diff --git a/testing/security-manager-utilities/build.gradle b/testing/security-manager-utilities/build.gradle
index 75013923563..428eeb4e769 100644
--- a/testing/security-manager-utilities/build.gradle
+++ b/testing/security-manager-utilities/build.gradle
@@ -10,7 +10,7 @@ dependencies {
 
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-    implementation "org.mockito:mockito-core:$mockitoVersion"
+    implementation libs.mockito.core
 
     implementation project(':components:security-manager')
 }
diff --git a/testing/test-serialization/build.gradle b/testing/test-serialization/build.gradle
index c86c6b4f47f..70945ad68a8 100644
--- a/testing/test-serialization/build.gradle
+++ b/testing/test-serialization/build.gradle
@@ -20,6 +20,6 @@ dependencies {
     implementation project(':components:membership:membership-group-read')
     implementation project(":libs:virtual-node:sandbox-group-context")
 
-    implementation "org.mockito:mockito-core:$mockitoVersion"
-    implementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    implementation libs.mockito.core
+    implementation libs.mockito.kotlin
 }
diff --git a/testing/test-utilities/build.gradle b/testing/test-utilities/build.gradle
index 88c86012189..25d3fb6e616 100644
--- a/testing/test-utilities/build.gradle
+++ b/testing/test-utilities/build.gradle
@@ -12,8 +12,8 @@ dependencies {
     api project(':libs:metrics')
     api project(':libs:utilities')
     implementation 'org.jetbrains.kotlin:kotlin-osgi-bundle'
-    implementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    implementation "org.mockito:mockito-core:$mockitoVersion"
+    implementation libs.junit.api
+    implementation libs.mockito.core
     implementation project(":components:virtual-node:cpi-info-read-service")
     implementation project(":libs:virtual-node:virtual-node-info")
     implementation project(":libs:lifecycle:lifecycle")
diff --git a/testing/uniqueness/backing-store-fake/build.gradle b/testing/uniqueness/backing-store-fake/build.gradle
index c7acfb3b5d0..48eca8027e5 100644
--- a/testing/uniqueness/backing-store-fake/build.gradle
+++ b/testing/uniqueness/backing-store-fake/build.gradle
@@ -21,8 +21,4 @@ dependencies {
     implementation platform("net.corda:corda-api:$cordaApiVersion")
     implementation project(":components:uniqueness:backing-store")
     implementation project(":libs:uniqueness:common")
-
-    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 }
diff --git a/testing/uniqueness/uniqueness-utilities/build.gradle b/testing/uniqueness/uniqueness-utilities/build.gradle
index 67ee8dd85ab..d33a9093ef5 100644
--- a/testing/uniqueness/uniqueness-utilities/build.gradle
+++ b/testing/uniqueness/uniqueness-utilities/build.gradle
@@ -15,8 +15,8 @@ dependencies {
     implementation "net.corda:corda-application"
     implementation platform("net.corda:corda-api:$cordaApiVersion")
 
-    implementation "org.junit.jupiter:junit-jupiter-api:$junit5Version"
-    implementation "org.assertj:assertj-core:$assertjVersion"
+    implementation libs.assertj.core
+    implementation libs.junit.api
 
     implementation project(":libs:uniqueness:common")
     implementation project(":libs:utilities")
diff --git a/tools/plugins/db-config/build.gradle b/tools/plugins/db-config/build.gradle
index 27d453df2fc..b5c2922200e 100644
--- a/tools/plugins/db-config/build.gradle
+++ b/tools/plugins/db-config/build.gradle
@@ -39,8 +39,7 @@ dependencies {
     runtimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
     testImplementation 'org.jetbrains.kotlin:kotlin-stdlib'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    testImplementation libs.bundles.test
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
     testCompileOnly "net.corda.cli.host:api:$pluginHostVersion"
 }
diff --git a/tools/plugins/initial-config/build.gradle b/tools/plugins/initial-config/build.gradle
index 8997d456a45..ea7392bc99a 100644
--- a/tools/plugins/initial-config/build.gradle
+++ b/tools/plugins/initial-config/build.gradle
@@ -33,6 +33,7 @@ dependencies {
     implementation project(':libs:db:db-core')
     compileOnly "net.corda.cli.host:api:$pluginHostVersion"
 
+    testImplementation libs.bundles.test
     testImplementation 'org.jetbrains.kotlin:kotlin-stdlib'
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
     testImplementation "com.github.stefanbirkner:system-lambda:$systemLambdaVersion"
diff --git a/tools/plugins/network/build.gradle b/tools/plugins/network/build.gradle
index 0348792f06d..192cf0a2105 100644
--- a/tools/plugins/network/build.gradle
+++ b/tools/plugins/network/build.gradle
@@ -41,9 +41,9 @@ dependencies {
     implementation project(':libs:configuration:configuration-endpoints')
     implementation project(':libs:membership:membership-common')
 
+    testImplementation libs.bundles.test
     testImplementation "net.corda.cli.host:api:$pluginHostVersion"
     testImplementation "org.pf4j:pf4j:${pf4jVersion}"
-    testImplementation "org.junit.jupiter:junit-jupiter:${junit5Version}"
     testImplementation "com.github.stefanbirkner:system-lambda:1.2.1"
 
     pluginSmokeTestImplementation project(':testing:e2e-test-utilities')
diff --git a/tools/plugins/package/build.gradle b/tools/plugins/package/build.gradle
index 55695d61d8c..b22030b3fd7 100644
--- a/tools/plugins/package/build.gradle
+++ b/tools/plugins/package/build.gradle
@@ -29,9 +29,9 @@ dependencies {
     implementation "org.pf4j:pf4j:${pf4jVersion}"
     kapt "org.pf4j:pf4j:${pf4jVersion}"
 
+    testImplementation libs.bundles.test
     testImplementation "net.corda.cli.host:api:$pluginHostVersion"
     testImplementation "org.pf4j:pf4j:${pf4jVersion}"
-    testImplementation "org.junit.jupiter:junit-jupiter:${junit5Version}"
     testImplementation project(":testing:test-utilities")
     testImplementation project(":testing:packaging-test-utilities")
 
diff --git a/tools/plugins/preinstall/build.gradle b/tools/plugins/preinstall/build.gradle
index fa387ae4c8f..aca67235725 100644
--- a/tools/plugins/preinstall/build.gradle
+++ b/tools/plugins/preinstall/build.gradle
@@ -16,11 +16,11 @@ dependencies {
     kapt "org.pf4j:pf4j:$pf4jVersion"
     kapt "info.picocli:picocli:$picocliVersion"
 
+    testImplementation libs.bundles.test
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
     testCompileOnly "net.corda.cli.host:api:$pluginHostVersion"
     testImplementation "com.github.stefanbirkner:system-lambda:$systemLambdaVersion"
     testImplementation "info.picocli:picocli:$picocliVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
     implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion"
     implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
diff --git a/tools/plugins/secret-config/build.gradle b/tools/plugins/secret-config/build.gradle
index 5edd1dc2061..a209244098f 100644
--- a/tools/plugins/secret-config/build.gradle
+++ b/tools/plugins/secret-config/build.gradle
@@ -23,9 +23,8 @@ dependencies {
 
     implementation project(":libs:configuration:configuration-core")
 
-    testImplementation 'org.jetbrains.kotlin:kotlin-stdlib'
+    testImplementation libs.bundles.test
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
-    testImplementation "org.junit.jupiter:junit-jupiter-params:$junit5Version"
     testImplementation "com.github.stefanbirkner:system-lambda:$systemLambdaVersion"
     testImplementation "net.corda.cli.host:api:$pluginHostVersion"
 }
diff --git a/tools/plugins/topic-config/build.gradle b/tools/plugins/topic-config/build.gradle
index 24dc6feb25c..9d99f46cb5e 100644
--- a/tools/plugins/topic-config/build.gradle
+++ b/tools/plugins/topic-config/build.gradle
@@ -36,9 +36,8 @@ dependencies {
         }
     }
 
+    testImplementation libs.bundles.test
     testImplementation 'org.jetbrains.kotlin:kotlin-stdlib'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
     testImplementation "net.corda.cli.host:api:$pluginHostVersion"
     testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
diff --git a/tools/plugins/virtual-node/build.gradle b/tools/plugins/virtual-node/build.gradle
index 1cddca00853..d7477f68e92 100644
--- a/tools/plugins/virtual-node/build.gradle
+++ b/tools/plugins/virtual-node/build.gradle
@@ -47,9 +47,7 @@ dependencies {
     // the Postgres drivers are packaged within the tool itself.
     runtimeOnly "org.postgresql:postgresql:$postgresDriverVersion"
 
-    testImplementation 'org.jetbrains.kotlin:kotlin-stdlib'
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+    testImplementation libs.bundles.test
     testImplementation "org.pf4j:pf4j:$pf4jVersion"
     testCompileOnly "net.corda.cli.host:api:$pluginHostVersion"
 }