From 828066a6465a7cc5556ce4f2cd3809f9aa8c65f7 Mon Sep 17 00:00:00 2001 From: Adel El-Beik Date: Wed, 9 Oct 2024 14:46:57 +0100 Subject: [PATCH] Backport contract key rotation to 4.9. --- core-deterministic/build.gradle | 3 +++ .../net/corda/core/contracts/RotatedKeys.kt | 7 +------ .../contracts/ConstraintsPropagationTests.kt | 1 - ...tsClassLoaderWithStoragePersistenceTests.kt | 13 +++++-------- .../net/corda/core/contracts/RotatedKeys.kt | 4 +--- .../corda/core/internal/ConstraintsUtils.kt | 1 + .../TransactionVerifierServiceInternal.kt | 3 ++- .../kotlin/net/corda/core/node/ServiceHub.kt | 6 ++++++ .../internal/AttachmentsClassLoader.kt | 5 ++--- .../core/transactions/TransactionBuilder.kt | 18 ++++++++++++++---- .../corda/core/transactions/WireTransaction.kt | 3 --- .../net/corda/node/djvm/LtxSupplierFactory.kt | 4 +++- .../corda/node/ContractWithRotatedKeyTest.kt | 11 +++++------ .../cordapp/JarScanningCordappLoader.kt | 5 +++-- .../kotlin/net/corda/testing/dsl/TestDSL.kt | 1 - 15 files changed, 46 insertions(+), 39 deletions(-) diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index d2b38682be..f654de6eaf 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -77,6 +77,9 @@ def patchCore = tasks.register('patchCore', Zip) { exclude 'net/corda/core/serialization/internal/AttachmentsHolderImpl.class' exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class' exclude 'net/corda/core/internal/rules/*.class' + exclude 'net/corda/core/contracts/CordaRotatedKeys.class' + exclude 'net/corda/core/contracts/RotatedKeysKt.class' + exclude 'net/corda/core/contracts/RotatedKeys.class' } reproducibleFileOrder = true diff --git a/core-deterministic/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt b/core-deterministic/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt index c3521d16ee..8078308f12 100644 --- a/core-deterministic/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt +++ b/core-deterministic/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt @@ -1,18 +1,14 @@ package net.corda.core.contracts -import net.corda.core.DeleteForDJVM -import net.corda.core.KeepForDJVM import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.internal.hash import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SingletonSerializeAsToken import java.security.PublicKey import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap -@DeleteForDJVM object CordaRotatedKeys { val keys = RotatedKeys() } @@ -47,8 +43,7 @@ private val CORDA_SIGNING_KEY_ROTATIONS = listOf( * */ @CordaSerializable -@DeleteForDJVM -data class RotatedKeys(val rotatedSigningKeys: List> = emptyList()): SingletonSerializeAsToken() { +data class RotatedKeys(val rotatedSigningKeys: List> = emptyList()) { private val canBeTransitionedMap: ConcurrentMap, Boolean> = ConcurrentHashMap() private val rotateMap: Map = HashMap().apply { (rotatedSigningKeys + CORDA_SIGNING_KEY_ROTATIONS).forEach { rotatedKeyList -> diff --git a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt index 42311139ca..c668c8a294 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt @@ -1,6 +1,5 @@ package net.corda.coretests.contracts -< import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderWithStoragePersistenceTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderWithStoragePersistenceTests.kt index 35d0878bb7..f91ba5d62b 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderWithStoragePersistenceTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderWithStoragePersistenceTests.kt @@ -1,18 +1,19 @@ package net.corda.coretests.transactions import com.codahale.metrics.MetricRegistry +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.TransactionVerificationException import net.corda.core.crypto.SecureHash import net.corda.core.internal.AttachmentTrustCalculator import net.corda.core.internal.hash -import net.corda.core.internal.verification.NodeVerificationSupport import net.corda.core.node.NetworkParameters +import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.internal.AttachmentsClassLoader import net.corda.coretesting.internal.rigorousMock import net.corda.node.services.attachments.NodeAttachmentTrustCalculator import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.node.services.persistence.toInternal import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters @@ -30,8 +31,6 @@ import net.corda.testing.node.MockServices import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever import java.net.URL import kotlin.test.assertFailsWith @@ -49,12 +48,11 @@ class AttachmentsClassLoaderWithStoragePersistenceTests { private lateinit var database: CordaPersistence private lateinit var storage: NodeAttachmentService - private lateinit var attachmentTrustCalculator: AttachmentTrustCalculator private lateinit var attachmentTrustCalculator2: AttachmentTrustCalculator private val networkParameters = testNetworkParameters() private val cacheFactory = TestingNamedCacheFactory(1) private val cacheFactory2 = TestingNamedCacheFactory() - private val nodeVerificationSupport = rigorousMock().also { + private val services = rigorousMock().also { doReturn(testNetworkParameters()).whenever(it).networkParameters } @@ -86,8 +84,7 @@ class AttachmentsClassLoaderWithStoragePersistenceTests { it.start() } } - storage.nodeVerificationSupport = nodeVerificationSupport - attachmentTrustCalculator = NodeAttachmentTrustCalculator(storage.toInternal(), cacheFactory) + storage.servicesForResolution = services attachmentTrustCalculator2 = NodeAttachmentTrustCalculator(storage, database, cacheFactory2) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt b/core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt index 2ac318d884..71d3aca7f6 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt @@ -1,6 +1,5 @@ -package net.corda.core.node.services +package net.corda.core.contracts -import net.corda.core.KeepForDJVM import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 @@ -45,7 +44,6 @@ private val CORDA_SIGNING_KEY_ROTATIONS = listOf( * */ @CordaSerializable -@KeepForDJVM data class RotatedKeys(val rotatedSigningKeys: List> = emptyList()): SingletonSerializeAsToken() { private val canBeTransitionedMap: ConcurrentMap, Boolean> = ConcurrentHashMap() private val rotateMap: Map = HashMap().apply { diff --git a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt index 78457a900d..149fa2ea84 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt @@ -3,6 +3,7 @@ package net.corda.core.internal import net.corda.core.contracts.* import net.corda.core.crypto.keys import net.corda.core.internal.cordapp.CordappImpl +import net.corda.core.contracts.RotatedKeys import net.corda.core.utilities.loggerFor /** diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt index e830b49c84..74cb1577f5 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt @@ -90,6 +90,7 @@ abstract class AbstractVerifier( /** * Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the * wrong object instance. This class helps + */ private class Validator(private val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader, private val rotatedKeys: RotatedKeys) { private val inputStates: List> = ltx.inputs.map(StateAndRef::state) private val allStates: List> = inputStates + ltx.references.map(StateAndRef::state) + ltx.outputs @@ -438,7 +439,7 @@ private class Validator(private val ltx: LedgerTransaction, private val transact private fun verifyConstraintUsingRotatedKeys(constraint: AttachmentConstraint, constraintAttachment: AttachmentWithContext, contract: ContractClassName ) { // constraint could be an input constraint so we manually have to rotate to updated constraint if (constraint is SignatureAttachmentConstraint && rotatedKeys.canBeTransitioned(constraint.key, constraintAttachment.signerKeys)) { - val constraintWithRotatedKeys = SignatureAttachmentConstraint.create(CompositeKey.Builder().addKeys(constraintAttachment.signerKeys).build()) + val constraintWithRotatedKeys = SignatureAttachmentConstraint(CompositeKey.Builder().addKeys(constraintAttachment.signerKeys).build()) if (!constraintWithRotatedKeys.isSatisfiedBy(constraintAttachment)) throw ContractConstraintRejection(ltx.id, contract) } else { diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 612e341a6f..fa9a7baf34 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -171,6 +171,12 @@ interface ServiceHub : ServicesForResolution { */ val transactionVerifierService: TransactionVerifierService + /** + * INTERNAL. DO NOT USE. + * @suppress + */ + val rotatedKeys: RotatedKeys + /** * A [Clock] representing the node's current time. This should be used in preference to directly accessing the * clock so the current time can be controlled during unit testing. diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt index a003b544fb..48db623eac 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt @@ -2,7 +2,6 @@ package net.corda.core.serialization.internal import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine -import net.corda.core.DeleteForDJVM import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.CordaRotatedKeys @@ -359,7 +358,7 @@ object AttachmentsClassLoaderBuilder { val cache = if (attachmentsClassLoaderCache is AttachmentsClassLoaderForRotatedKeysOnlyImpl) fallBackCache else attachmentsClassLoaderCache ?: fallBackCache - val cachedSerializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params)), Function { key -> + val cachedSerializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function { key -> // Create classloader and load serializers, whitelisted classes val transactionClassLoader = AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent) val serializers = try { @@ -539,7 +538,7 @@ class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int, override val rotated } class AttachmentsClassLoaderForRotatedKeysOnlyImpl(override val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys) : AttachmentsClassLoaderCache { - override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: (AttachmentsClassLoaderKey) -> SerializationContext): SerializationContext { + override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: Function): SerializationContext { throw NotImplementedError("AttachmentsClassLoaderForRotatedKeysOnlyImpl.computeIfAbsent should never be called. Should be replaced by the fallback cache") } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 220095cd9e..cbe925b08b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -15,7 +15,9 @@ import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.services.AttachmentId +import net.corda.core.contracts.CordaRotatedKeys import net.corda.core.node.services.KeyManagementService +import net.corda.core.contracts.RotatedKeys import net.corda.core.serialization.CustomSerializationScheme import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults @@ -524,11 +526,11 @@ open class TransactionBuilder( // This is the logic to determine the constraint which will replace the AutomaticPlaceholderConstraint. val (defaultOutputConstraint, constraintAttachment) = selectDefaultOutputConstraintAndConstraintAttachment(contractClassName, - inputStates, selectedAttachment.currentAttachment, serviceHub) + inputStates, attachmentToUse, services) // Sanity check that the selected attachment actually passes. require(defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) { - "Selected output constraint: $defaultOutputConstraint not satisfying $selectedAttachment" + "Selected output constraint: $defaultOutputConstraint not satisfying $attachmentToUse" } val resolvedOutputStates = outputStates.map { @@ -538,7 +540,7 @@ open class TransactionBuilder( } else { // If the constraint on the output state is already set, and is not a valid transition or can't be transitioned, then fail early. inputStates?.forEach { input -> - require(outputConstraint.canBeTransitionedFrom(input.constraint, selectedAttachment.currentAttachment, serviceHub.toVerifyingServiceHub().rotatedKeys)) { + require(outputConstraint.canBeTransitionedFrom(input.constraint, attachmentToUse, getRotatedKeys(serviceHub))) { "Output state constraint $outputConstraint cannot be transitioned from ${input.constraint}" } } @@ -550,6 +552,14 @@ open class TransactionBuilder( return Pair(selectedAttachmentId, resolvedOutputStates) } + private fun getRotatedKeys(services: ServiceHub?): RotatedKeys { + return services?.rotatedKeys ?: CordaRotatedKeys.keys.also { + log.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " + + "rotated keys defined in configuration. If you are writing a unit test then pass in a " + + "MockServices instance.") + } + } + private fun selectDefaultOutputConstraintAndConstraintAttachment( contractClassName: ContractClassName, inputStates: List>?, attachmentToUse: ContractAttachment, @@ -564,7 +574,7 @@ open class TransactionBuilder( if (!defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) { // The defaultOutputConstraint is the input constraint by the attachment in use currently may have a rotated key - if (defaultOutputConstraint is SignatureAttachmentConstraint && services.toVerifyingServiceHub().rotatedKeys.canBeTransitioned(defaultOutputConstraint.key, constraintAttachment.signerKeys)) { + if (defaultOutputConstraint is SignatureAttachmentConstraint && (getRotatedKeys(serviceHub).canBeTransitioned(defaultOutputConstraint.key, constraintAttachment.signerKeys))) { return Pair(makeSignatureAttachmentConstraint(attachmentToUse.signerKeys), constraintAttachment) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index ecd71dcc3e..37b7c5a397 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -6,15 +6,12 @@ import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP -import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP import net.corda.core.contracts.ContractState import net.corda.core.contracts.PrivacySalt -import net.corda.core.contracts.RotatedKeys import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionResolutionException import net.corda.core.contracts.TransactionState -import net.corda.core.contracts.TransactionVerificationException import net.corda.core.crypto.DigestService import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.SecureHash diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxSupplierFactory.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxSupplierFactory.kt index fd982d610a..69899508c5 100644 --- a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxSupplierFactory.kt +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxSupplierFactory.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.Attachment import net.corda.core.contracts.CommandData import net.corda.core.contracts.CommandWithParties import net.corda.core.contracts.ContractState +import net.corda.core.contracts.CordaRotatedKeys import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef @@ -54,7 +55,8 @@ class LtxSupplierFactory : Function, Supplier privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt, networkParameters = networkParameters, references = referencesProvider.get(), - digestService = txArgs[TX_DIGEST_SERVICE] as DigestService + digestService = txArgs[TX_DIGEST_SERVICE] as DigestService, + rotatedKeys = CordaRotatedKeys.keys ) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithRotatedKeyTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithRotatedKeyTest.kt index 299e71c52f..e58f63ea9f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithRotatedKeyTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithRotatedKeyTest.kt @@ -30,9 +30,8 @@ import org.apache.commons.io.FileUtils.deleteDirectory import org.junit.After import org.junit.Before import org.junit.Test -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import kotlin.io.path.div +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import kotlin.test.assertEquals class ContractWithRotatedKeyTest { @@ -62,7 +61,7 @@ class ContractWithRotatedKeyTest { ): TestStartedNode { node.internals.disableDBCloseOnStop() node.dispose() - val cordappsDir = network.baseDirectory(node) / "cordapps" + val cordappsDir = network.baseDirectory(node).resolve("cordapps") deleteDirectory(cordappsDir.toFile()) return network.createNode( parameters.copy(legalName = node.internals.configuration.myLegalName, forcedID = node.internals.id), @@ -75,8 +74,8 @@ class ContractWithRotatedKeyTest { val keyStoreDir1 = SelfCleaningDir() val keyStoreDir2 = SelfCleaningDir() - val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="1-testcordapp-rsa") - val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="1-testcordapp-rsa") + val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="alias1") + val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="alias1") val unsignedFinanceCorDapp1 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services") val unsignedFinanceCorDapp2 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services").copy(versionId = 2) diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index 9f5cefa3d5..bfa11ea49c 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -6,6 +6,7 @@ import io.github.classgraph.ScanResult import net.corda.common.logging.errorReporting.CordappErrors import net.corda.common.logging.errorReporting.ErrorCode import net.corda.core.CordaRuntimeException +import net.corda.core.contracts.CordaRotatedKeys import net.corda.core.contracts.RotatedKeys import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash @@ -90,9 +91,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. */ fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), - cordappsSignerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { + cordappsSignerKeyFingerprintBlacklist: List = emptyList(), rotatedKeys: RotatedKeys = CordaRotatedKeys.keys): JarScanningCordappLoader { val paths = scanJars.map { it.restricted() } - return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist) + return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist, rotatedKeys) } private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt index 2f12079345..72ff0e541e 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt @@ -10,7 +10,6 @@ import net.corda.core.flows.FlowException import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.notary.NotaryService - import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId