diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt index aefd494aff..17c68ac20f 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableFold.kt @@ -1,3 +1,4 @@ +@file:JvmName("ObservableFold") package net.corda.client.jfx.utils import javafx.application.Platform diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt index 198fa779c5..83cb40bf70 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ObservableUtilities.kt @@ -1,3 +1,4 @@ +@file:JvmName("ObservableUtilities") package net.corda.client.jfx.utils import javafx.application.Platform diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt index 30c3c334b4..a751e3ac99 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/utils/ReadOnlyBackedObservableMapBase.kt @@ -81,15 +81,16 @@ open class ReadOnlyBackedObservableMapBase : ObservableMap { throw UnsupportedOperationException("remove() can't be called on ReadOnlyObservableMapBase") } -} - -fun ObservableMap.createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change { - return object : MapChangeListener.Change(this) { - override fun getKey() = key - override fun wasRemoved() = removedValue != null - override fun wasAdded() = addedValue != null - override fun getValueRemoved() = removedValue - override fun getValueAdded() = addedValue + /** + * Construct an object modelling the given change to an observed map. + */ + fun createMapChange(key: K, removedValue: A?, addedValue: A?): MapChangeListener.Change { + return object : MapChangeListener.Change(this) { + override fun getKey() = key + override fun wasRemoved() = removedValue != null + override fun wasAdded() = addedValue != null + override fun getValueRemoved() = removedValue + override fun getValueAdded() = addedValue + } } } - diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt b/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt index 8da2e4e49b..f16f07445b 100644 --- a/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt +++ b/client/mock/src/main/kotlin/net/corda/client/mock/Generator.kt @@ -12,7 +12,7 @@ import java.util.* * [Generator.choice] picks a generator from the specified list and runs that. * [Generator.frequency] is similar to [choice] but the probability may be specified for each generator (it is normalised before picking). * [Generator.combine] combines two generators of A and B with a function (A, B) -> C. Variants exist for other arities. - * [Generator.flatMap] sequences two generators using an arbitrary A->Generator function. Keep the usage of this + * [Generator.flatMap] sequences two generators using an arbitrary A->Generator<B> function. Keep the usage of this * function minimal as it may explode the stack, especially when using recursion. * * There are other utilities as well, the type of which are usually descriptive. @@ -32,7 +32,6 @@ import java.util.* * The above will generate a random list of animals. */ class Generator(val generate: (SplittableRandom) -> Try) { - // Functor fun map(function: (A) -> B): Generator = Generator { generate(it).map(function) } @@ -58,16 +57,42 @@ class Generator(val generate: (SplittableRandom) -> Try) { return Generator { random -> generate(random).flatMap { function(it).generate(random) } } } + fun generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A { + var error: Throwable? = null + for (i in 0..numberOfTries - 1) { + val result = generate(random) + error = when (result) { + is Try.Success -> return result.value + is Try.Failure -> result.exception + } + } + if (error == null) { + throw IllegalArgumentException("numberOfTries cannot be <= 0") + } else { + throw Exception("Failed to generate", error) + } + } + companion object { fun pure(value: A) = Generator { Try.Success(value) } fun impure(valueClosure: () -> A) = Generator { Try.Success(valueClosure()) } fun fail(error: Exception) = Generator { Try.Failure(error) } - // Alternative + /** + * Pick a generator from the specified list and run it. + */ fun choice(generators: List>) = intRange(0, generators.size - 1).flatMap { generators[it] } fun success(generate: (SplittableRandom) -> A) = Generator { Try.Success(generate(it)) } + /** + * Pick a generator from the specified list, with a probability assigned to each generator, then run the + * chosen generator. + * + * @param generators a list of probabilities of a generator being chosen, and generators. Probabilities must be + * non-negative. + */ fun frequency(generators: List>>): Generator { + require(generators.all { it.first >= 0.0 }) { "Probabilities must not be negative" } val ranges = mutableListOf>() var current = 0.0 generators.forEach { @@ -88,6 +113,8 @@ class Generator(val generate: (SplittableRandom) -> Try) { } } + fun frequency(vararg generators: Pair>) = frequency(generators.toList()) + fun sequence(generators: List>) = Generator> { val result = mutableListOf() for (generator in generators) { @@ -99,129 +126,113 @@ class Generator(val generate: (SplittableRandom) -> Try) { } Try.Success(result) } - } -} -fun Generator.Companion.frequency(vararg generators: Pair>) = frequency(generators.toList()) - -fun Generator.generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A { - var error: Throwable? = null - for (i in 0..numberOfTries - 1) { - val result = generate(random) - error = when (result) { - is Try.Success -> return result.value - is Try.Failure -> result.exception + fun int() = Generator.success(SplittableRandom::nextInt) + fun long() = Generator.success(SplittableRandom::nextLong) + fun bytes(size: Int): Generator = Generator.success { random -> + ByteArray(size) { random.nextInt().toByte() } } - } - if (error == null) { - throw IllegalArgumentException("numberOfTries cannot be <= 0") - } else { - throw Exception("Failed to generate", error) - } -} -fun Generator.Companion.int() = Generator.success(SplittableRandom::nextInt) -fun Generator.Companion.long() = Generator.success(SplittableRandom::nextLong) -fun Generator.Companion.bytes(size: Int): Generator = Generator.success { random -> - ByteArray(size) { random.nextInt().toByte() } -} + fun intRange(range: IntRange) = intRange(range.first, range.last) + fun intRange(from: Int, to: Int): Generator = Generator.success { + (from + Math.abs(it.nextInt()) % (to - from + 1)).toInt() + } -fun Generator.Companion.intRange(range: IntRange) = intRange(range.first, range.last) -fun Generator.Companion.intRange(from: Int, to: Int): Generator = Generator.success { - (from + Math.abs(it.nextInt()) % (to - from + 1)).toInt() -} + fun longRange(range: LongRange) = longRange(range.first, range.last) + fun longRange(from: Long, to: Long): Generator = Generator.success { + (from + Math.abs(it.nextLong()) % (to - from + 1)).toLong() + } -fun Generator.Companion.longRange(range: LongRange) = longRange(range.first, range.last) -fun Generator.Companion.longRange(from: Long, to: Long): Generator = Generator.success { - (from + Math.abs(it.nextLong()) % (to - from + 1)).toLong() -} + fun double() = Generator.success { it.nextDouble() } + fun doubleRange(from: Double, to: Double): Generator = Generator.success { + from + it.nextDouble() * (to - from) + } -fun Generator.Companion.double() = Generator.success { it.nextDouble() } -fun Generator.Companion.doubleRange(from: Double, to: Double): Generator = Generator.success { - from + it.nextDouble() * (to - from) -} - -fun Generator.Companion.char() = Generator { - val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16)) - if (Character.isValidCodePoint(codePoint)) { - return@Generator Try.Success(codePoint.toChar()) - } else { - Try.Failure(IllegalStateException("Could not generate valid codepoint")) - } -} - -fun Generator.Companion.string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map { - val builder = StringBuilder() - it.forEach { - builder.append(it) - } - builder.toString() -} - -fun Generator.Companion.replicate(number: Int, generator: Generator): Generator> { - val generators = mutableListOf>() - for (i in 1..number) { - generators.add(generator) - } - return sequence(generators) -} - - -fun Generator.Companion.replicatePoisson(meanSize: Double, generator: Generator, atLeastOne: Boolean = false) = Generator> { - val chance = (meanSize - 1) / meanSize - val result = mutableListOf() - var finish = false - while (!finish) { - val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value -> - if (value < chance) { - generator.generate(it).map { result.add(it) } + fun char() = Generator { + val codePoint = Math.abs(it.nextInt()) % (17 * (1 shl 16)) + if (Character.isValidCodePoint(codePoint)) { + return@Generator Try.Success(codePoint.toChar()) } else { - finish = true - if (result.isEmpty() && atLeastOne) { - generator.generate(it).map { result.add(it) } - } else Try.Success(Unit) + Try.Failure(IllegalStateException("Could not generate valid codepoint")) } } - if (res is Try.Failure) { - return@Generator res + + fun string(meanSize: Double = 16.0) = replicatePoisson(meanSize, char()).map { + val builder = StringBuilder() + it.forEach { + builder.append(it) + } + builder.toString() } - } - Try.Success(result) -} -fun Generator.Companion.pickOne(list: List) = Generator.intRange(0, list.size - 1).map { list[it] } -fun Generator.Companion.pickN(number: Int, list: List) = Generator> { - val mask = BitSet(list.size) - val size = Math.min(list.size, number) - for (i in 0..size - 1) { - // mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit - mask[i] = true - } - for (i in 0..list.size - 1) { - val bit = mask[i] - val swapIndex = i + it.nextInt(size - i) - mask[i] = mask[swapIndex] - mask[swapIndex] = bit - } - val resultList = ArrayList() - list.forEachIndexed { index, a -> - if (mask[index]) { - resultList.add(a) + fun replicate(number: Int, generator: Generator): Generator> { + val generators = mutableListOf>() + for (i in 1..number) { + generators.add(generator) + } + return sequence(generators) } - } - Try.Success(resultList) -} -fun Generator.Companion.sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) = - sampleBernoulli(listOf(collection), maxRatio) -fun Generator.Companion.sampleBernoulli(collection: Collection, meanRatio: Double = 1.0): Generator> = - replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances -> + fun replicatePoisson(meanSize: Double, generator: Generator, atLeastOne: Boolean = false) = Generator> { + val chance = (meanSize - 1) / meanSize val result = mutableListOf() - collection.forEachIndexed { index, element -> - if (chances[index] < meanRatio) { - result.add(element) + var finish = false + while (!finish) { + val res = Generator.doubleRange(0.0, 1.0).generate(it).flatMap { value -> + if (value < chance) { + generator.generate(it).map { result.add(it) } + } else { + finish = true + if (result.isEmpty() && atLeastOne) { + generator.generate(it).map { result.add(it) } + } else Try.Success(Unit) + } + } + if (res is Try.Failure) { + return@Generator res } } - result + Try.Success(result) } + + fun pickOne(list: List) = Generator.intRange(0, list.size - 1).map { list[it] } + fun pickN(number: Int, list: List) = Generator> { + val mask = BitSet(list.size) + val size = Math.min(list.size, number) + for (i in 0..size - 1) { + // mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit + mask[i] = true + } + for (i in 0..list.size - 1) { + val bit = mask[i] + val swapIndex = i + it.nextInt(size - i) + mask[i] = mask[swapIndex] + mask[swapIndex] = bit + } + val resultList = ArrayList() + list.forEachIndexed { index, a -> + if (mask[index]) { + resultList.add(a) + } + } + Try.Success(resultList) + } + + fun sampleBernoulli(maxRatio: Double = 1.0, vararg collection: A) = + sampleBernoulli(listOf(collection), maxRatio) + + fun sampleBernoulli(collection: Collection, meanRatio: Double = 1.0): Generator> { + return replicate(collection.size, Generator.doubleRange(0.0, 1.0)).map { chances -> + val result = mutableListOf() + collection.forEachIndexed { index, element -> + if (chances[index] < meanRatio) { + result.add(element) + } + } + result + } + } + + } +} \ No newline at end of file diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt b/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt index 7b35b8d5f8..dc1315b175 100644 --- a/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt +++ b/client/mock/src/main/kotlin/net/corda/client/mock/Generators.kt @@ -1,3 +1,4 @@ +@file:JvmName("Generators") package net.corda.client.mock import net.corda.core.contracts.Amount diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 27d8157dde..e0a0c21ca0 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -82,6 +82,19 @@ class RPCClientProxyHandler( val log = loggerFor() // To check whether toString() is being invoked val toStringMethod: Method = Object::toString.javaMethod!! + + private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) { + var currentThrowable = throwable + while (true) { + val cause = currentThrowable.cause + if (cause == null) { + currentThrowable.initCause(callSite) + break + } else { + currentThrowable = cause + } + } + } } // Used for reaping @@ -393,6 +406,19 @@ object RpcClientObservableSerializer : Serializer>() { return serializationContext.withProperty(RpcObservableContextKey, observableContext) } + private fun pinInSubscriptions(observable: Observable, hardReferenceStore: MutableSet>): Observable { + val refCount = AtomicInteger(0) + return observable.doOnSubscribe { + if (refCount.getAndIncrement() == 0) { + require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" } + } + }.doOnUnsubscribe { + if (refCount.decrementAndGet() == 0) { + require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" } + } + } + } + override fun read(kryo: Kryo, input: Input, type: Class>): Observable { val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext val observableId = RPCApi.ObservableId(input.readLong(true)) @@ -405,7 +431,7 @@ object RpcClientObservableSerializer : Serializer>() { observableContext.callSiteMap?.put(observableId.toLong, rpcCallSite) // We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users // don't need to store a reference to the Observables themselves. - return observable.pinInSubscriptions(observableContext.hardReferenceStore).doOnUnsubscribe { + return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe { // This causes Future completions to give warnings because the corresponding OnComplete sent from the server // will arrive after the client unsubscribes from the observable and consequently invalidates the mapping. // The unsubscribe is due to [ObservableToFuture]'s use of first(). @@ -421,30 +447,4 @@ object RpcClientObservableSerializer : Serializer>() { val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Long return observableContext.callSiteMap?.get(rpcRequestOrObservableId) } -} - -private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) { - var currentThrowable = throwable - while (true) { - val cause = currentThrowable.cause - if (cause == null) { - currentThrowable.initCause(callSite) - break - } else { - currentThrowable = cause - } - } -} - -private fun Observable.pinInSubscriptions(hardReferenceStore: MutableSet>): Observable { - val refCount = AtomicInteger(0) - return this.doOnSubscribe { - if (refCount.getAndIncrement() == 0) { - require(hardReferenceStore.add(this)) { "Reference store already contained reference $this on add" } - } - }.doOnUnsubscribe { - if (refCount.decrementAndGet() == 0) { - require(hardReferenceStore.remove(this)) { "Reference store did not contain reference $this on remove" } - } - } -} +} \ No newline at end of file diff --git a/constants.properties b/constants.properties index 68c33706ba..36e2e051b9 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=0.16.1 +gradlePluginsVersion=0.16.2 kotlinVersion=1.1.4 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt index fd0947e1ca..548521a2e8 100644 --- a/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt +++ b/core/src/main/kotlin/net/corda/core/concurrent/ConcurrencyUtils.kt @@ -1,3 +1,4 @@ +@file:JvmName("ConcurrencyUtils") package net.corda.core.concurrent import net.corda.core.internal.concurrent.openFuture diff --git a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt index 723a585cae..b7145a90c0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt @@ -262,7 +262,9 @@ abstract class SignTransactionFlow(val otherParty: Party, * @param stx a partially signed transaction received from your counter-party. * @throws FlowException if the proposed transaction fails the checks. */ - @Suspendable abstract protected fun checkTransaction(stx: SignedTransaction) + @Suspendable + @Throws(FlowException::class) + abstract protected fun checkTransaction(stx: SignedTransaction) @Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) { require(signingKey in stx.tx.requiredSigningKeys) { diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index 75cdf0b74e..9cd9776061 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -57,16 +57,11 @@ data class FlowProgressHandleImpl( // Remember to add @Throws to FlowProgressHandle.close() if this throws an exception. override fun close() { - progress.notUsed() + try { + progress.subscribe({}, {}).unsubscribe() + } catch (e: Exception) { + // Swallow any other exceptions as well. + } returnValue.cancel(false) } -} - -// Private copy of the version in client:rpc. -private fun Observable.notUsed() { - try { - this.subscribe({}, {}).unsubscribe() - } catch (e: Exception) { - // Swallow any other exceptions as well. - } -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/ServiceInfo.kt b/core/src/main/kotlin/net/corda/core/node/services/ServiceInfo.kt index 742486f25f..c3f1354a81 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/ServiceInfo.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/ServiceInfo.kt @@ -25,5 +25,3 @@ data class ServiceInfo(val type: ServiceType, val name: X500Name? = null) { override fun toString() = if (name != null) "$type|$name" else type.toString() } - -fun Iterable.containsType(type: ServiceType) = any { it.type == type } diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 9440851fdd..de5f48dcaa 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -134,6 +134,7 @@ interface SerializationContext { * Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers. * (Requires the attachment storage to have been enabled). */ + @Throws(MissingAttachmentsException::class) fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index 0148421383..9bbb9ca5b7 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.TransactionSignature import net.corda.core.utilities.toBase58String import net.corda.core.identity.Party import net.corda.core.node.ServiceHub +import net.corda.core.serialization.CordaSerializable import java.security.PublicKey /** @@ -13,6 +14,7 @@ import java.security.PublicKey * old and new notaries. Output states can be computed by applying the notary modification to corresponding inputs * on the fly. */ +@CordaSerializable data class NotaryChangeWireTransaction( override val inputs: List, override val notary: Party, diff --git a/core/src/test/kotlin/net/corda/core/flows/IdentitySyncFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/IdentitySyncFlowTests.kt index 048d1822b1..401b864fb1 100644 --- a/core/src/test/kotlin/net/corda/core/flows/IdentitySyncFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/IdentitySyncFlowTests.kt @@ -41,14 +41,6 @@ class IdentitySyncFlowTests { val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) val alice: Party = aliceNode.services.myInfo.legalIdentity val bob: Party = bobNode.services.myInfo.legalIdentity - aliceNode.database.transaction { - aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert) - aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert) - } - bobNode.database.transaction { - bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert) - bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert) - } bobNode.registerInitiatedFlow(Receive::class.java) // Alice issues then pays some cash to a new confidential identity that Bob doesn't know about diff --git a/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt index c4042699fb..7ba1a726f0 100644 --- a/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt @@ -26,14 +26,6 @@ class TransactionKeyFlowTests { val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) val alice: Party = aliceNode.services.myInfo.legalIdentity val bob: Party = bobNode.services.myInfo.legalIdentity - aliceNode.database.transaction { - aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert) - aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert) - } - bobNode.database.transaction { - bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert) - bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert) - } // Run the flows val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob)) diff --git a/gradle-plugins/README.rst b/gradle-plugins/README.rst index cf22310800..0ede7c216f 100644 --- a/gradle-plugins/README.rst +++ b/gradle-plugins/README.rst @@ -10,6 +10,11 @@ the rest of the Corda libraries. currently known solution (such as publishing from buildSrc or setting up a separate project/repo) would introduce a two step build which is less convenient. +Version number +-------------- + +To modify the version number edit constants.properties in root dir + Installing ---------- diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy index c96a937fef..19e3a6b9b3 100644 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy @@ -101,7 +101,9 @@ class Node extends CordformNode { protected void build() { configureRpcUsers() installCordaJar() - installWebserverJar() + if (config.hasPath("webAddress")) { + installWebserverJar() + } installBuiltPlugin() installCordapps() installConfig() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 9cdd9c49e5..5ebac4b24f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream import java.io.NotSerializableException import java.util.* import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutionException val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled" @@ -42,19 +43,28 @@ data class SerializationContextImpl(override val preferredSerializationVersion: private val cache: Cache, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build() - // We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context. + /** + * {@inheritDoc} + * + * We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context. + */ override fun withAttachmentsClassLoader(attachmentHashes: List): SerializationContext { properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean ?: false || return this val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContextImpl ?: return this // Some tests don't set one. - return withClassLoader(cache.get(attachmentHashes) { - val missing = ArrayList() - val attachments = ArrayList() - attachmentHashes.forEach { id -> - serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id } - } - missing.isNotEmpty() && throw MissingAttachmentsException(missing) - AttachmentsClassLoader(attachments) - }) + try { + return withClassLoader(cache.get(attachmentHashes) { + val missing = ArrayList() + val attachments = ArrayList() + attachmentHashes.forEach { id -> + serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id } + } + missing.isNotEmpty() && throw MissingAttachmentsException(missing) + AttachmentsClassLoader(attachments) + }) + } catch (e: ExecutionException) { + // Caught from within the cache get, so unwrap. + throw e.cause!! + } } override fun withProperty(property: Any, value: Any): SerializationContext { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 488150a14b..7e277952c4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -15,7 +15,7 @@ import kotlin.reflect.jvm.javaConstructor open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer { override val type: Type get() = clazz open val kotlinConstructor = constructorForDeserialization(clazz) - val javaConstructor by lazy { kotlinConstructor?.javaConstructor?.apply { isAccessible = true } } + val javaConstructor by lazy { kotlinConstructor?.javaConstructor } private val logger = loggerFor() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 8374a7b898..12cea01c39 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -13,6 +13,7 @@ import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaType /** @@ -48,7 +49,9 @@ internal fun constructorForDeserialization(type: Type): KFunction? { preferredCandidate = kotlinConstructor } } - return preferredCandidate ?: throw NotSerializableException("No constructor for deserialization found for $clazz.") + + return preferredCandidate?.apply { isAccessible = true} + ?: throw NotSerializableException("No constructor for deserialization found for $clazz.") } else { return null } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt index c87d1cef54..78b5d201bb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.CordaRuntimeException import net.corda.core.CordaThrowable +import net.corda.core.utilities.loggerFor import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import net.corda.nodeapi.internal.serialization.amqp.constructorForDeserialization @@ -9,6 +10,11 @@ import net.corda.nodeapi.internal.serialization.amqp.propertiesForSerialization import java.io.NotSerializableException class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(Throwable::class.java, ThrowableProxy::class.java, factory) { + + companion object { + private val logger = loggerFor() + } + override val additionalSerializers: Iterable> = listOf(StackTraceElementSerializer(factory)) override fun toProxy(obj: Throwable): ThrowableProxy { @@ -33,7 +39,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy" - } - val flowFactory = InitiatedFlowFactory.CorDapp(version, appName, { ctor.newInstance(it) }) + val flowFactory = InitiatedFlowFactory.CorDapp(version, initiatedFlow.appName, { ctor.newInstance(it) }) val observable = internalRegisterFlowFactory(initiatingFlow, flowFactory, initiatedFlow, track) log.info("Registered ${initiatingFlow.name} to initiate ${initiatedFlow.name} (version $version)") return observable diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index d8fa038ed2..df6daf71e6 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -15,6 +15,7 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.abbreviate import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.isRegularFile import net.corda.core.internal.staticField import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.* @@ -27,6 +28,7 @@ import net.corda.node.utilities.DatabaseTransaction import net.corda.node.utilities.DatabaseTransactionManager import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.nio.file.Paths import java.sql.SQLException import java.util.* import java.util.concurrent.TimeUnit @@ -347,7 +349,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, val session = FlowSession(sessionFlow, random63BitValue(), null, FlowSessionState.Initiating(otherParty), retryable) openSessions[Pair(sessionFlow, otherParty)] = session val (version, initiatingFlowClass) = sessionFlow.javaClass.flowVersionAndInitiatingClass - val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, "not defined", firstPayload) + val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, sessionFlow.javaClass.appName, firstPayload) sendInternal(session, sessionInit) if (waitForConfirmation) { session.waitForConfirmation() @@ -491,3 +493,12 @@ val Class>.flowVersionAndInitiatingClass: Pair>.appName: String get() { + val jarFile = Paths.get(protectionDomain.codeSource.location.toURI()) + return if (jarFile.isRegularFile() && jarFile.toString().endsWith(".jar")) { + jarFile.fileName.toString().removeSuffix(".jar") + } else { + "" + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt index f9d561a43e..8895db530c 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt @@ -40,10 +40,10 @@ import net.corda.node.utilities.wrapWithDatabaseTransaction import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.withTokenContext import org.apache.activemq.artemis.utils.ReusableLatch -import org.bouncycastle.asn1.x500.X500Name import org.slf4j.Logger import rx.Observable import rx.subjects.PublishSubject +import java.io.NotSerializableException import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors @@ -609,13 +609,20 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, val serialized = try { message.serialize() - } catch (e: KryoException) { - if (message !is ErrorSessionEnd || message.errorResponse == null) throw e - logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + - "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) - // The subclass may have overridden toString so we use that - val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } - message.copy(errorResponse = FlowException(exMessage)).serialize() + } catch (e: Exception) { + when(e) { + // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. + is KryoException, + is NotSerializableException -> { + if (message !is ErrorSessionEnd || message.errorResponse == null) throw e + logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + + "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) + // The subclass may have overridden toString so we use that + val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } + message.copy(errorResponse = FlowException(exMessage)).serialize() + } + else -> throw e + } } serviceHub.networkService.apply { diff --git a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt b/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt index 26a913db24..f295c81398 100644 --- a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt +++ b/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt @@ -10,6 +10,7 @@ import net.corda.core.internal.list import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getX500Name +import net.corda.core.utilities.unwrap import net.corda.nodeapi.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess @@ -40,16 +41,19 @@ class CordappSmokeTest { fun `FlowContent appName returns the filename of the CorDapp jar`() { val pluginsDir = (factory.baseDirectory(aliceConfig) / "plugins").createDirectories() // Find the jar file for the smoke tests of this module - val selfCorDapp = Paths.get("build", "libs").list { + val selfCordapp = Paths.get("build", "libs").list { it.filter { "-smoke-test" in it.toString() }.toList().single() } - selfCorDapp.copyToDirectory(pluginsDir) + selfCordapp.copyToDirectory(pluginsDir) factory.create(aliceConfig).use { alice -> alice.connect().use { connectionToAlice -> val aliceIdentity = connectionToAlice.proxy.nodeIdentity().legalIdentity - val future = connectionToAlice.proxy.startFlow(::DummyInitiatingFlow, aliceIdentity).returnValue - assertThat(future.getOrThrow().appName).isEqualTo(selfCorDapp.fileName.toString().removeSuffix(".jar")) + val future = connectionToAlice.proxy.startFlow(::GatherContextsFlow, aliceIdentity).returnValue + val (sessionInitContext, sessionConfirmContext) = future.getOrThrow() + val selfCordappName = selfCordapp.fileName.toString().removeSuffix(".jar") + assertThat(sessionInitContext.appName).isEqualTo(selfCordappName) + assertThat(sessionConfirmContext.appName).isEqualTo(selfCordappName) } } } @@ -62,15 +66,26 @@ class CordappSmokeTest { @InitiatingFlow @StartableByRPC - class DummyInitiatingFlow(val otherParty: Party) : FlowLogic() { + class GatherContextsFlow(private val otherParty: Party) : FlowLogic>() { @Suspendable - override fun call() = getFlowContext(otherParty) + override fun call(): Pair { + // This receive will kick off SendBackInitiatorFlowContext by sending a session-init with our app name. + // SendBackInitiatorFlowContext will send back our context using the information from this session-init + val sessionInitContext = receive(otherParty).unwrap { it } + // This context is taken from the session-confirm message + val sessionConfirmContext = getFlowContext(otherParty) + return Pair(sessionInitContext, sessionConfirmContext) + } } @Suppress("unused") - @InitiatedBy(DummyInitiatingFlow::class) - class DummyInitiatedFlow(val otherParty: Party) : FlowLogic() { + @InitiatedBy(GatherContextsFlow::class) + class SendBackInitiatorFlowContext(private val otherParty: Party) : FlowLogic() { @Suspendable - override fun call() = Unit + override fun call() { + // An initiated flow calling getFlowContext on its initiator will get the context from the session-init + val sessionInitContext = getFlowContext(otherParty) + send(otherParty, sessionInitContext) + } } } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 370d7667a1..e89aa57f8c 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -122,7 +122,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { resetTestSerialization() } - class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, private val myIdentity: Party) : LinearState, SchedulableState { + class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState { override val participants: List get() = listOf(myIdentity) diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index a523e5f273..0c59ea2eaf 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -77,9 +77,9 @@ class IRSDemoTest : IntegrationTestCategory { assertThat(getFloatingLegFixCount(nodeAApi) == 0) // Wait until the initial trade and all scheduled fixings up to the current date have finished - nextFixingDates.firstWithTimeout(maxWaitTime){ it == null || it > currentDate } + nextFixingDates.firstWithTimeout(maxWaitTime) { it == null || it >= currentDate } runDateChange(nodeBApi) - nextFixingDates.firstWithTimeout(maxWaitTime) { it == null || it > futureDate } + nextFixingDates.firstWithTimeout(maxWaitTime) { it == null || it >= futureDate } assertThat(getFloatingLegFixCount(nodeAApi) > 0) } diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index fbc3ab9304..7998e98e6a 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -5,7 +5,7 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.CityDatabase import net.corda.core.node.WorldMapLocation import net.corda.core.node.services.ServiceInfo -import net.corda.core.node.services.containsType +import net.corda.core.node.services.ServiceType import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getX500Name import net.corda.core.utilities.locality @@ -278,3 +278,9 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, mockNet.stopNodes() } } + +/** + * Helper function for verifying that a service info contains the given type of advertised service. For non-simulation cases + * this is a configuration matter rather than implementation. + */ +fun Iterable.containsType(type: ServiceType) = any { it.type == type } \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index fb940a4233..7a8aad45ca 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -1,9 +1,6 @@ package net.corda.testing import net.corda.client.mock.Generator -import net.corda.client.mock.generateOrFail -import net.corda.client.mock.int -import net.corda.client.mock.string import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 5e6646d76f..d6e0b0575c 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -4,7 +4,6 @@ import joptsimple.OptionSet import net.corda.client.mock.ErrorFlowsEventGenerator import net.corda.client.mock.EventGenerator import net.corda.client.mock.Generator -import net.corda.client.mock.pickOne import net.corda.client.rpc.CordaRPCConnection import net.corda.core.contracts.Amount import net.corda.core.identity.Party diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt index f9657f4969..fbfa6aee61 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt @@ -1,7 +1,6 @@ package net.corda.loadtest.tests import net.corda.client.mock.Generator -import net.corda.client.mock.pickN import net.corda.core.contracts.Issued import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.AbstractParty diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt index a1aecd424a..db397a2e35 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt @@ -2,7 +2,6 @@ package net.corda.loadtest.tests import net.corda.client.mock.Generator import net.corda.client.mock.generateAmount -import net.corda.client.mock.pickOne import net.corda.core.contracts.Issued import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.withoutIssuer diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index f449ebc019..81ea0b4fa2 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -1,9 +1,6 @@ package net.corda.loadtest.tests import net.corda.client.mock.Generator -import net.corda.client.mock.int -import net.corda.client.mock.pickOne -import net.corda.client.mock.replicate import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowException import net.corda.core.internal.concurrent.thenMatch diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt index 25eeda5ec4..d29cf969a8 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt @@ -2,8 +2,6 @@ package net.corda.loadtest.tests import de.danielbechler.diff.ObjectDifferFactory import net.corda.client.mock.Generator -import net.corda.client.mock.pickOne -import net.corda.client.mock.replicatePoisson import net.corda.finance.contracts.asset.Cash import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index 6fa26cf776..f3d2fe6c5b 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -1,6 +1,5 @@ package net.corda.verifier -import net.corda.client.mock.generateOrFail import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow