From 534f60dc578480fec39e39d088a9f9d1e4144c02 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 12 Jul 2017 14:37:02 +0100 Subject: [PATCH 001/197] Make serialise putObject function an open function For testing I need to be able to mess with the schema before it gets added to the envelope, extract the function where that happens and make it open so the tests can do what they want --- .../corda/core/serialization/amqp/SerializationOutput.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt index 3cbfad41ba..cef69a03d6 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt @@ -34,7 +34,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory // Our object writeObject(obj, this) // The schema - putObject(Schema(schemaHistory.toList())) + putObject(Schema(schemaHistory.toList()), this) } } val bytes = ByteArray(data.encodedSize().toInt() + 8) @@ -53,6 +53,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory writeObject(obj, data, obj.javaClass) } + open fun putObject(schema: Schema, data: Data) { + data.putObject(schema) + } + internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) { if (obj == null) { data.putNull() From fa4577d2360c4e2cf075f0c726cae29eb3ed6135 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 11 Jul 2017 19:36:56 +0100 Subject: [PATCH 002/197] Cleaned up NonEmptySet and expanded its usage in the codebase --- .../corda/core/contracts/TransactionTypes.kt | 5 +- .../core/contracts/TransactionVerification.kt | 3 +- .../kotlin/net/corda/core/node/NodeInfo.kt | 3 +- .../net/corda/core/node/services/Services.kt | 13 +- .../serialization/DefaultKryoCustomizer.kt | 28 +++- .../core/transactions/SignedTransaction.kt | 12 +- .../net/corda/core/utilities/KotlinUtils.kt | 5 +- .../net/corda/core/utilities/NonEmptySet.kt | 152 ++++++------------ .../corda/flows/BroadcastTransactionFlow.kt | 3 +- .../kotlin/net/corda/flows/FinalityFlow.kt | 6 +- .../corda/core/utilities/NonEmptySetTest.kt | 103 +++--------- .../corda/contracts/asset/ObligationTests.kt | 20 +-- .../services/messaging/P2PSecurityTest.kt | 8 +- .../net/corda/node/internal/AbstractNode.kt | 3 +- .../node/services/vault/NodeVaultService.kt | 67 ++++---- .../services/vault/VaultSoftLockManager.kt | 14 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 7 +- .../services/vault/NodeVaultServiceTest.kt | 22 +-- .../node/services/vault/VaultQueryTests.kt | 3 +- .../corda/testing/node/MockNetworkMapCache.kt | 5 +- .../net/corda/testing/node/MockServices.kt | 3 +- 21 files changed, 205 insertions(+), 280 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt index db388b5c8c..c5af7e80d8 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt @@ -4,6 +4,7 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.toNonEmptySet import java.security.PublicKey /** Defines transaction build & validation logic for a specific transaction type */ @@ -20,7 +21,7 @@ sealed class TransactionType { fun verify(tx: LedgerTransaction) { require(tx.notary != null || tx.timeWindow == null) { "Transactions with time-windows must be notarised" } val duplicates = detectDuplicateInputs(tx) - if (duplicates.isNotEmpty()) throw TransactionVerificationException.DuplicateInputStates(tx.id, duplicates) + if (duplicates.isNotEmpty()) throw TransactionVerificationException.DuplicateInputStates(tx.id, duplicates.toNonEmptySet()) val missing = verifySigners(tx) if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx.id, missing.toList()) verifyTransaction(tx) @@ -51,7 +52,7 @@ sealed class TransactionType { } /** - * Return the list of public keys that that require signatures for the transaction type. + * Return the set of public keys that require signatures for the transaction type. * Note: the notary key is checked separately for all transactions and need not be included. */ abstract fun getRequiredSigners(tx: LedgerTransaction): Set diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt index 5417169459..6ce17e267a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt @@ -4,6 +4,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.NonEmptySet import java.security.PublicKey import java.util.* @@ -107,7 +108,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, cause: Throw override fun toString(): String = "Signers missing: ${missing.joinToString()}" } - class DuplicateInputStates(txId: SecureHash, val duplicates: Set) : TransactionVerificationException(txId, null) { + class DuplicateInputStates(txId: SecureHash, val duplicates: NonEmptySet) : TransactionVerificationException(txId, null) { override fun toString(): String = "Duplicate inputs: ${duplicates.joinToString()}" } diff --git a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt index 342e9997e1..7b71cdcde2 100644 --- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt +++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt @@ -6,6 +6,7 @@ import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.NonEmptySet /** * Information for an advertised service including the service specific identity information. @@ -21,7 +22,7 @@ data class ServiceEntry(val info: ServiceInfo, val identity: PartyAndCertificate @CordaSerializable data class NodeInfo(val addresses: List, val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services. - val legalIdentitiesAndCerts: Set, + val legalIdentitiesAndCerts: NonEmptySet, val platformVersion: Int, var advertisedServices: List = emptyList(), val worldMapLocation: WorldMapLocation? = null) { diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 6d1a486105..3b139f4734 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -17,11 +17,12 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.OpaqueBytes import net.corda.core.toFuture import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.OpaqueBytes import net.corda.flows.AnonymisedIdentity import rx.Observable import rx.subjects.PublishSubject @@ -294,7 +295,7 @@ interface VaultService { * @throws [StatesNotAvailableException] when not possible to softLock all of requested [StateRef] */ @Throws(StatesNotAvailableException::class) - fun softLockReserve(lockId: UUID, stateRefs: Set) + fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet) /** * Release all or an explicitly specified set of [StateRef] for a given [UUID] unique identifier. @@ -303,7 +304,7 @@ interface VaultService { * In the case of coin selection, softLock are automatically released once previously gathered unconsumed input refs * are consumed as part of cash spending. */ - fun softLockRelease(lockId: UUID, stateRefs: Set? = null) + fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet? = null) /** * Retrieve softLockStates for a given [UUID] or return all softLockStates in vault for a given @@ -318,7 +319,11 @@ interface VaultService { * is implemented in a separate module (finance) and requires access to it. */ @Suspendable - fun unconsumedStatesForSpending(amount: Amount, onlyFromIssuerParties: Set? = null, notary: Party? = null, lockId: UUID, withIssuerRefs: Set? = null): List> + fun unconsumedStatesForSpending(amount: Amount, + onlyFromIssuerParties: Set? = null, + notary: Party? = null, + lockId: UUID, + withIssuerRefs: Set? = null): List> } // TODO: Remove this from the interface diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt index 9b1a18be0c..18fc43b3b4 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt @@ -1,6 +1,9 @@ package net.corda.core.serialization import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.MapReferenceResolver @@ -8,13 +11,13 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer import de.javakaffee.kryoserializers.BitSetSerializer import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer import de.javakaffee.kryoserializers.guava.* -import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.MetaData +import net.corda.core.crypto.composite.CompositeKey import net.corda.core.node.CordaPluginRegistry import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.NonEmptySet -import net.corda.core.utilities.NonEmptySetSerializer +import net.corda.core.utilities.toNonEmptySet import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import org.bouncycastle.asn1.x500.X500Name @@ -36,6 +39,7 @@ import java.io.InputStream import java.lang.reflect.Modifier.isPublic import java.security.cert.CertPath import java.util.* +import kotlin.collections.ArrayList object DefaultKryoCustomizer { private val pluginRegistries: List by lazy { @@ -128,4 +132,22 @@ object DefaultKryoCustomizer { return strat.newInstantiatorOf(type) } } -} + + private object NonEmptySetSerializer : Serializer>() { + override fun write(kryo: Kryo, output: Output, obj: NonEmptySet) { + // Write out the contents as normal + output.writeInt(obj.size, true) + obj.forEach { kryo.writeClassAndObject(output, it) } + } + + override fun read(kryo: Kryo, input: Input, type: Class>): NonEmptySet { + val size = input.readInt(true) + require(size >= 1) { "Invalid size read off the wire: $size" } + val list = ArrayList(size) + repeat(size) { + list += kryo.readClassAndObject(input) + } + return list.toNonEmptySet() + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index 8a4453b2d6..89f7e082b5 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -7,10 +7,11 @@ import net.corda.core.contracts.TransactionVerificationException import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.isFulfilledBy -import net.corda.core.crypto.keys import net.corda.core.node.ServiceHub import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.toNonEmptySet import java.security.PublicKey import java.security.SignatureException import java.util.* @@ -50,11 +51,8 @@ data class SignedTransaction(val txBits: SerializedBytes, override val id: SecureHash get() = tx.id @CordaSerializable - class SignaturesMissingException(val missing: Set, val descriptions: List, override val id: SecureHash) : NamedByHash, SignatureException() { - override fun toString(): String { - return "Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}" - } - } + class SignaturesMissingException(val missing: NonEmptySet, val descriptions: List, override val id: SecureHash) + : NamedByHash, SignatureException("Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}") /** * Verifies the signatures on this transaction and throws if any are missing which aren't passed as parameters. @@ -80,7 +78,7 @@ data class SignedTransaction(val txBits: SerializedBytes, val allowed = allowedToBeMissing.toSet() val needed = missing - allowed if (needed.isNotEmpty()) - throw SignaturesMissingException(needed, getMissingKeyDescriptions(needed), id) + throw SignaturesMissingException(needed.toNonEmptySet(), getMissingKeyDescriptions(needed), id) } check(tx.id == id) return tx diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index e6e656a199..19e1a90e06 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -18,4 +18,7 @@ inline fun Logger.trace(msg: () -> String) { /** Log a DEBUG level message produced by evaluating the given lamdba, but only if DEBUG logging is enabled. */ inline fun Logger.debug(msg: () -> String) { if (isDebugEnabled) debug(msg()) -} \ No newline at end of file +} + +/** @see NonEmptySet.copyOf */ +fun Collection.toNonEmptySet(): NonEmptySet = NonEmptySet.copyOf(this) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt b/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt index 13920ba788..96a955baac 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/NonEmptySet.kt @@ -1,117 +1,63 @@ package net.corda.core.utilities -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output +import com.google.common.collect.Iterators import java.util.* +import java.util.function.Consumer +import java.util.stream.Stream /** - * A set which is constrained to ensure it can never be empty. An initial value must be provided at - * construction, and attempting to remove the last element will cause an IllegalStateException. - * The underlying set is exposed for Kryo to access, but should not be accessed directly. + * An immutable ordered non-empty set. */ -class NonEmptySet(initial: T) : MutableSet { - private val set: MutableSet = HashSet() +class NonEmptySet private constructor(private val elements: Set) : Set by elements { + companion object { + /** + * Returns a singleton set containing [element]. This behaves the same as [Collections.singleton] but returns a + * [NonEmptySet] for the extra type-safety. + */ + @JvmStatic + fun of(element: T): NonEmptySet = NonEmptySet(Collections.singleton(element)) - init { - set.add(initial) - } + /** Returns a non-empty set containing the given elements, minus duplicates, in the order each was specified. */ + @JvmStatic + fun of(first: T, second: T, vararg rest: T): NonEmptySet { + val elements = LinkedHashSet(rest.size + 2) + elements += first + elements += second + elements.addAll(rest) + return NonEmptySet(elements) + } - override val size: Int - get() = set.size - - override fun add(element: T): Boolean = set.add(element) - override fun addAll(elements: Collection): Boolean = set.addAll(elements) - override fun clear() = throw UnsupportedOperationException() - override fun contains(element: T): Boolean = set.contains(element) - override fun containsAll(elements: Collection): Boolean = set.containsAll(elements) - override fun isEmpty(): Boolean = false - - override fun iterator(): MutableIterator = Iterator(set.iterator()) - - override fun remove(element: T): Boolean = - // Test either there's more than one element, or the removal is a no-op - if (size > 1) - set.remove(element) - else if (!contains(element)) - false - else - throw IllegalStateException() - - override fun removeAll(elements: Collection): Boolean = - if (size > elements.size) - set.removeAll(elements) - else if (!containsAll(elements)) - // Remove the common elements - set.removeAll(elements) - else - throw IllegalStateException() - - override fun retainAll(elements: Collection): Boolean { - val iterator = iterator() - val ret = false - - // The iterator will throw an IllegalStateException if we try removing the last element - while (iterator.hasNext()) { - if (!elements.contains(iterator.next())) { - iterator.remove() + /** + * Returns a non-empty set containing each of [elements], minus duplicates, in the order each appears first in + * the source collection. + * @throws IllegalArgumentException If [elements] is empty. + */ + @JvmStatic + fun copyOf(elements: Collection): NonEmptySet { + if (elements is NonEmptySet) return elements + return when (elements.size) { + 0 -> throw IllegalArgumentException("elements is empty") + 1 -> of(elements.first()) + else -> { + val copy = LinkedHashSet(elements.size) + elements.forEach { copy += it } // Can't use Collection.addAll as it doesn't specify insertion order + NonEmptySet(copy) + } } } - - return ret } - override fun equals(other: Any?): Boolean = - if (other is Set<*>) - // Delegate down to the wrapped set's equals() function - set == other - else - false + /** Returns the first element of the set. */ + fun head(): T = elements.iterator().next() + override fun isEmpty(): Boolean = false + override fun iterator(): Iterator = Iterators.unmodifiableIterator(elements.iterator()) - override fun hashCode(): Int = set.hashCode() - override fun toString(): String = set.toString() - - inner class Iterator(val iterator: MutableIterator) : MutableIterator { - override fun hasNext(): Boolean = iterator.hasNext() - override fun next(): T = iterator.next() - override fun remove() = - if (set.size > 1) - iterator.remove() - else - throw IllegalStateException() - } -} - -fun nonEmptySetOf(initial: T, vararg elements: T): NonEmptySet { - val set = NonEmptySet(initial) - // We add the first element twice, but it's a set, so who cares - set.addAll(elements) - return set -} - -/** - * Custom serializer which understands it has to read in an item before - * trying to construct the set. - */ -object NonEmptySetSerializer : Serializer>() { - override fun write(kryo: Kryo, output: Output, obj: NonEmptySet) { - // Write out the contents as normal - output.writeInt(obj.size) - obj.forEach { kryo.writeClassAndObject(output, it) } - } - - override fun read(kryo: Kryo, input: Input, type: Class>): NonEmptySet { - val size = input.readInt() - require(size >= 1) { "Size is positive" } - // TODO: Is there an upper limit we can apply to how big one of these could be? - val first = kryo.readClassAndObject(input) - // Read the first item and use it to construct the NonEmptySet - val set = NonEmptySet(first) - // Read in the rest of the set - for (i in 2..size) { - set.add(kryo.readClassAndObject(input)) - } - return set - } + // Following methods are not delegated by Kotlin's Class delegation + override fun forEach(action: Consumer) = elements.forEach(action) + override fun stream(): Stream = elements.stream() + override fun parallelStream(): Stream = elements.parallelStream() + override fun spliterator(): Spliterator = elements.spliterator() + override fun equals(other: Any?): Boolean = other === this || other == elements + override fun hashCode(): Int = elements.hashCode() + override fun toString(): String = elements.toString() } diff --git a/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt b/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt index 140da37489..93ed85fc08 100644 --- a/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt @@ -6,6 +6,7 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.NonEmptySet /** * Notify the specified parties about a transaction. The remote peers will download this transaction and its @@ -18,7 +19,7 @@ import net.corda.core.transactions.SignedTransaction */ @InitiatingFlow class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction, - val participants: Set) : FlowLogic() { + val participants: NonEmptySet) : FlowLogic() { @CordaSerializable data class NotifyTxRequest(val tx: SignedTransaction) diff --git a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt index dfc3c3c20d..cb76ab342f 100644 --- a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt @@ -11,6 +11,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.toNonEmptySet /** * Verifies the given transactions, then sends them to the named notary. If the notary agrees that the transactions @@ -65,7 +66,10 @@ class FinalityFlow(val transactions: Iterable, progressTracker.currentStep = BROADCASTING val me = serviceHub.myInfo.legalIdentity for ((stx, parties) in notarisedTxns) { - subFlow(BroadcastTransactionFlow(stx, parties + extraRecipients - me)) + val participants = parties + extraRecipients - me + if (participants.isNotEmpty()) { + subFlow(BroadcastTransactionFlow(stx, participants.toNonEmptySet())) + } } return notarisedTxns.map { it.first } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt index 6a086b39ba..c6991c407a 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt @@ -4,119 +4,56 @@ import com.google.common.collect.testing.SetTestSuiteBuilder import com.google.common.collect.testing.TestIntegerSetGenerator import com.google.common.collect.testing.features.CollectionFeature import com.google.common.collect.testing.features.CollectionSize -import com.google.common.collect.testing.testers.* import junit.framework.TestSuite import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Suite -import kotlin.test.assertEquals @RunWith(Suite::class) @Suite.SuiteClasses( NonEmptySetTest.Guava::class, - NonEmptySetTest.Remove::class, - NonEmptySetTest.Serializer::class + NonEmptySetTest.General::class ) class NonEmptySetTest { - /** - * Guava test suite generator for NonEmptySet. - */ - class Guava { - companion object { - @JvmStatic - fun suite(): TestSuite - = SetTestSuiteBuilder - .using(NonEmptySetGenerator()) - .named("test NonEmptySet with several values") + object Guava { + @JvmStatic + fun suite(): TestSuite { + return SetTestSuiteBuilder + .using(NonEmptySetGenerator) + .named("Guava test suite") .withFeatures( CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES, - CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, - CollectionFeature.GENERAL_PURPOSE + CollectionFeature.KNOWN_ORDER ) - // Kotlin throws the wrong exception in this cases - .suppressing(CollectionAddAllTester::class.java.getMethod("testAddAll_nullCollectionReference")) - // Disable tests that try to remove everything: - .suppressing(CollectionRemoveAllTester::class.java.getMethod("testRemoveAll_nullCollectionReferenceNonEmptySubject")) - .suppressing(CollectionClearTester::class.java.methods.toList()) - .suppressing(CollectionRetainAllTester::class.java.methods.toList()) - .suppressing(CollectionRemoveIfTester::class.java.getMethod("testRemoveIf_allPresent")) .createTestSuite() } - - /** - * For some reason IntelliJ really wants to scan this class for tests and fail when - * it doesn't find any. This stops that error from occurring. - */ - @Test fun dummy() { - } } - /** - * Test removal, which Guava's standard tests can't cover for us. - */ - class Remove { + class General { @Test - fun `construction`() { - val expected = 17 - val basicSet = nonEmptySetOf(expected) - val actual = basicSet.first() - assertEquals(expected, actual) - } - - @Test(expected = IllegalStateException::class) - fun `remove sole element`() { - val basicSet = nonEmptySetOf(-17) - basicSet.remove(-17) + fun `copyOf - empty source`() { + assertThatThrownBy { NonEmptySet.copyOf(HashSet()) }.isInstanceOf(IllegalArgumentException::class.java) } @Test - fun `remove one of two elements`() { - val basicSet = nonEmptySetOf(-17, 17) - basicSet.remove(-17) + fun head() { + assertThat(NonEmptySet.of(1, 2).head()).isEqualTo(1) } - @Test - fun `remove element which does not exist`() { - val basicSet = nonEmptySetOf(-17) - basicSet.remove(-5) - assertEquals(1, basicSet.size) - } - - @Test(expected = IllegalStateException::class) - fun `remove via iterator`() { - val basicSet = nonEmptySetOf(-17, 17) - val iterator = basicSet.iterator() - while (iterator.hasNext()) { - iterator.remove() - } - } - } - - /** - * Test serialization/deserialization. - */ - class Serializer { @Test fun `serialize deserialize`() { - val expected: NonEmptySet = nonEmptySetOf(-17, 22, 17) - val serialized = expected.serialize().bytes - val actual = serialized.deserialize>() - - assertEquals(expected, actual) + val original = NonEmptySet.of(-17, 22, 17) + val copy = original.serialize().deserialize() + assertThat(copy).isEqualTo(original).isNotSameAs(original) } } -} -/** - * Generator of non empty set instances needed for testing. - */ -class NonEmptySetGenerator : TestIntegerSetGenerator() { - override fun create(elements: Array?): NonEmptySet? { - val set = nonEmptySetOf(elements!!.first()) - set.addAll(elements.toList()) - return set + private object NonEmptySetGenerator : TestIntegerSetGenerator() { + override fun create(elements: Array): NonEmptySet = NonEmptySet.copyOf(elements.asList()) } } diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index 5d393f71db..269234eb3b 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -4,15 +4,15 @@ import net.corda.contracts.Commodity import net.corda.contracts.NetType import net.corda.contracts.asset.Obligation.Lifecycle import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyState import net.corda.core.crypto.SecureHash -import net.corda.core.hours import net.corda.core.crypto.testing.NULL_PARTY +import net.corda.core.hours import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty +import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.* import net.corda.testing.* +import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices import org.junit.Test import java.time.Duration @@ -28,9 +28,9 @@ class ObligationTests { val defaultRef = OpaqueBytes.of(1) val defaultIssuer = MEGA_CORP.ref(defaultRef) val oneMillionDollars = 1000000.DOLLARS `issued by` defaultIssuer - val trustedCashContract = nonEmptySetOf(SecureHash.randomSHA256() as SecureHash) - val megaIssuedDollars = nonEmptySetOf(Issued(defaultIssuer, USD)) - val megaIssuedPounds = nonEmptySetOf(Issued(defaultIssuer, GBP)) + val trustedCashContract = NonEmptySet.of(SecureHash.randomSHA256() as SecureHash) + val megaIssuedDollars = NonEmptySet.of(Issued(defaultIssuer, USD)) + val megaIssuedPounds = NonEmptySet.of(Issued(defaultIssuer, GBP)) val fivePm: Instant = TEST_TX_TIME.truncatedTo(ChronoUnit.DAYS) + 17.hours val sixPm: Instant = fivePm + 1.hours val megaCorpDollarSettlement = Obligation.Terms(trustedCashContract, megaIssuedDollars, fivePm) @@ -500,7 +500,7 @@ class ObligationTests { fun `commodity settlement`() { val defaultFcoj = Issued(defaultIssuer, Commodity.getInstance("FCOJ")!!) val oneUnitFcoj = Amount(1, defaultFcoj) - val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME) + val obligationDef = Obligation.Terms(NonEmptySet.of(CommodityContract().legalContractReference), NonEmptySet.of(defaultFcoj), TEST_TX_TIME) val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE, obligationDef, oneUnitFcoj.quantity, NULL_PARTY) // Try settling a simple commodity obligation @@ -755,10 +755,10 @@ class ObligationTests { // States must not be nettable if the cash contract differs assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState, - fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.randomSHA256()))).bilateralNetState) + fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = NonEmptySet.of(SecureHash.randomSHA256()))).bilateralNetState) // States must not be nettable if the trusted issuers differ - val miniCorpIssuer = nonEmptySetOf(Issued(MINI_CORP.ref(1), USD)) + val miniCorpIssuer = NonEmptySet.of(Issued(MINI_CORP.ref(1), USD)) assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState, fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableIssuedProducts = miniCorpIssuer)).bilateralNetState) } @@ -875,7 +875,7 @@ class ObligationTests { } val Issued.OBLIGATION_DEF: Obligation.Terms - get() = Obligation.Terms(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(this), TEST_TX_TIME) + get() = Obligation.Terms(NonEmptySet.of(Cash().legalContractReference), NonEmptySet.of(this), TEST_TX_TIME) val Amount>.OBLIGATION: Obligation.State get() = Obligation.State(Obligation.Lifecycle.NORMAL, DUMMY_OBLIGATION_ISSUER, token.OBLIGATION_DEF, quantity, NULL_PARTY) } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index 5036db9077..4aeb1a4e24 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -2,12 +2,12 @@ package net.corda.services.messaging import com.google.common.util.concurrent.ListenableFuture import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.cert +import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow import net.corda.core.node.NodeInfo -import net.corda.core.crypto.random63BitValue import net.corda.core.seconds +import net.corda.core.utilities.NonEmptySet import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.messaging.sendRequest @@ -30,7 +30,7 @@ class P2PSecurityTest : NodeBasedTest() { @Test fun `incorrect legal name for the network map service config`() { - val incorrectNetworkMapName = X509Utilities.getDevX509Name("NetworkMap-${random63BitValue()}") + val incorrectNetworkMapName = getTestX509Name("NetworkMap-${random63BitValue()}") val node = startNode(BOB.name, configOverrides = mapOf( "networkMapService" to mapOf( "address" to networkMapNode.configuration.p2pAddress.toString(), @@ -67,7 +67,7 @@ class P2PSecurityTest : NodeBasedTest() { private fun SimpleNode.registerWithNetworkMap(registrationName: X500Name): ListenableFuture { val legalIdentity = getTestPartyAndCertificate(registrationName, identity.public) - val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), legalIdentity, setOf(legalIdentity), 1) + val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), legalIdentity, NonEmptySet.of(legalIdentity), 1) val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX) val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress) return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index b05b0d6c33..c3c12f93a9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -26,6 +26,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug +import net.corda.core.utilities.toNonEmptySet import net.corda.flows.* import net.corda.node.services.* import net.corda.node.services.api.* @@ -495,7 +496,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, private fun makeInfo(): NodeInfo { val advertisedServiceEntries = makeServiceEntries() val legalIdentity = obtainLegalIdentity() - val allIdentitiesSet = advertisedServiceEntries.map { it.identity }.toSet() + legalIdentity + val allIdentitiesSet = (advertisedServiceEntries.map { it.identity } + legalIdentity).toNonEmptySet() val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. return NodeInfo(addresses, legalIdentity, allIdentitiesSet, platformVersion, advertisedServiceEntries, findMyLocation()) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 2ba51cbf06..cd049ace71 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -33,10 +33,7 @@ import net.corda.core.serialization.storageKryo import net.corda.core.tee import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toHexString -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.vault.schemas.requery.* @@ -261,44 +258,42 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P } @Throws(StatesNotAvailableException::class) - override fun softLockReserve(lockId: UUID, stateRefs: Set) { - if (stateRefs.isNotEmpty()) { - val softLockTimestamp = services.clock.instant() - val stateRefArgs = stateRefArgs(stateRefs) - try { - session.withTransaction(TransactionIsolation.REPEATABLE_READ) { - val updatedRows = update(VaultStatesEntity::class) - .set(VaultStatesEntity.LOCK_ID, lockId.toString()) - .set(VaultStatesEntity.LOCK_UPDATE_TIME, softLockTimestamp) - .where(VaultStatesEntity.STATE_STATUS eq Vault.StateStatus.UNCONSUMED) - .and((VaultStatesEntity.LOCK_ID eq lockId.toString()) or (VaultStatesEntity.LOCK_ID.isNull())) + override fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet) { + val softLockTimestamp = services.clock.instant() + val stateRefArgs = stateRefArgs(stateRefs) + try { + session.withTransaction(TransactionIsolation.REPEATABLE_READ) { + val updatedRows = update(VaultStatesEntity::class) + .set(VaultStatesEntity.LOCK_ID, lockId.toString()) + .set(VaultStatesEntity.LOCK_UPDATE_TIME, softLockTimestamp) + .where(VaultStatesEntity.STATE_STATUS eq Vault.StateStatus.UNCONSUMED) + .and((VaultStatesEntity.LOCK_ID eq lockId.toString()) or (VaultStatesEntity.LOCK_ID.isNull())) + .and(stateRefCompositeColumn.`in`(stateRefArgs)).get().value() + if (updatedRows > 0 && updatedRows == stateRefs.size) { + log.trace("Reserving soft lock states for $lockId: $stateRefs") + FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true + } else { + // revert partial soft locks + val revertUpdatedRows = update(VaultStatesEntity::class) + .set(VaultStatesEntity.LOCK_ID, null) + .where(VaultStatesEntity.LOCK_UPDATE_TIME eq softLockTimestamp) + .and(VaultStatesEntity.LOCK_ID eq lockId.toString()) .and(stateRefCompositeColumn.`in`(stateRefArgs)).get().value() - if (updatedRows > 0 && updatedRows == stateRefs.size) { - log.trace("Reserving soft lock states for $lockId: $stateRefs") - FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true - } else { - // revert partial soft locks - val revertUpdatedRows = update(VaultStatesEntity::class) - .set(VaultStatesEntity.LOCK_ID, null) - .where(VaultStatesEntity.LOCK_UPDATE_TIME eq softLockTimestamp) - .and(VaultStatesEntity.LOCK_ID eq lockId.toString()) - .and(stateRefCompositeColumn.`in`(stateRefArgs)).get().value() - if (revertUpdatedRows > 0) { - log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId") - } - throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available") + if (revertUpdatedRows > 0) { + log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId") } + throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available") } - } catch (e: PersistenceException) { - log.error("""soft lock update error attempting to reserve states for $lockId and $stateRefs") + } + } catch (e: PersistenceException) { + log.error("""soft lock update error attempting to reserve states for $lockId and $stateRefs") $e. """) - if (e.cause is StatesNotAvailableException) throw (e.cause as StatesNotAvailableException) - } + if (e.cause is StatesNotAvailableException) throw (e.cause as StatesNotAvailableException) } } - override fun softLockRelease(lockId: UUID, stateRefs: Set?) { + override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { if (stateRefs == null) { session.withTransaction(TransactionIsolation.REPEATABLE_READ) { val update = update(VaultStatesEntity::class) @@ -310,7 +305,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P log.trace("Releasing ${update.value()} soft locked states for $lockId") } } - } else if (stateRefs.isNotEmpty()) { + } else { try { session.withTransaction(TransactionIsolation.REPEATABLE_READ) { val updatedRows = update(VaultStatesEntity::class) @@ -398,7 +393,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs") // update database - softLockReserve(lockId, stateAndRefs.map { it.ref }.toSet()) + softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet()) return stateAndRefs } log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}") diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 475c4f900a..2d063a1468 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -4,7 +4,9 @@ import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.node.services.VaultService +import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.trace import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager @@ -36,18 +38,18 @@ class VaultSoftLockManager(val vault: VaultService, smm: StateMachineManager) { // However, the lock can be programmatically released, like any other soft lock, // should we want a long running flow that creates a visible state mid way through. - vault.rawUpdates.subscribe { update -> - update.flowId?.let { - if (update.produced.isNotEmpty()) { - registerSoftLocks(update.flowId as UUID, update.produced.map { it.ref }) + vault.rawUpdates.subscribe { (_, produced, flowId) -> + flowId?.let { + if (produced.isNotEmpty()) { + registerSoftLocks(flowId, (produced.map { it.ref }).toNonEmptySet()) } } } } - private fun registerSoftLocks(flowId: UUID, stateRefs: List) { + private fun registerSoftLocks(flowId: UUID, stateRefs: NonEmptySet) { log.trace("Reserving soft locks for flow id $flowId and states $stateRefs") - vault.softLockReserve(flowId, stateRefs.toSet()) + vault.softLockReserve(flowId, stateRefs) } private fun unregisterSoftLocks(id: StateMachineRunId, logic: FlowLogic<*>) { diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 049e44a463..063de256dc 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -26,7 +26,7 @@ import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction -import net.corda.testing.LogHelper +import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap import net.corda.flows.TwoPartyTradeFlow.Buyer import net.corda.flows.TwoPartyTradeFlow.Seller @@ -155,7 +155,10 @@ class TwoPartyTradeFlowTests { val cashLockId = UUID.randomUUID() bobNode.database.transaction { // lock the cash states with an arbitrary lockId (to prevent the Buyer flow from claiming the states) - bobNode.services.vaultService.softLockReserve(cashLockId, cashStates.states.map { it.ref }.toSet()) + val refs = cashStates.states.map { it.ref } + if (refs.isNotEmpty()) { + bobNode.services.vaultService.softLockReserve(cashLockId, refs.toNonEmptySet()) + } } val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 2e100edf28..c69fc61f36 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -11,7 +11,9 @@ import net.corda.core.node.services.VaultService import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.toNonEmptySet import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.transaction import net.corda.testing.* @@ -116,7 +118,7 @@ class NodeVaultServiceTest { val unconsumedStates = vaultSvc.unconsumedStates().toList() assertThat(unconsumedStates).hasSize(3) - val stateRefsToSoftLock = setOf(unconsumedStates[1].ref, unconsumedStates[2].ref) + val stateRefsToSoftLock = NonEmptySet.of(unconsumedStates[1].ref, unconsumedStates[2].ref) // soft lock two of the three states val softLockId = UUID.randomUUID() @@ -132,7 +134,7 @@ class NodeVaultServiceTest { assertThat(unlockedStates1).hasSize(1) // soft lock release one of the states explicitly - vaultSvc.softLockRelease(softLockId, setOf(unconsumedStates[1].ref)) + vaultSvc.softLockRelease(softLockId, NonEmptySet.of(unconsumedStates[1].ref)) val unlockedStates2 = vaultSvc.unconsumedStates(includeSoftLockedStates = false).toList() assertThat(unlockedStates2).hasSize(2) @@ -160,7 +162,7 @@ class NodeVaultServiceTest { assertNull(vaultSvc.cashBalances[USD]) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L)) } - val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet() + val stateRefsToSoftLock = (vaultStates.states.map { it.ref }).toNonEmptySet() println("State Refs:: $stateRefsToSoftLock") // 1st tx locks states @@ -216,19 +218,19 @@ class NodeVaultServiceTest { assertNull(vaultSvc.cashBalances[USD]) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L)) } - val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet() + val stateRefsToSoftLock = vaultStates.states.map { it.ref } println("State Refs:: $stateRefsToSoftLock") // lock 1st state with LockId1 database.transaction { - vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first())) + vaultSvc.softLockReserve(softLockId1, NonEmptySet.of(stateRefsToSoftLock.first())) assertThat(vaultSvc.softLockedStates(softLockId1)).hasSize(1) } // attempt to lock all 3 states with LockId2 database.transaction { assertThatExceptionOfType(StatesNotAvailableException::class.java).isThrownBy( - { vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock) } + { vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock.toNonEmptySet()) } ).withMessageContaining("only 2 rows available").withNoCause() } } @@ -243,7 +245,7 @@ class NodeVaultServiceTest { assertNull(vaultSvc.cashBalances[USD]) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L)) } - val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet() + val stateRefsToSoftLock = (vaultStates.states.map { it.ref }).toNonEmptySet() println("State Refs:: $stateRefsToSoftLock") // lock states with LockId1 @@ -269,18 +271,18 @@ class NodeVaultServiceTest { assertNull(vaultSvc.cashBalances[USD]) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L)) } - val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet() + val stateRefsToSoftLock = vaultStates.states.map { it.ref } println("State Refs:: $stateRefsToSoftLock") // lock states with LockId1 database.transaction { - vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first())) + vaultSvc.softLockReserve(softLockId1, NonEmptySet.of(stateRefsToSoftLock.first())) assertThat(vaultSvc.softLockedStates(softLockId1)).hasSize(1) } // attempt to lock all states with LockId1 (including previously already locked one) database.transaction { - vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock) + vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock.toNonEmptySet()) assertThat(vaultSvc.softLockedStates(softLockId1)).hasSize(3) } } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 587e95081a..7ac25f718e 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -16,6 +16,7 @@ import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.toHexString import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.schema.NodeSchemaService @@ -470,7 +471,7 @@ class VaultQueryTests { database.transaction { val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L)) - vaultSvc.softLockReserve(UUID.randomUUID(), setOf(issuedStates.states.first().ref, issuedStates.states.last().ref)) + vaultSvc.softLockReserve(UUID.randomUUID(), NonEmptySet.of(issuedStates.states.first().ref, issuedStates.states.last().ref)) val criteria = VaultQueryCriteria(includeSoftlockedStates = false) val results = vaultQuerySvc.queryBy(criteria) diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index a9b26e75bb..ae80cc56da 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,6 +7,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.NonEmptySet import net.corda.node.services.network.InMemoryNetworkMapCache import net.corda.testing.getTestPartyAndCertificate import net.corda.testing.getTestX509Name @@ -28,8 +29,8 @@ class MockNetworkMapCache(serviceHub: ServiceHub) : InMemoryNetworkMapCache(serv override val changed: Observable = PublishSubject.create() init { - val mockNodeA = NodeInfo(listOf(BANK_C_ADDR), BANK_C, setOf(BANK_C), 1) - val mockNodeB = NodeInfo(listOf(BANK_D_ADDR), BANK_D, setOf(BANK_D), 1) + val mockNodeA = NodeInfo(listOf(BANK_C_ADDR), BANK_C, NonEmptySet.of(BANK_C), 1) + val mockNodeB = NodeInfo(listOf(BANK_D_ADDR), BANK_D, NonEmptySet.of(BANK_D), 1) registeredNodes[mockNodeA.legalIdentity.owningKey] = mockNodeA registeredNodes[mockNodeB.legalIdentity.owningKey] = mockNodeB runWithoutMapService() diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt index 9e1fba3192..dd6a1caffa 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -11,6 +11,7 @@ import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.NonEmptySet import net.corda.flows.AnonymisedIdentity import net.corda.node.VersionInfo import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage @@ -75,7 +76,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub { override val clock: Clock get() = Clock.systemUTC() override val myInfo: NodeInfo get() { val identity = getTestPartyAndCertificate(MEGA_CORP.name, key.public) - return NodeInfo(emptyList(), identity, setOf(identity), 1) + return NodeInfo(emptyList(), identity, NonEmptySet.of(identity), 1) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) From e93cdf29f8c152e2b56eddd90d7f90a2430cdb25 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 12 Jul 2017 12:19:36 +0100 Subject: [PATCH 003/197] Moved the core flows into net.corda.core.flows --- .../flows/AbstractStateReplacementFlow.kt | 10 +++------ .../flows/BroadcastTransactionFlow.kt | 4 +--- .../{ => core}/flows/CollectSignaturesFlow.kt | 8 +++---- .../{ => core}/flows/ContractUpgradeFlow.kt | 6 +---- .../{ => core}/flows/FetchAttachmentsFlow.kt | 3 +-- .../corda/{ => core}/flows/FetchDataFlow.kt | 8 +++---- .../{ => core}/flows/FetchTransactionsFlow.kt | 3 +-- .../corda/{ => core}/flows/FinalityFlow.kt | 3 +-- .../{ => core}/flows/NotaryChangeFlow.kt | 5 ++--- .../net/corda/{ => core}/flows/NotaryFlow.kt | 7 ++---- .../flows/ResolveTransactionsFlow.kt | 3 +-- .../{ => core}/flows/TransactionKeyFlow.kt | 6 ++--- .../corda/core/node/services/NotaryService.kt | 4 ++-- .../core/flows/CollectSignaturesFlowTests.kt | 5 +---- .../core/flows/ContractUpgradeFlowTest.kt | 8 +++---- .../core/flows/ResolveTransactionsFlowTest.kt | 1 - .../flows/TransactionKeyFlowTests.kt | 3 ++- .../AttachmentSerializationTest.kt | 2 +- docs/source/changelog.rst | 3 +++ .../java/net/corda/docs/FlowCookbookJava.java | 8 ++----- .../net/corda/docs/CustomNotaryTutorial.kt | 5 ++--- .../kotlin/net/corda/docs/FlowCookbook.kt | 10 +++------ .../corda/docs/FxTransactionBuildTutorial.kt | 7 +----- .../docs/WorkflowTransactionBuildTutorial.kt | 2 +- docs/source/hello-world-flow.rst | 2 +- .../net/corda/flows/AbstractCashFlow.kt | 4 +++- .../kotlin/net/corda/flows/CashIssueFlow.kt | 6 +++-- .../kotlin/net/corda/flows/CashPaymentFlow.kt | 1 + .../net/corda/flows/TwoPartyDealFlow.kt | 13 +++++------ .../net/corda/flows/TwoPartyTradeFlow.kt | 3 +-- .../AbstractStateReplacementFlowTest.java | 2 +- .../flows/BroadcastTransactionFlowTest.kt | 2 +- .../node/services/BFTNotaryServiceTests.kt | 10 ++++----- .../node/services/RaftNotaryServiceTests.kt | 10 ++++----- .../corda/node/services/CoreFlowHandlers.kt | 3 +-- .../BFTNonValidatingNotaryService.kt | 2 +- .../node/services/transactions/BFTSMaRt.kt | 4 ++-- .../transactions/NonValidatingNotaryFlow.kt | 4 ++-- .../RaftNonValidatingNotaryService.kt | 4 ++-- .../RaftValidatingNotaryService.kt | 4 ++-- .../transactions/SimpleNotaryService.kt | 4 ++-- .../transactions/ValidatingNotaryFlow.kt | 2 +- .../transactions/ValidatingNotaryService.kt | 4 ++-- .../corda/node/messaging/AttachmentTests.kt | 4 ++-- .../corda/node/services/NotaryChangeTests.kt | 4 ++-- .../services/events/ScheduledFlowTests.kt | 10 +++------ .../persistence/DataVendingServiceTests.kt | 2 +- .../statemachine/FlowFrameworkTests.kt | 22 +++++++------------ .../transactions/NotaryServiceTests.kt | 8 +++---- .../ValidatingNotaryServiceTests.kt | 8 +++---- .../corda/attachmentdemo/AttachmentDemo.kt | 6 +++-- .../kotlin/net/corda/notarydemo/Notarise.kt | 2 +- .../flows/RPCStartableNotaryFlowClient.kt | 2 +- .../kotlin/net/corda/vega/flows/SimmFlow.kt | 4 ++-- .../net/corda/vega/flows/StateRevisionFlow.kt | 7 ++---- .../net/corda/traderdemo/flow/SellerFlow.kt | 4 +--- .../testing/contracts/DummyContractV2.kt | 2 +- .../net/corda/loadtest/tests/NotaryTest.kt | 2 +- 58 files changed, 122 insertions(+), 173 deletions(-) rename core/src/main/kotlin/net/corda/{ => core}/flows/AbstractStateReplacementFlow.kt (96%) rename core/src/main/kotlin/net/corda/{ => core}/flows/BroadcastTransactionFlow.kt (93%) rename core/src/main/kotlin/net/corda/{ => core}/flows/CollectSignaturesFlow.kt (98%) rename core/src/main/kotlin/net/corda/{ => core}/flows/ContractUpgradeFlow.kt (94%) rename core/src/main/kotlin/net/corda/{ => core}/flows/FetchAttachmentsFlow.kt (95%) rename core/src/main/kotlin/net/corda/{ => core}/flows/FetchDataFlow.kt (94%) rename core/src/main/kotlin/net/corda/{ => core}/flows/FetchTransactionsFlow.kt (93%) rename core/src/main/kotlin/net/corda/{ => core}/flows/FinalityFlow.kt (99%) rename core/src/main/kotlin/net/corda/{ => core}/flows/NotaryChangeFlow.kt (96%) rename core/src/main/kotlin/net/corda/{ => core}/flows/NotaryFlow.kt (96%) rename core/src/main/kotlin/net/corda/{ => core}/flows/ResolveTransactionsFlow.kt (99%) rename core/src/main/kotlin/net/corda/{ => core}/flows/TransactionKeyFlow.kt (93%) rename core/src/test/kotlin/net/corda/{ => core}/flows/TransactionKeyFlowTests.kt (97%) diff --git a/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt similarity index 96% rename from core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt index a035bd256f..b34ab2a65b 100644 --- a/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt @@ -1,4 +1,4 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.ContractState @@ -6,10 +6,6 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.isFulfilledBy -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction @@ -56,7 +52,7 @@ abstract class AbstractStateReplacementFlow { abstract class Instigator( val originalState: StateAndRef, val modification: M, - override val progressTracker: ProgressTracker = tracker()) : FlowLogic>() { + override val progressTracker: ProgressTracker = Instigator.tracker()) : FlowLogic>() { companion object { object SIGNING : ProgressTracker.Step("Requesting signatures from other parties") object NOTARY : ProgressTracker.Step("Requesting notary signature") @@ -133,7 +129,7 @@ abstract class AbstractStateReplacementFlow { // Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964). // We use Void? instead of Unit? as that's what you'd use in Java. abstract class Acceptor(val otherSide: Party, - override val progressTracker: ProgressTracker = tracker()) : FlowLogic() { + override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic() { companion object { object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal") object APPROVING : ProgressTracker.Step("State replacement approved") diff --git a/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt similarity index 93% rename from core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt index 93ed85fc08..9227741230 100644 --- a/core/src/main/kotlin/net/corda/flows/BroadcastTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt @@ -1,8 +1,6 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction diff --git a/core/src/main/kotlin/net/corda/flows/CollectSignaturesFlow.kt b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt similarity index 98% rename from core/src/main/kotlin/net/corda/flows/CollectSignaturesFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt index 5bd80d9f98..1e49baed1c 100644 --- a/core/src/main/kotlin/net/corda/flows/CollectSignaturesFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt @@ -1,11 +1,9 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.toBase58String -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction @@ -62,7 +60,7 @@ import java.security.PublicKey // TODO: AbstractStateReplacementFlow needs updating to use this flow. // TODO: Update this flow to handle randomly generated keys when that works is complete. class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction, - override val progressTracker: ProgressTracker = tracker()): FlowLogic() { + override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()): FlowLogic() { companion object { object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.") @@ -175,7 +173,7 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction, * @param otherParty The counter-party which is providing you a transaction to sign. */ abstract class SignTransactionFlow(val otherParty: Party, - override val progressTracker: ProgressTracker = tracker()) : FlowLogic() { + override val progressTracker: ProgressTracker = SignTransactionFlow.tracker()) : FlowLogic() { companion object { object RECEIVING : ProgressTracker.Step("Receiving transaction proposal for signing.") diff --git a/core/src/main/kotlin/net/corda/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt similarity index 94% rename from core/src/main/kotlin/net/corda/flows/ContractUpgradeFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index ce785ed6f2..bc1ced6793 100644 --- a/core/src/main/kotlin/net/corda/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -1,10 +1,6 @@ -package net.corda.flows +package net.corda.core.flows import net.corda.core.contracts.* -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC -import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import java.security.PublicKey diff --git a/core/src/main/kotlin/net/corda/flows/FetchAttachmentsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt similarity index 95% rename from core/src/main/kotlin/net/corda/flows/FetchAttachmentsFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt index 805e25da14..1eaeff6e56 100644 --- a/core/src/main/kotlin/net/corda/flows/FetchAttachmentsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt @@ -1,10 +1,9 @@ -package net.corda.flows +package net.corda.core.flows import net.corda.core.contracts.AbstractAttachment import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.serialization.SerializationToken import net.corda.core.serialization.SerializeAsToken diff --git a/core/src/main/kotlin/net/corda/flows/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt similarity index 94% rename from core/src/main/kotlin/net/corda/flows/FetchDataFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt index efbe720210..28c5fdceb1 100644 --- a/core/src/main/kotlin/net/corda/flows/FetchDataFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt @@ -1,16 +1,14 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.NamedByHash import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch +import net.corda.core.flows.FetchDataFlow.HashNotFound import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap -import net.corda.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch -import net.corda.flows.FetchDataFlow.HashNotFound import java.util.* /** diff --git a/core/src/main/kotlin/net/corda/flows/FetchTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt similarity index 93% rename from core/src/main/kotlin/net/corda/flows/FetchTransactionsFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt index 0f99aad169..e39e16aa7c 100644 --- a/core/src/main/kotlin/net/corda/flows/FetchTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt @@ -1,7 +1,6 @@ -package net.corda.flows +package net.corda.core.flows import net.corda.core.crypto.SecureHash -import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction diff --git a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt similarity index 99% rename from core/src/main/kotlin/net/corda/flows/FinalityFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index cb76ab342f..650ba3e25d 100644 --- a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -1,11 +1,10 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.isFulfilledBy -import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.transactions.LedgerTransaction diff --git a/core/src/main/kotlin/net/corda/flows/NotaryChangeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt similarity index 96% rename from core/src/main/kotlin/net/corda/flows/NotaryChangeFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt index ee5453d167..f0edfa69d2 100644 --- a/core/src/main/kotlin/net/corda/flows/NotaryChangeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt @@ -1,7 +1,6 @@ -package net.corda.flows +package net.corda.core.flows import net.corda.core.contracts.* -import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder @@ -20,7 +19,7 @@ import net.corda.core.utilities.ProgressTracker class NotaryChangeFlow( originalState: StateAndRef, newNotary: Party, - progressTracker: ProgressTracker = tracker()) + progressTracker: ProgressTracker = AbstractStateReplacementFlow.Instigator.tracker()) : AbstractStateReplacementFlow.Instigator(originalState, newNotary, progressTracker) { override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { diff --git a/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt similarity index 96% rename from core/src/main/kotlin/net/corda/flows/NotaryFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 579f9c8125..a13c3ad650 100644 --- a/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -1,4 +1,4 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.StateRef @@ -7,9 +7,6 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.crypto.keys -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.node.services.* import net.corda.core.serialization.CordaSerializable @@ -32,7 +29,7 @@ object NotaryFlow { @InitiatingFlow open class Client(private val stx: SignedTransaction, override val progressTracker: ProgressTracker) : FlowLogic>() { - constructor(stx: SignedTransaction) : this(stx, Client.tracker()) + constructor(stx: SignedTransaction) : this(stx, tracker()) companion object { object REQUESTING : ProgressTracker.Step("Requesting signature by Notary service") diff --git a/core/src/main/kotlin/net/corda/flows/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt similarity index 99% rename from core/src/main/kotlin/net/corda/flows/ResolveTransactionsFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt index 92f3b9ebd0..c1b2045476 100644 --- a/core/src/main/kotlin/net/corda/flows/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt @@ -1,9 +1,8 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.checkedAdd import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowLogic import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable diff --git a/core/src/main/kotlin/net/corda/flows/TransactionKeyFlow.kt b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt similarity index 93% rename from core/src/main/kotlin/net/corda/flows/TransactionKeyFlow.kt rename to core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt index 1989ef5d82..b1567f0557 100644 --- a/core/src/main/kotlin/net/corda/flows/TransactionKeyFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt @@ -1,12 +1,10 @@ -package net.corda.flows +package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap +import net.corda.flows.AnonymisedIdentity /** * Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction. diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index aa3def742c..81ac8ff8e8 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -6,13 +6,13 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.flows.FlowLogic +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException import org.slf4j.Logger abstract class NotaryService : SingletonSerializeAsToken() { diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 8bccd7666c..f62050a35a 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -4,15 +4,12 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Command import net.corda.core.contracts.TransactionType import net.corda.core.contracts.requireThat -import net.corda.testing.contracts.DummyContract import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.CollectSignaturesFlow -import net.corda.flows.FinalityFlow -import net.corda.flows.SignTransactionFlow import net.corda.testing.MINI_CORP_KEY +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockServices import org.junit.After diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 1ee8601e34..169ca03947 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -3,8 +3,6 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.asset.Cash import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyContract -import net.corda.testing.contracts.DummyContractV2 import net.corda.core.crypto.SecureHash import net.corda.core.getOrThrow import net.corda.core.identity.AbstractParty @@ -12,17 +10,17 @@ import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.node.services.unconsumedStates -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.Emoji +import net.corda.core.utilities.OpaqueBytes import net.corda.flows.CashIssueFlow -import net.corda.flows.ContractUpgradeFlow -import net.corda.flows.FinalityFlow import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.services.startFlowPermission import net.corda.node.utilities.transaction import net.corda.nodeapi.User import net.corda.testing.RPCDriverExposedDSLInterface +import net.corda.testing.contracts.DummyContract +import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.node.MockNetwork import net.corda.testing.rpcDriver import net.corda.testing.rpcTestUser diff --git a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt index 341f9b0d5d..f0489562ec 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt @@ -7,7 +7,6 @@ import net.corda.core.identity.Party import net.corda.core.utilities.opaque import net.corda.core.transactions.SignedTransaction import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.flows.ResolveTransactionsFlow import net.corda.node.utilities.transaction import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_KEY diff --git a/core/src/test/kotlin/net/corda/flows/TransactionKeyFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt similarity index 97% rename from core/src/test/kotlin/net/corda/flows/TransactionKeyFlowTests.kt rename to core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt index 486e24b3e8..4bd6152651 100644 --- a/core/src/test/kotlin/net/corda/flows/TransactionKeyFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt @@ -1,8 +1,9 @@ -package net.corda.flows +package net.corda.core.flows import net.corda.core.getOrThrow import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.flows.AnonymisedIdentity import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index f1259a4f73..964a81a4b4 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -11,7 +11,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.unwrap -import net.corda.flows.FetchAttachmentsFlow +import net.corda.core.flows.FetchAttachmentsFlow import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.NetworkMapService diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7e4eb64739..97270867b4 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -31,6 +31,9 @@ UNRELEASED * Added additional common Sort attributes (see ``Sort.CommandStateAttribute``) for use in Vault Query criteria to include STATE_REF, STATE_REF_TXN_ID, STATE_REF_INDEX +* Moved the core flows previously found in ``net.corda.flows`` into ``net.corda.core.flows``. This is so that all packages + in the ``core`` module begin with ``net.corda.core``. + Milestone 13 ------------ diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index cf188f4e2c..fec84eb069 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -7,8 +7,6 @@ import net.corda.contracts.asset.Cash; import net.corda.core.contracts.*; import net.corda.core.contracts.TransactionType.General; import net.corda.core.contracts.TransactionType.NotaryChange; -import net.corda.testing.contracts.DummyContract; -import net.corda.testing.contracts.DummyState; import net.corda.core.crypto.DigitalSignature; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; @@ -24,10 +22,8 @@ import net.corda.core.transactions.WireTransaction; import net.corda.core.utilities.ProgressTracker; import net.corda.core.utilities.ProgressTracker.Step; import net.corda.core.utilities.UntrustworthyData; -import net.corda.flows.CollectSignaturesFlow; -import net.corda.flows.FinalityFlow; -import net.corda.flows.ResolveTransactionsFlow; -import net.corda.flows.SignTransactionFlow; +import net.corda.testing.contracts.DummyContract; +import net.corda.testing.contracts.DummyState; import org.bouncycastle.asn1.x500.X500Name; import java.security.PublicKey; diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt index f5ec3116a4..eacbfe96ff 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt @@ -1,8 +1,8 @@ -package net.corda.notarydemo +package net.corda.docs import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TransactionVerificationException -import net.corda.core.flows.FlowLogic +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.PluginServiceHub import net.corda.core.node.services.CordaService @@ -12,7 +12,6 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.* import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.ValidatingNotaryService import java.security.SignatureException diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 6414d50f9b..a5a0150b22 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -5,8 +5,6 @@ import net.corda.contracts.asset.Cash import net.corda.core.contracts.* import net.corda.core.contracts.TransactionType.General import net.corda.core.contracts.TransactionType.NotaryChange -import net.corda.testing.contracts.DummyContract -import net.corda.testing.contracts.DummyState import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.flows.* @@ -19,15 +17,13 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction -import net.corda.testing.DUMMY_PUBKEY_1 import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap -import net.corda.flows.CollectSignaturesFlow -import net.corda.flows.FinalityFlow -import net.corda.flows.ResolveTransactionsFlow -import net.corda.flows.SignTransactionFlow +import net.corda.testing.DUMMY_PUBKEY_1 +import net.corda.testing.contracts.DummyContract +import net.corda.testing.contracts.DummyState import org.bouncycastle.asn1.x500.X500Name import java.security.PublicKey import java.time.Duration diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt index ce6ea008c6..b684d9e2eb 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt @@ -8,20 +8,15 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.TransactionType import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.Vault import net.corda.core.node.services.queryBy -import net.corda.core.node.services.unconsumedStates import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.FinalityFlow -import net.corda.flows.ResolveTransactionsFlow import java.util.* @CordaSerializable diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index 7e66e01fd7..ffff6d14cb 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.containsAny +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow @@ -15,7 +16,6 @@ import net.corda.core.node.services.linearHeadsOfType import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.FinalityFlow import java.security.PublicKey import java.time.Duration diff --git a/docs/source/hello-world-flow.rst b/docs/source/hello-world-flow.rst index 3416be215d..b23a61b30d 100644 --- a/docs/source/hello-world-flow.rst +++ b/docs/source/hello-world-flow.rst @@ -50,7 +50,7 @@ the following: import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker - import net.corda.flows.FinalityFlow + import net.corda.core.flows.FinalityFlow @InitiatingFlow @StartableByRPC diff --git a/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt b/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt index 52bef42f21..fd809ab7e5 100644 --- a/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/AbstractCashFlow.kt @@ -1,8 +1,10 @@ package net.corda.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic +import net.corda.core.flows.NotaryException import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable @@ -12,7 +14,7 @@ import net.corda.core.utilities.ProgressTracker /** * Initiates a flow that produces an Issue/Move or Exit Cash transaction. */ -abstract class AbstractCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { +abstract class AbstractCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { companion object { object GENERATING_ID : ProgressTracker.Step("Generating anonymous identities") object GENERATING_TX : ProgressTracker.Step("Generating transaction") diff --git a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt index ec9659f1d5..0bcaa5fed8 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt @@ -5,10 +5,12 @@ import net.corda.contracts.asset.Cash import net.corda.core.contracts.Amount import net.corda.core.contracts.TransactionType import net.corda.core.contracts.issuedBy +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.TransactionKeyFlow import net.corda.core.identity.Party -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import java.util.* @@ -45,7 +47,7 @@ class CashIssueFlow(val amount: Amount, } else { emptyMap() } - val anonymousRecipient = txIdentities.get(recipient)?.identity ?: recipient + val anonymousRecipient = txIdentities[recipient]?.identity ?: recipient progressTracker.currentStep = GENERATING_TX val builder: TransactionBuilder = TransactionType.General.Builder(notary = notary) val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef) diff --git a/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt index 0567e2c77e..f7bc488b5c 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.InsufficientBalanceException import net.corda.core.contracts.TransactionType import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.TransactionKeyFlow import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt index 8509e55c9b..c421a58b57 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt @@ -4,7 +4,10 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.DealState import net.corda.core.contracts.requireThat import net.corda.core.crypto.SecureHash +import net.corda.core.flows.CollectSignaturesFlow +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic +import net.corda.core.flows.SignTransactionFlow import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo @@ -20,14 +23,10 @@ import java.security.PublicKey /** * Classes for manipulating a two party deal or agreement. - * - * TODO: The subclasses should probably be broken out into individual flows rather than making this an ever expanding collection of subclasses. - * - * TODO: Also, the term Deal is used here where we might prefer Agreement. - * - * TODO: Make this flow more generic. - * */ +// TODO: The subclasses should probably be broken out into individual flows rather than making this an ever expanding collection of subclasses. +// TODO: Also, the term Deal is used here where we might prefer Agreement. +// TODO: Make this flow more generic. object TwoPartyDealFlow { // This object is serialised to the network and is the first flow message the seller sends to the buyer. @CordaSerializable diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt index d384e1255e..7a529f7237 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt @@ -3,8 +3,7 @@ package net.corda.flows import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.asset.sumCashBy import net.corda.core.contracts.* -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic +import net.corda.core.flows.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party diff --git a/finance/src/test/java/net/corda/flows/AbstractStateReplacementFlowTest.java b/finance/src/test/java/net/corda/flows/AbstractStateReplacementFlowTest.java index 9c8d3086e3..4c11636d78 100644 --- a/finance/src/test/java/net/corda/flows/AbstractStateReplacementFlowTest.java +++ b/finance/src/test/java/net/corda/flows/AbstractStateReplacementFlowTest.java @@ -1,6 +1,6 @@ package net.corda.flows; -import net.corda.core.identity.Party; +import net.corda.core.flows.AbstractStateReplacementFlow; import net.corda.core.identity.Party; import net.corda.core.utilities.*; import org.jetbrains.annotations.*; diff --git a/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt index 49dc19751a..cc0ba55107 100644 --- a/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt @@ -7,9 +7,9 @@ import com.pholser.junit.quickcheck.generator.Generator import com.pholser.junit.quickcheck.random.SourceOfRandomness import com.pholser.junit.quickcheck.runner.JUnitQuickcheck import net.corda.contracts.testing.SignedTransactionGenerator +import net.corda.core.flows.BroadcastTransactionFlow.NotifyTxRequest import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest import org.junit.runner.RunWith import kotlin.test.assertEquals diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 7e7060cbc6..6d9e28218d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -4,18 +4,17 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType -import net.corda.testing.contracts.DummyContract -import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.composite.CompositeKey import net.corda.core.div +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException +import net.corda.core.flows.NotaryFlow import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.Try -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException -import net.corda.flows.NotaryFlow import net.corda.node.internal.AbstractNode import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.BFTNonValidatingNotaryService @@ -23,6 +22,7 @@ import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.node.utilities.transaction +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.bouncycastle.asn1.x500.X500Name import org.junit.After diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index 566c4674e1..b8d54616b5 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -4,16 +4,16 @@ import com.google.common.util.concurrent.Futures import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType -import net.corda.testing.contracts.DummyContract +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException +import net.corda.core.flows.NotaryFlow import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.map -import net.corda.testing.DUMMY_BANK_A -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException -import net.corda.flows.NotaryFlow import net.corda.node.internal.AbstractNode import net.corda.node.utilities.transaction +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.NodeBasedTest import org.bouncycastle.asn1.x500.X500Name import org.junit.Test diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index 3359d55e58..7853a3a04b 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -6,8 +6,7 @@ import net.corda.core.contracts.TransactionType import net.corda.core.contracts.UpgradedContract import net.corda.core.contracts.requireThat import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index a64cfaaa9d..0336132f50 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.SettableFuture import net.corda.core.crypto.DigitalSignature import net.corda.core.flows.FlowLogic +import net.corda.core.flows.NotaryException import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.node.services.NotaryService @@ -14,7 +15,6 @@ import net.corda.core.transactions.FilteredTransaction import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.core.utilities.unwrap -import net.corda.flows.NotaryException import net.corda.node.services.api.ServiceHubInternal import kotlin.concurrent.thread diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index f910c0ef1e..e7f4bca217 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -18,6 +18,8 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.crypto.sign +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException import net.corda.core.identity.Party import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider @@ -30,8 +32,6 @@ import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.transactions.BFTSMaRt.Client import net.corda.node.services.transactions.BFTSMaRt.Replica diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt index 354ef7799d..7538bb9bbd 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt @@ -1,12 +1,12 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.NotaryFlow +import net.corda.core.flows.TransactionParts import net.corda.core.identity.Party import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.transactions.FilteredTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.NotaryFlow -import net.corda.flows.TransactionParts class NonValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSide, service) { /** diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt index 05bcabe172..9c5d0b237e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt @@ -1,9 +1,9 @@ package net.corda.node.services.transactions +import net.corda.core.flows.NotaryFlow import net.corda.core.identity.Party -import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TimeWindowChecker -import net.corda.flows.NotaryFlow +import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal /** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt index deba64d1a3..498173e689 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt @@ -1,9 +1,9 @@ package net.corda.node.services.transactions +import net.corda.core.flows.NotaryFlow import net.corda.core.identity.Party -import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TimeWindowChecker -import net.corda.flows.NotaryFlow +import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal /** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt index c23d19532b..629ff1a5fd 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt @@ -1,10 +1,10 @@ package net.corda.node.services.transactions +import net.corda.core.flows.NotaryFlow import net.corda.core.identity.Party -import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.ServiceType import net.corda.core.node.services.TimeWindowChecker -import net.corda.flows.NotaryFlow +import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal /** A simple Notary service that does not perform transaction validation */ diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index dca4e5f5ad..afe9a81d99 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -2,12 +2,12 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TransactionVerificationException +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.* import java.security.SignatureException /** diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index c996a8979d..e85c15d2db 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -1,10 +1,10 @@ package net.corda.node.services.transactions +import net.corda.core.flows.NotaryFlow import net.corda.core.identity.Party -import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.ServiceType import net.corda.core.node.services.TimeWindowChecker -import net.corda.flows.NotaryFlow +import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal /** A Notary service that validates the transaction chain of the submitted transaction before committing it */ diff --git a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt index a9fb24ce5b..8085de959e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt @@ -6,8 +6,8 @@ import net.corda.core.crypto.sha256 import net.corda.core.getOrThrow import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.ServiceInfo -import net.corda.flows.FetchAttachmentsFlow -import net.corda.flows.FetchDataFlow +import net.corda.core.flows.FetchAttachmentsFlow +import net.corda.core.flows.FetchDataFlow import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.network.NetworkMapService diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 8aef7f445e..dedb3bd5c3 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -8,8 +8,8 @@ import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo import net.corda.core.seconds import net.corda.core.transactions.WireTransaction -import net.corda.flows.NotaryChangeFlow -import net.corda.flows.StateReplacementException +import net.corda.core.flows.NotaryChangeFlow +import net.corda.core.flows.StateReplacementException import net.corda.node.internal.AbstractNode import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.SimpleNotaryService diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 6fec905bae..23f32bee3d 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -2,22 +2,18 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.containsAny -import net.corda.core.flows.FlowInitiator -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowLogicRefFactory -import net.corda.core.flows.SchedulableFlow +import net.corda.core.flows.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.linearHeadsOfType -import net.corda.testing.DUMMY_NOTARY -import net.corda.flows.FinalityFlow import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.utilities.transaction +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Assert.assertTrue diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt index f912949506..2730cd9a63 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt @@ -6,13 +6,13 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.Issued import net.corda.core.contracts.TransactionType import net.corda.core.contracts.USD +import net.corda.core.flows.BroadcastTransactionFlow.NotifyTxRequest import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction -import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest import net.corda.node.services.NotifyTransactionHandler import net.corda.node.utilities.transaction import net.corda.testing.DUMMY_NOTARY diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index f83de31b69..88c298a34a 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -4,49 +4,43 @@ import co.paralleluniverse.fibers.Fiber import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture import net.corda.contracts.asset.Cash -import net.corda.core.* import net.corda.core.contracts.ContractState import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.StateAndRef -import net.corda.testing.contracts.DummyState import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.random63BitValue -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSessionException -import net.corda.core.flows.InitiatingFlow +import net.corda.core.flatMap +import net.corda.core.flows.* +import net.corda.core.getOrThrow import net.corda.core.identity.Party +import net.corda.core.map import net.corda.core.messaging.MessageRecipients import net.corda.core.node.services.PartyInfo import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.queryBy import net.corda.core.node.services.unconsumedStates -import net.corda.core.utilities.OpaqueBytes import net.corda.core.serialization.deserialize +import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.testing.LogHelper +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Change import net.corda.core.utilities.unwrap import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow -import net.corda.flows.FinalityFlow -import net.corda.flows.NotaryFlow import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.persistence.checkpoints import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.utilities.transaction -import net.corda.testing.expect -import net.corda.testing.expectEvents -import net.corda.testing.getTestX509Name +import net.corda.testing.* +import net.corda.testing.contracts.DummyState import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode -import net.corda.testing.sequence import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index c0da57821c..fbc80fd8db 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -4,18 +4,18 @@ import com.google.common.util.concurrent.ListenableFuture import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.DigitalSignature +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException +import net.corda.core.flows.NotaryFlow import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException -import net.corda.flows.NotaryFlow import net.corda.node.internal.AbstractNode import net.corda.node.services.network.NetworkMapService import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThat import org.junit.After diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 3072677dfd..f1ca383d4e 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -5,19 +5,19 @@ import net.corda.core.contracts.Command import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.DigitalSignature +import net.corda.core.flows.NotaryError +import net.corda.core.flows.NotaryException +import net.corda.core.flows.NotaryFlow import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.core.transactions.SignedTransaction -import net.corda.flows.NotaryError -import net.corda.flows.NotaryException -import net.corda.flows.NotaryFlow import net.corda.node.internal.AbstractNode import net.corda.node.services.issueInvalidState import net.corda.node.services.network.NetworkMapService import net.corda.testing.DUMMY_NOTARY import net.corda.testing.MEGA_CORP_KEY +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThat import org.junit.After diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 22f54033f3..7484959cfd 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.TransactionForContract import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.getOrThrow @@ -17,8 +18,9 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startTrackedFlow import net.corda.core.sizedInputStreamAndHash import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.* -import net.corda.flows.FinalityFlow +import net.corda.core.utilities.Emoji +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.ProgressTracker import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.poll diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt index 855fd1dd27..c0de6f27a2 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt @@ -11,9 +11,9 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.BOB import net.corda.notarydemo.flows.DummyIssueAndMove import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient +import net.corda.testing.BOB import kotlin.streams.asSequence fun main(args: Array) { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/RPCStartableNotaryFlowClient.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/RPCStartableNotaryFlowClient.kt index a3d16c4984..95eb184c09 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/RPCStartableNotaryFlowClient.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/flows/RPCStartableNotaryFlowClient.kt @@ -1,8 +1,8 @@ package net.corda.notarydemo.flows +import net.corda.core.flows.NotaryFlow import net.corda.core.flows.StartableByRPC import net.corda.core.transactions.SignedTransaction -import net.corda.flows.NotaryFlow @StartableByRPC class RPCStartableNotaryFlowClient(stx: SignedTransaction) : NotaryFlow.Client(stx) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt index 331f4b2b96..e3f031e880 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt @@ -19,8 +19,8 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap -import net.corda.flows.AbstractStateReplacementFlow.Proposal -import net.corda.flows.StateReplacementException +import net.corda.core.flows.AbstractStateReplacementFlow.Proposal +import net.corda.core.flows.StateReplacementException import net.corda.flows.TwoPartyDealFlow import net.corda.vega.analytics.* import net.corda.vega.contracts.* diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt index c9d56ae403..343282440c 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt @@ -1,14 +1,11 @@ package net.corda.vega.flows import net.corda.core.contracts.StateAndRef -import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.seconds -import net.corda.core.transactions.SignedTransaction -import net.corda.flows.AbstractStateReplacementFlow -import net.corda.flows.StateReplacementException +import net.corda.core.flows.AbstractStateReplacementFlow +import net.corda.core.flows.StateReplacementException import net.corda.vega.contracts.RevisionedState -import java.security.PublicKey /** * Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index 720f33ffe5..ca738a7bdb 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -5,8 +5,8 @@ import net.corda.contracts.CommercialPaper import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.generateKeyPair import net.corda.core.days +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.StartableByRPC @@ -17,8 +17,6 @@ import net.corda.core.node.NodeInfo import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker -import net.corda.flows.FinalityFlow -import net.corda.flows.NotaryFlow import net.corda.flows.TwoPartyTradeFlow import net.corda.testing.BOC import java.time.Instant diff --git a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt index b14b55937f..f71c8dfedb 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt @@ -4,7 +4,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.transactions.WireTransaction -import net.corda.flows.ContractUpgradeFlow +import net.corda.core.flows.ContractUpgradeFlow // The dummy contract doesn't do anything useful. It exists for testing purposes. val DUMMY_V2_PROGRAM_ID = DummyContractV2() 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 f684b08bb4..21a17d16bb 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 @@ -6,11 +6,11 @@ import net.corda.client.mock.pickOne import net.corda.client.mock.replicate import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY +import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowException import net.corda.core.messaging.startFlow import net.corda.core.thenMatch import net.corda.core.transactions.SignedTransaction -import net.corda.flows.FinalityFlow import net.corda.loadtest.LoadTest import net.corda.loadtest.NodeConnection import net.corda.testing.contracts.DummyContract From 0d0a6d7966da1247dd9698b6539ceb95445a60b4 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 12 Jul 2017 17:53:21 +0100 Subject: [PATCH 004/197] Fix toString generated method that breaks Introspection for BEANS Pass signature as null, not empty string, otherwise the class asm isn't correct. Using javap at the command line prior to the fix yields public class MyClass implements net.corda.core.serialization.carpenter.SimpleFieldAccess { protected final java.lang.Integer a; descriptor: Ljava/lang/Integer; public MyClass(java.lang.Integer); descriptor: (Ljava/lang/Integer;)V public java.lang.Integer getA(); descriptor: ()Ljava/lang/Integer; public java.lang.Object get(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/Object; Error: A serious internal error has occurred: java.lang.StringIndexOutOfBoundsException: String index out of range: 0 --- .../serialization/carpenter/ClassCarpenter.kt | 4 ++-- .../carpenter/ClassCarpenterTest.kt | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index 5d774df240..5f0f1abd99 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -216,7 +216,7 @@ class ClassCarpenter { * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to * construct instances of the generated class. * - * @throws DuplicateName if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) + * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) */ fun build(schema: Schema): Class<*> { validateSchema(schema) @@ -291,7 +291,7 @@ class ClassCarpenter { private fun ClassWriter.generateToString(schema: Schema) { val toStringHelper = "com/google/common/base/MoreObjects\$ToStringHelper" - with(visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", "", null)) { + with(visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null)) { visitCode() // com.google.common.base.MoreObjects.toStringHelper("TypeName") visitLdcInsn(schema.name.split('.').last()) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index 6dc4f5b12a..020d907e5f 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -1,12 +1,11 @@ package net.corda.core.serialization.carpenter - import org.junit.Test import java.lang.reflect.Field import java.lang.reflect.Method import kotlin.test.assertEquals -import kotlin.test.assertTrue - +import java.beans.Introspector +import kotlin.test.assertNotEquals class ClassCarpenterTest { interface DummyInterface { @@ -491,4 +490,17 @@ class ClassCarpenterTest { assertEquals (javax.annotation.Nonnull::class.java, clazz.getMethod("getB").annotations[0].annotationClass.java) } + @Test + fun beanTest() { + val schema = ClassCarpenter.ClassSchema( + "pantsPantsPants", + mapOf("a" to ClassCarpenter.NonNullableField(Integer::class.java))) + val clazz = cc.build(schema) + val descriptors = Introspector.getBeanInfo(clazz).propertyDescriptors + + assertEquals(2, descriptors.size) + assertNotEquals(null, descriptors.find { it.name == "a" }) + assertNotEquals(null, descriptors.find { it.name == "class" }) + } + } From 056811845bb1c3dd6917894a481bbc4a21766a18 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 13 Jul 2017 14:23:08 +0100 Subject: [PATCH 005/197] Review Comments - rename putObject to writeSchema --- .../net/corda/core/serialization/amqp/SerializationOutput.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt index cef69a03d6..41575ac377 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializationOutput.kt @@ -34,7 +34,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory // Our object writeObject(obj, this) // The schema - putObject(Schema(schemaHistory.toList()), this) + writeSchema(Schema(schemaHistory.toList()), this) } } val bytes = ByteArray(data.encodedSize().toInt() + 8) @@ -53,7 +53,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory writeObject(obj, data, obj.javaClass) } - open fun putObject(schema: Schema, data: Data) { + open fun writeSchema(schema: Schema, data: Data) { data.putObject(schema) } From 75473e2782d261820237107fa9de719de0f5486a Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 13 Jul 2017 15:47:06 +0100 Subject: [PATCH 006/197] Update DemoBench to use the new Vault APIs. (#1008) * Update to use the new Vault API. * Add a page specification for the Vault query. * Query vault for all contract states, not just cash states. Also only request one item per page from the vault because we're not interested in them anyway. * The first VaultQuery page is now page 1. --- .../corda/demobench/views/NodeTerminalView.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt index d2d81031c6..62ec8e0ae7 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt @@ -17,10 +17,13 @@ import javafx.scene.layout.StackPane import javafx.scene.layout.HBox import javafx.scene.layout.VBox import javafx.util.Duration +import net.corda.core.contracts.ContractState import net.corda.core.crypto.commonName import net.corda.core.match import net.corda.core.then import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.vaultTrackBy +import net.corda.core.node.services.vault.PageSpecification import net.corda.demobench.explorer.ExplorerController import net.corda.demobench.model.NodeConfig import net.corda.demobench.model.NodeController @@ -37,6 +40,10 @@ import tornadofx.* class NodeTerminalView : Fragment() { override val root by fxml() + private companion object { + val pageSpecification = PageSpecification(1, 1) + } + private val nodeController by inject() private val explorerController by inject() private val webServerController by inject() @@ -53,7 +60,7 @@ class NodeTerminalView : Fragment() { private val subscriptions: MutableList = mutableListOf() private var txCount: Int = 0 - private var stateCount: Int = 0 + private var stateCount: Long = 0 private var isDestroyed: Boolean = false private val explorer = explorerController.explorer() private val webServer = webServerController.webServer() @@ -182,11 +189,11 @@ class NodeTerminalView : Fragment() { private fun initialise(config: NodeConfig, ops: CordaRPCOps) { try { - val (txInit, txNext) = ops.verifiedTransactions() - val (stateInit, stateNext) = ops.vaultAndUpdates() + val (txInit, txNext) = ops.verifiedTransactionsFeed() + val (stateInit, stateNext) = ops.vaultTrackBy(paging = pageSpecification) txCount = txInit.size - stateCount = stateInit.size + stateCount = stateInit.totalStatesAvailable Platform.runLater { logo.opacityProperty().animate(1.0, Duration.seconds(2.5)) @@ -194,7 +201,7 @@ class NodeTerminalView : Fragment() { states.value = stateCount.toString() } - val fxScheduler = Schedulers.from({ Platform.runLater(it) }) + val fxScheduler = Schedulers.from(Platform::runLater) subscriptions.add(txNext.observeOn(fxScheduler).subscribe { transactions.value = (++txCount).toString() }) From a49baddd4b5e17d2b78eac7d5809d6467aed1620 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 13 Jul 2017 13:59:45 +0100 Subject: [PATCH 007/197] Moved KeyStoreUtilities out of core and into node --- .../net/corda/core/crypto/X509Utilities.kt | 56 +--------- .../corda/core/crypto/CompositeKeyTests.kt | 14 ++- .../core/crypto/X509NameConstraintsTest.kt | 7 +- .../corda/core/crypto/X509UtilitiesTest.kt | 35 +++--- .../messaging/MQSecurityAsNodeTest.kt | 16 ++- .../net/corda/node/internal/AbstractNode.kt | 12 +- .../node/services/config/ConfigUtilities.kt | 68 ++++++++++-- .../messaging/ArtemisMessagingServer.kt | 6 +- .../node/utilities}/KeyStoreUtilities.kt | 105 +++++++++--------- .../registration/NetworkRegistrationHelper.kt | 18 ++- .../NetworkisRegistrationHelperTest.kt | 8 +- 11 files changed, 182 insertions(+), 163 deletions(-) rename {core/src/main/kotlin/net/corda/core/crypto => node/src/main/kotlin/net/corda/node/utilities}/KeyStoreUtilities.kt (67%) diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 79973b514f..ec20ada436 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,11 +1,12 @@ package net.corda.core.crypto -import net.corda.core.crypto.Crypto.generateKeyPair import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle -import org.bouncycastle.asn1.x509.* +import org.bouncycastle.asn1.x509.KeyPurposeId +import org.bouncycastle.asn1.x509.KeyUsage +import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.openssl.jcajce.JcaPEMWriter @@ -15,10 +16,8 @@ import java.io.FileWriter import java.io.InputStream import java.nio.file.Path import java.security.KeyPair -import java.security.KeyStore import java.security.PublicKey import java.security.cert.* -import java.security.cert.Certificate import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit @@ -167,55 +166,6 @@ object X509Utilities { } } - /** - * An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine. - * @param sslKeyStorePath KeyStore path to save ssl key and cert to. - * @param clientCAKeystorePath KeyStore path to save client CA key and cert to. - * @param storePassword access password for KeyStore. - * @param keyPassword PrivateKey access password for the generated keys. - * It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same. - * @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore. - * @param caKeyPassword password to unlock private keys in the CA KeyStore. - * @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications. - */ - fun createKeystoreForCordaNode(sslKeyStorePath: Path, - clientCAKeystorePath: Path, - storePassword: String, - keyPassword: String, - caKeyStore: KeyStore, - caKeyPassword: String, - legalName: X500Name, - signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) { - - val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA) - val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword) - - val clientKey = generateKeyPair(signatureScheme) - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) - val clientCACert = createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints) - - val tlsKey = generateKeyPair(signatureScheme) - val clientTLSCert = createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public) - - val keyPass = keyPassword.toCharArray() - - val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword) - clientCAKeystore.addOrReplaceKey( - CORDA_CLIENT_CA, - clientKey.private, - keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) - clientCAKeystore.save(clientCAKeystorePath, storePassword) - - val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword) - tlsKeystore.addOrReplaceKey( - CORDA_CLIENT_TLS, - tlsKey.private, - keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) - tlsKeystore.save(sslKeyStorePath, storePassword) - } - fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme) } diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 5294898acd..c0816b10ed 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -6,10 +6,14 @@ import net.corda.core.crypto.composite.CompositeSignaturesWithKeys import net.corda.core.div import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes +import net.corda.node.utilities.loadKeyStore +import net.corda.node.utilities.loadOrCreateKeyStore +import net.corda.node.utilities.save import org.bouncycastle.asn1.x500.X500Name import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import java.security.PublicKey import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -24,9 +28,9 @@ class CompositeKeyTests { val bobKey = generateKeyPair() val charlieKey = generateKeyPair() - val alicePublicKey = aliceKey.public - val bobPublicKey = bobKey.public - val charliePublicKey = charlieKey.public + val alicePublicKey: PublicKey = aliceKey.public + val bobPublicKey: PublicKey = bobKey.public + val charliePublicKey: PublicKey = charlieKey.public val message = OpaqueBytes("Transaction".toByteArray()) @@ -332,12 +336,12 @@ class CompositeKeyTests { // Store certificate to keystore. val keystorePath = tempFolder.root.toPath() / "keystore.jks" - val keystore = KeyStoreUtilities.loadOrCreateKeyStore(keystorePath, "password") + val keystore = loadOrCreateKeyStore(keystorePath, "password") keystore.setCertificateEntry("CompositeKey", compositeKeyCert.cert) keystore.save(keystorePath, "password") // Load keystore from disk. - val keystore2 = KeyStoreUtilities.loadKeyStore(keystorePath, "password") + val keystore2 = loadKeyStore(keystorePath, "password") assertTrue { keystore2.containsAlias("CompositeKey") } val key = keystore2.getCertificate("CompositeKey").publicKey diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index 30292b00e7..974f449473 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,6 +1,9 @@ package net.corda.core.crypto import net.corda.core.toTypedArray +import net.corda.node.utilities.KEYSTORE_TYPE +import net.corda.node.utilities.addOrReplaceCertificate +import net.corda.node.utilities.addOrReplaceKey import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -26,14 +29,14 @@ class X509NameConstraintsTest { val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, X509Utilities.getX509Name("Corda Client CA","London","demo@r3.com",null), clientCAKeyPair.public, nameConstraints = nameConstraints) val keyPass = "password" - val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) + val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) trustStore.load(null, keyPass.toCharArray()) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public) - val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) keyStore.load(null, keyPass.toCharArray()) keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), Stream.of(tlsCert, clientCACert, intermediateCACert, rootCACert).map { it.cert }.toTypedArray()) diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt index 780c300044..e2247edb7d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt @@ -6,6 +6,8 @@ import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate import net.corda.core.div import net.corda.core.toTypedArray +import net.corda.node.services.config.createKeystoreForCordaNode +import net.corda.node.utilities.* import net.corda.testing.MEGA_CORP import net.corda.testing.getTestX509Name import org.bouncycastle.asn1.x500.X500Name @@ -89,13 +91,13 @@ class X509UtilitiesTest { assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded)) // Save the EdDSA private key with self sign cert in the keystore. - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), Stream.of(selfSignCert).map { it.cert }.toTypedArray()) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. - val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") val privateKey = keyStore2.getKey("Key", "password".toCharArray()) val pubKey = keyStore2.getCertificate("Key").publicKey @@ -114,13 +116,13 @@ class X509UtilitiesTest { val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public) // Save the EdDSA private key with cert chains. - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), Stream.of(ecDSACert, edDSACert).map { it.cert }.toTypedArray()) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. - val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") val privateKey = keyStore2.getKey("Key", "password".toCharArray()) val certs = keyStore2.getCertificateChain("Key") @@ -142,8 +144,8 @@ class X509UtilitiesTest { createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass") // Load back generated root CA Cert and private key from keystore and check against copy in truststore - val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass") - val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") + val keyStore = loadKeyStore(tmpKeyStore, "keystorepass") + val trustStore = loadKeyStore(tmpTrustStore, "trustpass") val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate @@ -182,14 +184,14 @@ class X509UtilitiesTest { "trustpass") // Load signing intermediate CA cert - val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") + val caKeyStore = loadKeyStore(tmpCAKeyStore, "cakeystorepass") val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass") // Generate server cert and private key and populate another keystore suitable for SSL - X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) + createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) // Load back server certificate - val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") + val serverKeyStore = loadKeyStore(tmpServerKeyStore, "serverstorepass") val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass") serverCertAndKey.certificate.isValidOn(Date()) @@ -198,7 +200,7 @@ class X509UtilitiesTest { assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) } // Load back server certificate - val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass") + val sslKeyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass") sslCertAndKey.certificate.isValidOn(Date()) @@ -227,9 +229,9 @@ class X509UtilitiesTest { "trustpass") // Generate server cert and private key and populate another keystore suitable for SSL - X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) - val keyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass") - val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") + createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) + val keyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") + val trustStore = loadKeyStore(tmpTrustStore, "trustpass") val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) @@ -348,7 +350,7 @@ class X509UtilitiesTest { val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getX509Name("Corda Node Intermediate CA","London","demo@r3.com",null), intermediateCAKeyPair.public) val keyPass = keyPassword.toCharArray() - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) + val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert.cert)) @@ -359,7 +361,7 @@ class X509UtilitiesTest { keyStore.save(keyStoreFilePath, storePassword) - val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) + val trustStore = loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert) @@ -373,7 +375,7 @@ class X509UtilitiesTest { fun `Get correct private key type from Keystore`() { val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") + val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert)) val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray()) @@ -382,5 +384,4 @@ class X509UtilitiesTest { assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey) } - } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index d4bb517abf..a5abf5f336 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -2,8 +2,11 @@ package net.corda.services.messaging import net.corda.core.copyTo import net.corda.core.createDirectories -import net.corda.core.crypto.* +import net.corda.core.crypto.CertificateType +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.X509Utilities import net.corda.core.exists +import net.corda.node.utilities.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.RPCApi @@ -94,7 +97,9 @@ class MQSecurityAsNodeTest : MQSecurityTest() { javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) } - val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore( + javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), + "cordacadevpass") val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") @@ -102,12 +107,13 @@ class MQSecurityAsNodeTest : MQSecurityTest() { // Set name constrain to the legal name. val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) - val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints) + val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, + intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints) val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) // Using different x500 name in the TLS cert which is not allowed in the name constraints. val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, MINI_CORP.name, tlsKey.public) val keyPass = keyStorePassword.toCharArray() - val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(nodeKeystore, keyStorePassword) + val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword) clientCAKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_CA, clientKey.private, @@ -115,7 +121,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert))) clientCAKeystore.save(nodeKeystore, keyStorePassword) - val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword) + val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) tlsKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index c3c12f93a9..51165a5719 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -59,10 +59,8 @@ import net.corda.node.services.vault.CashBalanceAsMetricsObserver import net.corda.node.services.vault.HibernateVaultQueryImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager +import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import org.apache.activemq.artemis.utils.ReusableLatch import org.bouncycastle.asn1.x500.X500Name import org.jetbrains.exposed.sql.Database @@ -520,8 +518,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, private fun validateKeystore() { val containCorrectKeys = try { // This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. - val sslKeystore = KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) - val identitiesKeystore = KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) + val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) + val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && identitiesKeystore.containsAlias(X509Utilities.CORDA_CLIENT_CA) } catch (e: KeyStoreException) { log.warn("Certificate key store found but key store password does not match configuration.") @@ -535,7 +533,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, "or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " + "Read more at: https://docs.corda.net/permissioning.html" } - val identitiesKeystore = KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) + val identitiesKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) val tlsIdentity = identitiesKeystore.getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subject require(tlsIdentity == configuration.myLegalName) { @@ -839,7 +837,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) { - constructor(storePath: Path, storePassword: String) : this(KeyStoreUtilities.loadKeyStore(storePath, storePassword), storePath, storePassword) + constructor(storePath: Path, storePassword: String) : this(loadKeyStore(storePath, storePassword), storePath, storePassword) fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? { return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 8fdae16bdb..882de35155 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -1,6 +1,3 @@ -// TODO: Remove when configureTestSSL() is moved. -@file:JvmName("ConfigUtilities") - package net.corda.node.services.config import com.typesafe.config.Config @@ -9,17 +6,21 @@ import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions import net.corda.core.copyTo import net.corda.core.createDirectories -import net.corda.core.crypto.KeyStoreUtilities -import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.* import net.corda.core.div import net.corda.core.exists import net.corda.core.utilities.loggerFor +import net.corda.node.utilities.* import net.corda.nodeapi.config.SSLConfiguration import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.GeneralSubtree +import org.bouncycastle.asn1.x509.NameConstraints import java.nio.file.Path +import java.security.KeyStore -fun configOf(vararg pairs: Pair) = ConfigFactory.parseMap(mapOf(*pairs)) -operator fun Config.plus(overrides: Map) = ConfigFactory.parseMap(overrides).withFallback(this) +fun configOf(vararg pairs: Pair): Config = ConfigFactory.parseMap(mapOf(*pairs)) +operator fun Config.plus(overrides: Map): Config = ConfigFactory.parseMap(overrides).withFallback(this) object ConfigHelper { private val log = loggerFor() @@ -55,7 +56,56 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) { javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) } if (!sslKeystore.exists() || !nodeKeystore.exists()) { - val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") - X509Utilities.createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) + val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) } } + +/** + * An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine. + * @param sslKeyStorePath KeyStore path to save ssl key and cert to. + * @param clientCAKeystorePath KeyStore path to save client CA key and cert to. + * @param storePassword access password for KeyStore. + * @param keyPassword PrivateKey access password for the generated keys. + * It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same. + * @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore. + * @param caKeyPassword password to unlock private keys in the CA KeyStore. + * @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications. + */ +fun createKeystoreForCordaNode(sslKeyStorePath: Path, + clientCAKeystorePath: Path, + storePassword: String, + keyPassword: String, + caKeyStore: KeyStore, + caKeyPassword: String, + legalName: X500Name, + signatureScheme: SignatureScheme = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) { + + val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) + val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, caKeyPassword) + + val clientKey = Crypto.generateKeyPair(signatureScheme) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) + val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints) + + val tlsKey = Crypto.generateKeyPair(signatureScheme) + val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public) + + val keyPass = keyPassword.toCharArray() + + val clientCAKeystore = loadOrCreateKeyStore(clientCAKeystorePath, storePassword) + clientCAKeystore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_CA, + clientKey.private, + keyPass, + org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) + clientCAKeystore.save(clientCAKeystorePath, storePassword) + + val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword) + tlsKeystore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_TLS, + tlsKey.private, + keyPass, + org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) + tlsKeystore.save(sslKeyStorePath, storePassword) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 66b2da18f6..c300cfd69a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -20,6 +20,8 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE +import net.corda.node.utilities.getX509Certificate +import net.corda.node.utilities.loadKeyStore import net.corda.nodeapi.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER @@ -264,8 +266,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { - val keyStore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) + val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS) // This is a sanity check and should not fail unless things have been misconfigured diff --git a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt similarity index 67% rename from core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt rename to node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt index 0c88ee2f27..f4a26d5b28 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt @@ -1,5 +1,8 @@ -package net.corda.core.crypto +package net.corda.node.utilities +import net.corda.core.crypto.CertificateAndKeyPair +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.cert import net.corda.core.exists import net.corda.core.read import net.corda.core.write @@ -12,60 +15,58 @@ import java.nio.file.Path import java.security.* import java.security.cert.Certificate -object KeyStoreUtilities { - val KEYSTORE_TYPE = "JKS" +val KEYSTORE_TYPE = "JKS" - /** - * Helper method to either open an existing keystore for modification, or create a new blank keystore. - * @param keyStoreFilePath location of KeyStore file. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened/created. - */ - fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - if (keyStoreFilePath.exists()) { - keyStoreFilePath.read { keyStore.load(it, pass) } - } else { - keyStore.load(null, pass) - keyStoreFilePath.write { keyStore.store(it, pass) } - } - return keyStore +/** + * Helper method to either open an existing keystore for modification, or create a new blank keystore. + * @param keyStoreFilePath location of KeyStore file. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened/created. + */ +fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + if (keyStoreFilePath.exists()) { + keyStoreFilePath.read { keyStore.load(it, pass) } + } else { + keyStore.load(null, pass) + keyStoreFilePath.write { keyStore.store(it, pass) } } + return keyStore +} - /** - * Helper method to open an existing keystore for modification/read. - * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened. - * @throws IOException if there was an error reading the key store from the file. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - return keyStoreFilePath.read { loadKeyStore(it, storePassword) } - } +/** + * Helper method to open an existing keystore for modification/read. + * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened. + * @throws IOException if there was an error reading the key store from the file. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ +@Throws(KeyStoreException::class, IOException::class) +fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + return keyStoreFilePath.read { loadKeyStore(it, storePassword) } +} - /** - * Helper method to open an existing keystore for modification/read. - * @param input stream containing a KeyStore e.g. loaded from a resource file. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened. - * @throws IOException if there was an error reading the key store from the stream. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - input.use { - keyStore.load(input, pass) - } - return keyStore +/** + * Helper method to open an existing keystore for modification/read. + * @param input stream containing a KeyStore e.g. loaded from a resource file. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened. + * @throws IOException if there was an error reading the key store from the stream. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ +@Throws(KeyStoreException::class, IOException::class) +fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + input.use { + keyStore.load(input, pass) } + return keyStore } /** @@ -107,7 +108,6 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) { this.setCertificateEntry(alias, cert) } - /** * Helper method save KeyStore to storage. * @param keyStoreFilePath the file location to save to. @@ -118,7 +118,6 @@ fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFileP fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray()) - /** * Extract public and private keys from a KeyStore file assuming storage alias is known. * @param alias The name to lookup the Key and Certificate chain from. @@ -146,7 +145,7 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi * @return The X509Certificate found in the KeyStore under the specified alias. */ fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder { - val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"${alias}\"") + val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"$alias\"") return X509CertificateHolder(encoded) } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 9701203d45..b362153d6d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -1,24 +1,30 @@ package net.corda.node.utilities.registration import net.corda.core.* -import net.corda.core.crypto.* +import net.corda.core.crypto.CertificateType +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.core.crypto.cert import net.corda.node.services.config.NodeConfiguration +import net.corda.node.utilities.* import org.bouncycastle.cert.path.CertPath import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter import java.security.KeyPair +import java.security.KeyStore import java.security.cert.Certificate import kotlin.system.exitProcess /** * This checks the config.certificatesDirectory field for certificates required to connect to a Corda network. * If the certificates are not found, a [org.bouncycastle.pkcs.PKCS10CertificationRequest] will be submitted to - * Corda network permissioning server using [NetworkRegistrationService]. This process will enter a polling loop until the request has been approved, and then - * the certificate chain will be downloaded and stored in [Keystore] reside in the certificates directory. + * Corda network permissioning server using [NetworkRegistrationService]. This process will enter a polling loop until + * the request has been approved, and then the certificate chain will be downloaded and stored in [KeyStore] reside in + * the certificates directory. */ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: NetworkRegistrationService) { companion object { @@ -33,7 +39,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: fun buildKeystore() { config.certificatesDirectory.createDirectories() - val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) + val caKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { // Create or load self signed keypair from the key store. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. @@ -64,7 +70,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.save(config.nodeKeystore, keystorePassword) // Save root certificates to trust store. - val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) + val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) // Assumes certificate chain always starts with client certificate and end with root certificate. trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) trustStore.save(config.trustStoreFile, config.trustStorePassword) @@ -74,7 +80,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA) val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public) - val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword) + val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) sslKeyStore.save(config.sslKeystore, config.keyStorePassword) diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 201fb206f4..c08e5bc72b 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -6,6 +6,7 @@ import com.nhaarman.mockito_kotlin.mock import net.corda.core.crypto.* import net.corda.core.exists import net.corda.core.toTypedArray +import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE import net.corda.testing.getTestX509Name import net.corda.testing.testNodeConfiguration @@ -52,10 +53,9 @@ class NetworkRegistrationHelperTest { assertTrue(config.sslKeystore.exists()) assertTrue(config.trustStoreFile.exists()) - val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword) - val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) - + val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) + val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) + val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) nodeKeystore.run { assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA)) From 5be63adae02b1752a4a6041b4f842051332e75e0 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Fri, 14 Jul 2017 09:07:20 +0100 Subject: [PATCH 008/197] Comment that vault returns the total number of available states, irresepective of the query's pagination. (#1045) --- .../main/kotlin/net/corda/demobench/views/NodeTerminalView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt index 62ec8e0ae7..9d9d110934 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt @@ -193,6 +193,7 @@ class NodeTerminalView : Fragment() { val (stateInit, stateNext) = ops.vaultTrackBy(paging = pageSpecification) txCount = txInit.size + // This is the total number of states in the vault, regardless of pagination. stateCount = stateInit.totalStatesAvailable Platform.runLater { From e919d23d1f12e3a20af84ef9d73c7d8d6b977b76 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 13 Jul 2017 11:49:31 +0100 Subject: [PATCH 009/197] Fix a couple of tests that fail after the broadcast phase of finality flow has returned. Remove redundant namespace on symbol --- .../corda/client/jfx/NodeMonitorModelTest.kt | 19 ++++++------------- .../net/corda/traderdemo/flow/BuyerFlow.kt | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 5c93d9e820..87a3c4da54 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -19,12 +19,8 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.SignedTransaction -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.CHARLIE -import net.corda.testing.DUMMY_NOTARY +import net.corda.core.utilities.OpaqueBytes import net.corda.flows.CashExitFlow import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow @@ -32,11 +28,9 @@ import net.corda.node.services.network.NetworkMapService import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User +import net.corda.testing.* import net.corda.testing.driver.driver -import net.corda.testing.expect -import net.corda.testing.expectEvents import net.corda.testing.node.DriverBasedTest -import net.corda.testing.sequence import org.bouncycastle.asn1.x500.X500Name import org.junit.Test import rx.Observable @@ -148,7 +142,7 @@ class NodeMonitorModelTest : DriverBasedTest() { var moveSmId: StateMachineRunId? = null var issueTx: SignedTransaction? = null var moveTx: SignedTransaction? = null - stateMachineUpdates.expectEvents { + stateMachineUpdates.expectEvents(isStrict = false) { sequence( // ISSUE expect { add: StateMachineUpdate.Added -> @@ -159,14 +153,13 @@ class NodeMonitorModelTest : DriverBasedTest() { expect { remove: StateMachineUpdate.Removed -> require(remove.id == issueSmId) }, - // MOVE - expect { add: StateMachineUpdate.Added -> + // MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow + expect(match = { it is StateMachineUpdate.Added && it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added -> moveSmId = add.id val initiator = add.stateMachineInfo.initiator require(initiator is FlowInitiator.RPC && initiator.username == "user1") }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == moveSmId) + expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) { } ) } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt index 10f2e030f0..5dbdcbf7ef 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt @@ -37,8 +37,6 @@ class BuyerFlow(val otherParty: Party) : FlowLogic() { // This invokes the trading flow and out pops our finished transaction. val tradeTX: SignedTransaction = subFlow(buyer) - // TODO: This should be moved into the flow itself. - serviceHub.recordTransactions(tradeTX) println("Purchase complete - we are a happy customer! Final transaction is: " + "\n\n${Emoji.renderIfSupported(tradeTX.tx)}") From 4cbf31681b088cc64ea7c57910f6a2a913188be2 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 15 Jun 2017 14:49:18 +0100 Subject: [PATCH 010/197] Initial work towards integrating the serializer with the carpenter Unit tests that pull out the envelope from a deserialsed object (which we have already serialised) then using the AMQP schema contained within convert that to a carpenter schema and build an object Currently testing only simple classes with a single type member Squashed merge commits: * Fix for the type issue in the SerializerFactory Needs to pull in the actual Java types otherwise we use the Kotlin types as the map keys which will never correspond to the java types that the wrapper wraps around the primitive before doing a map lookup Boolean just doesn't seem to work as pulling that in starts breaking Kotlin and Character also seems broken. There is a fix for this also on Rick's branch so pushing this in for now and can use his actual changes when they're available on Master * Better tests * Add support for sub classes known to the JVM * Initial work towards integrating serializer with the carpenter Unit tests that pull out the envelope from a deserialsed object (which we have already serialized) then using the AMQP schema contained within convert that to a carpenter schema and build an object Currently testing only simple classes with a single type member --- .../corda/core/serialization/amqp/Schema.kt | 27 ++ .../serialization/amqp/SerializerFactory.kt | 7 + .../carpenter/ClassCarpenterSchemaTests.kt | 263 ++++++++++++++++++ ...berCompositeSchemaToClassCarpenterTests.kt | 78 ++++++ ...berCompositeSchemaToClassCarpenterTests.kt | 86 ++++++ ...berCompositeSchemaToClassCarpenterTests.kt | 263 ++++++++++++++++++ 6 files changed, 724 insertions(+) create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index 844f7ce51b..0117379e6a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -168,6 +168,25 @@ data class Field(val name: String, val type: String, val requires: List, sb.append("/>") return sb.toString() } + + fun getPrimType() = when (type) { + "int" -> Int::class.javaPrimitiveType!! + "string" -> String::class.java + "short" -> Short::class.javaPrimitiveType!! + "long" -> Long::class.javaPrimitiveType!! + "char" -> Char::class.javaPrimitiveType!! + "boolean" -> Boolean::class.javaPrimitiveType!! + "double" -> Double::class.javaPrimitiveType!! + "float" -> Double::class.javaPrimitiveType!! + else -> { + try { + ClassLoader.getSystemClassLoader().loadClass(type) + } + catch (e: ClassNotFoundException) { + throw IllegalArgumentException ("pants") + } + } + } } sealed class TypeNotation : DescribedType { @@ -233,6 +252,14 @@ data class CompositeType(override val name: String, override val label: String?, sb.append("") return sb.toString() } + + fun carpenterSchema() : Map> { + var m : MutableMap> = mutableMapOf() + + fields.forEach { m[it.name] = it.getPrimType() } + + return m + } } data class RestrictedType(override val name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt index a4f887be8b..436e4fac60 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt @@ -12,6 +12,12 @@ import java.lang.reflect.GenericArrayType import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.WildcardType +import java.lang.Float +import java.lang.Long +import java.lang.Short +import java.lang.Double +import java.lang.Character +//import java.lang.Boolean import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList @@ -269,6 +275,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } private val primitiveTypeNames: Map, String> = mapOf( + Character::class.java to "char", Boolean::class.java to "boolean", Byte::class.java to "byte", UnsignedByte::class.java to "ubyte", diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt new file mode 100644 index 0000000000..b287706106 --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt @@ -0,0 +1,263 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + + +class ClassCarpenterScehmaTestsSingleMemberComposite { + private var factory = SerializerFactory() + + fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) + + @Test + fun singleInteger() { + val test = 10 + + @CordaSerializable + data class A(val a : Int) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("int", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleString() { + val test = "ten" + + @CordaSerializable + data class A(val a : String) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("string", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + /* + @Test + fun singleChar () { + val test = 'c' + + @CordaSerializable + data class A(val a : Char) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("char", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + */ + + @Test + fun singleLong() { + val test = 10L + + @CordaSerializable + data class A(val a : Long) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("long", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleShort() { + val test = 10.toShort() + + @CordaSerializable + data class A(val a : Short) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("short", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + /* + @Test + fun singleBool() { + val test = true + + @CordaSerializable + data class A(val a : Boolean) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("boolean", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + */ + + @Test + fun singleDouble() { + val test = 10.0 + + @CordaSerializable + data class A(val a : Double) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("double", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleFloat() { + val test = 10.0F + + @CordaSerializable + data class A(val a : Float) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("float", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + +// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } +} \ No newline at end of file diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt new file mode 100644 index 0000000000..b8872dae7a --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -0,0 +1,78 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + +class CompositeMemberCompositeSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) + + @Test + fun nestedInts() { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(val a: Int) + + @CordaSerializable + class B (val a: A, var b: Int) + + var b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val amqpObj = obj.first as B + + assertEquals(testB, amqpObj.b) + assertEquals(testA, amqpObj.a.a) + assertEquals(2, obj.second.schema.types.size) + assert(obj.second.schema.types[0] is CompositeType) + assert(obj.second.schema.types[1] is CompositeType) + + var amqpSchemaA : CompositeType? = null + var amqpSchemaB : CompositeType? = null + + for (type in obj.second.schema.types) { + when (type.name.split ("$").last()) { + "A" -> amqpSchemaA = type as CompositeType + "B" -> amqpSchemaB = type as CompositeType + } + } + + assert (amqpSchemaA != null) + assert (amqpSchemaB != null) + + assertEquals(1, amqpSchemaA?.fields?.size) + assertEquals("a", amqpSchemaA!!.fields[0].name) + assertEquals("int", amqpSchemaA!!.fields[0].type) + + assertEquals(2, amqpSchemaB?.fields?.size) + assertEquals("a", amqpSchemaB!!.fields[0].name) + assertEquals("net.corda.carpenter.CompositeMemberCompositeSchemaToClassCarpenterTests\$nestedInts\$A", + amqpSchemaB!!.fields[0].type) + assertEquals("b", amqpSchemaB!!.fields[1].name) + assertEquals("int", amqpSchemaB!!.fields[1].type) + + var ccA = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaA.name, amqpSchemaA.carpenterSchema())) + var ccB = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaB.name, amqpSchemaB.carpenterSchema())) + + /* + * Since A is known to the JVM we can't constuct B with and instance of the carpented A but + * need to use the defined one above + */ + val instanceA = ccA.constructors[0].newInstance(testA) + val instanceB = ccB.constructors[0].newInstance(A (testA), testB) + + assertEquals (ccA.getMethod("getA").invoke(instanceA), amqpObj.a.a) + assertEquals ((ccB.getMethod("getA").invoke(instanceB) as A).a, amqpObj.a.a) + assertEquals (ccB.getMethod("getB").invoke(instanceB), amqpObj.b) + } + +} + diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt new file mode 100644 index 0000000000..51d9a0c128 --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -0,0 +1,86 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + +class MultiMemberCompositeSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) + + @Test + fun twoInts() { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(val a: Int, val b: Int) + + var a = A(testA, testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + val amqpObj = obj.first as A + + assertEquals(testA, amqpObj.a) + assertEquals(testB, amqpObj.b) + assertEquals(1, obj.second.schema.types.size) + assert(obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals(2, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("int", amqpSchema.fields[0].type) + assertEquals("b", amqpSchema.fields[1].name) + assertEquals("int", amqpSchema.fields[1].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance(testA, testB) + + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b) + } + + @Test + fun intAndStr() { + val testA = 10 + val testB = "twenty" + + @CordaSerializable + data class A(val a: Int, val b: String) + + var a = A(testA, testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + val amqpObj = obj.first as A + + assertEquals(testA, amqpObj.a) + assertEquals(testB, amqpObj.b) + assertEquals(1, obj.second.schema.types.size) + assert(obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals(2, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("int", amqpSchema.fields[0].type) + assertEquals("b", amqpSchema.fields[1].name) + assertEquals("string", amqpSchema.fields[1].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance(testA, testB) + + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b) + } + +} + diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt new file mode 100644 index 0000000000..87f2c51379 --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -0,0 +1,263 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + + +class SingleMemberCompositeSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) + + @Test + fun singleInteger() { + val test = 10 + + @CordaSerializable + data class A(val a : Int) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("int", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleString() { + val test = "ten" + + @CordaSerializable + data class A(val a : String) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("string", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + /* + @Test + fun singleChar () { + val test = 'c' + + @CordaSerializable + data class A(val a : Char) + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("char", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + */ + + @Test + fun singleLong() { + val test = 10L + + @CordaSerializable + data class A(val a : Long) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("long", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleShort() { + val test = 10.toShort() + + @CordaSerializable + data class A(val a : Short) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("short", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + /* + @Test + fun singleBool() { + val test = true + + @CordaSerializable + data class A(val a : Boolean) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("boolean", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + */ + + @Test + fun singleDouble() { + val test = 10.0 + + @CordaSerializable + data class A(val a : Double) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("double", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } + + @Test + fun singleFloat() { + val test = 10.0F + + @CordaSerializable + data class A(val a : Float) + + var a = A (test) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + + assert (obj.first is A) + val amqpObj = obj.first as A + + assertEquals (test, amqpObj.a) + assertEquals (1, obj.second.schema.types.size) + assert (obj.second.schema.types[0] is CompositeType) + + var amqpSchema = obj.second.schema.types[0] as CompositeType + + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("float", amqpSchema.fields[0].type) + + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + + val p = pinochio.constructors[0].newInstance (test) + +// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + } +} \ No newline at end of file From ce172887d009fa81c1a9709bae4abd71f4faae10 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 16 Jun 2017 09:05:47 +0100 Subject: [PATCH 011/197] Unit Tests for the amqp -> carpenter schema Squahed commit mesages: * Better tests * WIP * WIP --- .../corda/core/serialization/amqp/Schema.kt | 86 +++- .../serialization/carpenter/ClassCarpenter.kt | 60 +-- .../core/serialization/carpenter/Schema.kt | 44 ++ .../carpenter/ClassCarpenterTest.kt | 29 +- .../carpenter/ClassCarpenterSchemaTests.kt | 263 ------------ ...berCompositeSchemaToClassCarpenterTests.kt | 16 +- .../InheritanceSchemaToClassCarpenterTests.kt | 389 ++++++++++++++++++ ...berCompositeSchemaToClassCarpenterTests.kt | 4 +- ...berCompositeSchemaToClassCarpenterTests.kt | 17 +- 9 files changed, 560 insertions(+), 348 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt delete mode 100644 experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index 0117379e6a..2c34698e42 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -15,6 +15,8 @@ import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* +import net.corda.core.serialization.ClassCarpenterSchema + // TODO: get an assigned number as per AMQP spec val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 @@ -87,9 +89,37 @@ data class Schema(val types: List) : DescribedType { override fun getDescribed(): Any = listOf(types) override fun toString(): String = types.joinToString("\n") + + fun carpenterSchema(loaders : List = listOf(ClassLoader.getSystemClassLoader())) + : List + { + var rtn = mutableListOf() + + for (type in types) { + if (type is CompositeType) { + var foundIt = false + for (loader in loaders) { + try { + loader.loadClass(type.name) + foundIt = true + break + } catch (e: ClassNotFoundException) { + continue + } + } + + if (foundIt) continue + else { + rtn.add(type.carpenterSchema()) + } + } + } + + return rtn + } } -data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType { +data class Descriptor(var name: String?, val code: UnsignedLong? = null) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(3L or DESCRIPTOR_TOP_32BITS) @@ -127,7 +157,7 @@ data class Descriptor(val name: String?, val code: UnsignedLong? = null) : Descr } } -data class Field(val name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { +data class Field(var name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(4L or DESCRIPTOR_TOP_32BITS) @@ -169,6 +199,16 @@ data class Field(val name: String, val type: String, val requires: List, return sb.toString() } + inline fun isKnownClass (type : String) : Class<*> { + try { + return ClassLoader.getSystemClassLoader().loadClass(type) + } + catch (e: ClassNotFoundException) { + // call carpenter + throw IllegalArgumentException ("${type} - ${name} - pants") + } + } + fun getPrimType() = when (type) { "int" -> Int::class.javaPrimitiveType!! "string" -> String::class.java @@ -178,14 +218,8 @@ data class Field(val name: String, val type: String, val requires: List, "boolean" -> Boolean::class.javaPrimitiveType!! "double" -> Double::class.javaPrimitiveType!! "float" -> Double::class.javaPrimitiveType!! - else -> { - try { - ClassLoader.getSystemClassLoader().loadClass(type) - } - catch (e: ClassNotFoundException) { - throw IllegalArgumentException ("pants") - } - } + "*" -> isKnownClass(requires[0]) + else -> isKnownClass(type) } } @@ -203,13 +237,13 @@ sealed class TypeNotation : DescribedType { } } - abstract val name: String + abstract var name: String abstract val label: String? abstract val provides: List abstract val descriptor: Descriptor } -data class CompositeType(override val name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { +data class CompositeType(override var name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(5L or DESCRIPTOR_TOP_32BITS) @@ -253,16 +287,36 @@ data class CompositeType(override val name: String, override val label: String?, return sb.toString() } - fun carpenterSchema() : Map> { + fun carpenterSchema(classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) : ClassCarpenterSchema { var m : MutableMap> = mutableMapOf() fields.forEach { m[it.name] = it.getPrimType() } - return m + var providesList = mutableListOf>() + + for (iface in provides) { + var found = false + for (loader in classLoaders) { + try { + providesList.add (loader.loadClass(iface)) + found = true + break + } + catch (e: ClassNotFoundException) { + continue + } + } + if (found == false) { + println ("This needs to work but it wont - ${iface}") + } + else println ("found it ${iface}") + } + + return ClassCarpenterSchema (name, m, interfaces = providesList) } } -data class RestrictedType(override val name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { +data class RestrictedType(override var name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(6L or DESCRIPTOR_TOP_32BITS) @@ -305,7 +359,7 @@ data class RestrictedType(override val name: String, override val label: String? } } -data class Choice(val name: String, val value: String) : DescribedType { +data class Choice(var name: String, val value: String) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(7L or DESCRIPTOR_TOP_32BITS) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index 5f0f1abd99..efda30bea4 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -159,58 +159,18 @@ class ClassCarpenter { } } - /** - * A Schema represents a desired class. - */ - abstract class Schema( - val name: String, - fields: Map, - val superclass: Schema? = null, - val interfaces: List> = emptyList()) - { - private fun Map.descriptors() = - LinkedHashMap(this.mapValues { it.value.descriptor }) - - /* Fix the order up front if the user didn't, inject the name into the field as it's - neater when iterating */ - val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) }) - - fun fieldsIncludingSuperclasses(): Map = - (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields) - - fun descriptorsIncludingSuperclasses(): Map = - (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors() - - val jvmName: String - get() = name.replace(".", "/") - } - - private val String.jvm: String get() = replace(".", "/") - - class ClassSchema( - name: String, - fields: Map, - superclass: Schema? = null, - interfaces: List> = emptyList() - ) : Schema(name, fields, superclass, interfaces) - - class InterfaceSchema( - name: String, - fields: Map, - superclass: Schema? = null, - interfaces: List> = emptyList() - ) : Schema(name, fields, superclass, interfaces) - private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) } private val classloader = CarpenterClassLoader() + fun classLoader() = classloader as ClassLoader + private val _loaded = HashMap>() /** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */ - val loaded: Map> = HashMap(_loaded) + fun loaded() : Map> = HashMap(_loaded) /** * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to @@ -218,18 +178,17 @@ class ClassCarpenter { * * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) */ - fun build(schema: Schema): Class<*> { + fun build(schema: ClassCarpenterSchema): Class<*> { validateSchema(schema) // Walk up the inheritance hierarchy and then start walking back down once we either hit the top, or // find a class we haven't generated yet. - val hierarchy = ArrayList() + val hierarchy = ArrayList() hierarchy += schema var cursor = schema.superclass while (cursor != null && cursor.name !in _loaded) { hierarchy += cursor cursor = cursor.superclass } - hierarchy.reversed().forEach { when (it) { is InterfaceSchema -> generateInterface(it) @@ -346,6 +305,7 @@ class ClassCarpenter { visitEnd() } } + } private fun ClassWriter.generateAbstractGetters(schema: Schema) { @@ -374,14 +334,16 @@ class ClassCarpenter { // Calculate the super call. val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap() visitVarInsn(ALOAD, 0) - if (schema.superclass == null) { + val sc = schema.superclass + if (sc == null) { visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) } else { var slot = 1 for (fieldType in superclassFields.values) slot += load(slot, fieldType) - val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("") - visitMethodInsn(INVOKESPECIAL, schema.superclass.name.jvm, "", "($superDesc)V", false) + //val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("") + val superDesc = sc.descriptorsIncludingSuperclasses().values.joinToString("") + visitMethodInsn(INVOKESPECIAL, sc.name.jvm, "", "($superDesc)V", false) } // Assign the fields from parameters. diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt new file mode 100644 index 0000000000..0a2b73b490 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -0,0 +1,44 @@ +package net.corda.core.serialization.carpenter + +import org.objectweb.asm.Type +import java.util.LinkedHashMap + +/** + * A Schema represents a desired class. + */ +abstract class Schema( + val name: String, + fields: Map, + val superclass: Schema? = null, + val interfaces: List> = emptyList()) +{ + private fun Map.descriptors() = + LinkedHashMap(this.mapValues { it.value.descriptor }) + + /* Fix the order up front if the user didn't, inject the name into the field as it's + neater when iterating */ + val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) }) + + fun fieldsIncludingSuperclasses(): Map = + (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields) + + fun descriptorsIncludingSuperclasses(): Map = + (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors() + + val jvmName: String + get() = name.replace(".", "/") +} + +class ClassSchema( + name: String, + fields: Map>, + superclass: Schema? = null, + interfaces: List> = emptyList() +) : ClassCarpenter.Schema (name, fields, superclass, interfaces) + +class InterfaceSchema( + name: String, + fields: Map>, + superclass: Schema? = null, + interfaces: List> = emptyList() +) : ClassCarpenter.Schema (name, fields, superclass, interfaces) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index 020d907e5f..fbe6fa31b5 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -8,11 +8,31 @@ import java.beans.Introspector import kotlin.test.assertNotEquals class ClassCarpenterTest { + /* + cw.visitInnerClass( + "net/corda/carpenter/ClassCarpenterTest$DummyInterface", + "net/corda/carpenter/ClassCarpenterTest", + "DummyInterface", + ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE); + */ interface DummyInterface { val a: String val b: Int } + /* + cw.visitInnerClass( + "net/corda/carpenter/ClassCarpenterTest$Dummy", + "net/corda/carpenter/ClassCarpenterTest", + "Dummy", + ACC_PUBLIC + ACC_FINAL + ACC_STATIC); + */ + class Dummy (override val a: String, override val b: Int) : DummyInterface + + val dummy = Dummy ("hi", 1) + val dummy2 = Dummy2 ("hi", 1) + + val cc = ClassCarpenter() // We have to ignore synthetic fields even though ClassCarpenter doesn't create any because the JaCoCo @@ -22,7 +42,7 @@ class ClassCarpenterTest { @Test fun empty() { - val clazz = cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) + val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) assertEquals(0, clazz.nonSyntheticFields.size) assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString assertEquals(0, clazz.declaredConstructors[0].parameterCount) @@ -69,7 +89,7 @@ class ClassCarpenterTest { } private fun genPerson(): Pair, Any> { - val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf( + val clazz = cc.build(ClassSchema("gen.Person", mapOf( "age" to Int::class.javaPrimitiveType!!, "name" to String::class.java ).mapValues { ClassCarpenter.NonNullableField (it.value) } )) @@ -92,8 +112,8 @@ class ClassCarpenterTest { @Test(expected = ClassCarpenter.DuplicateNameException::class) fun duplicates() { - cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) - cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) + cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) + cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) } @Test @@ -134,6 +154,7 @@ class ClassCarpenterTest { mapOf("b" to ClassCarpenter.NonNullableField(Int::class.java)), schema1, interfaces = listOf(DummyInterface::class.java)) + val clazz = cc.build(schema2) val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface assertEquals("xa", i.a) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt deleted file mode 100644 index b287706106..0000000000 --- a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt +++ /dev/null @@ -1,263 +0,0 @@ -package net.corda.carpenter - -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.amqp.* -import org.junit.Test -import kotlin.test.assertEquals - - -class ClassCarpenterScehmaTestsSingleMemberComposite { - private var factory = SerializerFactory() - - fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) - - @Test - fun singleInteger() { - val test = 10 - - @CordaSerializable - data class A(val a : Int) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("int", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleString() { - val test = "ten" - - @CordaSerializable - data class A(val a : String) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("string", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - /* - @Test - fun singleChar () { - val test = 'c' - - @CordaSerializable - data class A(val a : Char) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("char", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - - @Test - fun singleLong() { - val test = 10L - - @CordaSerializable - data class A(val a : Long) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("long", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleShort() { - val test = 10.toShort() - - @CordaSerializable - data class A(val a : Short) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("short", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - /* - @Test - fun singleBool() { - val test = true - - @CordaSerializable - data class A(val a : Boolean) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("boolean", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - - @Test - fun singleDouble() { - val test = 10.0 - - @CordaSerializable - data class A(val a : Double) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("double", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleFloat() { - val test = 10.0F - - @CordaSerializable - data class A(val a : Float) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("float", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - -// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } -} \ No newline at end of file diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index b8872dae7a..abbdb5acf5 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -2,10 +2,11 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* +import net.corda.core.serialization.ClassCarpenterSchema import org.junit.Test import kotlin.test.assertEquals -class CompositeMemberCompositeSchemaToClassCarpenterTests { +class Inheritance { private var factory = SerializerFactory() fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) @@ -35,6 +36,9 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assert(obj.second.schema.types[0] is CompositeType) assert(obj.second.schema.types[1] is CompositeType) + println (obj.second.schema.types[0] as CompositeType) + println (obj.second.schema.types[1] as CompositeType) + var amqpSchemaA : CompositeType? = null var amqpSchemaB : CompositeType? = null @@ -52,15 +56,15 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assertEquals("a", amqpSchemaA!!.fields[0].name) assertEquals("int", amqpSchemaA!!.fields[0].type) + assertEquals(2, amqpSchemaB?.fields?.size) assertEquals("a", amqpSchemaB!!.fields[0].name) - assertEquals("net.corda.carpenter.CompositeMemberCompositeSchemaToClassCarpenterTests\$nestedInts\$A", - amqpSchemaB!!.fields[0].type) + assertEquals("${this.javaClass.name}\$nestedInts\$A", amqpSchemaB!!.fields[0].type) assertEquals("b", amqpSchemaB!!.fields[1].name) assertEquals("int", amqpSchemaB!!.fields[1].type) - var ccA = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaA.name, amqpSchemaA.carpenterSchema())) - var ccB = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaB.name, amqpSchemaB.carpenterSchema())) + var ccA = ClassCarpenter().build(amqpSchemaA.carpenterSchema()) + var ccB = ClassCarpenter().build(amqpSchemaB.carpenterSchema()) /* * Since A is known to the JVM we can't constuct B with and instance of the carpented A but @@ -72,7 +76,7 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assertEquals (ccA.getMethod("getA").invoke(instanceA), amqpObj.a.a) assertEquals ((ccB.getMethod("getA").invoke(instanceB) as A).a, amqpObj.a.a) assertEquals (ccB.getMethod("getB").invoke(instanceB), amqpObj.b) - } + } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt new file mode 100644 index 0000000000..c009a1d356 --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -0,0 +1,389 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + +/*******************************************************************************************************/ + +@CordaSerializable +interface J { + val j : Int +} + +/*******************************************************************************************************/ + +@CordaSerializable +interface I { + val i : Int +} + +@CordaSerializable +interface II { + val ii : Int +} + +@CordaSerializable +interface III : I { + val iii : Int + override val i: Int +} + +@CordaSerializable +interface IIII { + val iiii : Int + val i : I +} + +/*******************************************************************************************************/ + +class InheritanceSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) + + fun curruptName(name: String) = "${name}__carpenter" + + /* given a list of class names work through the amqp envelope schema and alter any that + match in the fashion defined above */ + fun curruptName (schema: Schema, names: List) : Schema { + val types = schema.types + + val newTypes : MutableList = mutableListOf() + + for (type in types) { + val newName = if (type.name in names) curruptName (type.name) else type.name + + val newProvides = type.provides.map { + it -> if (it in names) curruptName (it) else it + } + + val newFields = (type as CompositeType).fields.map { + it -> if ( it.requires.isNotEmpty() && it.requires[0] in names) + it.copy (requires=listOf (curruptName (it.requires[0]))) else it + } + + newTypes.add (type.copy (name=newName, provides=newProvides, fields=newFields)) + } + + return Schema (types=newTypes) + } + + @Test + fun interfaceParent1() { + val testJ = 20 + val testName = "interfaceParent1" + + class A(override val j: Int) : J + + val a = A(testJ) + + assertEquals(testJ, a.j) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(2, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(1, l2[0].interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objJ = aBuilder.constructors[0].newInstance(testJ) + val j = objJ as J + + assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ) + assertEquals(a.j, j.j) + } + + + @Test + fun interfaceParent2() { + val testJ = 20 + val testJJ = 40 + val testName = "interfaceParent2" + + class A(override val j: Int, val jj: Int) : J + + val a = A(testJ, testJJ) + + assertEquals(testJ, a.j) + assertEquals(testJJ, a.jj) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(2, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(1, l2[0].interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ) + val j = objJ as J + + assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ) + assertEquals(aBuilder.getMethod("getJj").invoke(objJ), testJJ) + + assertEquals(a.j, j.j) + } + + @Test + fun multipleInterfaces() { + val testI = 20 + val testII = 40 + val testName = "multipleInterfaces" + + class A(override val i: Int, override val ii: Int) : I, II + + val a = A(testI, testII) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(3, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus + needs some carpentry */ + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(2, l2[0].interfaces.size) + assert (net.corda.carpenter.I::class.java in l2[0].interfaces) + assert (net.corda.carpenter.II::class.java in l2[0].interfaces) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objA = aBuilder.constructors[0].newInstance(testI, testII) + val i = objA as I + val ii = objA as II + + assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) + assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII) + + assertEquals(a.i, i.i) + assertEquals(a.ii, ii.ii) + } + + @Test + fun nestedInterfaces() { + val testI = 20 + val testIII = 60 + val testName = "nestedInterfaces" + + class A(override val i: Int, override val iii : Int) : III + + val a = A(testI, testIII) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(3, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(2, l2[0].interfaces.size) + assert (net.corda.carpenter.I::class.java in l2[0].interfaces) + assert (net.corda.carpenter.III::class.java in l2[0].interfaces) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objA = aBuilder.constructors[0].newInstance(testI, testIII) + val i = objA as I + val iii = objA as III + + assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) + assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII) + + assertEquals(a.i, i.i) + assertEquals(a.i, iii.i) + assertEquals(a.iii, iii.iii) + } + + @Test + fun memberInterface() { + val testI = 25 + val testIIII = 50 + val testName = "memberInterface" + + class A(override val i: Int) : I + class B(override val i : I, override val iiii : Int) : IIII + + val a = A(testI) + val b = B(a, testIIII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val serSchema = obj.second.schema + + /* + * class A + * class A's interface (class I) + * class B + * class B's interface (class IIII) + */ + assertEquals(4, serSchema.types.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A", + "${this.javaClass.name}\$$testName\$B")) + + val cSchema = curruptSchema.carpenterSchema() + assertEquals(2, cSchema.size) + + val aCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } + val bCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$B") } + + assert(aCarpenterSchema != null) + assert(bCarpenterSchema != null) + + val cc = ClassCarpenter() + val cc2 = ClassCarpenter() + + val bBuilder = cc.build(bCarpenterSchema!!) + bBuilder.constructors[0].newInstance(a, testIIII) + + val aBuilder = cc.build(aCarpenterSchema!!) + val objA = aBuilder.constructors[0].newInstance(testI) + + /* build a second B this time using our constructed instane of A and not the + local one we pre defined */ + bBuilder.constructors[0].newInstance(objA, testIIII) + + /* whittle and instantiate a different A with a new class loader */ + val aBuilder2 = cc2.build(aCarpenterSchema) + val objA2 = aBuilder2.constructors[0].newInstance(testI) + + bBuilder.constructors[0].newInstance(objA2, testIIII) + } + + /* this time remove the nested interface from out set of known classes forcing us + to whittle it */ + @Test + fun memberInterface2() { + val testI = 25 + val testIIII = 50 + val testName = "memberInterface2" + + class A(override val i: Int) : I + class B(override val i : I, override val iiii : Int) : IIII + + val a = A(testI) + val b = B(a, testIIII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val serSchema = obj.second.schema + + /* + * The classes we're expecting to find: + * class A + * class A's interface (class I) + * class B + * class B's interface (class IIII) + */ + assertEquals(4, serSchema.types.size) + + val curruptSchema = curruptName (serSchema, listOf ( + "${this.javaClass.name}\$$testName\$A", + "${this.javaClass.getPackage().name}.I")) + + + val carpenterSchema = curruptSchema.carpenterSchema() + + val aCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } +// val bCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.getPackage().name}.I") } + + val cc = ClassCarpenter() + + val aBuilder = cc.build (aCarpenterSchema!!) + aBuilder.constructors[0].newInstance(testI) + + + /* + + var cc2 = ClassCarpenter() + + var bBuilder = cc.build(bCarpenterSchema!!) + val objB = bBuilder.constructors[0].newInstance(a, testIIII) + + var aBuilder = cc.build(aCarpenterSchema!!) + val objA = aBuilder.constructors[0].newInstance(testI) + + /* build a second B this time using our constructed instane of A and not the + local one we pre defined */ + val objB2 = bBuilder.constructors[0].newInstance(objA, testIIII) + + /* whittle and instantiate a different A with a new class loader */ + var aBuilder2 = cc2.build(aCarpenterSchema!!) + val objA2 = aBuilder2.constructors[0].newInstance(testI) + + val objB3 = bBuilder.constructors[0].newInstance(objA2, testIIII) + */ + } + +} + + diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 51d9a0c128..018ead9912 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -38,7 +38,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("int", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance(testA, testB) @@ -74,7 +74,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("string", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance(testA, testB) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 87f2c51379..d3d26075d5 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,6 +1,7 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.ClassCarpenterSchema import net.corda.core.serialization.amqp.* import org.junit.Test import kotlin.test.assertEquals @@ -17,8 +18,8 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : Int) - var a = A (test) + val a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) assert (obj.first is A) @@ -28,13 +29,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("int", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + val pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -64,7 +65,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("string", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -128,7 +129,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("long", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -159,7 +160,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("short", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -223,7 +224,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("double", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -254,7 +255,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("float", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) From b4a62722fc9f3e0422d6460fd77a60338731d56f Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 23 Jun 2017 09:02:00 +0100 Subject: [PATCH 012/197] Add recursive dependency resolution to schema generation A complete amqp schema can now be used to generate a set of carpetner schemas representing all of the classes not found on the deserialzing end. A dependency and depdendent chain is setup such that all classes are created in the order required Squashed commit messages: * IntelliJ reformat of the code * Merge the interface synthesis changes and rebase onto the tip of master Somethign is very broken in the AMQP -> Carpenter schema code but want a commit so I at least know the actual carpenter is merged * Nested schema creation now works with dependencies recursvly created in the carpenter * Unit test fixes * Remove spurious prints from tests * Remove warnings * Don't add cladd member dep by name Since that's the name of the field, not the type we depend on. If we do we'll never actually be able to craft the type as the dependency chain will be horribly broken Various bug fixes --- .../corda/core/serialization/amqp/Schema.kt | 164 ++++++--- .../serialization/carpenter/ClassCarpenter.kt | 35 +- .../serialization/carpenter/MetaCarpenter.kt | 28 ++ .../core/serialization/carpenter/Schema.kt | 16 +- .../carpenter/ClassCarpenterTest.kt | 30 +- .../net/corda/carpenter/MetaCapenter.kt | 53 +++ .../carpenter/ClassCarpenterTestUtils.kt | 56 +++ ...berCompositeSchemaToClassCarpenterTests.kt | 292 +++++++++++++-- .../InheritanceSchemaToClassCarpenterTests.kt | 340 ++++++++++++------ ...berCompositeSchemaToClassCarpenterTests.kt | 32 +- ...berCompositeSchemaToClassCarpenterTests.kt | 72 ++-- 11 files changed, 855 insertions(+), 263 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt create mode 100644 experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index 2c34698e42..bc7d7efc15 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -15,7 +15,9 @@ import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* -import net.corda.core.serialization.ClassCarpenterSchema +import net.corda.core.serialization.carpenter.CarpenterSchemas +import net.corda.core.serialization.carpenter.Schema as CarpenterSchema +import net.corda.core.serialization.carpenter.CarpenterSchemaFactory // TODO: get an assigned number as per AMQP spec val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 @@ -25,6 +27,24 @@ val DESCRIPTOR_DOMAIN: String = "net.corda" // "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray()) +private fun List.exists (clazz: String) = + this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null + +private fun List.loadIfExists (clazz: String) : Class<*> { + this.forEach { + try { + return it.loadClass(clazz) + } catch (e: ClassNotFoundException) { + return@forEach + } + } + throw ClassNotFoundException(clazz) +} + +class UncarpentableException (name: String, field: String, type: String) : + Throwable ("Class $name is loadable yet contains field $field of unknown type $type") + + /** * This class wraps all serialized data, so that the schema can be carried along with it. We will provide various internal utilities * to decompose and recompose with/without schema etc so that e.g. we can store objects with a (relationally) normalised out schema to @@ -91,28 +111,12 @@ data class Schema(val types: List) : DescribedType { override fun toString(): String = types.joinToString("\n") fun carpenterSchema(loaders : List = listOf(ClassLoader.getSystemClassLoader())) - : List + : CarpenterSchemas { - var rtn = mutableListOf() + var rtn = CarpenterSchemas.newInstance() - for (type in types) { - if (type is CompositeType) { - var foundIt = false - for (loader in loaders) { - try { - loader.loadClass(type.name) - foundIt = true - break - } catch (e: ClassNotFoundException) { - continue - } - } - - if (foundIt) continue - else { - rtn.add(type.carpenterSchema()) - } - } + types.filterIsInstance().forEach { + it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn) } return rtn @@ -199,17 +203,9 @@ data class Field(var name: String, val type: String, val requires: List, return sb.toString() } - inline fun isKnownClass (type : String) : Class<*> { - try { - return ClassLoader.getSystemClassLoader().loadClass(type) - } - catch (e: ClassNotFoundException) { - // call carpenter - throw IllegalArgumentException ("${type} - ${name} - pants") - } - } - - fun getPrimType() = when (type) { + fun getTypeAsClass( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) + ) = when (type) { "int" -> Int::class.javaPrimitiveType!! "string" -> String::class.java "short" -> Short::class.javaPrimitiveType!! @@ -217,10 +213,27 @@ data class Field(var name: String, val type: String, val requires: List, "char" -> Char::class.javaPrimitiveType!! "boolean" -> Boolean::class.javaPrimitiveType!! "double" -> Double::class.javaPrimitiveType!! - "float" -> Double::class.javaPrimitiveType!! - "*" -> isKnownClass(requires[0]) - else -> isKnownClass(type) + "float" -> Float::class.javaPrimitiveType!! + "*" -> classLoaders.loadIfExists(requires[0]) + else -> classLoaders.loadIfExists(type) } + + fun validateType( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) + ) = when (type) { + "int" -> Int::class.javaPrimitiveType!! + "string" -> String::class.java + "short" -> Short::class.javaPrimitiveType!! + "long" -> Long::class.javaPrimitiveType!! + "char" -> Char::class.javaPrimitiveType!! + "boolean" -> Boolean::class.javaPrimitiveType!! + "double" -> Double::class.javaPrimitiveType!! + "float" -> Float::class.javaPrimitiveType!! + "*" -> if (classLoaders.exists(requires[0])) requires[0] else null + else -> if (classLoaders.exists (type)) type else null + } + + fun typeAsString() = if (type =="*") requires[0] else type } sealed class TypeNotation : DescribedType { @@ -287,32 +300,69 @@ data class CompositeType(override var name: String, override val label: String?, return sb.toString() } - fun carpenterSchema(classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) : ClassCarpenterSchema { - var m : MutableMap> = mutableMapOf() + /** if we can load the class then we MUST know about all of it's sub elements, + otherwise we couldn't know about this */ + private fun validateKnown ( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) + { + fields.forEach { + if (it.validateType(classLoaders) == null) throw UncarpentableException (name, it.name, it.type) + } + } - fields.forEach { m[it.name] = it.getPrimType() } + fun carpenterSchema( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()), + carpenterSchemas : CarpenterSchemas, + force : Boolean = false) + { + /* first question, do we know about this type or not */ + if (classLoaders.exists(name)) { + validateKnown(classLoaders) - var providesList = mutableListOf>() - - for (iface in provides) { - var found = false - for (loader in classLoaders) { - try { - providesList.add (loader.loadClass(iface)) - found = true - break - } - catch (e: ClassNotFoundException) { - continue - } - } - if (found == false) { - println ("This needs to work but it wont - ${iface}") - } - else println ("found it ${iface}") + if (!force) return } - return ClassCarpenterSchema (name, m, interfaces = providesList) + val providesList = mutableListOf>() + + var isInterface = false + var isCreatable = true + + provides.forEach { + if (name.equals(it)) { + isInterface = true + return@forEach + } + + try { + providesList.add (classLoaders.loadIfExists(it)) + } + catch (e: ClassNotFoundException) { + carpenterSchemas.addDepPair(this, name, it) + + isCreatable = false + } + } + + val m : MutableMap> = mutableMapOf() + + fields.forEach { + try { + m[it.name] = it.getTypeAsClass(classLoaders) + } + catch (e: ClassNotFoundException) { + carpenterSchemas.addDepPair(this, name, it.typeAsString()) + + isCreatable = false + } + } + + if (isCreatable) { + carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance( + name = name, + fields = m, + interfaces = providesList, + isInterface = isInterface)) + } } } diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index efda30bea4..d046d71de2 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -8,6 +8,10 @@ import org.objectweb.asm.Type import java.lang.Character.isJavaIdentifierPart import java.lang.Character.isJavaIdentifierStart +import net.corda.core.serialization.carpenter.Schema +import net.corda.core.serialization.carpenter.ClassSchema +import net.corda.core.serialization.carpenter.InterfaceSchema + import java.util.* /** @@ -64,6 +68,15 @@ interface SimpleFieldAccess { * * Equals/hashCode methods are not yet supported. */ + +/**********************************************************************************************************************/ + +class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { + fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) +} + +/**********************************************************************************************************************/ + class ClassCarpenter { // TODO: Generics. // TODO: Sandbox the generated code when a security manager is in use. @@ -159,18 +172,13 @@ class ClassCarpenter { } } - private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { - fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) - } - - private val classloader = CarpenterClassLoader() - - fun classLoader() = classloader as ClassLoader + val classloader = CarpenterClassLoader() private val _loaded = HashMap>() + private val String.jvm: String get() = replace(".", "/") /** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */ - fun loaded() : Map> = HashMap(_loaded) + val loaded: Map> = HashMap(_loaded) /** * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to @@ -178,17 +186,18 @@ class ClassCarpenter { * * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) */ - fun build(schema: ClassCarpenterSchema): Class<*> { + fun build(schema: Schema): Class<*> { validateSchema(schema) // Walk up the inheritance hierarchy and then start walking back down once we either hit the top, or // find a class we haven't generated yet. - val hierarchy = ArrayList() + val hierarchy = ArrayList() hierarchy += schema var cursor = schema.superclass while (cursor != null && cursor.name !in _loaded) { hierarchy += cursor cursor = cursor.superclass } + hierarchy.reversed().forEach { when (it) { is InterfaceSchema -> generateInterface(it) @@ -196,6 +205,8 @@ class ClassCarpenter { } } + assert (schema.name in _loaded) + return _loaded[schema.name]!! } @@ -288,7 +299,8 @@ class ClassCarpenter { private fun ClassWriter.generateGetters(schema: Schema) { for ((name, type) in schema.fields) { - with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + type.descriptor, null, null)) { + val opcodes = ACC_PUBLIC + with(visitMethod(opcodes, "get" + name.capitalize(), "()" + type.descriptor, null, null)) { type.addNullabilityAnnotation(this) visitCode() visitVarInsn(ALOAD, 0) // Load 'this' @@ -305,7 +317,6 @@ class ClassCarpenter { visitEnd() } } - } private fun ClassWriter.generateAbstractGetters(schema: Schema) { diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt new file mode 100644 index 0000000000..821308658c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt @@ -0,0 +1,28 @@ +package net.corda.core.serialization.carpenter + +import net.corda.core.serialization.amqp.TypeNotation + +data class CarpenterSchemas ( + val carpenterSchemas : MutableList, + val dependencies : MutableMap>>, + val dependsOn : MutableMap>) { + companion object CarpenterSchemaConstructor { + fun newInstance(): CarpenterSchemas { + return CarpenterSchemas( + mutableListOf(), + mutableMapOf>>(), + mutableMapOf>()) + } + } + + fun addDepPair(type: TypeNotation, dependant: String, dependee: String) { + fun String.name() = this.split ('.').last().split('$').last() + println ("add dep ${dependant.name()} on ${dependee.name()}") + dependsOn.computeIfAbsent(dependee, { mutableListOf() }).add(dependant) + dependencies.computeIfAbsent(dependant, { Pair(type, mutableListOf()) }).second.add(dependee) + } + + + val size + get() = carpenterSchemas.size +} diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt index 0a2b73b490..7bca4512b0 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -34,11 +34,23 @@ class ClassSchema( fields: Map>, superclass: Schema? = null, interfaces: List> = emptyList() -) : ClassCarpenter.Schema (name, fields, superclass, interfaces) +) : Schema (name, fields, superclass, interfaces) class InterfaceSchema( name: String, fields: Map>, superclass: Schema? = null, interfaces: List> = emptyList() -) : ClassCarpenter.Schema (name, fields, superclass, interfaces) +) : Schema (name, fields, superclass, interfaces) + +object CarpenterSchemaFactory { + fun newInstance ( + name: String, + fields: Map>, + superclass: Schema? = null, + interfaces: List> = emptyList(), + isInterface: Boolean = false + ) : Schema = + if (isInterface) InterfaceSchema (name, fields, superclass, interfaces) + else ClassSchema (name, fields, superclass, interfaces) +} diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index fbe6fa31b5..b816b86351 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -8,31 +8,11 @@ import java.beans.Introspector import kotlin.test.assertNotEquals class ClassCarpenterTest { - /* - cw.visitInnerClass( - "net/corda/carpenter/ClassCarpenterTest$DummyInterface", - "net/corda/carpenter/ClassCarpenterTest", - "DummyInterface", - ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE); - */ interface DummyInterface { val a: String val b: Int } - /* - cw.visitInnerClass( - "net/corda/carpenter/ClassCarpenterTest$Dummy", - "net/corda/carpenter/ClassCarpenterTest", - "Dummy", - ACC_PUBLIC + ACC_FINAL + ACC_STATIC); - */ - class Dummy (override val a: String, override val b: Int) : DummyInterface - - val dummy = Dummy ("hi", 1) - val dummy2 = Dummy2 ("hi", 1) - - val cc = ClassCarpenter() // We have to ignore synthetic fields even though ClassCarpenter doesn't create any because the JaCoCo @@ -191,7 +171,7 @@ class ClassCarpenterTest { assertEquals(iface.declaredMethods.size, 1) assertEquals(iface.declaredMethods[0].name, "getA") - val schema2 = ClassCarpenter.ClassSchema( + val schema2 = ClassSchema( "gen.Derived", mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)), interfaces = listOf(iface)) @@ -217,7 +197,7 @@ class ClassCarpenterTest { "c" to ClassCarpenter.NonNullableField(Int::class.java), "d" to ClassCarpenter.NonNullableField(String::class.java))) - val class1 = ClassCarpenter.ClassSchema( + val class1 = ClassSchema( "gen.Derived", mapOf( "a" to ClassCarpenter.NonNullableField(Int::class.java), @@ -241,20 +221,20 @@ class ClassCarpenterTest { @Test fun `interface implementing interface`() { - val iFace1 = ClassCarpenter.InterfaceSchema( + val iFace1 = InterfaceSchema( "gen.Interface1", mapOf( "a" to ClassCarpenter.NonNullableField (Int::class.java), "b" to ClassCarpenter.NonNullableField(String::class.java))) - val iFace2 = ClassCarpenter.InterfaceSchema( + val iFace2 = InterfaceSchema( "gen.Interface2", mapOf( "c" to ClassCarpenter.NonNullableField(Int::class.java), "d" to ClassCarpenter.NonNullableField(String::class.java)), interfaces = listOf(cc.build(iFace1))) - val class1 = ClassCarpenter.ClassSchema( + val class1 = ClassSchema( "gen.Derived", mapOf( "a" to ClassCarpenter.NonNullableField(Int::class.java), diff --git a/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt b/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt new file mode 100644 index 0000000000..3595716cf2 --- /dev/null +++ b/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt @@ -0,0 +1,53 @@ +package net.corda.carpenter + +import net.corda.core.serialization.carpenter.CarpenterSchemas +import net.corda.core.serialization.carpenter.Schema + +import net.corda.core.serialization.amqp.CompositeType + +abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { + + private val cc = ClassCarpenter() + val objects = mutableMapOf>() + + fun step (newObject : Schema) { + objects[newObject.name] = cc.build (newObject) + + /* go over the list of everything that had a dependency on the newly + carpented class existing and remove it from their dependency list, If that + list is now empty we have no impediment to carpenting that class up */ + schemas.dependsOn.remove(newObject.name)?.forEach { dependent -> + assert (newObject.name in schemas.dependencies[dependent]!!.second) + + schemas.dependencies[dependent]?.second?.remove(newObject.name) + + /* we're out of blockers so we can now create the type */ + if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) { + (schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema ( + classLoaders = listOf ( + ClassLoader.getSystemClassLoader(), + cc.classloader), + carpenterSchemas = schemas) + } + } + } + + abstract fun build() +} + +class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { + override fun build() { + while (schemas.carpenterSchemas.isNotEmpty()) { + val newObject = schemas.carpenterSchemas.removeAt(0) + step (newObject) + } + } +} + +class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { + override fun build() { + println ("TestMetaCarpenter::build") + if (schemas.carpenterSchemas.isEmpty()) return + step (schemas.carpenterSchemas.removeAt(0)) + } +} diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt new file mode 100644 index 0000000000..09f768dcef --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt @@ -0,0 +1,56 @@ +package net.corda.carpenter.test + +import net.corda.core.serialization.amqp.CompositeType +import net.corda.core.serialization.amqp.Field +import net.corda.core.serialization.amqp.Schema +import net.corda.core.serialization.amqp.TypeNotation +import net.corda.core.serialization.amqp.SerializerFactory +import net.corda.core.serialization.amqp.SerializationOutput + +/**********************************************************************************************************************/ + +fun curruptName(name: String) = "${name}__carpenter" + +/**********************************************************************************************************************/ + +/* given a list of class names work through the amqp envelope schema and alter any that + match in the fashion defined above */ +fun Schema.curruptName (names: List) : Schema { + val newTypes : MutableList = mutableListOf() + + for (type in types) { + val newName = if (type.name in names) curruptName (type.name) else type.name + + val newProvides = type.provides.map { + it -> if (it in names) curruptName (it) else it + } + + val newFields = mutableListOf() + + (type as CompositeType).fields.forEach { + val type = if (it.type in names) curruptName (it.type) else it.type + + val requires = if (it.requires.isNotEmpty() && (it.requires[0] in names)) + listOf (curruptName (it.requires[0])) else it.requires + + newFields.add (it.copy (type=type, requires=requires)) + } + + newTypes.add (type.copy (name=newName, provides=newProvides, fields=newFields)) + } + + return Schema (types=newTypes) +} + +/**********************************************************************************************************************/ + +open class AmqpCarpenterBase { + var factory = SerializerFactory() + + fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) + fun testName() = Thread.currentThread().stackTrace[2].methodName + inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" +} + +/**********************************************************************************************************************/ + diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index abbdb5acf5..ced1e4f345 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -2,17 +2,25 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* -import net.corda.core.serialization.ClassCarpenterSchema + import org.junit.Test import kotlin.test.assertEquals +import net.corda.carpenter.test.* +import kotlin.test.assertFalse +import kotlin.test.assertTrue -class Inheritance { - private var factory = SerializerFactory() +@CordaSerializable +interface I_ { + val a: Int +} - fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) +/* + * Where a class has a member that is also a composite type or interface + */ +class CompositeMembers : AmqpCarpenterBase() { @Test - fun nestedInts() { + fun bothKnown () { val testA = 10 val testB = 20 @@ -20,9 +28,9 @@ class Inheritance { data class A(val a: Int) @CordaSerializable - class B (val a: A, var b: Int) + data class B (val a: A, var b: Int) - var b = B(A(testA), testB) + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) @@ -36,9 +44,6 @@ class Inheritance { assert(obj.second.schema.types[0] is CompositeType) assert(obj.second.schema.types[1] is CompositeType) - println (obj.second.schema.types[0] as CompositeType) - println (obj.second.schema.types[1] as CompositeType) - var amqpSchemaA : CompositeType? = null var amqpSchemaB : CompositeType? = null @@ -52,31 +57,266 @@ class Inheritance { assert (amqpSchemaA != null) assert (amqpSchemaB != null) + /* + * Just ensure the amqp schema matches what we want before we go messing + * around with the internals + */ assertEquals(1, amqpSchemaA?.fields?.size) assertEquals("a", amqpSchemaA!!.fields[0].name) - assertEquals("int", amqpSchemaA!!.fields[0].type) - + assertEquals("int", amqpSchemaA.fields[0].type) assertEquals(2, amqpSchemaB?.fields?.size) assertEquals("a", amqpSchemaB!!.fields[0].name) - assertEquals("${this.javaClass.name}\$nestedInts\$A", amqpSchemaB!!.fields[0].type) - assertEquals("b", amqpSchemaB!!.fields[1].name) - assertEquals("int", amqpSchemaB!!.fields[1].type) + assertEquals(classTestName("A"), amqpSchemaB.fields[0].type) + assertEquals("b", amqpSchemaB.fields[1].name) + assertEquals("int", amqpSchemaB.fields[1].type) - var ccA = ClassCarpenter().build(amqpSchemaA.carpenterSchema()) - var ccB = ClassCarpenter().build(amqpSchemaB.carpenterSchema()) + val metaSchema = obj.second.schema.carpenterSchema() - /* - * Since A is known to the JVM we can't constuct B with and instance of the carpented A but - * need to use the defined one above - */ - val instanceA = ccA.constructors[0].newInstance(testA) - val instanceB = ccB.constructors[0].newInstance(A (testA), testB) + /* if we know all the classes there is nothing to really achieve here */ + assert (metaSchema.carpenterSchemas.isEmpty()) + assert (metaSchema.dependsOn.isEmpty()) + assert (metaSchema.dependencies.isEmpty()) + } - assertEquals (ccA.getMethod("getA").invoke(instanceA), amqpObj.a.a) - assertEquals ((ccB.getMethod("getA").invoke(instanceB) as A).a, amqpObj.a.a) - assertEquals (ccB.getMethod("getB").invoke(instanceB), amqpObj.b) + /* you cannot have an element of a composite class we know about + that is unknown as that should be impossible. If we have the containing + class in the class path then we must have all of it's constituent elements */ + @Test(expected = UncarpentableException::class) + fun nestedIsUnknown () { + val testA = 10 + val testB = 20 + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"))) + + assert(obj.first is B) + + amqpSchema.carpenterSchema() + } + + @Test + fun ParentIsUnknown () { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("B"))) + + val carpenterSchema = amqpSchema.carpenterSchema() + + assertEquals (1, carpenterSchema.size) + + val metaCarpenter = MetaCarpenter (carpenterSchema) + + metaCarpenter.build() + + assert (curruptName(classTestName("B")) in metaCarpenter.objects) + } + + @Test + fun BothUnkown () { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + + val carpenterSchema = amqpSchema.carpenterSchema() + + /* just verify we're in the expected initial state, A is carpentable, B is not because + it depends on A and the dependency chains are in place */ + assertEquals (1, carpenterSchema.size) + assertEquals (curruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) + assertEquals (1, carpenterSchema.dependencies.size) + assert (curruptName(classTestName("B")) in carpenterSchema.dependencies) + assertEquals (1, carpenterSchema.dependsOn.size) + assert (curruptName(classTestName("A")) in carpenterSchema.dependsOn) + + /* test meta carpenter lets us single step over the creation */ + val metaCarpenter = TestMetaCarpenter (carpenterSchema) + + /* we've built nothing so nothing should be there */ + assertEquals (0, metaCarpenter.objects.size) + + /* first iteration, carpent A, resolve deps and mark B as carpentable */ + metaCarpenter.build() + + /* one build iteration should have carpetned up A and worked out that B is now buildable + given it's depedencies have been satisfied */ + assertTrue (curruptName(classTestName("A")) in metaCarpenter.objects) + assertFalse (curruptName(classTestName("B")) in metaCarpenter.objects) + + assertEquals (1, carpenterSchema.carpenterSchemas.size) + assertEquals (curruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) + assertTrue (carpenterSchema.dependencies.isEmpty()) + assertTrue (carpenterSchema.dependsOn.isEmpty()) + + /* second manual iteration, will carpent B */ + metaCarpenter.build() + assert (curruptName(classTestName("A")) in metaCarpenter.objects) + assert (curruptName(classTestName("B")) in metaCarpenter.objects) + + assertTrue (carpenterSchema.carpenterSchemas.isEmpty()) + } + + @Test(expected = UncarpentableException::class) + fun nestedIsUnkownInherited () { + val testA = 10 + val testB = 20 + val testC = 30 + + @CordaSerializable + open class A(val a: Int) + + @CordaSerializable + class B(a: Int, var b: Int) : A (a) + + @CordaSerializable + data class C(val b: B, var c: Int) + + val c = C(B(testA, testB), testC) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + + assert(obj.first is C) + + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + + amqpSchema.carpenterSchema() + } + + @Test(expected = UncarpentableException::class) + fun nestedIsUnknownInheritedUnkown () { + val testA = 10 + val testB = 20 + val testC = 30 + + @CordaSerializable + open class A(val a: Int) + + @CordaSerializable + class B(a: Int, var b: Int) : A (a) + + @CordaSerializable + data class C(val b: B, var c: Int) + + val c = C(B(testA, testB), testC) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + + assert(obj.first is C) + + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + + amqpSchema.carpenterSchema() + } + + @Test + fun parentsIsUnknownWithUnkownInheritedMember () { + val testA = 10 + val testB = 20 + val testC = 30 + + @CordaSerializable + open class A(val a: Int) + + @CordaSerializable + class B(a: Int, var b: Int) : A (a) + + @CordaSerializable + data class C(val b: B, var c: Int) + + val c = C(B(testA, testB), testC) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + + assert(obj.first is C) + + val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + } + + + /* + * In this case B holds an element of Interface I_ which is an A but we don't know of A + * but we do know about I_ + */ + @Test + fun nestedIsInterfaceToUnknown () { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + } + + @Test + fun nestedIsUnknownInterface() { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + } + + @Test + fun ParentsIsInterfaceToUnkown() { + val testA = 10 + val testB = 20 + + @CordaSerializable + data class A(override val a: Int) : I_ + + @CordaSerializable + data class B(val a: A, var b: Int) + val b = B(A(testA), testB) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt index c009a1d356..129dfea0a7 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -3,7 +3,9 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* import org.junit.Test -import kotlin.test.assertEquals +import net.corda.carpenter.test.* +import net.corda.carpenter.MetaCarpenter +import kotlin.test.* /*******************************************************************************************************/ @@ -38,42 +40,11 @@ interface IIII { /*******************************************************************************************************/ -class InheritanceSchemaToClassCarpenterTests { - private var factory = SerializerFactory() - - fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) - - fun curruptName(name: String) = "${name}__carpenter" - - /* given a list of class names work through the amqp envelope schema and alter any that - match in the fashion defined above */ - fun curruptName (schema: Schema, names: List) : Schema { - val types = schema.types - - val newTypes : MutableList = mutableListOf() - - for (type in types) { - val newName = if (type.name in names) curruptName (type.name) else type.name - - val newProvides = type.provides.map { - it -> if (it in names) curruptName (it) else it - } - - val newFields = (type as CompositeType).fields.map { - it -> if ( it.requires.isNotEmpty() && it.requires[0] in names) - it.copy (requires=listOf (curruptName (it.requires[0]))) else it - } - - newTypes.add (type.copy (name=newName, provides=newProvides, fields=newFields)) - } - - return Schema (types=newTypes) - } +class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun interfaceParent1() { val testJ = 20 - val testName = "interfaceParent1" class A(override val j: Int) : J @@ -95,17 +66,20 @@ class InheritanceSchemaToClassCarpenterTests { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) val l2 = curruptSchema.carpenterSchema() assertEquals(1, l2.size) + val aSchema = l2.carpenterSchemas.find { it.name == curruptName(classTestName("A")) } - assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) - assertEquals(1, l2[0].interfaces.size) - assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + assertNotEquals (null, aSchema) - val aBuilder = ClassCarpenter().build(l2[0]) + assertEquals(curruptName(classTestName("A")), aSchema!!.name) + assertEquals(1, aSchema.interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, aSchema.interfaces[0]) + + val aBuilder = ClassCarpenter().build(aSchema) val objJ = aBuilder.constructors[0].newInstance(testJ) val j = objJ as J @@ -119,7 +93,6 @@ class InheritanceSchemaToClassCarpenterTests { fun interfaceParent2() { val testJ = 20 val testJJ = 40 - val testName = "interfaceParent2" class A(override val j: Int, val jj: Int) : J @@ -142,17 +115,22 @@ class InheritanceSchemaToClassCarpenterTests { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val curruptSchema = serSchema.curruptName (listOf (classTestName("A"))) + val aName = curruptName(classTestName("A")) val l2 = curruptSchema.carpenterSchema() assertEquals(1, l2.size) - assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) - assertEquals(1, l2[0].interfaces.size) - assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + val aSchema = l2.carpenterSchemas.find { it.name == aName } - val aBuilder = ClassCarpenter().build(l2[0]) + assertNotEquals(null, aSchema) + + assertEquals(aName, aSchema!!.name) + assertEquals(1, aSchema.interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, aSchema.interfaces[0]) + + val aBuilder = ClassCarpenter().build(aSchema) val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ) val j = objJ as J @@ -167,7 +145,6 @@ class InheritanceSchemaToClassCarpenterTests { fun multipleInterfaces() { val testI = 20 val testII = 40 - val testName = "multipleInterfaces" class A(override val i: Int, override val ii: Int) : I, II @@ -189,17 +166,22 @@ class InheritanceSchemaToClassCarpenterTests { /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus needs some carpentry */ - val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) - val l2 = curruptSchema.carpenterSchema() + val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) + val l2 = curruptSchema.carpenterSchema() + val aName = curruptName(classTestName("A")) assertEquals(1, l2.size) - assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) - assertEquals(2, l2[0].interfaces.size) - assert (net.corda.carpenter.I::class.java in l2[0].interfaces) - assert (net.corda.carpenter.II::class.java in l2[0].interfaces) + val aSchema = l2.carpenterSchemas.find { it.name == aName } - val aBuilder = ClassCarpenter().build(l2[0]) + assertNotEquals(null, aSchema) + + assertEquals(aName, aSchema!!.name) + assertEquals(2, aSchema.interfaces.size) + assert (net.corda.carpenter.I::class.java in aSchema.interfaces) + assert (net.corda.carpenter.II::class.java in aSchema.interfaces) + + val aBuilder = ClassCarpenter().build(aSchema) val objA = aBuilder.constructors[0].newInstance(testI, testII) val i = objA as I @@ -216,7 +198,6 @@ class InheritanceSchemaToClassCarpenterTests { fun nestedInterfaces() { val testI = 20 val testIII = 60 - val testName = "nestedInterfaces" class A(override val i: Int, override val iii : Int) : III @@ -235,17 +216,22 @@ class InheritanceSchemaToClassCarpenterTests { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) val l2 = curruptSchema.carpenterSchema() + val aName = curruptName(classTestName("A")) assertEquals(1, l2.size) - assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) - assertEquals(2, l2[0].interfaces.size) - assert (net.corda.carpenter.I::class.java in l2[0].interfaces) - assert (net.corda.carpenter.III::class.java in l2[0].interfaces) + val aSchema = l2.carpenterSchemas.find { it.name == aName } - val aBuilder = ClassCarpenter().build(l2[0]) + assertNotEquals(null, aSchema) + + assertEquals(aName, aSchema!!.name) + assertEquals(2, aSchema.interfaces.size) + assert (net.corda.carpenter.I::class.java in aSchema.interfaces) + assert (net.corda.carpenter.III::class.java in aSchema.interfaces) + + val aBuilder = ClassCarpenter().build(aSchema) val objA = aBuilder.constructors[0].newInstance(testI, testIII) val i = objA as I @@ -263,7 +249,6 @@ class InheritanceSchemaToClassCarpenterTests { fun memberInterface() { val testI = 25 val testIIII = 50 - val testName = "memberInterface" class A(override val i: Int) : I class B(override val i : I, override val iiii : Int) : IIII @@ -285,17 +270,18 @@ class InheritanceSchemaToClassCarpenterTests { */ assertEquals(4, serSchema.types.size) - val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A", - "${this.javaClass.name}\$$testName\$B")) + val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"), classTestName ("B"))) + val cSchema = curruptSchema.carpenterSchema() + val aName = curruptName(classTestName("A")) + val bName = curruptName(classTestName("B")) - val cSchema = curruptSchema.carpenterSchema() assertEquals(2, cSchema.size) - val aCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } - val bCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$B") } + val aCarpenterSchema = cSchema.carpenterSchemas.find { it.name == aName } + val bCarpenterSchema = cSchema.carpenterSchemas.find { it.name == bName } - assert(aCarpenterSchema != null) - assert(bCarpenterSchema != null) + assertNotEquals (null, aCarpenterSchema) + assertNotEquals (null, bCarpenterSchema) val cc = ClassCarpenter() val cc2 = ClassCarpenter() @@ -317,13 +303,12 @@ class InheritanceSchemaToClassCarpenterTests { bBuilder.constructors[0].newInstance(objA2, testIIII) } - /* this time remove the nested interface from out set of known classes forcing us - to whittle it */ - @Test + /* if we remove the neted interface we should get an error as it's impossible + to have a concrete class loaded without having access to all of it's elements */ + @Test(expected = UncarpentableException::class) fun memberInterface2() { val testI = 25 val testIIII = 50 - val testName = "memberInterface2" class A(override val i: Int) : I class B(override val i : I, override val iiii : Int) : IIII @@ -346,44 +331,185 @@ class InheritanceSchemaToClassCarpenterTests { */ assertEquals(4, serSchema.types.size) - val curruptSchema = curruptName (serSchema, listOf ( - "${this.javaClass.name}\$$testName\$A", - "${this.javaClass.getPackage().name}.I")) - - - val carpenterSchema = curruptSchema.carpenterSchema() - - val aCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } -// val bCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.getPackage().name}.I") } - - val cc = ClassCarpenter() - - val aBuilder = cc.build (aCarpenterSchema!!) - aBuilder.constructors[0].newInstance(testI) - - - /* - - var cc2 = ClassCarpenter() - - var bBuilder = cc.build(bCarpenterSchema!!) - val objB = bBuilder.constructors[0].newInstance(a, testIIII) - - var aBuilder = cc.build(aCarpenterSchema!!) - val objA = aBuilder.constructors[0].newInstance(testI) - - /* build a second B this time using our constructed instane of A and not the - local one we pre defined */ - val objB2 = bBuilder.constructors[0].newInstance(objA, testIIII) - - /* whittle and instantiate a different A with a new class loader */ - var aBuilder2 = cc2.build(aCarpenterSchema!!) - val objA2 = aBuilder2.constructors[0].newInstance(testI) - - val objB3 = bBuilder.constructors[0].newInstance(objA2, testIIII) - */ + /* ignore the return as we expect this to throw */ + serSchema.curruptName (listOf ( + classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema() } + @Test + fun interfaceAndImplementation() { + val testI = 25 + + class A(override val i: Int) : I + + val a = A(testI) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + /* + * The classes we're expecting to find: + * class A + * class A's interface (class I) + */ + assertEquals(2, serSchema.types.size) + + val amqpSchema = serSchema.curruptName (listOf (classTestName("A"), "${this.javaClass.`package`.name}.I")) + + val aName = curruptName (classTestName("A")) + val iName = curruptName ("${this.javaClass.`package`.name}.I") + + val carpenterSchema = amqpSchema.carpenterSchema() + + /* whilst there are two unknown classes within the envelope A depends on I so we can't construct a + schema for A until we have for I */ + assertEquals (1, carpenterSchema.size) + assertNotEquals (null, carpenterSchema.carpenterSchemas.find { it.name == iName }) + + /* since we can't build A it should list I as a dependency*/ + assert (aName in carpenterSchema.dependencies) + assertEquals (1, carpenterSchema.dependencies[aName]!!.second.size) + assertEquals (iName, carpenterSchema.dependencies[aName]!!.second[0]) + + /* and conversly I should have A listed as a dependent */ + assert(iName in carpenterSchema.dependsOn) + assertEquals(1, carpenterSchema.dependsOn[iName]!!.size) + assertEquals(aName, carpenterSchema.dependsOn[iName]!![0]) + + val mc = MetaCarpenter (carpenterSchema) + + mc.build() + + assertEquals (0, mc.schemas.carpenterSchemas.size) + assertEquals (0, mc.schemas.dependencies.size) + assertEquals (0, mc.schemas.dependsOn.size) + assertEquals (2, mc.objects.size) + assert (aName in mc.objects) + assert (iName in mc.objects) + + mc.objects[aName]!!.constructors[0].newInstance(testI) + } + + @Test + fun twoInterfacesAndImplementation() { + val testI = 69 + val testII = 96 + + class A(override val i: Int, override val ii: Int) : I, II + + val a = A(testI, testII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + val amqpSchema = obj.second.schema.curruptName(listOf( + classTestName("A"), + "${this.javaClass.`package`.name}.I", + "${this.javaClass.`package`.name}.II")) + + val aName = curruptName (classTestName("A")) + val iName = curruptName ("${this.javaClass.`package`.name}.I") + val iiName = curruptName ("${this.javaClass.`package`.name}.II") + + val carpenterSchema = amqpSchema.carpenterSchema() + + /* there is nothing preventing us from carpenting up the two interfaces so + our initial list should contain both interface with A being dependent on both + and each having A as a dependent */ + assertEquals (2, carpenterSchema.carpenterSchemas.size) + assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iName }) + assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iiName }) + assertNull (carpenterSchema.carpenterSchemas.find { it.name == aName }) + + assert (iName in carpenterSchema.dependsOn) + assertEquals (1, carpenterSchema.dependsOn[iName]?.size) + assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == aName })) + + assert (iiName in carpenterSchema.dependsOn) + assertEquals (1, carpenterSchema.dependsOn[iiName]?.size) + assertNotNull (carpenterSchema.dependsOn[iiName]?.find { it == aName }) + + assert (aName in carpenterSchema.dependencies) + assertEquals (2, carpenterSchema.dependencies[aName]!!.second.size) + assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iName }) + assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iiName }) + + val mc = MetaCarpenter (carpenterSchema) + + mc.build() + assertEquals (0, mc.schemas.carpenterSchemas.size) + assertEquals (0, mc.schemas.dependencies.size) + assertEquals (0, mc.schemas.dependsOn.size) + assertEquals (3, mc.objects.size) + assert (aName in mc.objects) + assert (iName in mc.objects) + assert (iiName in mc.objects) + } + + @Test + fun nestedInterfacesAndImplementation() { + val testI = 7 + val testIII = 11 + + class A(override val i: Int, override val iii : Int) : III + + val a = A(testI, testIII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + val amqpSchema = obj.second.schema.curruptName(listOf( + classTestName("A"), + "${this.javaClass.`package`.name}.I", + "${this.javaClass.`package`.name}.III")) + + val aName = curruptName (classTestName("A")) + val iName = curruptName ("${this.javaClass.`package`.name}.I") + val iiiName = curruptName ("${this.javaClass.`package`.name}.III") + + val carpenterSchema = amqpSchema.carpenterSchema() + + /* Since A depends on III and III extends I we will have to construct them + * in that reverse order (I -> III -> A) */ + assertEquals (1, carpenterSchema.carpenterSchemas.size) + assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iName }) + assertNull (carpenterSchema.carpenterSchemas.find { it.name == iiiName }) + assertNull (carpenterSchema.carpenterSchemas.find { it.name == aName }) + + /* I has III as a direct dependent and A as an indirect one */ + assert (iName in carpenterSchema.dependsOn) + assertEquals (2, carpenterSchema.dependsOn[iName]?.size) + assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == iiiName })) + assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == aName })) + + /* III has A as a dependent */ + assert (iiiName in carpenterSchema.dependsOn) + assertEquals (1, carpenterSchema.dependsOn[iiiName]?.size) + assertNotNull (carpenterSchema.dependsOn[iiiName]?.find { it == aName }) + + /* converly III depends on I */ + assert (iiiName in carpenterSchema.dependencies) + assertEquals (1, carpenterSchema.dependencies[iiiName]!!.second.size) + assertNotNull (carpenterSchema.dependencies[iiiName]!!.second.find { it == iName }) + + /* and A depends on III and I*/ + assert (aName in carpenterSchema.dependencies) + assertEquals (2, carpenterSchema.dependencies[aName]!!.second.size) + assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iiiName }) + assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iName }) + + val mc = MetaCarpenter (carpenterSchema) + + mc.build() + assertEquals (0, mc.schemas.carpenterSchemas.size) + assertEquals (0, mc.schemas.dependencies.size) + assertEquals (0, mc.schemas.dependsOn.size) + assertEquals (3, mc.objects.size) + assert (aName in mc.objects) + assert (iName in mc.objects) + assert (iiiName in mc.objects) + } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 018ead9912..0a22d5b132 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,14 +1,14 @@ package net.corda.carpenter +import net.corda.carpenter.test.AmqpCarpenterBase import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* +import net.corda.core.serialization.carpenter.CarpenterSchemas import org.junit.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals -class MultiMemberCompositeSchemaToClassCarpenterTests { - private var factory = SerializerFactory() - - fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) +class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun twoInts() { @@ -38,7 +38,15 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("int", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + + assertEquals (1, carpenterSchema.size) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") } + + assertNotEquals (null, aSchema) + + val pinochio = ClassCarpenter().build(aSchema!!) val p = pinochio.constructors[0].newInstance(testA, testB) @@ -54,7 +62,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a: Int, val b: String) - var a = A(testA, testB) + val a = A(testA, testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) @@ -66,7 +74,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals(1, obj.second.schema.types.size) assert(obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals(2, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) @@ -74,7 +82,15 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("string", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + + assertEquals (1, carpenterSchema.size) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") } + + assertNotEquals(null, aSchema) + + val pinochio = ClassCarpenter().build(aSchema!!) val p = pinochio.constructors[0].newInstance(testA, testB) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index d3d26075d5..52906461b2 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,16 +1,14 @@ package net.corda.carpenter +import net.corda.carpenter.test.AmqpCarpenterBase import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.ClassCarpenterSchema import net.corda.core.serialization.amqp.* +import net.corda.core.serialization.carpenter.CarpenterSchemas import org.junit.Test import kotlin.test.assertEquals -class SingleMemberCompositeSchemaToClassCarpenterTests { - private var factory = SerializerFactory() - - fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) +class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleInteger() { @@ -35,7 +33,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("int", amqpSchema.fields[0].type) - val pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) @@ -48,7 +50,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : String) - var a = A (test) + val a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -59,13 +61,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("string", amqpSchema.fields[0].type) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) @@ -79,9 +81,9 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : Char) - var a = A (test) + val a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) assert (obj.first is A) val amqpObj = obj.first as A @@ -91,13 +93,19 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("char", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + val carpenterSchema = CarpenterSchema.newInstance() + amqpSchema.carpenterSchema(carpenterSchema = carpenterSchema, force = true) + + assert (classTestName ("A") in carpenterSchema.carpenterSchemas) + + val aSchema = carpenterSchema.carpenterSchemas[classTestName ("A")]!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) @@ -129,8 +137,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("long", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -160,8 +171,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("short", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A")}!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -207,7 +221,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : Double) - var a = A (test) + val a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -218,14 +232,17 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("double", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A")}!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -233,12 +250,12 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @Test fun singleFloat() { - val test = 10.0F + val test : Float = 10.0F @CordaSerializable data class A(val a : Float) - var a = A (test) + val a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -249,16 +266,19 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("float", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) -// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) } } \ No newline at end of file From d76c1e75a4a330623c424178ea959fd9d8dd8c04 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 15 Jun 2017 14:49:18 +0100 Subject: [PATCH 013/197] Carpenter / amqp syntesis tidyup - squashes a lot of WIP / rebase / code reformat commits --- .../corda/core/serialization/amqp/Schema.kt | 2 +- ...berCompositeSchemaToClassCarpenterTests.kt | 4 ++ .../InheritanceSchemaToClassCarpenterTests.kt | 1 - ...berCompositeSchemaToClassCarpenterTests.kt | 71 +++++++------------ 4 files changed, 30 insertions(+), 48 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index bc7d7efc15..c1ba797610 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -533,4 +533,4 @@ private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: Mu } interfacesForSerialization(type).map { fingerprintForType(it, type, alreadySeen, hasher, factory) } return hasher -} \ No newline at end of file +} diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index ced1e4f345..046a331f89 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -44,6 +44,9 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.second.schema.types[0] is CompositeType) assert(obj.second.schema.types[1] is CompositeType) + println (obj.second.schema.types[0] as CompositeType) + println (obj.second.schema.types[1] as CompositeType) + var amqpSchemaA : CompositeType? = null var amqpSchemaB : CompositeType? = null @@ -65,6 +68,7 @@ class CompositeMembers : AmqpCarpenterBase() { assertEquals("a", amqpSchemaA!!.fields[0].name) assertEquals("int", amqpSchemaA.fields[0].type) + assertEquals(2, amqpSchemaB?.fields?.size) assertEquals("a", amqpSchemaB!!.fields[0].name) assertEquals(classTestName("A"), amqpSchemaB.fields[0].type) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt index 129dfea0a7..a9c61866a8 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -309,7 +309,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { fun memberInterface2() { val testI = 25 val testIIII = 50 - class A(override val i: Int) : I class B(override val i : I, override val iiii : Int) : IIII diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 52906461b2..21dbd972f2 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,14 +1,15 @@ package net.corda.carpenter -import net.corda.carpenter.test.AmqpCarpenterBase import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* -import net.corda.core.serialization.carpenter.CarpenterSchemas import org.junit.Test import kotlin.test.assertEquals -class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { +class SingleMemberCompositeSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) @Test fun singleInteger() { @@ -33,11 +34,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("int", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! - val pinochio = ClassCarpenter().build(aSchema) + val pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -50,7 +47,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @CordaSerializable data class A(val a : String) - val a = A (test) + var a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -61,13 +58,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.second.schema.types[0] as CompositeType - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + assertEquals (1, amqpSchema.fields.size) + assertEquals ("a", amqpSchema.fields[0].name) + assertEquals ("string", amqpSchema.fields[0].type) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! - val pinochio = ClassCarpenter().build(aSchema) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -81,9 +78,9 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @CordaSerializable data class A(val a : Char) - val a = A(test) + var a = A (test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) assert (obj.first is A) val amqpObj = obj.first as A @@ -93,19 +90,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("char", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchema.newInstance() - amqpSchema.carpenterSchema(carpenterSchema = carpenterSchema, force = true) - - assert (classTestName ("A") in carpenterSchema.carpenterSchemas) - - val aSchema = carpenterSchema.carpenterSchemas[classTestName ("A")]!! - val pinochio = ClassCarpenter().build(aSchema) + var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) val p = pinochio.constructors[0].newInstance (test) @@ -137,11 +128,8 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("long", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! - val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -171,11 +159,8 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("short", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A")}!! - val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -221,7 +206,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @CordaSerializable data class A(val a : Double) - val a = A (test) + var a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -232,17 +217,14 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("double", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A")}!! - val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) @@ -250,12 +232,12 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleFloat() { - val test : Float = 10.0F + val test = 10.0F @CordaSerializable data class A(val a : Float) - val a = A (test) + var a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) @@ -266,19 +248,16 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("float", amqpSchema.fields[0].type) - val carpenterSchema = CarpenterSchemas.newInstance() - amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") }!! - val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) +// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) } } \ No newline at end of file From 9ab6a72ea8cad77d537e7a3060761148eb25ba67 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 28 Jun 2017 16:55:42 +0100 Subject: [PATCH 014/197] Carpenter bug fixes Squashed commit messages: * Nested schema creation now works with dependencies recursvly created in the carpenter * Remove spurious prints from tests * Remove warnings * Don't add cladd member dep by name Since that's the name of the field, not the type we depend on. If we do we'll never actually be able to craft the type as the dependency chain will be horribly broken Various bug fixes * Fix merge issue where types weren't being seen as Prims * IntelliJ auto code cleanup / reformat * Whitespace changes * Add comment blocking as I like seeing it in files --- .../serialization/amqp/SerializerFactory.kt | 6 - .../amqp/SerializationOutputTests.kt | 63 ++++- .../net/corda/carpenter/MetaCapenter.kt | 8 + .../carpenter/ClassCarpenterTestUtils.kt | 21 +- ...berCompositeSchemaToClassCarpenterTests.kt | 112 ++++---- .../InheritanceSchemaToClassCarpenterTests.kt | 231 ++++++++--------- ...berCompositeSchemaToClassCarpenterTests.kt | 22 +- ...berCompositeSchemaToClassCarpenterTests.kt | 240 ++++++++++-------- 8 files changed, 395 insertions(+), 308 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt index 436e4fac60..7f2d95fddc 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/SerializerFactory.kt @@ -12,12 +12,6 @@ import java.lang.reflect.GenericArrayType import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.WildcardType -import java.lang.Float -import java.lang.Long -import java.lang.Short -import java.lang.Double -import java.lang.Character -//import java.lang.Boolean import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt index 54771ac805..36d84c26a6 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt @@ -7,10 +7,12 @@ import net.corda.core.identity.AbstractParty import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.EmptyWhitelist import net.corda.core.serialization.KryoAMQPSerializer -import net.corda.core.CordaRuntimeException +import net.corda.core.serialization.amqp.SerializerFactory.Companion.isPrimitive +import net.corda.core.utilities.CordaRuntimeException import net.corda.nodeapi.RPCException import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY +import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl import org.junit.Test @@ -27,6 +29,14 @@ import kotlin.test.assertTrue class SerializationOutputTests { data class Foo(val bar: String, val pub: Int) + data class testFloat(val f: Float) + + data class testDouble(val d: Double) + + data class testShort(val s: Short) + + data class testBoolean(val b : Boolean) + interface FooInterface { val pub: Int } @@ -159,12 +169,61 @@ class SerializationOutputTests { return desObj2 } + @Test + fun isPrimitive() { + assertTrue(isPrimitive(Character::class.java)) + assertTrue(isPrimitive(Boolean::class.java)) + assertTrue(isPrimitive(Byte::class.java)) + assertTrue(isPrimitive(UnsignedByte::class.java)) + assertTrue(isPrimitive(Short::class.java)) + assertTrue(isPrimitive(UnsignedShort::class.java)) + assertTrue(isPrimitive(Int::class.java)) + assertTrue(isPrimitive(UnsignedInteger::class.java)) + assertTrue(isPrimitive(Long::class.java)) + assertTrue(isPrimitive(UnsignedLong::class.java)) + assertTrue(isPrimitive(Float::class.java)) + assertTrue(isPrimitive(Double::class.java)) + assertTrue(isPrimitive(Decimal32::class.java)) + assertTrue(isPrimitive(Decimal64::class.java)) + assertTrue(isPrimitive(Decimal128::class.java)) + assertTrue(isPrimitive(Char::class.java)) + assertTrue(isPrimitive(Date::class.java)) + assertTrue(isPrimitive(UUID::class.java)) + assertTrue(isPrimitive(ByteArray::class.java)) + assertTrue(isPrimitive(String::class.java)) + assertTrue(isPrimitive(Symbol::class.java)) + } + @Test fun `test foo`() { val obj = Foo("Hello World!", 123) serdes(obj) } + @Test + fun `test float`() { + val obj = testFloat(10.0F) + serdes(obj) + } + + @Test + fun `test double`() { + val obj = testDouble(10.0) + serdes(obj) + } + + @Test + fun `test short`() { + val obj = testShort(1) + serdes(obj) + } + + @Test + fun `test bool`() { + val obj = testBoolean(true) + serdes(obj) + } + @Test fun `test foo implements`() { val obj = FooImplements("Hello World!", 123) @@ -524,4 +583,4 @@ class SerializationOutputTests { val obj = StateRef(SecureHash.randomSHA256(), 0) serdes(obj, factory, factory2) } -} \ No newline at end of file +} diff --git a/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt b/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt index 3595716cf2..cdbdcadf58 100644 --- a/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt +++ b/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt @@ -5,6 +5,8 @@ import net.corda.core.serialization.carpenter.Schema import net.corda.core.serialization.amqp.CompositeType +/**********************************************************************************************************************/ + abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { private val cc = ClassCarpenter() @@ -35,6 +37,8 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { abstract fun build() } +/**********************************************************************************************************************/ + class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { override fun build() { while (schemas.carpenterSchemas.isNotEmpty()) { @@ -44,6 +48,8 @@ class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { } } +/**********************************************************************************************************************/ + class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { override fun build() { println ("TestMetaCarpenter::build") @@ -51,3 +57,5 @@ class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schema step (schemas.carpenterSchemas.removeAt(0)) } } + +/**********************************************************************************************************************/ diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt index 09f768dcef..8b2c97cf07 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt @@ -15,31 +15,32 @@ fun curruptName(name: String) = "${name}__carpenter" /* given a list of class names work through the amqp envelope schema and alter any that match in the fashion defined above */ -fun Schema.curruptName (names: List) : Schema { - val newTypes : MutableList = mutableListOf() +fun Schema.curruptName(names: List): Schema { + val newTypes: MutableList = mutableListOf() for (type in types) { - val newName = if (type.name in names) curruptName (type.name) else type.name + val newName = if (type.name in names) curruptName(type.name) else type.name val newProvides = type.provides.map { - it -> if (it in names) curruptName (it) else it + it -> + if (it in names) curruptName(it) else it } val newFields = mutableListOf() (type as CompositeType).fields.forEach { - val type = if (it.type in names) curruptName (it.type) else it.type + val type = if (it.type in names) curruptName(it.type) else it.type val requires = if (it.requires.isNotEmpty() && (it.requires[0] in names)) - listOf (curruptName (it.requires[0])) else it.requires + listOf(curruptName(it.requires[0])) else it.requires - newFields.add (it.copy (type=type, requires=requires)) + newFields.add(it.copy(type = type, requires = requires)) } - newTypes.add (type.copy (name=newName, provides=newProvides, fields=newFields)) + newTypes.add(type.copy(name = newName, provides = newProvides, fields = newFields)) } - return Schema (types=newTypes) + return Schema(types = newTypes) } /**********************************************************************************************************************/ @@ -47,7 +48,7 @@ fun Schema.curruptName (names: List) : Schema { open class AmqpCarpenterBase { var factory = SerializerFactory() - fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) + fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) fun testName() = Thread.currentThread().stackTrace[2].methodName inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index 046a331f89..07c0898f13 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -20,7 +20,7 @@ interface I_ { class CompositeMembers : AmqpCarpenterBase() { @Test - fun bothKnown () { + fun bothKnown() { val testA = 10 val testB = 20 @@ -28,7 +28,7 @@ class CompositeMembers : AmqpCarpenterBase() { data class A(val a: Int) @CordaSerializable - data class B (val a: A, var b: Int) + data class B(val a: A, var b: Int) val b = B(A(testA), testB) @@ -44,11 +44,8 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.second.schema.types[0] is CompositeType) assert(obj.second.schema.types[1] is CompositeType) - println (obj.second.schema.types[0] as CompositeType) - println (obj.second.schema.types[1] as CompositeType) - - var amqpSchemaA : CompositeType? = null - var amqpSchemaB : CompositeType? = null + var amqpSchemaA: CompositeType? = null + var amqpSchemaB: CompositeType? = null for (type in obj.second.schema.types) { when (type.name.split ("$").last()) { @@ -57,37 +54,36 @@ class CompositeMembers : AmqpCarpenterBase() { } } - assert (amqpSchemaA != null) - assert (amqpSchemaB != null) + assert(amqpSchemaA != null) + assert(amqpSchemaB != null) /* * Just ensure the amqp schema matches what we want before we go messing * around with the internals */ - assertEquals(1, amqpSchemaA?.fields?.size) - assertEquals("a", amqpSchemaA!!.fields[0].name) + assertEquals(1, amqpSchemaA?.fields?.size) + assertEquals("a", amqpSchemaA!!.fields[0].name) assertEquals("int", amqpSchemaA.fields[0].type) - - assertEquals(2, amqpSchemaB?.fields?.size) - assertEquals("a", amqpSchemaB!!.fields[0].name) + assertEquals(2, amqpSchemaB?.fields?.size) + assertEquals("a", amqpSchemaB!!.fields[0].name) assertEquals(classTestName("A"), amqpSchemaB.fields[0].type) - assertEquals("b", amqpSchemaB.fields[1].name) + assertEquals("b", amqpSchemaB.fields[1].name) assertEquals("int", amqpSchemaB.fields[1].type) val metaSchema = obj.second.schema.carpenterSchema() /* if we know all the classes there is nothing to really achieve here */ - assert (metaSchema.carpenterSchemas.isEmpty()) - assert (metaSchema.dependsOn.isEmpty()) - assert (metaSchema.dependencies.isEmpty()) + assert(metaSchema.carpenterSchemas.isEmpty()) + assert(metaSchema.dependsOn.isEmpty()) + assert(metaSchema.dependencies.isEmpty()) } /* you cannot have an element of a composite class we know about that is unknown as that should be impossible. If we have the containing class in the class path then we must have all of it's constituent elements */ @Test(expected = UncarpentableException::class) - fun nestedIsUnknown () { + fun nestedIsUnknown() { val testA = 10 val testB = 20 @@ -96,7 +92,8 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) - val b = B(A(testA), testB) + + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"))) @@ -107,7 +104,7 @@ class CompositeMembers : AmqpCarpenterBase() { } @Test - fun ParentIsUnknown () { + fun ParentIsUnknown() { val testA = 10 val testB = 20 @@ -116,27 +113,28 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) assert(obj.first is B) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("B"))) + val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() - assertEquals (1, carpenterSchema.size) + assertEquals(1, carpenterSchema.size) - val metaCarpenter = MetaCarpenter (carpenterSchema) + val metaCarpenter = MetaCarpenter(carpenterSchema) metaCarpenter.build() - assert (curruptName(classTestName("B")) in metaCarpenter.objects) + assert(curruptName(classTestName("B")) in metaCarpenter.objects) } @Test - fun BothUnkown () { + fun BothUnkown() { val testA = 10 val testB = 20 @@ -145,54 +143,55 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) assert(obj.first is B) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() /* just verify we're in the expected initial state, A is carpentable, B is not because it depends on A and the dependency chains are in place */ - assertEquals (1, carpenterSchema.size) - assertEquals (curruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) - assertEquals (1, carpenterSchema.dependencies.size) - assert (curruptName(classTestName("B")) in carpenterSchema.dependencies) - assertEquals (1, carpenterSchema.dependsOn.size) - assert (curruptName(classTestName("A")) in carpenterSchema.dependsOn) + assertEquals(1, carpenterSchema.size) + assertEquals(curruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) + assertEquals(1, carpenterSchema.dependencies.size) + assert(curruptName(classTestName("B")) in carpenterSchema.dependencies) + assertEquals(1, carpenterSchema.dependsOn.size) + assert(curruptName(classTestName("A")) in carpenterSchema.dependsOn) /* test meta carpenter lets us single step over the creation */ - val metaCarpenter = TestMetaCarpenter (carpenterSchema) + val metaCarpenter = TestMetaCarpenter(carpenterSchema) /* we've built nothing so nothing should be there */ - assertEquals (0, metaCarpenter.objects.size) + assertEquals(0, metaCarpenter.objects.size) /* first iteration, carpent A, resolve deps and mark B as carpentable */ metaCarpenter.build() /* one build iteration should have carpetned up A and worked out that B is now buildable given it's depedencies have been satisfied */ - assertTrue (curruptName(classTestName("A")) in metaCarpenter.objects) - assertFalse (curruptName(classTestName("B")) in metaCarpenter.objects) + assertTrue(curruptName(classTestName("A")) in metaCarpenter.objects) + assertFalse(curruptName(classTestName("B")) in metaCarpenter.objects) - assertEquals (1, carpenterSchema.carpenterSchemas.size) - assertEquals (curruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) - assertTrue (carpenterSchema.dependencies.isEmpty()) - assertTrue (carpenterSchema.dependsOn.isEmpty()) + assertEquals(1, carpenterSchema.carpenterSchemas.size) + assertEquals(curruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) + assertTrue(carpenterSchema.dependencies.isEmpty()) + assertTrue(carpenterSchema.dependsOn.isEmpty()) /* second manual iteration, will carpent B */ metaCarpenter.build() - assert (curruptName(classTestName("A")) in metaCarpenter.objects) - assert (curruptName(classTestName("B")) in metaCarpenter.objects) + assert(curruptName(classTestName("A")) in metaCarpenter.objects) + assert(curruptName(classTestName("B")) in metaCarpenter.objects) - assertTrue (carpenterSchema.carpenterSchemas.isEmpty()) + assertTrue(carpenterSchema.carpenterSchemas.isEmpty()) } @Test(expected = UncarpentableException::class) - fun nestedIsUnkownInherited () { + fun nestedIsUnkownInherited() { val testA = 10 val testB = 20 val testC = 30 @@ -201,7 +200,7 @@ class CompositeMembers : AmqpCarpenterBase() { open class A(val a: Int) @CordaSerializable - class B(a: Int, var b: Int) : A (a) + class B(a: Int, var b: Int) : A(a) @CordaSerializable data class C(val b: B, var c: Int) @@ -212,13 +211,13 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.first is C) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @Test(expected = UncarpentableException::class) - fun nestedIsUnknownInheritedUnkown () { + fun nestedIsUnknownInheritedUnkown() { val testA = 10 val testB = 20 val testC = 30 @@ -227,7 +226,7 @@ class CompositeMembers : AmqpCarpenterBase() { open class A(val a: Int) @CordaSerializable - class B(a: Int, var b: Int) : A (a) + class B(a: Int, var b: Int) : A(a) @CordaSerializable data class C(val b: B, var c: Int) @@ -238,13 +237,13 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.first is C) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @Test - fun parentsIsUnknownWithUnkownInheritedMember () { + fun parentsIsUnknownWithUnkownInheritedMember() { val testA = 10 val testB = 20 val testC = 30 @@ -253,7 +252,7 @@ class CompositeMembers : AmqpCarpenterBase() { open class A(val a: Int) @CordaSerializable - class B(a: Int, var b: Int) : A (a) + class B(a: Int, var b: Int) : A(a) @CordaSerializable data class C(val b: B, var c: Int) @@ -264,7 +263,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.first is C) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"), classTestName ("B"))) + val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) } @@ -273,7 +272,7 @@ class CompositeMembers : AmqpCarpenterBase() { * but we do know about I_ */ @Test - fun nestedIsInterfaceToUnknown () { + fun nestedIsInterfaceToUnknown() { val testA = 10 val testB = 20 @@ -282,7 +281,8 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) - val b = B(A(testA), testB) + + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) @@ -299,6 +299,7 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) @@ -316,6 +317,7 @@ class CompositeMembers : AmqpCarpenterBase() { @CordaSerializable data class B(val a: A, var b: Int) + val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt index a9c61866a8..a7d25841be 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -11,31 +11,31 @@ import kotlin.test.* @CordaSerializable interface J { - val j : Int + val j: Int } /*******************************************************************************************************/ @CordaSerializable interface I { - val i : Int + val i: Int } @CordaSerializable interface II { - val ii : Int + val ii: Int } @CordaSerializable interface III : I { - val iii : Int + val iii: Int override val i: Int } @CordaSerializable interface IIII { - val iiii : Int - val i : I + val iiii: Int + val i: I } /*******************************************************************************************************/ @@ -66,14 +66,14 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) + val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) val l2 = curruptSchema.carpenterSchema() assertEquals(1, l2.size) val aSchema = l2.carpenterSchemas.find { it.name == curruptName(classTestName("A")) } - assertNotEquals (null, aSchema) + assertNotEquals(null, aSchema) assertEquals(curruptName(classTestName("A")), aSchema!!.name) assertEquals(1, aSchema.interfaces.size) @@ -115,7 +115,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName (listOf (classTestName("A"))) + val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) val aName = curruptName(classTestName("A")) val l2 = curruptSchema.carpenterSchema() @@ -143,13 +143,13 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun multipleInterfaces() { - val testI = 20 + val testI = 20 val testII = 40 class A(override val i: Int, override val ii: Int) : I, II - val a = A(testI, testII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val a = A(testI, testII) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.first is A) @@ -166,8 +166,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus needs some carpentry */ - val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) - val l2 = curruptSchema.carpenterSchema() + val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) + val l2 = curruptSchema.carpenterSchema() val aName = curruptName(classTestName("A")) assertEquals(1, l2.size) @@ -178,31 +178,31 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) - assert (net.corda.carpenter.I::class.java in aSchema.interfaces) - assert (net.corda.carpenter.II::class.java in aSchema.interfaces) + assert(net.corda.carpenter.I::class.java in aSchema.interfaces) + assert(net.corda.carpenter.II::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) val objA = aBuilder.constructors[0].newInstance(testI, testII) - val i = objA as I + val i = objA as I val ii = objA as II assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII) - assertEquals(a.i, i.i) + assertEquals(a.i, i.i) assertEquals(a.ii, ii.ii) } @Test fun nestedInterfaces() { - val testI = 20 - val testIII = 60 + val testI = 20 + val testIII = 60 - class A(override val i: Int, override val iii : Int) : III + class A(override val i: Int, override val iii: Int) : III - val a = A(testI, testIII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val a = A(testI, testIII) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.first is A) @@ -216,7 +216,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"))) + val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) val l2 = curruptSchema.carpenterSchema() val aName = curruptName(classTestName("A")) @@ -228,30 +228,30 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) - assert (net.corda.carpenter.I::class.java in aSchema.interfaces) - assert (net.corda.carpenter.III::class.java in aSchema.interfaces) + assert(net.corda.carpenter.I::class.java in aSchema.interfaces) + assert(net.corda.carpenter.III::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) val objA = aBuilder.constructors[0].newInstance(testI, testIII) - val i = objA as I + val i = objA as I val iii = objA as III assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII) - assertEquals(a.i, i.i) - assertEquals(a.i, iii.i) + assertEquals(a.i, i.i) + assertEquals(a.i, iii.i) assertEquals(a.iii, iii.iii) } @Test fun memberInterface() { - val testI = 25 - val testIIII = 50 + val testI = 25 + val testIIII = 50 class A(override val i: Int) : I - class B(override val i : I, override val iiii : Int) : IIII + class B(override val i: I, override val iiii: Int) : IIII val a = A(testI) val b = B(a, testIIII) @@ -270,20 +270,20 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { */ assertEquals(4, serSchema.types.size) - val curruptSchema = serSchema.curruptName (listOf (classTestName ("A"), classTestName ("B"))) - val cSchema = curruptSchema.carpenterSchema() - val aName = curruptName(classTestName("A")) - val bName = curruptName(classTestName("B")) + val curruptSchema = serSchema.curruptName(listOf(classTestName("A"), classTestName("B"))) + val cSchema = curruptSchema.carpenterSchema() + val aName = curruptName(classTestName("A")) + val bName = curruptName(classTestName("B")) assertEquals(2, cSchema.size) val aCarpenterSchema = cSchema.carpenterSchemas.find { it.name == aName } val bCarpenterSchema = cSchema.carpenterSchemas.find { it.name == bName } - assertNotEquals (null, aCarpenterSchema) - assertNotEquals (null, bCarpenterSchema) + assertNotEquals(null, aCarpenterSchema) + assertNotEquals(null, bCarpenterSchema) - val cc = ClassCarpenter() + val cc = ClassCarpenter() val cc2 = ClassCarpenter() val bBuilder = cc.build(bCarpenterSchema!!) @@ -307,10 +307,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { to have a concrete class loaded without having access to all of it's elements */ @Test(expected = UncarpentableException::class) fun memberInterface2() { - val testI = 25 - val testIIII = 50 + val testI = 25 + val testIIII = 50 + class A(override val i: Int) : I - class B(override val i : I, override val iiii : Int) : IIII + class B(override val i: I, override val iiii: Int) : IIII val a = A(testI) val b = B(a, testIIII) @@ -331,13 +332,13 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(4, serSchema.types.size) /* ignore the return as we expect this to throw */ - serSchema.curruptName (listOf ( + serSchema.curruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema() } @Test fun interfaceAndImplementation() { - val testI = 25 + val testI = 25 class A(override val i: Int) : I @@ -356,38 +357,38 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { */ assertEquals(2, serSchema.types.size) - val amqpSchema = serSchema.curruptName (listOf (classTestName("A"), "${this.javaClass.`package`.name}.I")) + val amqpSchema = serSchema.curruptName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) - val aName = curruptName (classTestName("A")) - val iName = curruptName ("${this.javaClass.`package`.name}.I") + val aName = curruptName(classTestName("A")) + val iName = curruptName("${this.javaClass.`package`.name}.I") val carpenterSchema = amqpSchema.carpenterSchema() /* whilst there are two unknown classes within the envelope A depends on I so we can't construct a schema for A until we have for I */ - assertEquals (1, carpenterSchema.size) - assertNotEquals (null, carpenterSchema.carpenterSchemas.find { it.name == iName }) + assertEquals(1, carpenterSchema.size) + assertNotEquals(null, carpenterSchema.carpenterSchemas.find { it.name == iName }) /* since we can't build A it should list I as a dependency*/ - assert (aName in carpenterSchema.dependencies) - assertEquals (1, carpenterSchema.dependencies[aName]!!.second.size) - assertEquals (iName, carpenterSchema.dependencies[aName]!!.second[0]) + assert(aName in carpenterSchema.dependencies) + assertEquals(1, carpenterSchema.dependencies[aName]!!.second.size) + assertEquals(iName, carpenterSchema.dependencies[aName]!!.second[0]) /* and conversly I should have A listed as a dependent */ assert(iName in carpenterSchema.dependsOn) assertEquals(1, carpenterSchema.dependsOn[iName]!!.size) assertEquals(aName, carpenterSchema.dependsOn[iName]!![0]) - val mc = MetaCarpenter (carpenterSchema) + val mc = MetaCarpenter(carpenterSchema) mc.build() - assertEquals (0, mc.schemas.carpenterSchemas.size) - assertEquals (0, mc.schemas.dependencies.size) - assertEquals (0, mc.schemas.dependsOn.size) - assertEquals (2, mc.objects.size) - assert (aName in mc.objects) - assert (iName in mc.objects) + assertEquals(0, mc.schemas.carpenterSchemas.size) + assertEquals(0, mc.schemas.dependencies.size) + assertEquals(0, mc.schemas.dependsOn.size) + assertEquals(2, mc.objects.size) + assert(aName in mc.objects) + assert(iName in mc.objects) mc.objects[aName]!!.constructors[0].newInstance(testI) } @@ -408,51 +409,51 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.II")) - val aName = curruptName (classTestName("A")) - val iName = curruptName ("${this.javaClass.`package`.name}.I") - val iiName = curruptName ("${this.javaClass.`package`.name}.II") + val aName = curruptName(classTestName("A")) + val iName = curruptName("${this.javaClass.`package`.name}.I") + val iiName = curruptName("${this.javaClass.`package`.name}.II") val carpenterSchema = amqpSchema.carpenterSchema() /* there is nothing preventing us from carpenting up the two interfaces so our initial list should contain both interface with A being dependent on both and each having A as a dependent */ - assertEquals (2, carpenterSchema.carpenterSchemas.size) - assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iName }) - assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iiName }) - assertNull (carpenterSchema.carpenterSchemas.find { it.name == aName }) + assertEquals(2, carpenterSchema.carpenterSchemas.size) + assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName }) + assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iiName }) + assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName }) - assert (iName in carpenterSchema.dependsOn) - assertEquals (1, carpenterSchema.dependsOn[iName]?.size) - assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == aName })) + assert(iName in carpenterSchema.dependsOn) + assertEquals(1, carpenterSchema.dependsOn[iName]?.size) + assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName })) - assert (iiName in carpenterSchema.dependsOn) - assertEquals (1, carpenterSchema.dependsOn[iiName]?.size) - assertNotNull (carpenterSchema.dependsOn[iiName]?.find { it == aName }) + assert(iiName in carpenterSchema.dependsOn) + assertEquals(1, carpenterSchema.dependsOn[iiName]?.size) + assertNotNull(carpenterSchema.dependsOn[iiName]?.find { it == aName }) - assert (aName in carpenterSchema.dependencies) - assertEquals (2, carpenterSchema.dependencies[aName]!!.second.size) - assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iName }) - assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iiName }) + assert(aName in carpenterSchema.dependencies) + assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size) + assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName }) + assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiName }) - val mc = MetaCarpenter (carpenterSchema) + val mc = MetaCarpenter(carpenterSchema) mc.build() - assertEquals (0, mc.schemas.carpenterSchemas.size) - assertEquals (0, mc.schemas.dependencies.size) - assertEquals (0, mc.schemas.dependsOn.size) - assertEquals (3, mc.objects.size) - assert (aName in mc.objects) - assert (iName in mc.objects) - assert (iiName in mc.objects) + assertEquals(0, mc.schemas.carpenterSchemas.size) + assertEquals(0, mc.schemas.dependencies.size) + assertEquals(0, mc.schemas.dependsOn.size) + assertEquals(3, mc.objects.size) + assert(aName in mc.objects) + assert(iName in mc.objects) + assert(iiName in mc.objects) } @Test fun nestedInterfacesAndImplementation() { - val testI = 7 + val testI = 7 val testIII = 11 - class A(override val i: Int, override val iii : Int) : III + class A(override val i: Int, override val iii: Int) : III val a = A(testI, testIII) @@ -463,51 +464,51 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.III")) - val aName = curruptName (classTestName("A")) - val iName = curruptName ("${this.javaClass.`package`.name}.I") - val iiiName = curruptName ("${this.javaClass.`package`.name}.III") + val aName = curruptName(classTestName("A")) + val iName = curruptName("${this.javaClass.`package`.name}.I") + val iiiName = curruptName("${this.javaClass.`package`.name}.III") val carpenterSchema = amqpSchema.carpenterSchema() /* Since A depends on III and III extends I we will have to construct them * in that reverse order (I -> III -> A) */ - assertEquals (1, carpenterSchema.carpenterSchemas.size) - assertNotNull (carpenterSchema.carpenterSchemas.find { it.name == iName }) - assertNull (carpenterSchema.carpenterSchemas.find { it.name == iiiName }) - assertNull (carpenterSchema.carpenterSchemas.find { it.name == aName }) + assertEquals(1, carpenterSchema.carpenterSchemas.size) + assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName }) + assertNull(carpenterSchema.carpenterSchemas.find { it.name == iiiName }) + assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName }) /* I has III as a direct dependent and A as an indirect one */ - assert (iName in carpenterSchema.dependsOn) - assertEquals (2, carpenterSchema.dependsOn[iName]?.size) - assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == iiiName })) - assertNotNull (carpenterSchema.dependsOn[iName]?.find ( { it == aName })) + assert(iName in carpenterSchema.dependsOn) + assertEquals(2, carpenterSchema.dependsOn[iName]?.size) + assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == iiiName })) + assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName })) /* III has A as a dependent */ - assert (iiiName in carpenterSchema.dependsOn) - assertEquals (1, carpenterSchema.dependsOn[iiiName]?.size) - assertNotNull (carpenterSchema.dependsOn[iiiName]?.find { it == aName }) + assert(iiiName in carpenterSchema.dependsOn) + assertEquals(1, carpenterSchema.dependsOn[iiiName]?.size) + assertNotNull(carpenterSchema.dependsOn[iiiName]?.find { it == aName }) /* converly III depends on I */ - assert (iiiName in carpenterSchema.dependencies) - assertEquals (1, carpenterSchema.dependencies[iiiName]!!.second.size) - assertNotNull (carpenterSchema.dependencies[iiiName]!!.second.find { it == iName }) + assert(iiiName in carpenterSchema.dependencies) + assertEquals(1, carpenterSchema.dependencies[iiiName]!!.second.size) + assertNotNull(carpenterSchema.dependencies[iiiName]!!.second.find { it == iName }) /* and A depends on III and I*/ - assert (aName in carpenterSchema.dependencies) - assertEquals (2, carpenterSchema.dependencies[aName]!!.second.size) - assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iiiName }) - assertNotNull (carpenterSchema.dependencies[aName]!!.second.find { it == iName }) + assert(aName in carpenterSchema.dependencies) + assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size) + assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiiName }) + assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName }) - val mc = MetaCarpenter (carpenterSchema) + val mc = MetaCarpenter(carpenterSchema) mc.build() - assertEquals (0, mc.schemas.carpenterSchemas.size) - assertEquals (0, mc.schemas.dependencies.size) - assertEquals (0, mc.schemas.dependsOn.size) - assertEquals (3, mc.objects.size) - assert (aName in mc.objects) - assert (iName in mc.objects) - assert (iiiName in mc.objects) + assertEquals(0, mc.schemas.carpenterSchemas.size) + assertEquals(0, mc.schemas.dependencies.size) + assertEquals(0, mc.schemas.dependsOn.size) + assertEquals(3, mc.objects.size) + assert(aName in mc.objects) + assert(iName in mc.objects) + assert(iiiName in mc.objects) } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 0a22d5b132..0e53d438f9 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -32,19 +32,19 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { var amqpSchema = obj.second.schema.types[0] as CompositeType - assertEquals(2, amqpSchema.fields.size) - assertEquals("a", amqpSchema.fields[0].name) + assertEquals(2, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) assertEquals("int", amqpSchema.fields[0].type) - assertEquals("b", amqpSchema.fields[1].name) + assertEquals("b", amqpSchema.fields[1].name) assertEquals("int", amqpSchema.fields[1].type) val carpenterSchema = CarpenterSchemas.newInstance() amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - assertEquals (1, carpenterSchema.size) - val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName ("A") } + assertEquals(1, carpenterSchema.size) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") } - assertNotEquals (null, aSchema) + assertNotEquals(null, aSchema) val pinochio = ClassCarpenter().build(aSchema!!) @@ -76,16 +76,16 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { val amqpSchema = obj.second.schema.types[0] as CompositeType - assertEquals(2, amqpSchema.fields.size) - assertEquals("a", amqpSchema.fields[0].name) - assertEquals("int", amqpSchema.fields[0].type) - assertEquals("b", amqpSchema.fields[1].name) + assertEquals(2, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("int", amqpSchema.fields[0].type) + assertEquals("b", amqpSchema.fields[1].name) assertEquals("string", amqpSchema.fields[1].type) val carpenterSchema = CarpenterSchemas.newInstance() amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - assertEquals (1, carpenterSchema.size) + assertEquals(1, carpenterSchema.size) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") } assertNotEquals(null, aSchema) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 21dbd972f2..0150e34a76 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,44 +1,47 @@ package net.corda.carpenter +import net.corda.carpenter.test.AmqpCarpenterBase import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* +import net.corda.core.serialization.carpenter.CarpenterSchemas import org.junit.Test import kotlin.test.assertEquals -class SingleMemberCompositeSchemaToClassCarpenterTests { - private var factory = SerializerFactory() - - fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) +class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleInteger() { val test = 10 @CordaSerializable - data class A(val a : Int) + data class A(val a: Int) - val a = A (test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val a = A(test) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("int", amqpSchema.fields[0].type) + assertEquals(1, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("int", amqpSchema.fields[0].type) - val pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + val p = pinochio.constructors[0].newInstance(test) + + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } @Test @@ -46,29 +49,30 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { val test = "ten" @CordaSerializable - data class A(val a : String) - var a = A (test) + data class A(val a: String) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val a = A(test) - assert (obj.first is A) - val amqpObj = obj.first as A + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assert(obj.obj is A) + val amqpObj = obj.obj as A - var amqpSchema = obj.second.schema.types[0] as CompositeType + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("string", amqpSchema.fields[0].type) + val amqpSchema = obj.envelope.schema.types[0] as CompositeType - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + val p = pinochio.constructors[0].newInstance(test) + + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } /* @@ -78,25 +82,31 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : Char) - var a = A (test) + val a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert (obj.obj is A) + val amqpObj = obj.obj as A assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals (1, obj.envelope.schema.types.size) + assertEquals (1, obj.envelope.schema.types.size) + assert (obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("char", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + val carpenterSchema = CarpenterSchema.newInstance() + amqpSchema.carpenterSchema(carpenterSchema = carpenterSchema, force = true) + + assert (classTestName ("A") in carpenterSchema.carpenterSchemas) + + val aSchema = carpenterSchema.carpenterSchemas[classTestName ("A")]!! + val pinochio = ClassCarpenter().build(aSchema) val p = pinochio.constructors[0].newInstance (test) @@ -109,30 +119,33 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { val test = 10L @CordaSerializable - data class A(val a : Long) + data class A(val a: Long) - var a = A (test) + var a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.envelope.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("long", amqpSchema.fields[0].type) + assertEquals(1, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("long", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) + val p = pinochio.constructors[0].newInstance(test) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } @Test @@ -140,30 +153,33 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { val test = 10.toShort() @CordaSerializable - data class A(val a : Short) + data class A(val a: Short) - var a = A (test) + var a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.envelope.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("short", amqpSchema.fields[0].type) + assertEquals(1, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("short", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) + val p = pinochio.constructors[0].newInstance(test) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } /* @@ -178,14 +194,14 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert (obj.obj is A) + val amqpObj = obj.obj as A assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals (1, obj.envelope.schema.types.size) + assert (obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) @@ -204,60 +220,66 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { val test = 10.0 @CordaSerializable - data class A(val a : Double) + data class A(val a: Double) - var a = A (test) + val a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("double", amqpSchema.fields[0].type) + assertEquals(1, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("double", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) + val p = pinochio.constructors[0].newInstance(test) - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } @Test fun singleFloat() { - val test = 10.0F + val test: Float = 10.0F @CordaSerializable - data class A(val a : Float) + data class A(val a: Float) - var a = A (test) + val a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert (obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) + assertEquals(test, amqpObj.a) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("float", amqpSchema.fields[0].type) + assertEquals(1, amqpSchema.fields.size) + assertEquals("a", amqpSchema.fields[0].name) + assertEquals("float", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) + val carpenterSchema = CarpenterSchemas.newInstance() + amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) - val p = pinochio.constructors[0].newInstance (test) + val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! + val pinochio = ClassCarpenter().build(aSchema) + val p = pinochio.constructors[0].newInstance(test) -// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) + assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } } \ No newline at end of file From 4dd37f009e76da204bcfc3f3aef07d20a2ee2b9f Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 3 Jul 2017 16:33:20 +0100 Subject: [PATCH 015/197] Integrate with the carpenter now it's in core --- .../corda/core/serialization/amqp/Schema.kt | 10 +-- .../serialization/carpenter/MetaCarpenter.kt | 59 ++++++++++++++++- .../core/serialization/carpenter/Schema.kt | 10 +++ .../carpenter/ClassCarpenterTestUtils.kt | 2 +- ...berCompositeSchemaToClassCarpenterTests.kt | 58 ++++++++--------- .../InheritanceSchemaToClassCarpenterTests.kt | 65 ++++++++++--------- ...berCompositeSchemaToClassCarpenterTests.kt | 31 ++++----- ...berCompositeSchemaToClassCarpenterTests.kt | 21 +++--- .../net/corda/carpenter/MetaCapenter.kt | 61 ----------------- 9 files changed, 163 insertions(+), 154 deletions(-) rename {experimental/src/test/kotlin/net/corda => core/src/test/kotlin/net/corda/core/serialization}/carpenter/ClassCarpenterTestUtils.kt (97%) rename {experimental/src/test/kotlin/net/corda => core/src/test/kotlin/net/corda/core/serialization}/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt (82%) rename {experimental/src/test/kotlin/net/corda => core/src/test/kotlin/net/corda/core/serialization}/carpenter/InheritanceSchemaToClassCarpenterTests.kt (88%) rename {experimental/src/test/kotlin/net/corda => core/src/test/kotlin/net/corda/core/serialization}/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt (76%) rename {experimental/src/test/kotlin/net/corda => core/src/test/kotlin/net/corda/core/serialization}/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt (94%) delete mode 100644 experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index c1ba797610..f0f0f80647 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -113,7 +113,7 @@ data class Schema(val types: List) : DescribedType { fun carpenterSchema(loaders : List = listOf(ClassLoader.getSystemClassLoader())) : CarpenterSchemas { - var rtn = CarpenterSchemas.newInstance() + val rtn = CarpenterSchemas.newInstance() types.filterIsInstance().forEach { it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn) @@ -250,7 +250,7 @@ sealed class TypeNotation : DescribedType { } } - abstract var name: String + abstract val name: String abstract val label: String? abstract val provides: List abstract val descriptor: Descriptor @@ -328,7 +328,7 @@ data class CompositeType(override var name: String, override val label: String?, var isCreatable = true provides.forEach { - if (name.equals(it)) { + if (name == it) { isInterface = true return@forEach } @@ -366,7 +366,7 @@ data class CompositeType(override var name: String, override val label: String?, } } -data class RestrictedType(override var name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { +data class RestrictedType(override val name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(6L or DESCRIPTOR_TOP_32BITS) @@ -409,7 +409,7 @@ data class RestrictedType(override var name: String, override val label: String? } } -data class Choice(var name: String, val value: String) : DescribedType { +data class Choice(val name: String, val value: String) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(7L or DESCRIPTOR_TOP_32BITS) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt index 821308658c..3f0414c1ad 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt @@ -1,7 +1,10 @@ package net.corda.core.serialization.carpenter +import net.corda.core.serialization.amqp.CompositeType import net.corda.core.serialization.amqp.TypeNotation +/**********************************************************************************************************************/ + data class CarpenterSchemas ( val carpenterSchemas : MutableList, val dependencies : MutableMap>>, @@ -22,7 +25,61 @@ data class CarpenterSchemas ( dependencies.computeIfAbsent(dependant, { Pair(type, mutableListOf()) }).second.add(dependee) } - val size get() = carpenterSchemas.size } + +/**********************************************************************************************************************/ + +abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { + + private val cc = ClassCarpenter() + val objects = mutableMapOf>() + + fun step (newObject : Schema) { + objects[newObject.name] = cc.build (newObject) + + /* go over the list of everything that had a dependency on the newly + carpented class existing and remove it from their dependency list, If that + list is now empty we have no impediment to carpenting that class up */ + schemas.dependsOn.remove(newObject.name)?.forEach { dependent -> + assert (newObject.name in schemas.dependencies[dependent]!!.second) + + schemas.dependencies[dependent]?.second?.remove(newObject.name) + + /* we're out of blockers so we can now create the type */ + if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) { + (schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema ( + classLoaders = listOf ( + ClassLoader.getSystemClassLoader(), + cc.classloader), + carpenterSchemas = schemas) + } + } + } + + abstract fun build() +} + +/**********************************************************************************************************************/ + +class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { + override fun build() { + while (schemas.carpenterSchemas.isNotEmpty()) { + val newObject = schemas.carpenterSchemas.removeAt(0) + step (newObject) + } + } +} + +/**********************************************************************************************************************/ + +class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { + override fun build() { + println ("TestMetaCarpenter::build") + if (schemas.carpenterSchemas.isEmpty()) return + step (schemas.carpenterSchemas.removeAt(0)) + } +} + +/**********************************************************************************************************************/ diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt index 7bca4512b0..dfc37fc471 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -3,6 +3,8 @@ package net.corda.core.serialization.carpenter import org.objectweb.asm.Type import java.util.LinkedHashMap +/**********************************************************************************************************************/ + /** * A Schema represents a desired class. */ @@ -29,6 +31,8 @@ abstract class Schema( get() = name.replace(".", "/") } +/**********************************************************************************************************************/ + class ClassSchema( name: String, fields: Map>, @@ -36,6 +40,8 @@ class ClassSchema( interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) +/**********************************************************************************************************************/ + class InterfaceSchema( name: String, fields: Map>, @@ -43,6 +49,8 @@ class InterfaceSchema( interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) +/**********************************************************************************************************************/ + object CarpenterSchemaFactory { fun newInstance ( name: String, @@ -54,3 +62,5 @@ object CarpenterSchemaFactory { if (isInterface) InterfaceSchema (name, fields, superclass, interfaces) else ClassSchema (name, fields, superclass, interfaces) } + +/**********************************************************************************************************************/ diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt similarity index 97% rename from experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt rename to core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt index 8b2c97cf07..5ec3dbc61d 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterTestUtils.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -1,4 +1,4 @@ -package net.corda.carpenter.test +package net.corda.core.serialization.carpenter.test import net.corda.core.serialization.amqp.CompositeType import net.corda.core.serialization.amqp.Field diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt similarity index 82% rename from experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt rename to core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index 07c0898f13..cc196cd5f3 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,11 +1,11 @@ -package net.corda.carpenter +package net.corda.core.serialization.carpenter +import net.corda.core.serialization.carpenter.test.* import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* import org.junit.Test import kotlin.test.assertEquals -import net.corda.carpenter.test.* import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -32,22 +32,22 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) - val amqpObj = obj.first as B + val amqpObj = obj.obj as B assertEquals(testB, amqpObj.b) assertEquals(testA, amqpObj.a.a) - assertEquals(2, obj.second.schema.types.size) - assert(obj.second.schema.types[0] is CompositeType) - assert(obj.second.schema.types[1] is CompositeType) + assertEquals(2, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) + assert(obj.envelope.schema.types[1] is CompositeType) var amqpSchemaA: CompositeType? = null var amqpSchemaB: CompositeType? = null - for (type in obj.second.schema.types) { + for (type in obj.envelope.schema.types) { when (type.name.split ("$").last()) { "A" -> amqpSchemaA = type as CompositeType "B" -> amqpSchemaB = type as CompositeType @@ -71,7 +71,7 @@ class CompositeMembers : AmqpCarpenterBase() { assertEquals("b", amqpSchemaB.fields[1].name) assertEquals("int", amqpSchemaB.fields[1].type) - val metaSchema = obj.second.schema.carpenterSchema() + val metaSchema = obj.envelope.schema.carpenterSchema() /* if we know all the classes there is nothing to really achieve here */ assert(metaSchema.carpenterSchemas.isEmpty()) @@ -95,10 +95,10 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) - val amqpSchema = obj.second.schema.curruptName(listOf (classTestName ("A"))) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) + val amqpSchema = obj.envelope.schema.curruptName(listOf (classTestName ("A"))) - assert(obj.first is B) + assert(obj.obj is B) amqpSchema.carpenterSchema() } @@ -116,9 +116,9 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("B"))) @@ -146,9 +146,9 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) @@ -207,9 +207,9 @@ class CompositeMembers : AmqpCarpenterBase() { val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - assert(obj.first is C) + assert(obj.obj is C) val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) @@ -233,9 +233,9 @@ class CompositeMembers : AmqpCarpenterBase() { val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - assert(obj.first is C) + assert(obj.obj is C) val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) @@ -259,9 +259,9 @@ class CompositeMembers : AmqpCarpenterBase() { val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(c)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) - assert(obj.first is C) + assert(obj.obj is C) val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) } @@ -284,9 +284,9 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) } @Test @@ -302,9 +302,9 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) } @Test @@ -320,9 +320,9 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt similarity index 88% rename from experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt rename to core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt index a7d25841be..7516b4228e 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -1,10 +1,11 @@ -package net.corda.carpenter +package net.corda.core.serialization.carpenter import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.carpenter.MetaCarpenter +import net.corda.core.serialization.carpenter.test.* import net.corda.core.serialization.amqp.* + import org.junit.Test -import net.corda.carpenter.test.* -import net.corda.carpenter.MetaCarpenter import kotlin.test.* /*******************************************************************************************************/ @@ -52,11 +53,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(testJ, a.j) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) + assert(obj.obj is A) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema assertEquals(2, serSchema.types.size) @@ -77,7 +78,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(curruptName(classTestName("A")), aSchema!!.name) assertEquals(1, aSchema.interfaces.size) - assertEquals(net.corda.carpenter.J::class.java, aSchema.interfaces[0]) + assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0]) val aBuilder = ClassCarpenter().build(aSchema) @@ -101,11 +102,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(testJ, a.j) assertEquals(testJJ, a.jj) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) + assert(obj.obj is A) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema assertEquals(2, serSchema.types.size) @@ -128,7 +129,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(aName, aSchema!!.name) assertEquals(1, aSchema.interfaces.size) - assertEquals(net.corda.carpenter.J::class.java, aSchema.interfaces[0]) + assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0]) val aBuilder = ClassCarpenter().build(aSchema) @@ -151,9 +152,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) + assert(obj.obj is A) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema assertEquals(3, serSchema.types.size) @@ -178,8 +179,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) - assert(net.corda.carpenter.I::class.java in aSchema.interfaces) - assert(net.corda.carpenter.II::class.java in aSchema.interfaces) + assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces) + assert(net.corda.core.serialization.carpenter.II::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) @@ -204,9 +205,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) + assert(obj.obj is A) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema assertEquals(3, serSchema.types.size) @@ -228,8 +229,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) - assert(net.corda.carpenter.I::class.java in aSchema.interfaces) - assert(net.corda.carpenter.III::class.java in aSchema.interfaces) + assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces) + assert(net.corda.core.serialization.carpenter.III::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) @@ -256,11 +257,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI) val b = B(a, testIIII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema /* * class A @@ -316,11 +317,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI) val b = B(a, testIIII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - assert(obj.first is B) + assert(obj.obj is B) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema /* * The classes we're expecting to find: @@ -344,11 +345,11 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) + assert(obj.obj is A) - val serSchema = obj.second.schema + val serSchema = obj.envelope.schema /* * The classes we're expecting to find: @@ -402,9 +403,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.second.schema.curruptName(listOf( + val amqpSchema = obj.envelope.schema.curruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.II")) @@ -457,9 +458,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testIII) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.second.schema.curruptName(listOf( + val amqpSchema = obj.envelope.schema.curruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.III")) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt similarity index 76% rename from experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt rename to core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 0e53d438f9..71b96e1c8b 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,9 +1,10 @@ -package net.corda.carpenter +package net.corda.core.serialization.carpenter -import net.corda.carpenter.test.AmqpCarpenterBase +import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase +import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* -import net.corda.core.serialization.carpenter.CarpenterSchemas + import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -20,17 +21,17 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { var a = A(testA, testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A assertEquals(testA, amqpObj.a) assertEquals(testB, amqpObj.b) - assertEquals(1, obj.second.schema.types.size) - assert(obj.second.schema.types[0] is CompositeType) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + var amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals(2, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) @@ -64,17 +65,17 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testA, testB) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.first is A) - val amqpObj = obj.first as A + assert(obj.obj is A) + val amqpObj = obj.obj as A assertEquals(testA, amqpObj.a) assertEquals(testB, amqpObj.b) - assertEquals(1, obj.second.schema.types.size) - assert(obj.second.schema.types[0] is CompositeType) + assertEquals(1, obj.envelope.schema.types.size) + assert(obj.envelope.schema.types[0] is CompositeType) - val amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals(2, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt similarity index 94% rename from experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt rename to core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 0150e34a76..304d7185c4 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,9 +1,10 @@ -package net.corda.carpenter +package net.corda.core.serialization.carpenter -import net.corda.carpenter.test.AmqpCarpenterBase +import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase +import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* -import net.corda.core.serialization.carpenter.CarpenterSchemas + import org.junit.Test import kotlin.test.assertEquals @@ -84,7 +85,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { data class A(val a : Char) val a = A(test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert (obj.obj is A) val amqpObj = obj.obj as A @@ -121,7 +122,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @CordaSerializable data class A(val a: Long) - var a = A(test) + val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -132,7 +133,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(1, obj.envelope.schema.types.size) assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.envelope.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals(1, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) @@ -155,7 +156,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @CordaSerializable data class A(val a: Short) - var a = A(test) + val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -166,7 +167,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(1, obj.envelope.schema.types.size) assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.envelope.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals(1, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) @@ -192,7 +193,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { var a = A (test) - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) + val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise (a)) assert (obj.obj is A) val amqpObj = obj.obj as A @@ -282,4 +283,4 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) } -} \ No newline at end of file +} diff --git a/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt b/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt deleted file mode 100644 index cdbdcadf58..0000000000 --- a/experimental/src/main/kotlin/net/corda/carpenter/MetaCapenter.kt +++ /dev/null @@ -1,61 +0,0 @@ -package net.corda.carpenter - -import net.corda.core.serialization.carpenter.CarpenterSchemas -import net.corda.core.serialization.carpenter.Schema - -import net.corda.core.serialization.amqp.CompositeType - -/**********************************************************************************************************************/ - -abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { - - private val cc = ClassCarpenter() - val objects = mutableMapOf>() - - fun step (newObject : Schema) { - objects[newObject.name] = cc.build (newObject) - - /* go over the list of everything that had a dependency on the newly - carpented class existing and remove it from their dependency list, If that - list is now empty we have no impediment to carpenting that class up */ - schemas.dependsOn.remove(newObject.name)?.forEach { dependent -> - assert (newObject.name in schemas.dependencies[dependent]!!.second) - - schemas.dependencies[dependent]?.second?.remove(newObject.name) - - /* we're out of blockers so we can now create the type */ - if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) { - (schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema ( - classLoaders = listOf ( - ClassLoader.getSystemClassLoader(), - cc.classloader), - carpenterSchemas = schemas) - } - } - } - - abstract fun build() -} - -/**********************************************************************************************************************/ - -class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { - override fun build() { - while (schemas.carpenterSchemas.isNotEmpty()) { - val newObject = schemas.carpenterSchemas.removeAt(0) - step (newObject) - } - } -} - -/**********************************************************************************************************************/ - -class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { - override fun build() { - println ("TestMetaCarpenter::build") - if (schemas.carpenterSchemas.isEmpty()) return - step (schemas.carpenterSchemas.removeAt(0)) - } -} - -/**********************************************************************************************************************/ From c29673a4a47fe8ff82f7b616a3fe71b3a16950c6 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 5 Jul 2017 21:03:06 +0100 Subject: [PATCH 016/197] Tidy up --- .../corda/core/serialization/amqp/Schema.kt | 39 ++++--- .../serialization/carpenter/MetaCarpenter.kt | 35 +++++- .../carpenter/ClassCarpenterTestUtils.kt | 14 +-- ...berCompositeSchemaToClassCarpenterTests.kt | 95 ++++++--------- .../InheritanceSchemaToClassCarpenterTests.kt | 58 ++++----- ...berCompositeSchemaToClassCarpenterTests.kt | 110 +++--------------- 6 files changed, 143 insertions(+), 208 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index f0f0f80647..f145b86bb8 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -123,7 +123,7 @@ data class Schema(val types: List) : DescribedType { } } -data class Descriptor(var name: String?, val code: UnsignedLong? = null) : DescribedType { +data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(3L or DESCRIPTOR_TOP_32BITS) @@ -161,7 +161,7 @@ data class Descriptor(var name: String?, val code: UnsignedLong? = null) : Descr } } -data class Field(var name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { +data class Field(val name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(4L or DESCRIPTOR_TOP_32BITS) @@ -221,16 +221,9 @@ data class Field(var name: String, val type: String, val requires: List, fun validateType( classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) ) = when (type) { - "int" -> Int::class.javaPrimitiveType!! - "string" -> String::class.java - "short" -> Short::class.javaPrimitiveType!! - "long" -> Long::class.javaPrimitiveType!! - "char" -> Char::class.javaPrimitiveType!! - "boolean" -> Boolean::class.javaPrimitiveType!! - "double" -> Double::class.javaPrimitiveType!! - "float" -> Float::class.javaPrimitiveType!! - "*" -> if (classLoaders.exists(requires[0])) requires[0] else null - else -> if (classLoaders.exists (type)) type else null + "int", "string", "short", "long", "char", "boolean", "double", "float" -> true + "*" -> classLoaders.exists(requires[0]) + else -> classLoaders.exists (type) } fun typeAsString() = if (type =="*") requires[0] else type @@ -256,7 +249,7 @@ sealed class TypeNotation : DescribedType { abstract val descriptor: Descriptor } -data class CompositeType(override var name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { +data class CompositeType(override val name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(5L or DESCRIPTOR_TOP_32BITS) @@ -300,16 +293,30 @@ data class CompositeType(override var name: String, override val label: String?, return sb.toString() } - /** if we can load the class then we MUST know about all of it's sub elements, - otherwise we couldn't know about this */ + /** + * if we can load the class then we MUST know about all of it's composite elements + */ private fun validateKnown ( classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) { fields.forEach { - if (it.validateType(classLoaders) == null) throw UncarpentableException (name, it.name, it.type) + if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type) } } + /** + * based upon this AMQP schema either + * a) add the corespending carpenter schema to the [carpenterSchemas] param + * b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated + * at this time + * + * @param classLoaders list of classLoaders, defaulting toe the system class loader, that might + * be used to load objects + * @param carpenterSchemas structure that holds the dependency tree and list of classes that + * need constructing + * @param force by default a schema is not added to [carpenterSchemas] if it already exists + * on the class path. For testing purposes schema generation can be forced + */ fun carpenterSchema( classLoaders: List = listOf (ClassLoader.getSystemClassLoader()), carpenterSchemas : CarpenterSchemas, diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt index 3f0414c1ad..012df7173e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt @@ -5,10 +5,30 @@ import net.corda.core.serialization.amqp.TypeNotation /**********************************************************************************************************************/ +/** + * Generated from an AMQP schema this class represents the classes unknown to the deserialiser and that thusly + * require carpenting up in bytecode form. This is a multi step process as carpenting one object may be depedent + * upon the creation of others, this information is tracked in the dependency tree represented by + * [dependencies] and [dependsOn]. Creatable classes are stored in [carpenterSchemas]. + * + * The state of this class after initial generation is expected to mutate as classes are built by the carpenter + * enablaing the resolution of dependencies and thus new carpenter schemas added whilst those already + * carpented schemas are removed. + * + * @property carpenterSchemas The list of carpentable classes + * @property dependencies Maps a class to a list of classes that depend on it being built first + * @property dependsOn Maps a class to a list of classes it depends on being built before it + * + * Once a class is constructed we can quickly check for resolution by first looking at all of its dependents in the + * [dependencies] map. This will give us a list of classes that depended on that class being carpented. We can then + * in turn look up all of those classes in the [dependsOn] list, remove their dependency on the newly created class, + * and if that list is reduced to zero know we can now generate a [Schema] for them and carpent them up + */ data class CarpenterSchemas ( val carpenterSchemas : MutableList, val dependencies : MutableMap>>, - val dependsOn : MutableMap>) { + val dependsOn : MutableMap>) +{ companion object CarpenterSchemaConstructor { fun newInstance(): CarpenterSchemas { return CarpenterSchemas( @@ -19,8 +39,6 @@ data class CarpenterSchemas ( } fun addDepPair(type: TypeNotation, dependant: String, dependee: String) { - fun String.name() = this.split ('.').last().split('$').last() - println ("add dep ${dependant.name()} on ${dependee.name()}") dependsOn.computeIfAbsent(dependee, { mutableListOf() }).add(dependant) dependencies.computeIfAbsent(dependant, { Pair(type, mutableListOf()) }).second.add(dependee) } @@ -31,8 +49,16 @@ data class CarpenterSchemas ( /**********************************************************************************************************************/ +/** + * Take a dependency tree of [CarpenterSchemas] and reduce it to zero by carpenting those classes that + * require it. As classes are carpented check for depdency resolution, if now free generate a [Schema] for + * that class and add it to the list of classes ([CarpenterSchemas.carpenterSchemas]) that require + * carpenting + * + * @property cc a reference to the actual class carpenter we're using to constuct classes + * @property objects a list of carpented classes loaded into the carpenters class loader + */ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { - private val cc = ClassCarpenter() val objects = mutableMapOf>() @@ -76,7 +102,6 @@ class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { override fun build() { - println ("TestMetaCarpenter::build") if (schemas.carpenterSchemas.isEmpty()) return step (schemas.carpenterSchemas.removeAt(0)) } diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt index 5ec3dbc61d..90d8dc52fc 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -9,32 +9,32 @@ import net.corda.core.serialization.amqp.SerializationOutput /**********************************************************************************************************************/ -fun curruptName(name: String) = "${name}__carpenter" +fun corruptName(name: String) = "${name}__carpenter" /**********************************************************************************************************************/ /* given a list of class names work through the amqp envelope schema and alter any that match in the fashion defined above */ -fun Schema.curruptName(names: List): Schema { +fun Schema.corruptName(names: List): Schema { val newTypes: MutableList = mutableListOf() for (type in types) { - val newName = if (type.name in names) curruptName(type.name) else type.name + val newName = if (type.name in names) corruptName(type.name) else type.name val newProvides = type.provides.map { it -> - if (it in names) curruptName(it) else it + if (it in names) corruptName(it) else it } val newFields = mutableListOf() (type as CompositeType).fields.forEach { - val type = if (it.type in names) curruptName(it.type) else it.type + val fieldType = if (it.type in names) corruptName(it.type) else it.type val requires = if (it.requires.isNotEmpty() && (it.requires[0] in names)) - listOf(curruptName(it.requires[0])) else it.requires + listOf(corruptName(it.requires[0])) else it.requires - newFields.add(it.copy(type = type, requires = requires)) + newFields.add(it.copy(type = fieldType, requires = requires)) } newTypes.add(type.copy(name = newName, provides = newProvides, fields = newFields)) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index cc196cd5f3..58b5c84225 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -96,7 +96,7 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - val amqpSchema = obj.envelope.schema.curruptName(listOf (classTestName ("A"))) + val amqpSchema = obj.envelope.schema.corruptName(listOf (classTestName ("A"))) assert(obj.obj is B) @@ -120,7 +120,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("B"))) + val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() @@ -130,7 +130,7 @@ class CompositeMembers : AmqpCarpenterBase() { metaCarpenter.build() - assert(curruptName(classTestName("B")) in metaCarpenter.objects) + assert(corruptName(classTestName("B")) in metaCarpenter.objects) } @Test @@ -150,18 +150,18 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() /* just verify we're in the expected initial state, A is carpentable, B is not because it depends on A and the dependency chains are in place */ assertEquals(1, carpenterSchema.size) - assertEquals(curruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) + assertEquals(corruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) assertEquals(1, carpenterSchema.dependencies.size) - assert(curruptName(classTestName("B")) in carpenterSchema.dependencies) + assert(corruptName(classTestName("B")) in carpenterSchema.dependencies) assertEquals(1, carpenterSchema.dependsOn.size) - assert(curruptName(classTestName("A")) in carpenterSchema.dependsOn) + assert(corruptName(classTestName("A")) in carpenterSchema.dependsOn) /* test meta carpenter lets us single step over the creation */ val metaCarpenter = TestMetaCarpenter(carpenterSchema) @@ -174,18 +174,18 @@ class CompositeMembers : AmqpCarpenterBase() { /* one build iteration should have carpetned up A and worked out that B is now buildable given it's depedencies have been satisfied */ - assertTrue(curruptName(classTestName("A")) in metaCarpenter.objects) - assertFalse(curruptName(classTestName("B")) in metaCarpenter.objects) + assertTrue(corruptName(classTestName("A")) in metaCarpenter.objects) + assertFalse(corruptName(classTestName("B")) in metaCarpenter.objects) assertEquals(1, carpenterSchema.carpenterSchemas.size) - assertEquals(curruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) + assertEquals(corruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) assertTrue(carpenterSchema.dependencies.isEmpty()) assertTrue(carpenterSchema.dependsOn.isEmpty()) /* second manual iteration, will carpent B */ metaCarpenter.build() - assert(curruptName(classTestName("A")) in metaCarpenter.objects) - assert(curruptName(classTestName("B")) in metaCarpenter.objects) + assert(corruptName(classTestName("A")) in metaCarpenter.objects) + assert(corruptName(classTestName("B")) in metaCarpenter.objects) assertTrue(carpenterSchema.carpenterSchemas.isEmpty()) } @@ -211,7 +211,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @@ -237,13 +237,13 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } - @Test - fun parentsIsUnknownWithUnkownInheritedMember() { + @Test(expected = UncarpentableException::class) + fun parentsIsUnknownWithUnknownInheritedMember() { val testA = 10 val testB = 20 val testC = 30 @@ -263,66 +263,41 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.curruptName(listOf(classTestName("A"), classTestName("B"))) + val carpenterSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) + + TestMetaCarpenter(carpenterSchema.carpenterSchema()) } + /* - * In this case B holds an element of Interface I_ which is an A but we don't know of A - * but we do know about I_ - */ + * TODO serializer doesn't support inheritnace at the moment, when it does this should work @Test - fun nestedIsInterfaceToUnknown() { + fun `inheritance`() { val testA = 10 val testB = 20 @CordaSerializable - data class A(override val a: Int) : I_ + open class A(open val a: Int) @CordaSerializable - data class B(val a: A, var b: Int) + class B(override val a: Int, val b: Int) : A (a) - val b = B(A(testA), testB) - - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - - assert(obj.obj is B) - } - - @Test - fun nestedIsUnknownInterface() { - val testA = 10 - val testB = 20 - - @CordaSerializable - data class A(override val a: Int) : I_ - - @CordaSerializable - data class B(val a: A, var b: Int) - - val b = B(A(testA), testB) - - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - - assert(obj.obj is B) - } - - @Test - fun ParentsIsInterfaceToUnkown() { - val testA = 10 - val testB = 20 - - @CordaSerializable - data class A(override val a: Int) : I_ - - @CordaSerializable - data class B(val a: A, var b: Int) - - val b = B(A(testA), testB) + val b = B(testA, testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) + + val carpenterSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) + + val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema()) + + assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size) + assertEquals(corruptName(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name) + assertEquals(1, metaCarpenter.schemas.dependencies.size) + assertTrue(corruptName(classTestName("A")) in metaCarpenter.schemas.dependencies) } + */ } diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt index 7516b4228e..638074a8e6 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -67,16 +67,16 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) + val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) - val l2 = curruptSchema.carpenterSchema() + val l2 = corruptSchema.carpenterSchema() assertEquals(1, l2.size) - val aSchema = l2.carpenterSchemas.find { it.name == curruptName(classTestName("A")) } + val aSchema = l2.carpenterSchemas.find { it.name == corruptName(classTestName("A")) } assertNotEquals(null, aSchema) - assertEquals(curruptName(classTestName("A")), aSchema!!.name) + assertEquals(corruptName(classTestName("A")), aSchema!!.name) assertEquals(1, aSchema.interfaces.size) assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0]) @@ -116,10 +116,10 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) - val aName = curruptName(classTestName("A")) + val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) + val aName = corruptName(classTestName("A")) - val l2 = curruptSchema.carpenterSchema() + val l2 = corruptSchema.carpenterSchema() assertEquals(1, l2.size) @@ -167,9 +167,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus needs some carpentry */ - val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) - val l2 = curruptSchema.carpenterSchema() - val aName = curruptName(classTestName("A")) + val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) + val l2 = corruptSchema.carpenterSchema() + val aName = corruptName(classTestName("A")) assertEquals(1, l2.size) @@ -217,9 +217,9 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val curruptSchema = serSchema.curruptName(listOf(classTestName("A"))) - val l2 = curruptSchema.carpenterSchema() - val aName = curruptName(classTestName("A")) + val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) + val l2 = corruptSchema.carpenterSchema() + val aName = corruptName(classTestName("A")) assertEquals(1, l2.size) @@ -271,10 +271,10 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { */ assertEquals(4, serSchema.types.size) - val curruptSchema = serSchema.curruptName(listOf(classTestName("A"), classTestName("B"))) - val cSchema = curruptSchema.carpenterSchema() - val aName = curruptName(classTestName("A")) - val bName = curruptName(classTestName("B")) + val corruptSchema = serSchema.corruptName(listOf(classTestName("A"), classTestName("B"))) + val cSchema = corruptSchema.carpenterSchema() + val aName = corruptName(classTestName("A")) + val bName = corruptName(classTestName("B")) assertEquals(2, cSchema.size) @@ -333,7 +333,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(4, serSchema.types.size) /* ignore the return as we expect this to throw */ - serSchema.curruptName(listOf( + serSchema.corruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema() } @@ -358,10 +358,10 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { */ assertEquals(2, serSchema.types.size) - val amqpSchema = serSchema.curruptName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) + val amqpSchema = serSchema.corruptName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) - val aName = curruptName(classTestName("A")) - val iName = curruptName("${this.javaClass.`package`.name}.I") + val aName = corruptName(classTestName("A")) + val iName = corruptName("${this.javaClass.`package`.name}.I") val carpenterSchema = amqpSchema.carpenterSchema() @@ -405,14 +405,14 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.curruptName(listOf( + val amqpSchema = obj.envelope.schema.corruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.II")) - val aName = curruptName(classTestName("A")) - val iName = curruptName("${this.javaClass.`package`.name}.I") - val iiName = curruptName("${this.javaClass.`package`.name}.II") + val aName = corruptName(classTestName("A")) + val iName = corruptName("${this.javaClass.`package`.name}.I") + val iiName = corruptName("${this.javaClass.`package`.name}.II") val carpenterSchema = amqpSchema.carpenterSchema() @@ -460,14 +460,14 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.curruptName(listOf( + val amqpSchema = obj.envelope.schema.corruptName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.III")) - val aName = curruptName(classTestName("A")) - val iName = curruptName("${this.javaClass.`package`.name}.I") - val iiiName = curruptName("${this.javaClass.`package`.name}.III") + val aName = corruptName(classTestName("A")) + val iName = corruptName("${this.javaClass.`package`.name}.I") + val iiiName = corruptName("${this.javaClass.`package`.name}.III") val carpenterSchema = amqpSchema.carpenterSchema() diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 304d7185c4..302ca58a4a 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -38,11 +38,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) + val aBuilder = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } @Test @@ -69,52 +69,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) + val aBuilder = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } - /* - @Test - fun singleChar () { - val test = 'c' - - @CordaSerializable - data class A(val a : Char) - val a = A(test) - - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - - assert (obj.obj is A) - val amqpObj = obj.obj as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.envelope.schema.types.size) - assertEquals (1, obj.envelope.schema.types.size) - assert (obj.envelope.schema.types[0] is CompositeType) - - val amqpSchema = obj.envelope.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("char", amqpSchema.fields[0].type) - - val carpenterSchema = CarpenterSchema.newInstance() - amqpSchema.carpenterSchema(carpenterSchema = carpenterSchema, force = true) - - assert (classTestName ("A") in carpenterSchema.carpenterSchemas) - - val aSchema = carpenterSchema.carpenterSchemas[classTestName ("A")]!! - val pinochio = ClassCarpenter().build(aSchema) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - @Test fun singleLong() { val test = 10L @@ -143,10 +104,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val aBuilder = ClassCarpenter().build(aSchema) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } @Test @@ -177,47 +138,14 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val aBuilder = ClassCarpenter().build(aSchema) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } - /* @Test - fun singleBool() { - val test = true - - @CordaSerializable - data class A(val a : Boolean) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise (a)) - - assert (obj.obj is A) - val amqpObj = obj.obj as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.envelope.schema.types.size) - assert (obj.envelope.schema.types[0] is CompositeType) - - var amqpSchema = obj.envelope.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("boolean", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - - @Test - fun singleDouble() { +fun singleDouble() { val test = 10.0 @CordaSerializable @@ -244,10 +172,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val aBuilder = ClassCarpenter().build(aSchema) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } @Test @@ -278,9 +206,9 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! - val pinochio = ClassCarpenter().build(aSchema) - val p = pinochio.constructors[0].newInstance(test) + val aBuilder = ClassCarpenter().build(aSchema) + val p = aBuilder.constructors[0].newInstance(test) - assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) + assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) } } From 5ab26dd275437dd93db080f4e17b560afe447047 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Tue, 11 Jul 2017 14:02:22 +0100 Subject: [PATCH 017/197] Update to account for nullability awareness in the carpenter --- .../corda/core/serialization/amqp/Schema.kt | 6 +- .../serialization/carpenter/ClassCarpenter.kt | 111 ++------------ .../serialization/carpenter/Exceptions.kt | 9 ++ .../core/serialization/carpenter/Schema.kt | 110 +++++++++++++- .../amqp/SerializationOutputTests.kt | 2 +- .../carpenter/ClassCarpenterTest.kt | 136 +++++++++--------- ...berCompositeSchemaToClassCarpenterTests.kt | 2 +- 7 files changed, 198 insertions(+), 178 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index f145b86bb8..2be185e8b5 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -17,7 +17,9 @@ import java.util.* import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.carpenter.Schema as CarpenterSchema +import net.corda.core.serialization.carpenter.Field as CarpenterField import net.corda.core.serialization.carpenter.CarpenterSchemaFactory +import net.corda.core.serialization.carpenter.FieldFactory // TODO: get an assigned number as per AMQP spec val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 @@ -350,11 +352,11 @@ data class CompositeType(override val name: String, override val label: String?, } } - val m : MutableMap> = mutableMapOf() + val m : MutableMap = mutableMapOf() fields.forEach { try { - m[it.name] = it.getTypeAsClass(classLoaders) + m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders)) } catch (e: ClassNotFoundException) { carpenterSchemas.addDepPair(this, name, it.typeAsString()) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index d046d71de2..617dfd5c75 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -3,17 +3,14 @@ package net.corda.core.serialization.carpenter import org.objectweb.asm.ClassWriter import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes.* -import org.objectweb.asm.Type import java.lang.Character.isJavaIdentifierPart import java.lang.Character.isJavaIdentifierStart -import net.corda.core.serialization.carpenter.Schema -import net.corda.core.serialization.carpenter.ClassSchema -import net.corda.core.serialization.carpenter.InterfaceSchema - import java.util.* +/**********************************************************************************************************************/ + /** * Any object that implements this interface is expected to expose its own fields via the [get] method, exactly * as if `this.class.getMethod("get" + name.capitalize()).invoke(this)` had been called. It is intended as a more @@ -23,6 +20,13 @@ interface SimpleFieldAccess { operator fun get(name: String): Any? } +/**********************************************************************************************************************/ + +class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { + fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) +} + +/**********************************************************************************************************************/ /** * A class carpenter generates JVM bytecodes for a class given a schema and then loads it into a sub-classloader. @@ -68,15 +72,6 @@ interface SimpleFieldAccess { * * Equals/hashCode methods are not yet supported. */ - -/**********************************************************************************************************************/ - -class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { - fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) -} - -/**********************************************************************************************************************/ - class ClassCarpenter { // TODO: Generics. // TODO: Sandbox the generated code when a security manager is in use. @@ -84,94 +79,6 @@ class ClassCarpenter { // TODO: Support annotations. // TODO: isFoo getter patterns for booleans (this is what Kotlin generates) - class DuplicateNameException : RuntimeException("An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.") - class InterfaceMismatchException(msg: String) : RuntimeException(msg) - class NullablePrimitiveException(msg: String) : RuntimeException(msg) - - abstract class Field(val field: Class) { - companion object { - const val unsetName = "Unset" - } - - var name: String = unsetName - abstract val nullabilityAnnotation: String - - val descriptor: String - get() = Type.getDescriptor(this.field) - - val type: String - get() = if (this.field.isPrimitive) this.descriptor else "Ljava/lang/Object;" - - fun generateField(cw: ClassWriter) { - val fieldVisitor = cw.visitField(ACC_PROTECTED + ACC_FINAL, name, descriptor, null, null) - fieldVisitor.visitAnnotation(nullabilityAnnotation, true).visitEnd() - fieldVisitor.visitEnd() - } - - fun addNullabilityAnnotation(mv: MethodVisitor) { - mv.visitAnnotation(nullabilityAnnotation, true).visitEnd() - } - - fun visitParameter(mv: MethodVisitor, idx: Int) { - with(mv) { - visitParameter(name, 0) - if (!field.isPrimitive) { - visitParameterAnnotation(idx, nullabilityAnnotation, true).visitEnd() - } - } - } - - abstract fun copy(name: String, field: Class): Field - abstract fun nullTest(mv: MethodVisitor, slot: Int) - } - - class NonNullableField(field: Class) : Field(field) { - override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;" - - constructor(name: String, field: Class) : this(field) { - this.name = name - } - - override fun copy(name: String, field: Class) = NonNullableField(name, field) - - override fun nullTest(mv: MethodVisitor, slot: Int) { - assert(name != unsetName) - - if (!field.isPrimitive) { - with(mv) { - visitVarInsn(ALOAD, 0) // load this - visitVarInsn(ALOAD, slot) // load parameter - visitLdcInsn("param \"$name\" cannot be null") - visitMethodInsn(INVOKESTATIC, - "java/util/Objects", - "requireNonNull", - "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false) - visitInsn(POP) - } - } - } - } - - - class NullableField(field: Class) : Field(field) { - override val nullabilityAnnotation = "Ljavax/annotation/Nullable;" - - constructor(name: String, field: Class) : this(field) { - if (field.isPrimitive) { - throw NullablePrimitiveException ( - "Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable") - } - - this.name = name - } - - override fun copy(name: String, field: Class) = NullableField(name, field) - - override fun nullTest(mv: MethodVisitor, slot: Int) { - assert(name != unsetName) - } - } - val classloader = CarpenterClassLoader() private val _loaded = HashMap>() diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt new file mode 100644 index 0000000000..69608cc21a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt @@ -0,0 +1,9 @@ +package net.corda.core.serialization.carpenter + + +class DuplicateNameException : RuntimeException ( + "An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.") + +class InterfaceMismatchException(msg: String) : RuntimeException(msg) + +class NullablePrimitiveException(msg: String) : RuntimeException(msg) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt index dfc37fc471..f15eb1d431 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -1,5 +1,10 @@ package net.corda.core.serialization.carpenter +import jdk.internal.org.objectweb.asm.Opcodes.* + +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.MethodVisitor + import org.objectweb.asm.Type import java.util.LinkedHashMap @@ -14,7 +19,7 @@ abstract class Schema( val superclass: Schema? = null, val interfaces: List> = emptyList()) { - private fun Map.descriptors() = + private fun Map.descriptors() = LinkedHashMap(this.mapValues { it.value.descriptor }) /* Fix the order up front if the user didn't, inject the name into the field as it's @@ -35,7 +40,7 @@ abstract class Schema( class ClassSchema( name: String, - fields: Map>, + fields: Map, superclass: Schema? = null, interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) @@ -44,7 +49,7 @@ class ClassSchema( class InterfaceSchema( name: String, - fields: Map>, + fields: Map, superclass: Schema? = null, interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) @@ -54,7 +59,7 @@ class InterfaceSchema( object CarpenterSchemaFactory { fun newInstance ( name: String, - fields: Map>, + fields: Map, superclass: Schema? = null, interfaces: List> = emptyList(), isInterface: Boolean = false @@ -64,3 +69,100 @@ object CarpenterSchemaFactory { } /**********************************************************************************************************************/ + +abstract class Field(val field: Class) { + companion object { + const val unsetName = "Unset" + } + + var name: String = unsetName + abstract val nullabilityAnnotation: String + + val descriptor: String + get() = Type.getDescriptor(this.field) + + val type: String + get() = if (this.field.isPrimitive) this.descriptor else "Ljava/lang/Object;" + + fun generateField(cw: ClassWriter) { + val fieldVisitor = cw.visitField(ACC_PROTECTED + ACC_FINAL, name, descriptor, null, null) + fieldVisitor.visitAnnotation(nullabilityAnnotation, true).visitEnd() + fieldVisitor.visitEnd() + } + + fun addNullabilityAnnotation(mv: MethodVisitor) { + mv.visitAnnotation(nullabilityAnnotation, true).visitEnd() + } + + fun visitParameter(mv: MethodVisitor, idx: Int) { + with(mv) { + visitParameter(name, 0) + if (!field.isPrimitive) { + visitParameterAnnotation(idx, nullabilityAnnotation, true).visitEnd() + } + } + } + + abstract fun copy(name: String, field: Class): Field + abstract fun nullTest(mv: MethodVisitor, slot: Int) +} + +/**********************************************************************************************************************/ + +class NonNullableField(field: Class) : Field(field) { + override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;" + + constructor(name: String, field: Class) : this(field) { + this.name = name + } + + override fun copy(name: String, field: Class) = NonNullableField(name, field) + + override fun nullTest(mv: MethodVisitor, slot: Int) { + assert(name != unsetName) + + if (!field.isPrimitive) { + with(mv) { + visitVarInsn(ALOAD, 0) // load this + visitVarInsn(ALOAD, slot) // load parameter + visitLdcInsn("param \"$name\" cannot be null") + visitMethodInsn(INVOKESTATIC, + "java/util/Objects", + "requireNonNull", + "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false) + visitInsn(POP) + } + } + } +} + +/**********************************************************************************************************************/ + +class NullableField(field: Class) : Field(field) { + override val nullabilityAnnotation = "Ljavax/annotation/Nullable;" + + constructor(name: String, field: Class) : this(field) { + if (field.isPrimitive) { + throw NullablePrimitiveException ( + "Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable") + } + + this.name = name + } + + override fun copy(name: String, field: Class) = NullableField(name, field) + + override fun nullTest(mv: MethodVisitor, slot: Int) { + assert(name != unsetName) + } +} + +/**********************************************************************************************************************/ + +object FieldFactory { + fun newInstance (mandatory: Boolean, name: String, field: Class) = + if (mandatory) NonNullableField (name, field) else NullableField (name, field) + +} + +/**********************************************************************************************************************/ diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt index 36d84c26a6..f1e9df1707 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt @@ -8,7 +8,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.EmptyWhitelist import net.corda.core.serialization.KryoAMQPSerializer import net.corda.core.serialization.amqp.SerializerFactory.Companion.isPrimitive -import net.corda.core.utilities.CordaRuntimeException +import net.corda.core.CordaRuntimeException import net.corda.nodeapi.RPCException import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index b816b86351..42683be4aa 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -31,7 +31,7 @@ class ClassCarpenterTest { @Test fun prims() { - val clazz = cc.build(ClassCarpenter.ClassSchema( + val clazz = cc.build(ClassSchema( "gen.Prims", mapOf( "anIntField" to Int::class.javaPrimitiveType!!, @@ -42,7 +42,7 @@ class ClassCarpenterTest { "floatMyBoat" to Float::class.javaPrimitiveType!!, "byteMe" to Byte::class.javaPrimitiveType!!, "booleanField" to Boolean::class.javaPrimitiveType!!).mapValues { - ClassCarpenter.NonNullableField (it.value) + NonNullableField (it.value) })) assertEquals(8, clazz.nonSyntheticFields.size) assertEquals(10, clazz.nonSyntheticMethods.size) @@ -72,7 +72,7 @@ class ClassCarpenterTest { val clazz = cc.build(ClassSchema("gen.Person", mapOf( "age" to Int::class.javaPrimitiveType!!, "name" to String::class.java - ).mapValues { ClassCarpenter.NonNullableField (it.value) } )) + ).mapValues { NonNullableField (it.value) } )) val i = clazz.constructors[0].newInstance(32, "Mike") return Pair(clazz, i) } @@ -90,7 +90,7 @@ class ClassCarpenterTest { assertEquals("Person{age=32, name=Mike}", i.toString()) } - @Test(expected = ClassCarpenter.DuplicateNameException::class) + @Test(expected = DuplicateNameException::class) fun duplicates() { cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) @@ -99,8 +99,8 @@ class ClassCarpenterTest { @Test fun `can refer to each other`() { val (clazz1, i) = genPerson() - val clazz2 = cc.build(ClassCarpenter.ClassSchema("gen.Referee", mapOf( - "ref" to ClassCarpenter.NonNullableField (clazz1) + val clazz2 = cc.build(ClassSchema("gen.Referee", mapOf( + "ref" to NonNullableField (clazz1) ))) val i2 = clazz2.constructors[0].newInstance(i) assertEquals(i, (i2 as SimpleFieldAccess)["ref"]) @@ -108,13 +108,13 @@ class ClassCarpenterTest { @Test fun superclasses() { - val schema1 = ClassCarpenter.ClassSchema( + val schema1 = ClassSchema( "gen.A", - mapOf("a" to ClassCarpenter.NonNullableField (String::class.java))) + mapOf("a" to NonNullableField (String::class.java))) - val schema2 = ClassCarpenter.ClassSchema( + val schema2 = ClassSchema( "gen.B", - mapOf("b" to ClassCarpenter.NonNullableField (String::class.java)), + mapOf("b" to NonNullableField (String::class.java)), schema1) val clazz = cc.build(schema2) @@ -126,12 +126,12 @@ class ClassCarpenterTest { @Test fun interfaces() { - val schema1 = ClassCarpenter.ClassSchema( + val schema1 = ClassSchema( "gen.A", - mapOf("a" to ClassCarpenter.NonNullableField(String::class.java))) + mapOf("a" to NonNullableField(String::class.java))) - val schema2 = ClassCarpenter.ClassSchema("gen.B", - mapOf("b" to ClassCarpenter.NonNullableField(Int::class.java)), + val schema2 = ClassSchema("gen.B", + mapOf("b" to NonNullableField(Int::class.java)), schema1, interfaces = listOf(DummyInterface::class.java)) @@ -141,15 +141,15 @@ class ClassCarpenterTest { assertEquals(1, i.b) } - @Test(expected = ClassCarpenter.InterfaceMismatchException::class) + @Test(expected = InterfaceMismatchException::class) fun `mismatched interface`() { - val schema1 = ClassCarpenter.ClassSchema( + val schema1 = ClassSchema( "gen.A", - mapOf("a" to ClassCarpenter.NonNullableField(String::class.java))) + mapOf("a" to NonNullableField(String::class.java))) - val schema2 = ClassCarpenter.ClassSchema( + val schema2 = ClassSchema( "gen.B", - mapOf("c" to ClassCarpenter.NonNullableField(Int::class.java)), + mapOf("c" to NonNullableField(Int::class.java)), schema1, interfaces = listOf(DummyInterface::class.java)) @@ -160,9 +160,9 @@ class ClassCarpenterTest { @Test fun `generate interface`() { - val schema1 = ClassCarpenter.InterfaceSchema( + val schema1 = InterfaceSchema( "gen.Interface", - mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java))) + mapOf("a" to NonNullableField (Int::class.java))) val iface = cc.build(schema1) @@ -173,7 +173,7 @@ class ClassCarpenterTest { val schema2 = ClassSchema( "gen.Derived", - mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)), + mapOf("a" to NonNullableField (Int::class.java)), interfaces = listOf(iface)) val clazz = cc.build(schema2) @@ -185,25 +185,25 @@ class ClassCarpenterTest { @Test fun `generate multiple interfaces`() { - val iFace1 = ClassCarpenter.InterfaceSchema( + val iFace1 = InterfaceSchema( "gen.Interface1", mapOf( - "a" to ClassCarpenter.NonNullableField(Int::class.java), - "b" to ClassCarpenter.NonNullableField(String::class.java))) + "a" to NonNullableField(Int::class.java), + "b" to NonNullableField(String::class.java))) - val iFace2 = ClassCarpenter.InterfaceSchema( + val iFace2 = InterfaceSchema( "gen.Interface2", mapOf( - "c" to ClassCarpenter.NonNullableField(Int::class.java), - "d" to ClassCarpenter.NonNullableField(String::class.java))) + "c" to NonNullableField(Int::class.java), + "d" to NonNullableField(String::class.java))) val class1 = ClassSchema( "gen.Derived", mapOf( - "a" to ClassCarpenter.NonNullableField(Int::class.java), - "b" to ClassCarpenter.NonNullableField(String::class.java), - "c" to ClassCarpenter.NonNullableField(Int::class.java), - "d" to ClassCarpenter.NonNullableField(String::class.java)), + "a" to NonNullableField(Int::class.java), + "b" to NonNullableField(String::class.java), + "c" to NonNullableField(Int::class.java), + "d" to NonNullableField(String::class.java)), interfaces = listOf(cc.build(iFace1), cc.build(iFace2))) val clazz = cc.build(class1) @@ -224,23 +224,23 @@ class ClassCarpenterTest { val iFace1 = InterfaceSchema( "gen.Interface1", mapOf( - "a" to ClassCarpenter.NonNullableField (Int::class.java), - "b" to ClassCarpenter.NonNullableField(String::class.java))) + "a" to NonNullableField (Int::class.java), + "b" to NonNullableField(String::class.java))) val iFace2 = InterfaceSchema( "gen.Interface2", mapOf( - "c" to ClassCarpenter.NonNullableField(Int::class.java), - "d" to ClassCarpenter.NonNullableField(String::class.java)), + "c" to NonNullableField(Int::class.java), + "d" to NonNullableField(String::class.java)), interfaces = listOf(cc.build(iFace1))) val class1 = ClassSchema( "gen.Derived", mapOf( - "a" to ClassCarpenter.NonNullableField(Int::class.java), - "b" to ClassCarpenter.NonNullableField(String::class.java), - "c" to ClassCarpenter.NonNullableField(Int::class.java), - "d" to ClassCarpenter.NonNullableField(String::class.java)), + "a" to NonNullableField(Int::class.java), + "b" to NonNullableField(String::class.java), + "c" to NonNullableField(Int::class.java), + "d" to NonNullableField(String::class.java)), interfaces = listOf(cc.build(iFace2))) val clazz = cc.build(class1) @@ -259,9 +259,9 @@ class ClassCarpenterTest { @Test(expected = java.lang.IllegalArgumentException::class) fun `null parameter small int`() { val className = "iEnjoySwede" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java))) + mapOf("a" to NonNullableField (Int::class.java))) val clazz = cc.build(schema) @@ -269,12 +269,12 @@ class ClassCarpenterTest { clazz.constructors[0].newInstance(a) } - @Test(expected = ClassCarpenter.NullablePrimitiveException::class) + @Test(expected = NullablePrimitiveException::class) fun `nullable parameter small int`() { val className = "iEnjoySwede" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NullableField (Int::class.java))) + mapOf("a" to NullableField (Int::class.java))) cc.build(schema) } @@ -282,9 +282,9 @@ class ClassCarpenterTest { @Test fun `nullable parameter integer`() { val className = "iEnjoyWibble" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NullableField (Integer::class.java))) + mapOf("a" to NullableField (Integer::class.java))) val clazz = cc.build(schema) val a1 : Int? = null @@ -297,9 +297,9 @@ class ClassCarpenterTest { @Test fun `non nullable parameter integer with non null`() { val className = "iEnjoyWibble" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java))) + mapOf("a" to NonNullableField (Integer::class.java))) val clazz = cc.build(schema) @@ -310,9 +310,9 @@ class ClassCarpenterTest { @Test(expected = java.lang.reflect.InvocationTargetException::class) fun `non nullable parameter integer with null`() { val className = "iEnjoyWibble" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java))) + mapOf("a" to NonNullableField (Integer::class.java))) val clazz = cc.build(schema) @@ -324,9 +324,9 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `int array`() { val className = "iEnjoyPotato" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java))) + mapOf("a" to NonNullableField(IntArray::class.java))) val clazz = cc.build(schema) @@ -343,9 +343,9 @@ class ClassCarpenterTest { @Test(expected = java.lang.reflect.InvocationTargetException::class) fun `nullable int array throws`() { val className = "iEnjoySwede" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java))) + mapOf("a" to NonNullableField(IntArray::class.java))) val clazz = cc.build(schema) @@ -357,9 +357,9 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `integer array`() { val className = "iEnjoyFlan" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NonNullableField(Array::class.java))) + mapOf("a" to NonNullableField(Array::class.java))) val clazz = cc.build(schema) @@ -377,11 +377,11 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `int array with ints`() { val className = "iEnjoyCrumble" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", mapOf( "a" to Int::class.java, "b" to IntArray::class.java, - "c" to Int::class.java).mapValues { ClassCarpenter.NonNullableField(it.value) }) + "c" to Int::class.java).mapValues { NonNullableField(it.value) }) val clazz = cc.build(schema) @@ -399,11 +399,11 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `multiple int arrays`() { val className = "iEnjoyJam" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", mapOf( "a" to IntArray::class.java, "b" to Int::class.java, - "c" to IntArray::class.java).mapValues { ClassCarpenter.NonNullableField(it.value) }) + "c" to IntArray::class.java).mapValues { NonNullableField(it.value) }) val clazz = cc.build(schema) val i = clazz.constructors[0].newInstance(intArrayOf(1, 2), 3, intArrayOf(4, 5, 6)) @@ -422,9 +422,9 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `string array`() { val className = "iEnjoyToast" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NullableField(Array::class.java))) + mapOf("a" to NullableField(Array::class.java))) val clazz = cc.build(schema) @@ -440,12 +440,12 @@ class ClassCarpenterTest { @Suppress("UNCHECKED_CAST") fun `string arrays`() { val className = "iEnjoyToast" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", mapOf( "a" to Array::class.java, "b" to String::class.java, - "c" to Array::class.java).mapValues { ClassCarpenter.NullableField (it.value) }) + "c" to Array::class.java).mapValues { NullableField (it.value) }) val clazz = cc.build(schema) @@ -469,10 +469,10 @@ class ClassCarpenterTest { @Test fun `nullable sets annotations`() { val className = "iEnjoyJam" - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "gen.$className", - mapOf("a" to ClassCarpenter.NullableField(String::class.java), - "b" to ClassCarpenter.NonNullableField(String::class.java))) + mapOf("a" to NullableField(String::class.java), + "b" to NonNullableField(String::class.java))) val clazz = cc.build(schema) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 302ca58a4a..c464c21445 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -145,7 +145,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { } @Test -fun singleDouble() { + fun singleDouble() { val test = 10.0 @CordaSerializable From 86902e6356bb2c546d75161732a650f33de47411 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 12 Jul 2017 10:37:23 +0100 Subject: [PATCH 018/197] Review comments * Move all alterations on the amqp schema object out of the actual amqp/Schema file and have them live in the carpenter as extension functions * Move carpenter exceptions to their own file * Rename the schema name corrupter to the name mangler * reduce whitespace * alter comment style --- .../corda/core/serialization/amqp/Schema.kt | 135 ----------- .../carpenter/AMQPSchemaExtensions.kt | 127 ++++++++++ .../serialization/carpenter/ClassCarpenter.kt | 13 +- .../serialization/carpenter/Exceptions.kt | 4 +- .../serialization/carpenter/MetaCarpenter.kt | 20 +- .../carpenter/ClassCarpenterTest.kt | 11 - .../carpenter/ClassCarpenterTestUtils.kt | 33 +-- ...berCompositeSchemaToClassCarpenterTests.kt | 89 +++---- .../InheritanceSchemaToClassCarpenterTests.kt | 221 +++++++----------- ...berCompositeSchemaToClassCarpenterTests.kt | 19 +- ...berCompositeSchemaToClassCarpenterTests.kt | 28 +-- 11 files changed, 283 insertions(+), 417 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index 2be185e8b5..46a6d03748 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -15,11 +15,8 @@ import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* -import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.carpenter.Schema as CarpenterSchema import net.corda.core.serialization.carpenter.Field as CarpenterField -import net.corda.core.serialization.carpenter.CarpenterSchemaFactory -import net.corda.core.serialization.carpenter.FieldFactory // TODO: get an assigned number as per AMQP spec val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 @@ -29,24 +26,6 @@ val DESCRIPTOR_DOMAIN: String = "net.corda" // "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray()) -private fun List.exists (clazz: String) = - this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null - -private fun List.loadIfExists (clazz: String) : Class<*> { - this.forEach { - try { - return it.loadClass(clazz) - } catch (e: ClassNotFoundException) { - return@forEach - } - } - throw ClassNotFoundException(clazz) -} - -class UncarpentableException (name: String, field: String, type: String) : - Throwable ("Class $name is loadable yet contains field $field of unknown type $type") - - /** * This class wraps all serialized data, so that the schema can be carried along with it. We will provide various internal utilities * to decompose and recompose with/without schema etc so that e.g. we can store objects with a (relationally) normalised out schema to @@ -111,18 +90,6 @@ data class Schema(val types: List) : DescribedType { override fun getDescribed(): Any = listOf(types) override fun toString(): String = types.joinToString("\n") - - fun carpenterSchema(loaders : List = listOf(ClassLoader.getSystemClassLoader())) - : CarpenterSchemas - { - val rtn = CarpenterSchemas.newInstance() - - types.filterIsInstance().forEach { - it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn) - } - - return rtn - } } data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType { @@ -205,29 +172,6 @@ data class Field(val name: String, val type: String, val requires: List, return sb.toString() } - fun getTypeAsClass( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) - ) = when (type) { - "int" -> Int::class.javaPrimitiveType!! - "string" -> String::class.java - "short" -> Short::class.javaPrimitiveType!! - "long" -> Long::class.javaPrimitiveType!! - "char" -> Char::class.javaPrimitiveType!! - "boolean" -> Boolean::class.javaPrimitiveType!! - "double" -> Double::class.javaPrimitiveType!! - "float" -> Float::class.javaPrimitiveType!! - "*" -> classLoaders.loadIfExists(requires[0]) - else -> classLoaders.loadIfExists(type) - } - - fun validateType( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) - ) = when (type) { - "int", "string", "short", "long", "char", "boolean", "double", "float" -> true - "*" -> classLoaders.exists(requires[0]) - else -> classLoaders.exists (type) - } - fun typeAsString() = if (type =="*") requires[0] else type } @@ -294,85 +238,6 @@ data class CompositeType(override val name: String, override val label: String?, sb.append("") return sb.toString() } - - /** - * if we can load the class then we MUST know about all of it's composite elements - */ - private fun validateKnown ( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) - { - fields.forEach { - if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type) - } - } - - /** - * based upon this AMQP schema either - * a) add the corespending carpenter schema to the [carpenterSchemas] param - * b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated - * at this time - * - * @param classLoaders list of classLoaders, defaulting toe the system class loader, that might - * be used to load objects - * @param carpenterSchemas structure that holds the dependency tree and list of classes that - * need constructing - * @param force by default a schema is not added to [carpenterSchemas] if it already exists - * on the class path. For testing purposes schema generation can be forced - */ - fun carpenterSchema( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()), - carpenterSchemas : CarpenterSchemas, - force : Boolean = false) - { - /* first question, do we know about this type or not */ - if (classLoaders.exists(name)) { - validateKnown(classLoaders) - - if (!force) return - } - - val providesList = mutableListOf>() - - var isInterface = false - var isCreatable = true - - provides.forEach { - if (name == it) { - isInterface = true - return@forEach - } - - try { - providesList.add (classLoaders.loadIfExists(it)) - } - catch (e: ClassNotFoundException) { - carpenterSchemas.addDepPair(this, name, it) - - isCreatable = false - } - } - - val m : MutableMap = mutableMapOf() - - fields.forEach { - try { - m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders)) - } - catch (e: ClassNotFoundException) { - carpenterSchemas.addDepPair(this, name, it.typeAsString()) - - isCreatable = false - } - } - - if (isCreatable) { - carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance( - name = name, - fields = m, - interfaces = providesList, - isInterface = isInterface)) - } - } } data class RestrictedType(override val name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt new file mode 100644 index 0000000000..954cf490b5 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt @@ -0,0 +1,127 @@ +package net.corda.core.serialization.carpenter + +import net.corda.core.serialization.amqp.Schema as AMQPSchema +import net.corda.core.serialization.amqp.Field as AMQPField +import net.corda.core.serialization.amqp.CompositeType + +fun AMQPSchema.carpenterSchema( + loaders : List = listOf(ClassLoader.getSystemClassLoader())) + : CarpenterSchemas { + val rtn = CarpenterSchemas.newInstance() + + types.filterIsInstance().forEach { + it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn) + } + + return rtn +} + +/** + * if we can load the class then we MUST know about all of it's composite elements + */ +private fun CompositeType.validateKnown ( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader())){ + fields.forEach { + if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type) + } +} + +/** + * based upon this AMQP schema either + * a) add the corresponding carpenter schema to the [carpenterSchemas] param + * b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated + * at this time + * + * @param classLoaders list of classLoaders, defaulting toe the system class loader, that might + * be used to load objects + * @param carpenterSchemas structure that holds the dependency tree and list of classes that + * need constructing + * @param force by default a schema is not added to [carpenterSchemas] if it already exists + * on the class path. For testing purposes schema generation can be forced + */ +fun CompositeType.carpenterSchema( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()), + carpenterSchemas : CarpenterSchemas, + force : Boolean = false) { + if (classLoaders.exists(name)) { + validateKnown(classLoaders) + if (!force) return + } + + val providesList = mutableListOf>() + + var isInterface = false + var isCreatable = true + + provides.forEach { + if (name == it) { + isInterface = true + return@forEach + } + + try { + providesList.add (classLoaders.loadIfExists(it)) + } + catch (e: ClassNotFoundException) { + carpenterSchemas.addDepPair(this, name, it) + isCreatable = false + } + } + + val m : MutableMap = mutableMapOf() + + fields.forEach { + try { + m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders)) + } + catch (e: ClassNotFoundException) { + carpenterSchemas.addDepPair(this, name, it.typeAsString()) + isCreatable = false + } + } + + if (isCreatable) { + carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance( + name = name, + fields = m, + interfaces = providesList, + isInterface = isInterface)) + } +} + +fun AMQPField.getTypeAsClass( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) +) = when (type) { + "int" -> Int::class.javaPrimitiveType!! + "string" -> String::class.java + "short" -> Short::class.javaPrimitiveType!! + "long" -> Long::class.javaPrimitiveType!! + "char" -> Char::class.javaPrimitiveType!! + "boolean" -> Boolean::class.javaPrimitiveType!! + "double" -> Double::class.javaPrimitiveType!! + "float" -> Float::class.javaPrimitiveType!! + "*" -> classLoaders.loadIfExists(requires[0]) + else -> classLoaders.loadIfExists(type) +} + +fun AMQPField.validateType( + classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) +) = when (type) { + "int", "string", "short", "long", "char", "boolean", "double", "float" -> true + "*" -> classLoaders.exists(requires[0]) + else -> classLoaders.exists (type) +} + +private fun List.exists (clazz: String) = + this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null + +private fun List.loadIfExists (clazz: String) : Class<*> { + this.forEach { + try { + return it.loadClass(clazz) + } catch (e: ClassNotFoundException) { + return@forEach + } + } + throw ClassNotFoundException(clazz) +} diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index 617dfd5c75..a7facc0c69 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -9,8 +9,6 @@ import java.lang.Character.isJavaIdentifierStart import java.util.* -/**********************************************************************************************************************/ - /** * Any object that implements this interface is expected to expose its own fields via the [get] method, exactly * as if `this.class.getMethod("get" + name.capitalize()).invoke(this)` had been called. It is intended as a more @@ -20,14 +18,10 @@ interface SimpleFieldAccess { operator fun get(name: String): Any? } -/**********************************************************************************************************************/ - class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) } -/**********************************************************************************************************************/ - /** * A class carpenter generates JVM bytecodes for a class given a schema and then loads it into a sub-classloader. * The generated classes have getters, a toString method and implement a simple property access interface. The @@ -91,7 +85,8 @@ class ClassCarpenter { * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to * construct instances of the generated class. * - * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) + * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a + * new ClassCarpenter if you're OK with ambiguous names) */ fun build(schema: Schema): Class<*> { validateSchema(schema) @@ -257,9 +252,7 @@ class ClassCarpenter { visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) } else { var slot = 1 - for (fieldType in superclassFields.values) - slot += load(slot, fieldType) - //val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("") + superclassFields.values.forEach { slot += load(slot, it) } val superDesc = sc.descriptorsIncludingSuperclasses().values.joinToString("") visitMethodInsn(INVOKESPECIAL, sc.name.jvm, "", "($superDesc)V", false) } diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt index 69608cc21a..e85572fa7d 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Exceptions.kt @@ -1,9 +1,11 @@ package net.corda.core.serialization.carpenter - class DuplicateNameException : RuntimeException ( "An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.") class InterfaceMismatchException(msg: String) : RuntimeException(msg) class NullablePrimitiveException(msg: String) : RuntimeException(msg) + +class UncarpentableException (name: String, field: String, type: String) : + Exception ("Class $name is loadable yet contains field $field of unknown type $type") diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt index 012df7173e..ddb237f0df 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/MetaCarpenter.kt @@ -3,8 +3,6 @@ package net.corda.core.serialization.carpenter import net.corda.core.serialization.amqp.CompositeType import net.corda.core.serialization.amqp.TypeNotation -/**********************************************************************************************************************/ - /** * Generated from an AMQP schema this class represents the classes unknown to the deserialiser and that thusly * require carpenting up in bytecode form. This is a multi step process as carpenting one object may be depedent @@ -27,8 +25,7 @@ import net.corda.core.serialization.amqp.TypeNotation data class CarpenterSchemas ( val carpenterSchemas : MutableList, val dependencies : MutableMap>>, - val dependsOn : MutableMap>) -{ + val dependsOn : MutableMap>) { companion object CarpenterSchemaConstructor { fun newInstance(): CarpenterSchemas { return CarpenterSchemas( @@ -47,8 +44,6 @@ data class CarpenterSchemas ( get() = carpenterSchemas.size } -/**********************************************************************************************************************/ - /** * Take a dependency tree of [CarpenterSchemas] and reduce it to zero by carpenting those classes that * require it. As classes are carpented check for depdency resolution, if now free generate a [Schema] for @@ -65,15 +60,15 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { fun step (newObject : Schema) { objects[newObject.name] = cc.build (newObject) - /* go over the list of everything that had a dependency on the newly - carpented class existing and remove it from their dependency list, If that - list is now empty we have no impediment to carpenting that class up */ + // go over the list of everything that had a dependency on the newly + // carpented class existing and remove it from their dependency list, If that + // list is now empty we have no impediment to carpenting that class up schemas.dependsOn.remove(newObject.name)?.forEach { dependent -> assert (newObject.name in schemas.dependencies[dependent]!!.second) schemas.dependencies[dependent]?.second?.remove(newObject.name) - /* we're out of blockers so we can now create the type */ + // we're out of blockers so we can now create the type if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) { (schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema ( classLoaders = listOf ( @@ -87,8 +82,6 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { abstract fun build() } -/**********************************************************************************************************************/ - class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { override fun build() { while (schemas.carpenterSchemas.isNotEmpty()) { @@ -98,8 +91,6 @@ class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { } } -/**********************************************************************************************************************/ - class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) { override fun build() { if (schemas.carpenterSchemas.isEmpty()) return @@ -107,4 +98,3 @@ class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schema } } -/**********************************************************************************************************************/ diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index 42683be4aa..d0d11f7fd8 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -264,7 +264,6 @@ class ClassCarpenterTest { mapOf("a" to NonNullableField (Int::class.java))) val clazz = cc.build(schema) - val a : Int? = null clazz.constructors[0].newInstance(a) } @@ -364,7 +363,6 @@ class ClassCarpenterTest { val clazz = cc.build(schema) val i = clazz.constructors[0].newInstance(arrayOf(1, 2, 3)) as SimpleFieldAccess - val arr = clazz.getMethod("getA").invoke(i) assertEquals(1, (arr as Array)[0]) @@ -384,14 +382,12 @@ class ClassCarpenterTest { "c" to Int::class.java).mapValues { NonNullableField(it.value) }) val clazz = cc.build(schema) - val i = clazz.constructors[0].newInstance(2, intArrayOf(4, 8), 16) as SimpleFieldAccess assertEquals(2, clazz.getMethod("getA").invoke(i)) assertEquals(4, (clazz.getMethod("getB").invoke(i) as IntArray)[0]) assertEquals(8, (clazz.getMethod("getB").invoke(i) as IntArray)[1]) assertEquals(16, clazz.getMethod("getC").invoke(i)) - assertEquals("$className{a=2, b=[4, 8], c=16}", i.toString()) } @@ -414,7 +410,6 @@ class ClassCarpenterTest { assertEquals(4, (clazz.getMethod("getC").invoke(i) as IntArray)[0]) assertEquals(5, (clazz.getMethod("getC").invoke(i) as IntArray)[1]) assertEquals(6, (clazz.getMethod("getC").invoke(i) as IntArray)[2]) - assertEquals("$className{a=[1, 2], b=3, c=[4, 5, 6]}", i.toString()) } @@ -454,7 +449,6 @@ class ClassCarpenterTest { "and on the side", arrayOf("some pickles", "some fries")) - val arr1 = clazz.getMethod("getA").invoke(i) as Array val arr2 = clazz.getMethod("getC").invoke(i) as Array @@ -477,16 +471,12 @@ class ClassCarpenterTest { val clazz = cc.build(schema) assertEquals (2, clazz.declaredFields.size) - assertEquals (1, clazz.getDeclaredField("a").annotations.size) assertEquals (javax.annotation.Nullable::class.java, clazz.getDeclaredField("a").annotations[0].annotationClass.java) - assertEquals (1, clazz.getDeclaredField("b").annotations.size) assertEquals (javax.annotation.Nonnull::class.java, clazz.getDeclaredField("b").annotations[0].annotationClass.java) - assertEquals (1, clazz.getMethod("getA").annotations.size) assertEquals (javax.annotation.Nullable::class.java, clazz.getMethod("getA").annotations[0].annotationClass.java) - assertEquals (1, clazz.getMethod("getB").annotations.size) assertEquals (javax.annotation.Nonnull::class.java, clazz.getMethod("getB").annotations[0].annotationClass.java) } @@ -503,5 +493,4 @@ class ClassCarpenterTest { assertNotEquals(null, descriptors.find { it.name == "a" }) assertNotEquals(null, descriptors.find { it.name == "class" }) } - } diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt index 90d8dc52fc..c4a6cf2119 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -7,32 +7,25 @@ import net.corda.core.serialization.amqp.TypeNotation import net.corda.core.serialization.amqp.SerializerFactory import net.corda.core.serialization.amqp.SerializationOutput -/**********************************************************************************************************************/ +fun mangleName(name: String) = "${name}__carpenter" -fun corruptName(name: String) = "${name}__carpenter" - -/**********************************************************************************************************************/ - -/* given a list of class names work through the amqp envelope schema and alter any that - match in the fashion defined above */ -fun Schema.corruptName(names: List): Schema { +/** + * given a list of class names work through the amqp envelope schema and alter any that + * match in the fashion defined above + */ +fun Schema.mangleName(names: List): Schema { val newTypes: MutableList = mutableListOf() for (type in types) { - val newName = if (type.name in names) corruptName(type.name) else type.name - - val newProvides = type.provides.map { - it -> - if (it in names) corruptName(it) else it - } - + val newName = if (type.name in names) mangleName(type.name) else type.name + val newProvides = type.provides.map { it -> if (it in names) mangleName(it) else it } val newFields = mutableListOf() (type as CompositeType).fields.forEach { - val fieldType = if (it.type in names) corruptName(it.type) else it.type - - val requires = if (it.requires.isNotEmpty() && (it.requires[0] in names)) - listOf(corruptName(it.requires[0])) else it.requires + val fieldType = if (it.type in names) mangleName(it.type) else it.type + val requires = + if (it.requires.isNotEmpty() && (it.requires[0] in names)) listOf(mangleName(it.requires[0])) + else it.requires newFields.add(it.copy(type = fieldType, requires = requires)) } @@ -43,8 +36,6 @@ fun Schema.corruptName(names: List): Schema { return Schema(types = newTypes) } -/**********************************************************************************************************************/ - open class AmqpCarpenterBase { var factory = SerializerFactory() diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index 58b5c84225..a94302b1bb 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -14,11 +14,7 @@ interface I_ { val a: Int } -/* - * Where a class has a member that is also a composite type or interface - */ class CompositeMembers : AmqpCarpenterBase() { - @Test fun bothKnown() { val testA = 10 @@ -31,7 +27,6 @@ class CompositeMembers : AmqpCarpenterBase() { data class B(val a: A, var b: Int) val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) @@ -57,10 +52,8 @@ class CompositeMembers : AmqpCarpenterBase() { assert(amqpSchemaA != null) assert(amqpSchemaB != null) - /* - * Just ensure the amqp schema matches what we want before we go messing - * around with the internals - */ + // Just ensure the amqp schema matches what we want before we go messing + // around with the internals assertEquals(1, amqpSchemaA?.fields?.size) assertEquals("a", amqpSchemaA!!.fields[0].name) assertEquals("int", amqpSchemaA.fields[0].type) @@ -73,15 +66,15 @@ class CompositeMembers : AmqpCarpenterBase() { val metaSchema = obj.envelope.schema.carpenterSchema() - /* if we know all the classes there is nothing to really achieve here */ + // if we know all the classes there is nothing to really achieve here assert(metaSchema.carpenterSchemas.isEmpty()) assert(metaSchema.dependsOn.isEmpty()) assert(metaSchema.dependencies.isEmpty()) } - /* you cannot have an element of a composite class we know about - that is unknown as that should be impossible. If we have the containing - class in the class path then we must have all of it's constituent elements */ + // you cannot have an element of a composite class we know about + // that is unknown as that should be impossible. If we have the containing + // class in the class path then we must have all of it's constituent elements @Test(expected = UncarpentableException::class) fun nestedIsUnknown() { val testA = 10 @@ -96,7 +89,7 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - val amqpSchema = obj.envelope.schema.corruptName(listOf (classTestName ("A"))) + val amqpSchema = obj.envelope.schema.mangleName(listOf (classTestName ("A"))) assert(obj.obj is B) @@ -115,13 +108,11 @@ class CompositeMembers : AmqpCarpenterBase() { data class B(val a: A, var b: Int) val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("B"))) - + val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() assertEquals(1, carpenterSchema.size) @@ -130,11 +121,11 @@ class CompositeMembers : AmqpCarpenterBase() { metaCarpenter.build() - assert(corruptName(classTestName("B")) in metaCarpenter.objects) + assert(mangleName(classTestName("B")) in metaCarpenter.objects) } @Test - fun BothUnkown() { + fun BothUnknown() { val testA = 10 val testB = 20 @@ -145,52 +136,50 @@ class CompositeMembers : AmqpCarpenterBase() { data class B(val a: A, var b: Int) val b = B(A(testA), testB) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) - + val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() - /* just verify we're in the expected initial state, A is carpentable, B is not because - it depends on A and the dependency chains are in place */ + // just verify we're in the expected initial state, A is carpentable, B is not because + // it depends on A and the dependency chains are in place assertEquals(1, carpenterSchema.size) - assertEquals(corruptName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) + assertEquals(mangleName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name) assertEquals(1, carpenterSchema.dependencies.size) - assert(corruptName(classTestName("B")) in carpenterSchema.dependencies) + assert(mangleName(classTestName("B")) in carpenterSchema.dependencies) assertEquals(1, carpenterSchema.dependsOn.size) - assert(corruptName(classTestName("A")) in carpenterSchema.dependsOn) + assert(mangleName(classTestName("A")) in carpenterSchema.dependsOn) - /* test meta carpenter lets us single step over the creation */ val metaCarpenter = TestMetaCarpenter(carpenterSchema) - /* we've built nothing so nothing should be there */ assertEquals(0, metaCarpenter.objects.size) - /* first iteration, carpent A, resolve deps and mark B as carpentable */ + // first iteration, carpent A, resolve deps and mark B as carpentable metaCarpenter.build() - /* one build iteration should have carpetned up A and worked out that B is now buildable - given it's depedencies have been satisfied */ - assertTrue(corruptName(classTestName("A")) in metaCarpenter.objects) - assertFalse(corruptName(classTestName("B")) in metaCarpenter.objects) + // one build iteration should have carpetned up A and worked out that B is now buildable + // given it's depedencies have been satisfied + assertTrue(mangleName(classTestName("A")) in metaCarpenter.objects) + assertFalse(mangleName(classTestName("B")) in metaCarpenter.objects) assertEquals(1, carpenterSchema.carpenterSchemas.size) - assertEquals(corruptName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) + assertEquals(mangleName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name) assertTrue(carpenterSchema.dependencies.isEmpty()) assertTrue(carpenterSchema.dependsOn.isEmpty()) - /* second manual iteration, will carpent B */ + // second manual iteration, will carpent B metaCarpenter.build() - assert(corruptName(classTestName("A")) in metaCarpenter.objects) - assert(corruptName(classTestName("B")) in metaCarpenter.objects) + assert(mangleName(classTestName("A")) in metaCarpenter.objects) + assert(mangleName(classTestName("B")) in metaCarpenter.objects) + // and we must be finished assertTrue(carpenterSchema.carpenterSchemas.isEmpty()) } @Test(expected = UncarpentableException::class) + @Suppress("UNUSED") fun nestedIsUnkownInherited() { val testA = 10 val testB = 20 @@ -206,18 +195,18 @@ class CompositeMembers : AmqpCarpenterBase() { data class C(val b: B, var c: Int) val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @Test(expected = UncarpentableException::class) - fun nestedIsUnknownInheritedUnkown() { + @Suppress("UNUSED") + fun nestedIsUnknownInheritedUnknown() { val testA = 10 val testB = 20 val testC = 30 @@ -232,16 +221,16 @@ class CompositeMembers : AmqpCarpenterBase() { data class C(val b: B, var c: Int) val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } + @Suppress("UNUSED") @Test(expected = UncarpentableException::class) fun parentsIsUnknownWithUnknownInheritedMember() { val testA = 10 @@ -258,18 +247,14 @@ class CompositeMembers : AmqpCarpenterBase() { data class C(val b: B, var c: Int) val c = C(B(testA, testB), testC) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c)) assert(obj.obj is C) - val carpenterSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) - + val carpenterSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) TestMetaCarpenter(carpenterSchema.carpenterSchema()) } - - /* * TODO serializer doesn't support inheritnace at the moment, when it does this should work @Test @@ -284,19 +269,17 @@ class CompositeMembers : AmqpCarpenterBase() { class B(override val a: Int, val b: Int) : A (a) val b = B(testA, testB) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) - val carpenterSchema = obj.envelope.schema.corruptName(listOf(classTestName("A"), classTestName("B"))) - + val carpenterSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema()) assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size) - assertEquals(corruptName(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name) + assertEquals(mangleName(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name) assertEquals(1, metaCarpenter.schemas.dependencies.size) - assertTrue(corruptName(classTestName("A")) in metaCarpenter.schemas.dependencies) + assertTrue(mangleName(classTestName("A")) in metaCarpenter.schemas.dependencies) } */ } diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt index 638074a8e6..eedd4a0937 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -8,15 +8,11 @@ import net.corda.core.serialization.amqp.* import org.junit.Test import kotlin.test.* -/*******************************************************************************************************/ - @CordaSerializable interface J { val j: Int } -/*******************************************************************************************************/ - @CordaSerializable interface I { val i: Int @@ -39,49 +35,36 @@ interface IIII { val i: I } -/*******************************************************************************************************/ - class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { - @Test fun interfaceParent1() { - val testJ = 20 - class A(override val j: Int) : J + val testJ = 20 val a = A(testJ) assertEquals(testJ, a.j) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - assert(obj.obj is A) - val serSchema = obj.envelope.schema - assertEquals(2, serSchema.types.size) - val l1 = serSchema.carpenterSchema() - /* since we're using an envelope generated by seilaising classes defined locally - it's extremely unlikely we'd need to carpent any classes */ + // since we're using an envelope generated by seilaising classes defined locally + // it's extremely unlikely we'd need to carpent any classes assertEquals(0, l1.size) - val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) - - val l2 = corruptSchema.carpenterSchema() - + val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val l2 = mangleSchema.carpenterSchema() assertEquals(1, l2.size) - val aSchema = l2.carpenterSchemas.find { it.name == corruptName(classTestName("A")) } + val aSchema = l2.carpenterSchemas.find { it.name == mangleName(classTestName("A")) } assertNotEquals(null, aSchema) - - assertEquals(corruptName(classTestName("A")), aSchema!!.name) + assertEquals(mangleName(classTestName("A")), aSchema!!.name) assertEquals(1, aSchema.interfaces.size) assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0]) val aBuilder = ClassCarpenter().build(aSchema) - val objJ = aBuilder.constructors[0].newInstance(testJ) val j = objJ as J @@ -89,14 +72,12 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(a.j, j.j) } - @Test fun interfaceParent2() { - val testJ = 20 - val testJJ = 40 - class A(override val j: Int, val jj: Int) : J + val testJ = 20 + val testJJ = 40 val a = A(testJ, testJJ) assertEquals(testJ, a.j) @@ -112,14 +93,12 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val l1 = serSchema.carpenterSchema() - /* since we're using an envelope generated by seilaising classes defined locally - it's extremely unlikely we'd need to carpent any classes */ assertEquals(0, l1.size) - val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) - val aName = corruptName(classTestName("A")) + val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val aName = mangleName(classTestName("A")) - val l2 = corruptSchema.carpenterSchema() + val l2 = mangleSchema.carpenterSchema() assertEquals(1, l2.size) @@ -160,48 +139,43 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val l1 = serSchema.carpenterSchema() - /* since we're using an envelope generated by seilaising classes defined locally - it's extremely unlikely we'd need to carpent any classes */ + // since we're using an envelope generated by serialising classes defined locally + // it's extremely unlikely we'd need to carpent any classes assertEquals(0, l1.size) - /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus - needs some carpentry */ - - val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) - val l2 = corruptSchema.carpenterSchema() - val aName = corruptName(classTestName("A")) + // pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus + // needs some carpentry + val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val l2 = mangleSchema.carpenterSchema() + val aName = mangleName(classTestName("A")) assertEquals(1, l2.size) val aSchema = l2.carpenterSchemas.find { it.name == aName } assertNotEquals(null, aSchema) - assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces) assert(net.corda.core.serialization.carpenter.II::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) - val objA = aBuilder.constructors[0].newInstance(testI, testII) val i = objA as I val ii = objA as II assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII) - assertEquals(a.i, i.i) assertEquals(a.ii, ii.ii) } @Test fun nestedInterfaces() { - val testI = 20 - val testIII = 60 - class A(override val i: Int, override val iii: Int) : III + val testI = 20 + val testIII = 60 val a = A(testI, testIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -213,34 +187,31 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val l1 = serSchema.carpenterSchema() - /* since we're using an envelope generated by seilaising classes defined locally - it's extremely unlikely we'd need to carpent any classes */ + // since we're using an envelope generated by serialising classes defined locally + // it's extremely unlikely we'd need to carpent any classes assertEquals(0, l1.size) - val corruptSchema = serSchema.corruptName(listOf(classTestName("A"))) - val l2 = corruptSchema.carpenterSchema() - val aName = corruptName(classTestName("A")) + val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val l2 = mangleSchema.carpenterSchema() + val aName = mangleName(classTestName("A")) assertEquals(1, l2.size) val aSchema = l2.carpenterSchemas.find { it.name == aName } assertNotEquals(null, aSchema) - assertEquals(aName, aSchema!!.name) assertEquals(2, aSchema.interfaces.size) assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces) assert(net.corda.core.serialization.carpenter.III::class.java in aSchema.interfaces) val aBuilder = ClassCarpenter().build(aSchema) - val objA = aBuilder.constructors[0].newInstance(testI, testIII) val i = objA as I val iii = objA as III assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII) - assertEquals(a.i, i.i) assertEquals(a.i, iii.i) assertEquals(a.iii, iii.iii) @@ -248,33 +219,30 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun memberInterface() { - val testI = 25 - val testIIII = 50 - class A(override val i: Int) : I class B(override val i: I, override val iiii: Int) : IIII + val testI = 25 + val testIIII = 50 val a = A(testI) val b = B(a, testIIII) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) val serSchema = obj.envelope.schema - /* - * class A - * class A's interface (class I) - * class B - * class B's interface (class IIII) - */ + // Expected classes are + // * class A + // * class A's interface (class I) + // * class B + // * class B's interface (class IIII) assertEquals(4, serSchema.types.size) - val corruptSchema = serSchema.corruptName(listOf(classTestName("A"), classTestName("B"))) - val cSchema = corruptSchema.carpenterSchema() - val aName = corruptName(classTestName("A")) - val bName = corruptName(classTestName("B")) + val mangleSchema = serSchema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val cSchema = mangleSchema.carpenterSchema() + val aName = mangleName(classTestName("A")) + val bName = mangleName(classTestName("B")) assertEquals(2, cSchema.size) @@ -286,102 +254,90 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val cc = ClassCarpenter() val cc2 = ClassCarpenter() - val bBuilder = cc.build(bCarpenterSchema!!) bBuilder.constructors[0].newInstance(a, testIIII) val aBuilder = cc.build(aCarpenterSchema!!) val objA = aBuilder.constructors[0].newInstance(testI) - /* build a second B this time using our constructed instane of A and not the - local one we pre defined */ + // build a second B this time using our constructed instance of A and not the + // local one we pre defined bBuilder.constructors[0].newInstance(objA, testIIII) - /* whittle and instantiate a different A with a new class loader */ + // whittle and instantiate a different A with a new class loader val aBuilder2 = cc2.build(aCarpenterSchema) val objA2 = aBuilder2.constructors[0].newInstance(testI) bBuilder.constructors[0].newInstance(objA2, testIIII) } - /* if we remove the neted interface we should get an error as it's impossible - to have a concrete class loaded without having access to all of it's elements */ + // if we remove the nested interface we should get an error as it's impossible + // to have a concrete class loaded without having access to all of it's elements @Test(expected = UncarpentableException::class) fun memberInterface2() { - val testI = 25 - val testIIII = 50 - class A(override val i: Int) : I class B(override val i: I, override val iiii: Int) : IIII + val testI = 25 + val testIIII = 50 val a = A(testI) val b = B(a, testIIII) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) assert(obj.obj is B) val serSchema = obj.envelope.schema - /* - * The classes we're expecting to find: - * class A - * class A's interface (class I) - * class B - * class B's interface (class IIII) - */ + // The classes we're expecting to find: + // * class A + // * class A's interface (class I) + // * class B + // * class B's interface (class IIII) assertEquals(4, serSchema.types.size) - /* ignore the return as we expect this to throw */ - serSchema.corruptName(listOf( + // ignore the return as we expect this to throw + serSchema.mangleName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema() } @Test fun interfaceAndImplementation() { - val testI = 25 - class A(override val i: Int) : I + val testI = 25 val a = A(testI) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) val serSchema = obj.envelope.schema - /* - * The classes we're expecting to find: - * class A - * class A's interface (class I) - */ + // The classes we're expecting to find: + // * class A + // * class A's interface (class I) assertEquals(2, serSchema.types.size) - val amqpSchema = serSchema.corruptName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) - - val aName = corruptName(classTestName("A")) - val iName = corruptName("${this.javaClass.`package`.name}.I") - + val amqpSchema = serSchema.mangleName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) + val aName = mangleName(classTestName("A")) + val iName = mangleName("${this.javaClass.`package`.name}.I") val carpenterSchema = amqpSchema.carpenterSchema() - /* whilst there are two unknown classes within the envelope A depends on I so we can't construct a - schema for A until we have for I */ + // whilst there are two unknown classes within the envelope A depends on I so we can't construct a + // schema for A until we have for I assertEquals(1, carpenterSchema.size) assertNotEquals(null, carpenterSchema.carpenterSchemas.find { it.name == iName }) - /* since we can't build A it should list I as a dependency*/ + // since we can't build A it should list I as a dependency assert(aName in carpenterSchema.dependencies) assertEquals(1, carpenterSchema.dependencies[aName]!!.second.size) assertEquals(iName, carpenterSchema.dependencies[aName]!!.second[0]) - /* and conversly I should have A listed as a dependent */ + // and conversly I should have A listed as a dependent assert(iName in carpenterSchema.dependsOn) assertEquals(1, carpenterSchema.dependsOn[iName]!!.size) assertEquals(aName, carpenterSchema.dependsOn[iName]!![0]) val mc = MetaCarpenter(carpenterSchema) - mc.build() assertEquals(0, mc.schemas.carpenterSchemas.size) @@ -396,29 +352,26 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun twoInterfacesAndImplementation() { - val testI = 69 - val testII = 96 - class A(override val i: Int, override val ii: Int) : I, II + val testI = 69 + val testII = 96 val a = A(testI, testII) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.corruptName(listOf( + val amqpSchema = obj.envelope.schema.mangleName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.II")) - val aName = corruptName(classTestName("A")) - val iName = corruptName("${this.javaClass.`package`.name}.I") - val iiName = corruptName("${this.javaClass.`package`.name}.II") - + val aName = mangleName(classTestName("A")) + val iName = mangleName("${this.javaClass.`package`.name}.I") + val iiName = mangleName("${this.javaClass.`package`.name}.II") val carpenterSchema = amqpSchema.carpenterSchema() - /* there is nothing preventing us from carpenting up the two interfaces so - our initial list should contain both interface with A being dependent on both - and each having A as a dependent */ + // there is nothing preventing us from carpenting up the two interfaces so + // our initial list should contain both interface with A being dependent on both + // and each having A as a dependent assertEquals(2, carpenterSchema.carpenterSchemas.size) assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName }) assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iiName }) @@ -438,8 +391,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiName }) val mc = MetaCarpenter(carpenterSchema) - mc.build() + assertEquals(0, mc.schemas.carpenterSchemas.size) assertEquals(0, mc.schemas.dependencies.size) assertEquals(0, mc.schemas.dependsOn.size) @@ -451,58 +404,56 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun nestedInterfacesAndImplementation() { - val testI = 7 - val testIII = 11 - class A(override val i: Int, override val iii: Int) : III + val testI = 7 + val testIII = 11 val a = A(testI, testIII) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.corruptName(listOf( + val amqpSchema = obj.envelope.schema.mangleName(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.III")) - val aName = corruptName(classTestName("A")) - val iName = corruptName("${this.javaClass.`package`.name}.I") - val iiiName = corruptName("${this.javaClass.`package`.name}.III") + val aName = mangleName(classTestName("A")) + val iName = mangleName("${this.javaClass.`package`.name}.I") + val iiiName = mangleName("${this.javaClass.`package`.name}.III") val carpenterSchema = amqpSchema.carpenterSchema() - /* Since A depends on III and III extends I we will have to construct them - * in that reverse order (I -> III -> A) */ + // Since A depends on III and III extends I we will have to construct them + // in that reverse order (I -> III -> A) assertEquals(1, carpenterSchema.carpenterSchemas.size) assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName }) assertNull(carpenterSchema.carpenterSchemas.find { it.name == iiiName }) assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName }) - /* I has III as a direct dependent and A as an indirect one */ + // I has III as a direct dependent and A as an indirect one assert(iName in carpenterSchema.dependsOn) assertEquals(2, carpenterSchema.dependsOn[iName]?.size) assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == iiiName })) assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName })) - /* III has A as a dependent */ + // III has A as a dependent assert(iiiName in carpenterSchema.dependsOn) assertEquals(1, carpenterSchema.dependsOn[iiiName]?.size) assertNotNull(carpenterSchema.dependsOn[iiiName]?.find { it == aName }) - /* converly III depends on I */ + // conversly III depends on I assert(iiiName in carpenterSchema.dependencies) assertEquals(1, carpenterSchema.dependencies[iiiName]!!.second.size) assertNotNull(carpenterSchema.dependencies[iiiName]!!.second.find { it == iName }) - /* and A depends on III and I*/ + // and A depends on III and I assert(aName in carpenterSchema.dependencies) assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size) assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiiName }) assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName }) val mc = MetaCarpenter(carpenterSchema) - mc.build() + assertEquals(0, mc.schemas.carpenterSchemas.size) assertEquals(0, mc.schemas.dependencies.size) assertEquals(0, mc.schemas.dependsOn.size) @@ -512,5 +463,3 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assert(iiiName in mc.objects) } } - - diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 71b96e1c8b..462b3d8559 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,7 +1,6 @@ package net.corda.core.serialization.carpenter import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase -import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* @@ -13,14 +12,12 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun twoInts() { - val testA = 10 - val testB = 20 - @CordaSerializable data class A(val a: Int, val b: Int) - var a = A(testA, testB) - + val testA = 10 + val testB = 20 + val a = A(testA, testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) @@ -31,7 +28,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(1, obj.envelope.schema.types.size) assert(obj.envelope.schema.types[0] is CompositeType) - var amqpSchema = obj.envelope.schema.types[0] as CompositeType + val amqpSchema = obj.envelope.schema.types[0] as CompositeType assertEquals(2, amqpSchema.fields.size) assertEquals("a", amqpSchema.fields[0].name) @@ -48,7 +45,6 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertNotEquals(null, aSchema) val pinochio = ClassCarpenter().build(aSchema!!) - val p = pinochio.constructors[0].newInstance(testA, testB) assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) @@ -57,14 +53,12 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun intAndStr() { - val testA = 10 - val testB = "twenty" - @CordaSerializable data class A(val a: Int, val b: String) + val testA = 10 + val testB = "twenty" val a = A(testA, testB) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) @@ -92,7 +86,6 @@ class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertNotEquals(null, aSchema) val pinochio = ClassCarpenter().build(aSchema!!) - val p = pinochio.constructors[0].newInstance(testA, testB) assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index c464c21445..ef88298dd0 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,23 +1,19 @@ package net.corda.core.serialization.carpenter import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase -import net.corda.core.serialization.carpenter.CarpenterSchemas import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* import org.junit.Test import kotlin.test.assertEquals - class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { - @Test fun singleInteger() { - val test = 10 - @CordaSerializable data class A(val a: Int) + val test = 10 val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -39,7 +35,6 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! val aBuilder = ClassCarpenter().build(aSchema) - val p = aBuilder.constructors[0].newInstance(test) assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) @@ -47,11 +42,10 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleString() { - val test = "ten" - @CordaSerializable data class A(val a: String) + val test = "ten" val a = A(test) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) @@ -64,13 +58,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { assert(obj.envelope.schema.types[0] is CompositeType) val amqpSchema = obj.envelope.schema.types[0] as CompositeType - val carpenterSchema = CarpenterSchemas.newInstance() amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true) val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!! val aBuilder = ClassCarpenter().build(aSchema) - val p = aBuilder.constructors[0].newInstance(test) assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a) @@ -78,13 +70,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleLong() { - val test = 10L - @CordaSerializable data class A(val a: Long) + val test = 10L val a = A(test) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) @@ -112,13 +102,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleShort() { - val test = 10.toShort() - @CordaSerializable data class A(val a: Short) + val test = 10.toShort() val a = A(test) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) @@ -146,13 +134,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleDouble() { - val test = 10.0 - @CordaSerializable data class A(val a: Double) + val test = 10.0 val a = A(test) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) @@ -180,13 +166,11 @@ class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() { @Test fun singleFloat() { - val test: Float = 10.0F - @CordaSerializable data class A(val a: Float) + val test: Float = 10.0F val a = A(test) - val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) assert(obj.obj is A) From f19ec4e3ba32e195dfbee79b67f91fdc1879dcbd Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 12 Jul 2017 10:37:23 +0100 Subject: [PATCH 019/197] Review comments * Move all alterations on the amqp schema object out of the actual amqp/Schema file and have them live in the carpenter as extension functions * Move carpenter exceptions to their own file * Rename the schema name corrupter to the name mangler * reduce whitespace * alter comment style --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 21f2f41b5c..37fb7b0c14 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ lib/dokka.jar .idea/libraries .idea/shelf .idea/dataSources +.idea/markdown-navigator /gradle-plugins/.idea/ # Include the -parameters compiler option by default in IntelliJ required for serialization. From 1996c39b9ab0017cb4c4ebcbb96990f432d8f2cb Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 14 Jul 2017 14:06:59 +0100 Subject: [PATCH 020/197] Disable the BFT tests. (#1051) --- .../kotlin/net/corda/node/services/BFTNotaryServiceTests.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 6d9e28218d..242f5f9390 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -26,11 +26,14 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.bouncycastle.asn1.x500.X500Name import org.junit.After +import org.junit.Ignore import org.junit.Test import java.nio.file.Files import kotlin.test.assertEquals import kotlin.test.assertTrue +@Ignore("Replica 6 constructor goes around the loop in BaseStateManager.askCurrentConsensusId forever, so stop hangs when getting the Replica instance to dispose it." + + "Happens frequently on Windows and has happened on Linux.") class BFTNotaryServiceTests { companion object { private val clusterName = X500Name("CN=BFT,O=R3,OU=corda,L=Zurich,C=CH") From 05327f3826caf2d126d9b9f2d67882b71788b1d7 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Fri, 14 Jul 2017 14:39:59 +0100 Subject: [PATCH 021/197] decouple Exposed (#1028) Exposed library decoupled from transaction management and JDBC connection creation for Hibernate and ReQuery --- .../core/flows/ContractUpgradeFlowTest.kt | 1 - .../core/flows/ResolveTransactionsFlowTest.kt | 1 - .../AttachmentSerializationTest.kt | 1 - .../docs/FxTransactionBuildTutorialTest.kt | 1 - .../WorkflowTransactionBuildTutorialTest.kt | 1 - .../corda/contracts/CommercialPaperTests.kt | 7 +- .../net/corda/contracts/asset/CashTests.kt | 11 +- .../net/corda/flows/CashPaymentFlowTests.kt | 1 - .../kotlin/net/corda/flows/IssuerFlowTest.kt | 1 - .../node/services/BFTNotaryServiceTests.kt | 1 - .../node/services/RaftNotaryServiceTests.kt | 1 - .../node/utilities/JDBCHashMapTestSuite.kt | 20 +- .../net/corda/node/internal/AbstractNode.kt | 14 +- .../corda/node/internal/CordaRPCOpsImpl.kt | 5 +- .../node/services/api/ServiceHubInternal.kt | 4 +- .../database/HibernateConfiguration.kt | 11 +- .../KotlinConfigurationTransactionWrapper.kt | 9 +- .../services/database/RequeryConfiguration.kt | 8 +- .../services/messaging/NodeMessagingClient.kt | 3 +- .../statemachine/FlowStateMachineImpl.kt | 29 +- .../statemachine/StateMachineManager.kt | 3 +- .../node/services/transactions/BFTSMaRt.kt | 1 - .../transactions/DistributedImmutableMap.kt | 5 +- .../transactions/RaftUniquenessProvider.kt | 4 +- .../vault/CashBalanceAsMetricsObserver.kt | 5 +- .../corda/node/utilities/CordaPersistence.kt | 193 ++++++++++++++ .../corda/node/utilities/DatabaseSupport.kt | 251 ------------------ .../utilities/DatabaseTransactionManager.kt | 106 ++++++++ .../utilities/ExposedTransactionManager.kt | 54 ++++ .../net/corda/node/utilities/JDBCHashMap.kt | 3 +- .../services/vault/VaultQueryJavaTests.java | 41 ++- .../net/corda/node/CordaRPCOpsImplTest.kt | 1 - .../corda/node/messaging/AttachmentTests.kt | 5 - .../node/messaging/TwoPartyTradeFlowTests.kt | 5 +- .../node/services/MockServiceHubInternal.kt | 4 +- .../database/HibernateConfigurationTest.kt | 15 +- .../database/RequeryConfigurationTest.kt | 13 +- .../events/NodeSchedulerServiceTest.kt | 15 +- .../services/events/ScheduledFlowTests.kt | 1 - .../messaging/ArtemisMessagingTests.kt | 13 +- .../network/InMemoryNetworkMapCacheTest.kt | 1 - .../PersistentNetworkMapServiceTest.kt | 1 - .../persistence/DBCheckpointStorageTests.kt | 13 +- .../persistence/DBTransactionStorageTests.kt | 13 +- .../persistence/DataVendingServiceTests.kt | 1 - .../persistence/NodeAttachmentStorageTest.kt | 13 +- .../services/schema/HibernateObserverTests.kt | 13 +- .../statemachine/FlowFrameworkTests.kt | 2 +- .../DistributedImmutableMapTests.kt | 12 +- .../PersistentUniquenessProviderTests.kt | 15 +- .../services/vault/NodeVaultServiceTest.kt | 13 +- .../node/services/vault/VaultQueryTests.kt | 21 +- .../node/services/vault/VaultWithCashTest.kt | 15 +- .../corda/node/utilities/ObservablesTests.kt | 12 +- .../corda/irs/api/NodeInterestRatesTest.kt | 13 +- .../corda/netmap/simulation/IRSSimulation.kt | 1 - .../net/corda/netmap/simulation/Simulation.kt | 1 - .../testing/node/InMemoryMessagingNetwork.kt | 11 +- .../net/corda/testing/node/SimpleNode.kt | 9 +- 59 files changed, 503 insertions(+), 545 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt create mode 100644 node/src/main/kotlin/net/corda/node/utilities/DatabaseTransactionManager.kt create mode 100644 node/src/main/kotlin/net/corda/node/utilities/ExposedTransactionManager.kt diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 169ca03947..889d392ff2 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -16,7 +16,6 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.flows.CashIssueFlow import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.services.startFlowPermission -import net.corda.node.utilities.transaction import net.corda.nodeapi.User import net.corda.testing.RPCDriverExposedDSLInterface import net.corda.testing.contracts.DummyContract diff --git a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt index f0489562ec..1e0f222f10 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt @@ -7,7 +7,6 @@ import net.corda.core.identity.Party import net.corda.core.utilities.opaque import net.corda.core.transactions.SignedTransaction import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.node.utilities.transaction import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_KEY import net.corda.testing.MINI_CORP diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 964a81a4b4..6c8f11b6e9 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -18,7 +18,6 @@ import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.schemas.requery.AttachmentEntity import net.corda.node.services.statemachine.SessionInit -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index be1b5fa83f..117c072965 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -10,7 +10,6 @@ import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.flows.CashIssueFlow import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 2a819c25cc..e6731f96bf 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -14,7 +14,6 @@ import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before diff --git a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt index 35bf37e01e..e5428add4d 100644 --- a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt @@ -11,7 +11,6 @@ import net.corda.core.node.services.VaultService import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties @@ -212,8 +211,7 @@ class CommercialPaperTestsGeneric { fun `issue move and then redeem`() { val dataSourcePropsAlice = makeTestDataSourceProperties() - val dataSourceAndDatabaseAlice = configureDatabase(dataSourcePropsAlice) - val databaseAlice = dataSourceAndDatabaseAlice.second + val databaseAlice = configureDatabase(dataSourcePropsAlice) databaseAlice.transaction { aliceServices = object : MockServices(ALICE_KEY) { @@ -232,8 +230,7 @@ class CommercialPaperTestsGeneric { } val dataSourcePropsBigCorp = makeTestDataSourceProperties() - val dataSourceAndDatabaseBigCorp = configureDatabase(dataSourcePropsBigCorp) - val databaseBigCorp = dataSourceAndDatabaseBigCorp.second + val databaseBigCorp = configureDatabase(dataSourcePropsBigCorp) databaseBigCorp.transaction { bigCorpServices = object : MockServices(BIG_CORP_KEY) { diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt index 5a0771ed3d..0fa711a408 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt @@ -14,16 +14,14 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.node.services.vault.NodeVaultService +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.node.MockKeyManagementService import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties -import org.jetbrains.exposed.sql.Database import org.junit.Before import org.junit.Test -import java.io.Closeable import java.security.KeyPair import java.util.* import kotlin.test.* @@ -45,17 +43,14 @@ class CashTests { lateinit var miniCorpServices: MockServices val vault: VaultService get() = miniCorpServices.vaultService - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var vaultStatesUnconsumed: List> @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProps) database.transaction { miniCorpServices = object : MockServices(MINI_CORP_KEY) { override val keyManagementService: MockKeyManagementService = MockKeyManagementService(identityService, MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY) diff --git a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt index 3bdf21b4da..59261e384e 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt @@ -9,7 +9,6 @@ import net.corda.core.node.services.Vault import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes -import net.corda.node.utilities.transaction import net.corda.testing.expect import net.corda.testing.expectEvents import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin diff --git a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt index bc98ce9943..48fadb89ee 100644 --- a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt @@ -14,7 +14,6 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.OpaqueBytes import net.corda.flows.IssuerFlow.IssuanceRequester -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.contracts.calculateRandomlySizedAmounts import net.corda.testing.node.MockNetwork diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 242f5f9390..f361a57a2e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -21,7 +21,6 @@ import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator -import net.corda.node.utilities.transaction import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import org.bouncycastle.asn1.x500.X500Name diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index b8d54616b5..70a9165660 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -11,7 +11,6 @@ import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.map import net.corda.node.internal.AbstractNode -import net.corda.node.utilities.transaction import net.corda.testing.DUMMY_BANK_A import net.corda.testing.contracts.DummyContract import net.corda.testing.node.NodeBasedTest diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt index 261aaff04f..00d5cc66ac 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt @@ -12,13 +12,11 @@ import com.google.common.collect.testing.testers.* import junit.framework.TestSuite import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.transactions.TransactionManager import org.junit.* import org.junit.runner.RunWith import org.junit.runners.Suite -import java.io.Closeable import java.sql.Connection import java.util.* @@ -32,9 +30,8 @@ import java.util.* JDBCHashMapTestSuite.SetConstrained::class) class JDBCHashMapTestSuite { companion object { - lateinit var dataSource: Closeable lateinit var transaction: Transaction - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var loadOnInitFalseMap: JDBCHashMap lateinit var memoryConstrainedMap: JDBCHashMap lateinit var loadOnInitTrueMap: JDBCHashMap @@ -45,9 +42,7 @@ class JDBCHashMapTestSuite { @JvmStatic @BeforeClass fun before() { - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) setUpDatabaseTx() loadOnInitFalseMap = JDBCHashMap("test_map_false", loadOnInit = false) memoryConstrainedMap = JDBCHashMap("test_map_constrained", loadOnInit = false, maxBuckets = 1) @@ -61,7 +56,7 @@ class JDBCHashMapTestSuite { @AfterClass fun after() { closeDatabaseTx() - dataSource.close() + database.close() } @JvmStatic @@ -228,19 +223,16 @@ class JDBCHashMapTestSuite { private val transientMapForComparison = applyOpsToMap(LinkedHashMap()) - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun before() { - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) } @After fun after() { - dataSource.close() + database.close() } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 51165a5719..2420a736bd 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -63,7 +63,6 @@ import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove.ADD import org.apache.activemq.artemis.utils.ReusableLatch import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.slf4j.Logger import rx.Observable import java.io.IOException @@ -131,7 +130,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, var inNodeNetworkMapService: NetworkMapService? = null lateinit var network: MessagingService protected val runOnStop = ArrayList<() -> Any?>() - lateinit var database: Database + lateinit var database: CordaPersistence protected var dbCloser: (() -> Any?)? = null var isPreviousCheckpointsPresent = false @@ -547,11 +546,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, protected open fun initialiseDatabasePersistence(insideTransaction: () -> Unit) { val props = configuration.dataSourceProperties if (props.isNotEmpty()) { - val (toClose, database) = configureDatabase(props) - this.database = database + this.database = configureDatabase(props) // Now log the vendor string as this will also cause a connection to be tested eagerly. - log.info("Connected to ${database.vendor} database.") - toClose::close.let { + database.transaction { + log.info("Connected to ${database.database.vendor} database.") + } + this.database::close.let { dbCloser = it runOnStop += it } @@ -811,7 +811,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, override val clock: Clock get() = platformClock override val myInfo: NodeInfo get() = info override val schemaService by lazy { NodeSchemaService(pluginRegistries.flatMap { it.requiredSchemas }.toSet()) } - override val database: Database get() = this@AbstractNode.database + override val database: CordaPersistence get() = this@AbstractNode.database override val configuration: NodeConfiguration get() = this@AbstractNode.configuration override fun cordaService(type: Class): T { diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index f881064e27..98cd510381 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -24,9 +24,8 @@ import net.corda.node.services.messaging.requirePermission import net.corda.node.services.startFlowPermission import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager -import net.corda.node.utilities.transaction +import net.corda.node.utilities.CordaPersistence import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import rx.Observable import java.io.InputStream import java.security.PublicKey @@ -40,7 +39,7 @@ import java.util.* class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, - private val database: Database + private val database: CordaPersistence ) : CordaRPCOps { override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { return database.transaction { diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 861ff1db34..fedc2c6477 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -23,7 +23,7 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl -import org.jetbrains.exposed.sql.Database +import net.corda.node.utilities.CordaPersistence interface NetworkMapCacheInternal : NetworkMapCache { /** @@ -81,7 +81,7 @@ interface ServiceHubInternal : PluginServiceHub { val auditService: AuditService val rpcFlows: List>> val networkService: MessagingService - val database: Database + val database: CordaPersistence val configuration: NodeConfiguration @Suppress("DEPRECATION") diff --git a/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt index 4d2ba9f95c..85d777d8d0 100644 --- a/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt @@ -1,9 +1,9 @@ package net.corda.node.services.database import net.corda.core.schemas.MappedSchema -import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.node.services.api.SchemaService +import net.corda.node.utilities.DatabaseTransactionManager import org.hibernate.SessionFactory import org.hibernate.boot.MetadataSources import org.hibernate.boot.model.naming.Identifier @@ -13,7 +13,6 @@ import org.hibernate.cfg.Configuration import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment import org.hibernate.service.UnknownUnwrapTypeException -import org.jetbrains.exposed.sql.transactions.TransactionManager import java.sql.Connection import java.util.concurrent.ConcurrentHashMap @@ -94,17 +93,15 @@ class HibernateConfiguration(val schemaService: SchemaService, val useDefaultLog // during schema creation / update. class NodeDatabaseConnectionProvider : ConnectionProvider { override fun closeConnection(conn: Connection) { - val tx = TransactionManager.current() + val tx = DatabaseTransactionManager.current() tx.commit() tx.close() } override fun supportsAggressiveRelease(): Boolean = true - override fun getConnection(): Connection { - val tx = TransactionManager.manager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ) - return tx.connection - } + override fun getConnection(): Connection = + DatabaseTransactionManager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ).connection override fun unwrap(unwrapType: Class): T { try { diff --git a/node/src/main/kotlin/net/corda/node/services/database/KotlinConfigurationTransactionWrapper.kt b/node/src/main/kotlin/net/corda/node/services/database/KotlinConfigurationTransactionWrapper.kt index e9652e9556..78d4380161 100644 --- a/node/src/main/kotlin/net/corda/node/services/database/KotlinConfigurationTransactionWrapper.kt +++ b/node/src/main/kotlin/net/corda/node/services/database/KotlinConfigurationTransactionWrapper.kt @@ -13,7 +13,7 @@ import net.corda.core.schemas.requery.converters.InstantConverter import net.corda.core.schemas.requery.converters.SecureHashConverter import net.corda.core.schemas.requery.converters.StateRefConverter import net.corda.core.schemas.requery.converters.VaultStateStatusConverter -import org.jetbrains.exposed.sql.transactions.TransactionManager +import net.corda.node.utilities.DatabaseTransactionManager import java.sql.Connection import java.util.* import java.util.concurrent.Executor @@ -128,12 +128,7 @@ class KotlinConfigurationTransactionWrapper(private val model: EntityModel, } class CordaDataSourceConnectionProvider(val dataSource: DataSource) : ConnectionProvider { - override fun getConnection(): Connection { - val tx = TransactionManager.manager.currentOrNull() - return CordaConnection( - tx?.connection ?: throw IllegalStateException("Was expecting to find database transaction: must wrap calling code within a transaction.") - ) - } + override fun getConnection(): Connection = CordaConnection(DatabaseTransactionManager.current().connection) } class CordaConnection(val connection: Connection) : Connection by connection { diff --git a/node/src/main/kotlin/net/corda/node/services/database/RequeryConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/database/RequeryConfiguration.kt index 6bbc6ae0bb..49d0893abf 100644 --- a/node/src/main/kotlin/net/corda/node/services/database/RequeryConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/database/RequeryConfiguration.kt @@ -8,7 +8,7 @@ import io.requery.sql.KotlinEntityDataStore import io.requery.sql.SchemaModifier import io.requery.sql.TableCreationMode import net.corda.core.utilities.loggerFor -import org.jetbrains.exposed.sql.transactions.TransactionManager +import net.corda.node.utilities.DatabaseTransactionManager import java.sql.Connection import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -46,9 +46,5 @@ class RequeryConfiguration(val properties: Properties, val useDefaultLogging: Bo } // TODO: remove once Requery supports QUERY WITH COMPOSITE_KEY IN - fun jdbcSession(): Connection { - val ctx = TransactionManager.manager.currentOrNull() - return ctx?.connection ?: throw IllegalStateException("Was expecting to find database transaction: must wrap calling code within a transaction.") - } + fun jdbcSession(): Connection = DatabaseTransactionManager.current().connection } - diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 0b9a45d387..afd2df6204 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -37,7 +37,6 @@ import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.statements.InsertStatement import java.security.PublicKey @@ -74,7 +73,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, val serverAddress: NetworkHostAndPort, val myIdentity: PublicKey?, val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, - val database: Database, + val database: CordaPersistence, val networkMapRegistrationFuture: ListenableFuture, val monitoringService: MonitoringService, advertisedAddress: NetworkHostAndPort = serverAddress 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 b8b5825744..14f4013ed4 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 @@ -18,11 +18,7 @@ import net.corda.core.utilities.* import net.corda.node.services.api.FlowAppAuditEvent import net.corda.node.services.api.FlowPermissionAuditEvent import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.utilities.StrandLocalTransactionManager -import net.corda.node.utilities.createTransaction -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.Transaction -import org.jetbrains.exposed.sql.transactions.TransactionManager +import net.corda.node.utilities.* import org.slf4j.Logger import org.slf4j.LoggerFactory import java.sql.Connection @@ -53,23 +49,23 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable inline fun sleep(millis: Long) { if (currentStateMachine() != null) { - val db = StrandLocalTransactionManager.database - TransactionManager.current().commit() - TransactionManager.current().close() + val db = DatabaseTransactionManager.dataSource + DatabaseTransactionManager.current().commit() + DatabaseTransactionManager.current().close() Strand.sleep(millis) - StrandLocalTransactionManager.database = db - TransactionManager.manager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ) + DatabaseTransactionManager.dataSource = db + DatabaseTransactionManager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ) } else Strand.sleep(millis) } } // These fields shouldn't be serialised, so they are marked @Transient. @Transient override lateinit var serviceHub: ServiceHubInternal - @Transient internal lateinit var database: Database + @Transient internal lateinit var database: CordaPersistence @Transient internal lateinit var actionOnSuspend: (FlowIORequest) -> Unit @Transient internal lateinit var actionOnEnd: (Try, Boolean) -> Unit @Transient internal var fromCheckpoint: Boolean = false - @Transient private var txTrampoline: Transaction? = null + @Transient private var txTrampoline: DatabaseTransaction? = null /** * Return the logger for this state machine. The logger name incorporates [id] and so including it in the log message @@ -130,7 +126,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun createTransaction() { // Make sure we have a database transaction database.createTransaction() - logger.trace { "Starting database transaction ${TransactionManager.currentOrNull()} on ${Strand.currentStrand()}" } + logger.trace { "Starting database transaction ${DatabaseTransactionManager.currentOrNull()} on ${Strand.currentStrand()}" } } private fun processException(exception: Throwable, propagated: Boolean) { @@ -140,7 +136,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } internal fun commitTransaction() { - val transaction = TransactionManager.current() + val transaction = DatabaseTransactionManager.current() try { logger.trace { "Committing database transaction $transaction on ${Strand.currentStrand()}." } transaction.commit() @@ -383,8 +379,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun suspend(ioRequest: FlowIORequest) { // We have to pass the thread local database transaction across via a transient field as the fiber park // swaps them out. - txTrampoline = TransactionManager.currentOrNull() - StrandLocalTransactionManager.setThreadLocalTx(null) + txTrampoline = DatabaseTransactionManager.setThreadLocalTx(null) if (ioRequest is WaitingRequest) waitingForResponse = ioRequest @@ -393,7 +388,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, logger.trace { "Suspended on $ioRequest" } // restore the Tx onto the ThreadLocal so that we can commit the ensuing checkpoint to the DB try { - StrandLocalTransactionManager.setThreadLocalTx(txTrampoline) + DatabaseTransactionManager.setThreadLocalTx(txTrampoline) txTrampoline = null actionOnSuspend(ioRequest) } catch (t: Throwable) { 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 27ce753cd3..2d72684e41 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 @@ -39,7 +39,6 @@ import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession import net.corda.node.utilities.* import org.apache.activemq.artemis.utils.ReusableLatch -import org.jetbrains.exposed.sql.Database import org.slf4j.Logger import rx.Observable import rx.subjects.PublishSubject @@ -76,7 +75,7 @@ import kotlin.collections.ArrayList class StateMachineManager(val serviceHub: ServiceHubInternal, val checkpointStorage: CheckpointStorage, val executor: AffinityExecutor, - val database: Database, + val database: CordaPersistence, private val unfinishedFibers: ReusableLatch = ReusableLatch()) { inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index e7f4bca217..3846c81a0e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -36,7 +36,6 @@ import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.transactions.BFTSMaRt.Client import net.corda.node.services.transactions.BFTSMaRt.Replica import net.corda.node.utilities.JDBCHashMap -import net.corda.node.utilities.transaction import java.nio.file.Path import java.util.* diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt index 1b0b528e21..f1d9c823e7 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt @@ -8,9 +8,8 @@ import io.atomix.copycat.server.StateMachine import io.atomix.copycat.server.storage.snapshot.SnapshotReader import io.atomix.copycat.server.storage.snapshot.SnapshotWriter import net.corda.core.utilities.loggerFor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.JDBCHashMap -import net.corda.node.utilities.transaction -import org.jetbrains.exposed.sql.Database import java.util.* /** @@ -21,7 +20,7 @@ import java.util.* * to disk, and sharing them across the cluster. A new node joining the cluster will have to obtain and install a snapshot * containing the entire JDBC table contents. */ -class DistributedImmutableMap(val db: Database, tableName: String) : StateMachine(), Snapshottable { +class DistributedImmutableMap(val db: CordaPersistence, tableName: String) : StateMachine(), Snapshottable { companion object { private val log = loggerFor>() } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index b12c7ce477..d7024866ad 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -23,8 +23,8 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.utilities.CordaPersistence import net.corda.nodeapi.config.SSLConfiguration -import org.jetbrains.exposed.sql.Database import java.nio.file.Path import java.util.concurrent.CompletableFuture import javax.annotation.concurrent.ThreadSafe @@ -55,7 +55,7 @@ class RaftUniquenessProvider(services: ServiceHubInternal) : UniquenessProvider, */ private val clusterAddresses = services.configuration.notaryClusterAddresses /** The database to store the state machine state in */ - private val db: Database = services.database + private val db: CordaPersistence = services.database /** SSL configuration */ private val transportConfiguration: SSLConfiguration = services.configuration diff --git a/node/src/main/kotlin/net/corda/node/services/vault/CashBalanceAsMetricsObserver.kt b/node/src/main/kotlin/net/corda/node/services/vault/CashBalanceAsMetricsObserver.kt index 9eb1719ef6..fc0d59c982 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/CashBalanceAsMetricsObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/CashBalanceAsMetricsObserver.kt @@ -3,14 +3,13 @@ package net.corda.node.services.vault import com.codahale.metrics.Gauge import net.corda.core.node.services.VaultService import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.utilities.transaction -import org.jetbrains.exposed.sql.Database +import net.corda.node.utilities.CordaPersistence import java.util.* /** * This class observes the vault and reflect current cash balances as exposed metrics in the monitoring service. */ -class CashBalanceAsMetricsObserver(val serviceHubInternal: ServiceHubInternal, val database: Database) { +class CashBalanceAsMetricsObserver(val serviceHubInternal: ServiceHubInternal, val database: CordaPersistence) { init { // TODO: Need to consider failure scenarios. This needs to run if the TX is successfully recorded serviceHubInternal.vaultService.updates.subscribe { _ -> diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt new file mode 100644 index 0000000000..c45b1b9fc2 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -0,0 +1,193 @@ +package net.corda.node.utilities + +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import org.jetbrains.exposed.sql.Database + +import rx.Observable +import rx.Subscriber +import rx.subjects.UnicastSubject +import java.io.Closeable +import java.sql.Connection +import java.sql.SQLException +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList + + +//HikariDataSource implements also Closeable which allows CordaPersistence to be Closeable +class CordaPersistence(var dataSource: HikariDataSource): Closeable { + + /** Holds Exposed database, the field will be removed once Exposed library is removed */ + lateinit var database: Database + + companion object { + fun connect(dataSource: HikariDataSource): CordaPersistence { + return CordaPersistence(dataSource).apply { + DatabaseTransactionManager(this) + } + } + } + + fun createTransaction(): DatabaseTransaction { + // We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases. + DatabaseTransactionManager.dataSource = this + return DatabaseTransactionManager.currentOrNew(Connection.TRANSACTION_REPEATABLE_READ) + } + + fun isolatedTransaction(block: DatabaseTransaction.() -> T): T { + val context = DatabaseTransactionManager.setThreadLocalTx(null) + return try { + transaction(block) + } finally { + DatabaseTransactionManager.restoreThreadLocalTx(context) + } + } + + fun transaction(statement: DatabaseTransaction.() -> T): T { + DatabaseTransactionManager.dataSource = this + return transaction(Connection.TRANSACTION_REPEATABLE_READ, 3, statement) + } + + private fun transaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T { + val outer = DatabaseTransactionManager.currentOrNull() + + return if (outer != null) { + outer.statement() + } + else { + inTopLevelTransaction(transactionIsolation, repetitionAttempts, statement) + } + } + + private fun inTopLevelTransaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T { + var repetitions = 0 + while (true) { + val transaction = DatabaseTransactionManager.currentOrNew(transactionIsolation) + try { + val answer = transaction.statement() + transaction.commit() + return answer + } + catch (e: SQLException) { + transaction.rollback() + repetitions++ + if (repetitions >= repetitionAttempts) { + throw e + } + } + catch (e: Throwable) { + transaction.rollback() + throw e + } + finally { + transaction.close() + } + } + } + + override fun close() { + dataSource.close() + } +} + +fun configureDatabase(props: Properties): CordaPersistence { + val config = HikariConfig(props) + val dataSource = HikariDataSource(config) + val persistence = CordaPersistence.connect(dataSource) + + //org.jetbrains.exposed.sql.Database will be removed once Exposed library is removed + val database = Database.connect(dataSource) { _ -> ExposedTransactionManager() } + persistence.database = database + + // Check not in read-only mode. + persistence.transaction { + persistence.dataSource.connection.use { + check(!it.metaData.isReadOnly) { "Database should not be readonly." } + } + } + return persistence +} + +/** + * Buffer observations until after the current database transaction has been closed. Observations are never + * dropped, simply delayed. + * + * Primarily for use by component authors to publish observations during database transactions without racing against + * closing the database transaction. + * + * For examples, see the call hierarchy of this function. + */ +fun rx.Observer.bufferUntilDatabaseCommit(): rx.Observer { + val currentTxId = DatabaseTransactionManager.transactionId + val databaseTxBoundary: Observable = DatabaseTransactionManager.transactionBoundaries.filter { it.txId == currentTxId }.first() + val subject = UnicastSubject.create() + subject.delaySubscription(databaseTxBoundary).subscribe(this) + databaseTxBoundary.doOnCompleted { subject.onCompleted() } + return subject +} + +// A subscriber that delegates to multiple others, wrapping a database transaction around the combination. +private class DatabaseTransactionWrappingSubscriber(val db: CordaPersistence?) : Subscriber() { + // Some unsubscribes happen inside onNext() so need something that supports concurrent modification. + val delegates = CopyOnWriteArrayList>() + + fun forEachSubscriberWithDbTx(block: Subscriber.() -> Unit) { + (db ?: DatabaseTransactionManager.dataSource).transaction { + delegates.filter { !it.isUnsubscribed }.forEach { + it.block() + } + } + } + + override fun onCompleted() = forEachSubscriberWithDbTx { onCompleted() } + + override fun onError(e: Throwable?) = forEachSubscriberWithDbTx { onError(e) } + + override fun onNext(s: U) = forEachSubscriberWithDbTx { onNext(s) } + + override fun onStart() = forEachSubscriberWithDbTx { onStart() } + + fun cleanUp() { + if (delegates.removeIf { it.isUnsubscribed }) { + if (delegates.isEmpty()) { + unsubscribe() + } + } + } +} + +// A subscriber that wraps another but does not pass on observations to it. +private class NoOpSubscriber(t: Subscriber) : Subscriber(t) { + override fun onCompleted() { + } + + override fun onError(e: Throwable?) { + } + + override fun onNext(s: U) { + } +} + +/** + * Wrap delivery of observations in a database transaction. Multiple subscribers will receive the observations inside + * the same database transaction. This also lazily subscribes to the source [rx.Observable] to preserve any buffering + * that might be in place. + */ +fun rx.Observable.wrapWithDatabaseTransaction(db: CordaPersistence? = null): rx.Observable { + var wrappingSubscriber = DatabaseTransactionWrappingSubscriber(db) + // Use lift to add subscribers to a special subscriber that wraps a database transaction around observations. + // Each subscriber will be passed to this lambda when they subscribe, at which point we add them to wrapping subscriber. + return this.lift { toBeWrappedInDbTx: Subscriber -> + // Add the subscriber to the wrapping subscriber, which will invoke the original subscribers together inside a database transaction. + wrappingSubscriber.delegates.add(toBeWrappedInDbTx) + // If we are the first subscriber, return the shared subscriber, otherwise return a subscriber that does nothing. + if (wrappingSubscriber.delegates.size == 1) wrappingSubscriber else NoOpSubscriber(toBeWrappedInDbTx) + // Clean up the shared list of subscribers when they unsubscribe. + }.doOnUnsubscribe { + wrappingSubscriber.cleanUp() + // If cleanup removed the last subscriber reset the system, as future subscribers might need the stream again + if (wrappingSubscriber.delegates.isEmpty()) { + wrappingSubscriber = DatabaseTransactionWrappingSubscriber(db) + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt b/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt index b5f8e62e7c..028117015c 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt @@ -1,277 +1,26 @@ package net.corda.node.utilities -import co.paralleluniverse.strands.Strand -import com.zaxxer.hikari.HikariConfig -import com.zaxxer.hikari.HikariDataSource import net.corda.core.crypto.SecureHash import net.corda.core.crypto.parsePublicKeyBase58 import net.corda.core.crypto.toBase58String -import net.corda.node.utilities.StrandLocalTransactionManager.Boundary import org.bouncycastle.cert.X509CertificateHolder import org.h2.jdbc.JdbcBlob import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.TransactionInterface -import org.jetbrains.exposed.sql.transactions.TransactionManager -import rx.Observable -import rx.Subscriber -import rx.subjects.PublishSubject -import rx.subjects.Subject -import rx.subjects.UnicastSubject import java.io.ByteArrayInputStream -import java.io.Closeable import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.CertificateFactory -import java.sql.Connection import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneOffset import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList /** * Table prefix for all tables owned by the node module. */ const val NODE_DATABASE_PREFIX = "node_" -@Deprecated("Use Database.transaction instead.") -fun databaseTransaction(db: Database, statement: Transaction.() -> T) = db.transaction(statement) - -// TODO: Handle commit failure due to database unavailable. Better to shutdown and await database reconnect/recovery. -fun Database.transaction(statement: Transaction.() -> T): T { - // We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases. - StrandLocalTransactionManager.database = this - return org.jetbrains.exposed.sql.transactions.transaction(Connection.TRANSACTION_REPEATABLE_READ, 1, statement) -} - -fun Database.createTransaction(): Transaction { - // We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases. - StrandLocalTransactionManager.database = this - return TransactionManager.currentOrNew(Connection.TRANSACTION_REPEATABLE_READ) -} - -fun configureDatabase(props: Properties): Pair { - val config = HikariConfig(props) - val dataSource = HikariDataSource(config) - val database = Database.connect(dataSource) { db -> StrandLocalTransactionManager(db) } - // Check not in read-only mode. - database.transaction { - check(!database.metadata.isReadOnly) { "Database should not be readonly." } - } - return Pair(dataSource, database) -} - -fun Database.isolatedTransaction(block: Transaction.() -> T): T { - val oldContext = StrandLocalTransactionManager.setThreadLocalTx(null) - return try { - transaction(block) - } finally { - StrandLocalTransactionManager.restoreThreadLocalTx(oldContext) - } -} - -/** - * A relatively close copy of the [org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManager] - * in Exposed but with the following adjustments to suit our environment: - * - * Because the construction of a [Database] instance results in replacing the singleton [TransactionManager] instance, - * our tests involving two [MockNode]s effectively replace the database instances of each other and continue to trample - * over each other. So here we use a companion object to hold them as [ThreadLocal] and [StrandLocalTransactionManager] - * is otherwise effectively stateless so it's replacement does not matter. The [ThreadLocal] is then set correctly and - * explicitly just prior to initiating a transaction in [transaction] and [createTransaction] above. - * - * The [StrandLocalTransactionManager] instances have an [Observable] of the transaction close [Boundary]s which - * facilitates the use of [Observable.afterDatabaseCommit] to create event streams that only emit once the database - * transaction is closed and the data has been persisted and becomes visible to other observers. - */ -class StrandLocalTransactionManager(initWithDatabase: Database) : TransactionManager { - - companion object { - private val TX_ID = Key() - - private val threadLocalDb = ThreadLocal() - private val threadLocalTx = ThreadLocal() - private val databaseToInstance = ConcurrentHashMap() - - fun setThreadLocalTx(tx: Transaction?): Pair { - val oldTx = threadLocalTx.get() - threadLocalTx.set(tx) - return Pair(threadLocalDb.get(), oldTx) - } - - fun restoreThreadLocalTx(context: Pair) { - threadLocalDb.set(context.first) - threadLocalTx.set(context.second) - } - - var database: Database - get() = threadLocalDb.get() ?: throw IllegalStateException("Was expecting to find database set on current strand: ${Strand.currentStrand()}") - set(value) { - threadLocalDb.set(value) - } - - val transactionId: UUID - get() = threadLocalTx.get()?.getUserData(TX_ID) ?: throw IllegalStateException("Was expecting to find transaction set on current strand: ${Strand.currentStrand()}") - - val manager: StrandLocalTransactionManager get() = databaseToInstance[database]!! - - val transactionBoundaries: Subject get() = manager._transactionBoundaries - } - - - data class Boundary(val txId: UUID) - - private val _transactionBoundaries = PublishSubject.create().toSerialized() - - init { - // Found a unit test that was forgetting to close the database transactions. When you close() on the top level - // database transaction it will reset the threadLocalTx back to null, so if it isn't then there is still a - // databae transaction open. The [transaction] helper above handles this in a finally clause for you - // but any manual database transaction management is liable to have this problem. - if (threadLocalTx.get() != null) { - throw IllegalStateException("Was not expecting to find existing database transaction on current strand when setting database: ${Strand.currentStrand()}, ${threadLocalTx.get()}") - } - database = initWithDatabase - databaseToInstance[database] = this - } - - override fun newTransaction(isolation: Int): Transaction { - val impl = StrandLocalTransaction(database, isolation, threadLocalTx, transactionBoundaries) - return Transaction(impl).apply { - threadLocalTx.set(this) - putUserData(TX_ID, impl.id) - } - } - - override fun currentOrNull(): Transaction? = threadLocalTx.get() - - // Direct copy of [ThreadLocalTransaction]. - private class StrandLocalTransaction(override val db: Database, isolation: Int, val threadLocal: ThreadLocal, val transactionBoundaries: Subject) : TransactionInterface { - val id = UUID.randomUUID() - - override val connection: Connection by lazy(LazyThreadSafetyMode.NONE) { - db.connector().apply { - autoCommit = false - transactionIsolation = isolation - } - } - - override val outerTransaction = threadLocal.get() - - override fun commit() { - connection.commit() - } - - override fun rollback() { - if (!connection.isClosed) { - connection.rollback() - } - } - - override fun close() { - connection.close() - threadLocal.set(outerTransaction) - if (outerTransaction == null) { - transactionBoundaries.onNext(Boundary(id)) - } - } - } -} - -/** - * Buffer observations until after the current database transaction has been closed. Observations are never - * dropped, simply delayed. - * - * Primarily for use by component authors to publish observations during database transactions without racing against - * closing the database transaction. - * - * For examples, see the call hierarchy of this function. - */ -fun rx.Observer.bufferUntilDatabaseCommit(): rx.Observer { - val currentTxId = StrandLocalTransactionManager.transactionId - val databaseTxBoundary: Observable = StrandLocalTransactionManager.transactionBoundaries.filter { it.txId == currentTxId }.first() - val subject = UnicastSubject.create() - subject.delaySubscription(databaseTxBoundary).subscribe(this) - databaseTxBoundary.doOnCompleted { subject.onCompleted() } - return subject -} - -// A subscriber that delegates to multiple others, wrapping a database transaction around the combination. -private class DatabaseTransactionWrappingSubscriber(val db: Database?) : Subscriber() { - // Some unsubscribes happen inside onNext() so need something that supports concurrent modification. - val delegates = CopyOnWriteArrayList>() - - fun forEachSubscriberWithDbTx(block: Subscriber.() -> Unit) { - (db ?: StrandLocalTransactionManager.database).transaction { - delegates.filter { !it.isUnsubscribed }.forEach { - it.block() - } - } - } - - override fun onCompleted() { - forEachSubscriberWithDbTx { onCompleted() } - } - - override fun onError(e: Throwable?) { - forEachSubscriberWithDbTx { onError(e) } - } - - override fun onNext(s: U) { - forEachSubscriberWithDbTx { onNext(s) } - } - - override fun onStart() { - forEachSubscriberWithDbTx { onStart() } - } - - fun cleanUp() { - if (delegates.removeIf { it.isUnsubscribed }) { - if (delegates.isEmpty()) { - unsubscribe() - } - } - } -} - -// A subscriber that wraps another but does not pass on observations to it. -private class NoOpSubscriber(t: Subscriber) : Subscriber(t) { - override fun onCompleted() { - } - - override fun onError(e: Throwable?) { - } - - override fun onNext(s: U) { - } -} - -/** - * Wrap delivery of observations in a database transaction. Multiple subscribers will receive the observations inside - * the same database transaction. This also lazily subscribes to the source [rx.Observable] to preserve any buffering - * that might be in place. - */ -fun rx.Observable.wrapWithDatabaseTransaction(db: Database? = null): rx.Observable { - var wrappingSubscriber = DatabaseTransactionWrappingSubscriber(db) - // Use lift to add subscribers to a special subscriber that wraps a database transaction around observations. - // Each subscriber will be passed to this lambda when they subscribe, at which point we add them to wrapping subscriber. - return this.lift { toBeWrappedInDbTx: Subscriber -> - // Add the subscriber to the wrapping subscriber, which will invoke the original subscribers together inside a database transaction. - wrappingSubscriber.delegates.add(toBeWrappedInDbTx) - // If we are the first subscriber, return the shared subscriber, otherwise return a subscriber that does nothing. - if (wrappingSubscriber.delegates.size == 1) wrappingSubscriber else NoOpSubscriber(toBeWrappedInDbTx) - // Clean up the shared list of subscribers when they unsubscribe. - }.doOnUnsubscribe { - wrappingSubscriber.cleanUp() - // If cleanup removed the last subscriber reset the system, as future subscribers might need the stream again - if (wrappingSubscriber.delegates.isEmpty()) { - wrappingSubscriber = DatabaseTransactionWrappingSubscriber(db) - } - } -} - // Composite columns for use with below Exposed helpers. data class PartyColumns(val name: Column, val owningKey: Column) data class PartyAndCertificateColumns(val name: Column, val owningKey: Column, diff --git a/node/src/main/kotlin/net/corda/node/utilities/DatabaseTransactionManager.kt b/node/src/main/kotlin/net/corda/node/utilities/DatabaseTransactionManager.kt new file mode 100644 index 0000000000..79a47a534c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/DatabaseTransactionManager.kt @@ -0,0 +1,106 @@ +package net.corda.node.utilities + +import co.paralleluniverse.strands.Strand +import rx.subjects.PublishSubject +import rx.subjects.Subject +import java.sql.Connection +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class DatabaseTransaction(isolation: Int, val threadLocal: ThreadLocal, + val transactionBoundaries: Subject, + val cordaPersistence: CordaPersistence) { + + val id: UUID = UUID.randomUUID() + + val connection: Connection by lazy(LazyThreadSafetyMode.NONE) { + cordaPersistence.dataSource.connection + .apply { + autoCommit = false + transactionIsolation = isolation + } + } + + val outerTransaction: DatabaseTransaction? = threadLocal.get() + + fun commit() { + connection.commit() + } + + fun rollback() { + if (!connection.isClosed) { + connection.rollback() + } + } + + fun close() { + connection.close() + threadLocal.set(outerTransaction) + if (outerTransaction == null) { + transactionBoundaries.onNext(DatabaseTransactionManager.Boundary(id)) + } + } +} + +class DatabaseTransactionManager(initDataSource: CordaPersistence) { + companion object { + private val threadLocalDb = ThreadLocal() + private val threadLocalTx = ThreadLocal() + private val databaseToInstance = ConcurrentHashMap() + + fun setThreadLocalTx(tx: DatabaseTransaction?): DatabaseTransaction? { + val oldTx = threadLocalTx.get() + threadLocalTx.set(tx) + return oldTx + } + + fun restoreThreadLocalTx(context: DatabaseTransaction?) { + if (context != null) { + threadLocalDb.set(context.cordaPersistence) + } + threadLocalTx.set(context) + } + + var dataSource: CordaPersistence + get() = threadLocalDb.get() ?: throw IllegalStateException("Was expecting to find CordaPersistence set on current thread: ${Strand.currentStrand()}") + set(value) = threadLocalDb.set(value) + + val transactionId: UUID + get() = threadLocalTx.get()?.id ?: throw IllegalStateException("Was expecting to find transaction set on current strand: ${Strand.currentStrand()}") + + val manager: DatabaseTransactionManager get() = databaseToInstance[dataSource]!! + + val transactionBoundaries: Subject get() = manager._transactionBoundaries + + fun currentOrNull(): DatabaseTransaction? = manager.currentOrNull() + + fun currentOrNew(isolation: Int) = currentOrNull() ?: manager.newTransaction(isolation) + + fun current(): DatabaseTransaction = currentOrNull() ?: error("No transaction in context.") + + fun newTransaction(isolation: Int) = manager.newTransaction(isolation) + } + + data class Boundary(val txId: UUID) + + private val _transactionBoundaries = PublishSubject.create().toSerialized() + + init { + // Found a unit test that was forgetting to close the database transactions. When you close() on the top level + // database transaction it will reset the threadLocalTx back to null, so if it isn't then there is still a + // database transaction open. The [transaction] helper above handles this in a finally clause for you + // but any manual database transaction management is liable to have this problem. + if (threadLocalTx.get() != null) { + throw IllegalStateException("Was not expecting to find existing database transaction on current strand when setting database: ${Strand.currentStrand()}, ${threadLocalTx.get()}") + } + dataSource = initDataSource + databaseToInstance[dataSource] = this + } + + private fun newTransaction(isolation: Int) = + DatabaseTransaction(isolation, threadLocalTx, transactionBoundaries, dataSource).apply { + threadLocalTx.set(this) + } + + private fun currentOrNull(): DatabaseTransaction? = threadLocalTx.get() +} diff --git a/node/src/main/kotlin/net/corda/node/utilities/ExposedTransactionManager.kt b/node/src/main/kotlin/net/corda/node/utilities/ExposedTransactionManager.kt new file mode 100644 index 0000000000..1d90449c35 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/ExposedTransactionManager.kt @@ -0,0 +1,54 @@ +package net.corda.node.utilities + +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.transactions.TransactionInterface +import org.jetbrains.exposed.sql.transactions.TransactionManager +import java.sql.Connection + +/** + * Wrapper of [DatabaseTransaction], because the class is effectively used for [ExposedTransaction.connection] method only not all methods are implemented. + * The class will obsolete when Exposed library is phased out. + */ +class ExposedTransaction(override val db: Database, val databaseTransaction: DatabaseTransaction): TransactionInterface { + + override val outerTransaction: Transaction? + get() = throw UnsupportedOperationException() + + override val connection: Connection by lazy(LazyThreadSafetyMode.NONE) { + databaseTransaction.connection + } + + override fun commit() { + databaseTransaction.commit() + } + + override fun rollback() { + databaseTransaction.rollback() + } + + override fun close() { + databaseTransaction.close() + } +} + +/** + * Delegates methods to [DatabaseTransactionManager]. + * The class will obsolete when Exposed library is phased out. + */ +class ExposedTransactionManager: TransactionManager { + companion object { + val database: Database + get() = DatabaseTransactionManager.dataSource.database + } + + override fun newTransaction(isolation: Int): Transaction { + var databaseTransaction = DatabaseTransactionManager.newTransaction(isolation) + return Transaction(ExposedTransaction(database, databaseTransaction)) + } + + override fun currentOrNull(): Transaction? { + val databaseTransaction = DatabaseTransactionManager.currentOrNull() + return if (databaseTransaction != null) Transaction(ExposedTransaction(database, databaseTransaction)) else null + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt b/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt index 26df75020b..96147f2739 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt @@ -8,7 +8,6 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.InsertStatement -import org.jetbrains.exposed.sql.transactions.TransactionManager import java.sql.Blob import java.util.* import kotlin.system.measureTimeMillis @@ -60,7 +59,7 @@ class JDBCHashMap(tableName: String, } fun bytesToBlob(value: SerializedBytes<*>, finalizables: MutableList<() -> Unit>): Blob { - val blob = TransactionManager.current().connection.createBlob() + val blob = DatabaseTransactionManager.current().connection.createBlob() finalizables += { blob.free() } blob.setBytes(1, value.bytes) return blob diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index f5b44a3487..5583482b51 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -11,9 +11,11 @@ import net.corda.core.messaging.*; import net.corda.core.node.services.*; import net.corda.core.node.services.vault.*; import net.corda.core.node.services.vault.QueryCriteria.*; +import net.corda.testing.contracts.DummyLinearContract; import net.corda.core.schemas.*; import net.corda.core.transactions.*; import net.corda.core.utilities.*; +import net.corda.node.utilities.CordaPersistence; import net.corda.node.services.database.*; import net.corda.node.services.schema.*; import net.corda.schemas.*; @@ -22,7 +24,6 @@ import net.corda.testing.contracts.*; import net.corda.testing.node.*; import net.corda.testing.schemas.DummyLinearStateSchemaV1; import org.jetbrains.annotations.*; -import org.jetbrains.exposed.sql.*; import org.junit.*; import rx.Observable; @@ -34,8 +35,7 @@ import java.util.stream.*; import static net.corda.contracts.asset.CashKt.*; import static net.corda.core.contracts.ContractsDSL.*; import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; -import static net.corda.node.utilities.DatabaseSupportKt.*; -import static net.corda.node.utilities.DatabaseSupportKt.transaction; +import static net.corda.node.utilities.CordaPersistenceKt.configureDatabase; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.node.MockServicesKt.*; import static net.corda.core.utilities.ByteArrays.toHexString; @@ -46,19 +46,16 @@ public class VaultQueryJavaTests { private MockServices services; private VaultService vaultSvc; private VaultQueryService vaultQuerySvc; - private Closeable dataSource; - private Database database; + private CordaPersistence database; @Before public void setUp() { Properties dataSourceProps = makeTestDataSourceProperties(SecureHash.randomSHA256().toString()); - Pair dataSourceAndDatabase = configureDatabase(dataSourceProps); - dataSource = dataSourceAndDatabase.getFirst(); - database = dataSourceAndDatabase.getSecond(); + database = configureDatabase(dataSourceProps); Set customSchemas = new HashSet<>(Collections.singletonList(DummyLinearStateSchemaV1.INSTANCE)); HibernateConfiguration hibernateConfig = new HibernateConfiguration(new NodeSchemaService(customSchemas)); - transaction(database, + database.transaction( statement -> { services = new MockServices(getMEGA_CORP_KEY()) { @NotNull @Override @@ -91,7 +88,7 @@ public class VaultQueryJavaTests { @After public void cleanUp() throws IOException { - dataSource.close(); + database.close(); } /** @@ -104,7 +101,7 @@ public class VaultQueryJavaTests { @Test public void unconsumedLinearStates() throws VaultQueryException { - transaction(database, tx -> { + database.transaction(tx -> { VaultFiller.fillWithSomeTestLinearStates(services, 3); @@ -120,7 +117,7 @@ public class VaultQueryJavaTests { @Test public void unconsumedStatesForStateRefsSortedByTxnId() { - transaction(database, tx -> { + database.transaction(tx -> { VaultFiller.fillWithSomeTestLinearStates(services, 8); Vault issuedStates = VaultFiller.fillWithSomeTestLinearStates(services, 2); @@ -145,7 +142,7 @@ public class VaultQueryJavaTests { @Test public void consumedCashStates() { - transaction(database, tx -> { + database.transaction(tx -> { Amount amount = new Amount<>(100, Currency.getInstance("USD")); @@ -175,7 +172,7 @@ public class VaultQueryJavaTests { @Test public void consumedDealStatesPagedSorted() throws VaultQueryException { - transaction(database, tx -> { + database.transaction(tx -> { Vault states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null); StateAndRef linearState = states.getStates().iterator().next(); @@ -217,7 +214,7 @@ public class VaultQueryJavaTests { @Test @SuppressWarnings("unchecked") public void customQueryForCashStatesWithAmountOfCurrencyGreaterOrEqualThanQuantity() { - transaction(database, tx -> { + database.transaction(tx -> { Amount pounds = new Amount<>(100, Currency.getInstance("GBP")); Amount dollars100 = new Amount<>(100, Currency.getInstance("USD")); @@ -261,7 +258,7 @@ public class VaultQueryJavaTests { @Test public void trackCashStates() { - transaction(database, tx -> { + database.transaction(tx -> { VaultFiller.fillWithSomeTestCash(services, new Amount<>(100, Currency.getInstance("USD")), TestConstants.getDUMMY_NOTARY(), @@ -292,7 +289,7 @@ public class VaultQueryJavaTests { @Test public void trackDealStatesPagedSorted() { - transaction(database, tx -> { + database.transaction(tx -> { Vault states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null); UniqueIdentifier uid = states.getStates().iterator().next().component1().getData().getLinearId(); @@ -333,7 +330,7 @@ public class VaultQueryJavaTests { @Test public void consumedStatesDeprecated() { - transaction(database, tx -> { + database.transaction(tx -> { Amount amount = new Amount<>(100, USD); VaultFiller.fillWithSomeTestCash(services, new Amount<>(100, USD), @@ -365,7 +362,7 @@ public class VaultQueryJavaTests { @Test public void consumedStatesForLinearIdDeprecated() { - transaction(database, tx -> { + database.transaction(tx -> { Vault linearStates = VaultFiller.fillWithSomeTestLinearStates(services, 4,null); linearStates.getStates().iterator().next().component1().getData().getLinearId(); @@ -394,7 +391,7 @@ public class VaultQueryJavaTests { @Test @SuppressWarnings("unchecked") public void aggregateFunctionsWithoutGroupClause() { - transaction(database, tx -> { + database.transaction(tx -> { Amount dollars100 = new Amount<>(100, Currency.getInstance("USD")); Amount dollars200 = new Amount<>(200, Currency.getInstance("USD")); @@ -439,7 +436,7 @@ public class VaultQueryJavaTests { @Test @SuppressWarnings("unchecked") public void aggregateFunctionsWithSingleGroupClause() { - transaction(database, tx -> { + database.transaction(tx -> { Amount dollars100 = new Amount<>(100, Currency.getInstance("USD")); Amount dollars200 = new Amount<>(200, Currency.getInstance("USD")); @@ -510,7 +507,7 @@ public class VaultQueryJavaTests { @Test @SuppressWarnings("unchecked") public void aggregateFunctionsSumByIssuerAndCurrencyAndSortByAggregateSum() { - transaction(database, tx -> { + database.transaction(tx -> { Amount dollars100 = new Amount<>(100, Currency.getInstance("USD")); Amount dollars200 = new Amount<>(200, Currency.getInstance("USD")); diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 11009e3c42..97e50efa27 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -22,7 +22,6 @@ import net.corda.node.services.messaging.RpcContext import net.corda.node.services.network.NetworkMapService import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.SimpleNotaryService -import net.corda.node.utilities.transaction import net.corda.nodeapi.PermissionException import net.corda.nodeapi.User import net.corda.testing.expect diff --git a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt index 8085de959e..8789df1724 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt @@ -13,16 +13,13 @@ import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.schemas.requery.AttachmentEntity import net.corda.node.services.transactions.SimpleNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import net.corda.testing.node.makeTestDataSourceProperties -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.io.Closeable import java.math.BigInteger import java.security.KeyPair import java.util.jar.JarOutputStream @@ -32,8 +29,6 @@ import kotlin.test.assertFailsWith class AttachmentTests { lateinit var mockNet: MockNetwork - lateinit var dataSource: Closeable - lateinit var database: Database lateinit var configuration: RequeryConfiguration @Before diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 063de256dc..43f03c70bd 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -35,14 +35,13 @@ import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.checkpoints -import net.corda.node.utilities.transaction +import net.corda.node.utilities.CordaPersistence import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test @@ -681,7 +680,7 @@ class TwoPartyTradeFlowTests { } - class RecordingTransactionStorage(val database: Database, val delegate: WritableTransactionStorage) : WritableTransactionStorage { + class RecordingTransactionStorage(val database: CordaPersistence, val delegate: WritableTransactionStorage) : WritableTransactionStorage { override fun track(): DataFeed, SignedTransaction> { return database.transaction { delegate.track() diff --git a/node/src/test/kotlin/net/corda/node/services/MockServiceHubInternal.kt b/node/src/test/kotlin/net/corda/node/services/MockServiceHubInternal.kt index 693abdc855..b832cfb92f 100644 --- a/node/src/test/kotlin/net/corda/node/services/MockServiceHubInternal.kt +++ b/node/src/test/kotlin/net/corda/node/services/MockServiceHubInternal.kt @@ -15,16 +15,16 @@ import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.InMemoryTransactionVerifierService +import net.corda.node.utilities.CordaPersistence import net.corda.testing.MOCK_IDENTITY_SERVICE import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockNetworkMapCache import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage import net.corda.testing.node.MockTransactionStorage -import org.jetbrains.exposed.sql.Database import java.time.Clock open class MockServiceHubInternal( - override val database: Database, + override val database: CordaPersistence, override val configuration: NodeConfiguration, val customVault: VaultService? = null, val customVaultQuery: VaultQueryService? = null, diff --git a/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt index cc8c9a98b1..e26830be2f 100644 --- a/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt @@ -14,7 +14,6 @@ import net.corda.core.node.services.VaultService import net.corda.core.schemas.PersistentStateRef import net.corda.testing.schemas.DummyLinearStateSchemaV1 import net.corda.testing.schemas.DummyLinearStateSchemaV2 -import net.corda.core.serialization.deserialize import net.corda.core.serialization.storageKryo import net.corda.core.transactions.SignedTransaction import net.corda.testing.ALICE @@ -25,9 +24,10 @@ import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.NodeVaultService import net.corda.core.schemas.CommonSchemaV1 +import net.corda.core.serialization.deserialize import net.corda.node.services.vault.VaultSchemaV1 +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.schemas.CashSchemaV1 import net.corda.schemas.SampleCashSchemaV2 import net.corda.schemas.SampleCashSchemaV3 @@ -39,11 +39,9 @@ import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.hibernate.SessionFactory -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.time.Instant import java.util.* import javax.persistence.EntityManager @@ -53,8 +51,7 @@ import javax.persistence.criteria.CriteriaBuilder class HibernateConfigurationTest { lateinit var services: MockServices - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence val vault: VaultService get() = services.vaultService // Hibernate configuration objects @@ -70,11 +67,9 @@ class HibernateConfigurationTest { @Before fun setUp() { val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) + database = configureDatabase(dataSourceProps) val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second database.transaction { hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas)) @@ -104,7 +99,7 @@ class HibernateConfigurationTest { @After fun cleanUp() { - dataSource.close() + database.close() } private fun setUpDb() { diff --git a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt index ec8f6a57d2..a670cf2a4d 100644 --- a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt @@ -23,40 +23,35 @@ import net.corda.node.services.vault.schemas.requery.Models import net.corda.node.services.vault.schemas.requery.VaultCashBalancesEntity import net.corda.node.services.vault.schemas.requery.VaultSchema import net.corda.node.services.vault.schemas.requery.VaultStatesEntity +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import java.io.Closeable import java.time.Instant import java.util.* class RequeryConfigurationTest { - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var transactionStorage: DBTransactionStorage lateinit var requerySession: KotlinEntityDataStore @Before fun setUp() { val dataSourceProperties = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProperties) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProperties) newTransactionStorage() newRequeryStorage(dataSourceProperties) } @After fun cleanUp() { - dataSource.close() + database.close() } @Test 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 46ef15d5c8..8d21979c6f 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 @@ -18,9 +18,7 @@ import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.vault.NodeVaultService -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction +import net.corda.node.utilities.* import net.corda.testing.getTestX509Name import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockKeyManagementService @@ -29,11 +27,9 @@ import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.testNodeConfiguration import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.nio.file.Paths import java.security.PublicKey import java.time.Clock @@ -54,8 +50,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { lateinit var scheduler: NodeSchedulerService lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var countDown: CountDownLatch lateinit var smmHasRemovedAllFlows: CountDownLatch @@ -76,9 +71,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { smmHasRemovedAllFlows = CountDownLatch(1) calls = 0 val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProps) val identityService = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) val kms = MockKeyManagementService(identityService, ALICE_KEY) @@ -120,7 +113,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } smmExecutor.shutdown() smmExecutor.awaitTermination(60, TimeUnit.SECONDS) - dataSource.close() + database.close() } class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant) : LinearState, SchedulableState { diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 23f32bee3d..4b6c6da748 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -11,7 +11,6 @@ import net.corda.core.node.services.linearHeadsOfType import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.ValidatingNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.DUMMY_NOTARY import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index d92bec8a22..6835133980 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -19,8 +19,8 @@ import net.corda.node.services.network.InMemoryNetworkMapCache import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.freeLocalHostAndPort import net.corda.testing.freePort import net.corda.testing.node.MOCK_VERSION_INFO @@ -28,13 +28,11 @@ import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.testNodeConfiguration import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.io.Closeable import java.net.ServerSocket import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit.MILLISECONDS @@ -52,8 +50,7 @@ class ArtemisMessagingTests { val identity = generateKeyPair() lateinit var config: NodeConfiguration - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var userService: RPCUserService lateinit var networkMapRegistrationFuture: ListenableFuture @@ -75,9 +72,7 @@ class ArtemisMessagingTests { baseDirectory = baseDirectory, myLegalName = ALICE.name) LogHelper.setLevel(PersistentUniquenessProvider::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) networkMapRegistrationFuture = Futures.immediateFuture(Unit) } @@ -87,7 +82,7 @@ class ArtemisMessagingTests { messagingServer?.stop() messagingClient = null messagingServer = null - dataSource.close() + database.close() LogHelper.reset(PersistentUniquenessProvider::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt index 948228acb8..a77ae0e1ef 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt @@ -5,7 +5,6 @@ import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.BOB -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Test diff --git a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt index c5af8af7ae..c4f412d429 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt @@ -4,7 +4,6 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.ServiceInfo import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.config.NodeConfiguration -import net.corda.node.utilities.transaction import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import java.math.BigInteger diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt index 92180fde67..9ddc8f8b07 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -6,16 +6,14 @@ import net.corda.testing.LogHelper import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.transactions.PersistentUniquenessProvider +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable internal fun CheckpointStorage.checkpoints(): List { val checkpoints = mutableListOf() @@ -28,21 +26,18 @@ internal fun CheckpointStorage.checkpoints(): List { class DBCheckpointStorageTests { lateinit var checkpointStorage: DBCheckpointStorage - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) newCheckpointStorage() } @After fun cleanUp() { - dataSource.close() + database.close() LogHelper.reset(PersistentUniquenessProvider::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index c98f62dfa3..352e0f52ba 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -11,35 +11,30 @@ import net.corda.core.transactions.WireTransaction import net.corda.testing.DUMMY_NOTARY import net.corda.testing.LogHelper import net.corda.node.services.transactions.PersistentUniquenessProvider +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.util.concurrent.TimeUnit import kotlin.test.assertEquals class DBTransactionStorageTests { - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var transactionStorage: DBTransactionStorage @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) newTransactionStorage() } @After fun cleanUp() { - dataSource.close() + database.close() LogHelper.reset(PersistentUniquenessProvider::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt index 2730cd9a63..ab29d87541 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt @@ -14,7 +14,6 @@ import net.corda.core.identity.Party import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction import net.corda.node.services.NotifyTransactionHandler -import net.corda.node.utilities.transaction import net.corda.testing.DUMMY_NOTARY import net.corda.testing.MEGA_CORP import net.corda.testing.node.MockNetwork diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index f50738f6c3..5c33a9707a 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -13,14 +13,12 @@ import net.corda.core.writeLines import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.persistence.schemas.requery.AttachmentEntity import net.corda.node.services.transactions.PersistentUniquenessProvider +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.node.makeTestDataSourceProperties -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.nio.charset.Charset import java.nio.file.FileAlreadyExistsException import java.nio.file.FileSystem @@ -35,8 +33,7 @@ import kotlin.test.assertNull class NodeAttachmentStorageTest { // Use an in memory file system for testing attachment storage. lateinit var fs: FileSystem - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence lateinit var dataSourceProperties: Properties lateinit var configuration: RequeryConfiguration @@ -45,9 +42,7 @@ class NodeAttachmentStorageTest { LogHelper.setLevel(PersistentUniquenessProvider::class) dataSourceProperties = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProperties) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProperties) configuration = RequeryConfiguration(dataSourceProperties) fs = Jimfs.newFileSystem(Configuration.unix()) @@ -55,7 +50,7 @@ class NodeAttachmentStorageTest { @After fun tearDown() { - dataSource.close() + database.close() } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 6cb9b6c47c..f06c5486a1 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -10,38 +10,33 @@ import net.corda.core.schemas.QueryableState import net.corda.testing.LogHelper import net.corda.node.services.api.SchemaService import net.corda.node.services.database.HibernateConfiguration +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.MEGA_CORP import net.corda.testing.node.makeTestDataSourceProperties import org.hibernate.annotations.Cascade import org.hibernate.annotations.CascadeType -import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.transactions.TransactionManager import org.junit.After import org.junit.Before import org.junit.Test import rx.subjects.PublishSubject -import java.io.Closeable import javax.persistence.* import kotlin.test.assertEquals class HibernateObserverTests { - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(HibernateObserver::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) } @After fun cleanUp() { - dataSource.close() + database.close() LogHelper.reset(HibernateObserver::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 88c298a34a..7526d81740 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -33,7 +33,6 @@ import net.corda.flows.CashPaymentFlow import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.persistence.checkpoints import net.corda.node.services.transactions.ValidatingNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.contracts.DummyState import net.corda.testing.node.InMemoryMessagingNetwork @@ -90,6 +89,7 @@ class FlowFrameworkTests { @After fun cleanUp() { mockNet.stopNodes() + sessionTransfers.clear() } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt index 7895f4defc..41c79f7373 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt @@ -10,15 +10,14 @@ import net.corda.core.getOrThrow import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.LogHelper import net.corda.node.services.network.NetworkMapService +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.freeLocalHostAndPort import net.corda.testing.node.makeTestDataSourceProperties -import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Transaction import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -27,17 +26,14 @@ class DistributedImmutableMapTests { data class Member(val client: CopycatClient, val server: CopycatServer) lateinit var cluster: List - lateinit var dataSource: Closeable lateinit var transaction: Transaction - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setup() { LogHelper.setLevel("-org.apache.activemq") LogHelper.setLevel(NetworkMapService::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) cluster = setUpCluster() } @@ -49,7 +45,7 @@ class DistributedImmutableMapTests { it.client.close() it.server.shutdown() } - dataSource.close() + database.close() } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 1ff8c103f3..8bfb983ee9 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -2,17 +2,15 @@ package net.corda.node.services.transactions import net.corda.core.crypto.SecureHash import net.corda.core.node.services.UniquenessException -import net.corda.testing.LogHelper +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction +import net.corda.testing.LogHelper import net.corda.testing.MEGA_CORP import net.corda.testing.generateStateRef import net.corda.testing.node.makeTestDataSourceProperties -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -20,20 +18,17 @@ class PersistentUniquenessProviderTests { val identity = MEGA_CORP val txID = SecureHash.randomSHA256() - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) } @After fun tearDown() { - dataSource.close() + database.close() LogHelper.reset(PersistentUniquenessProvider::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index c69fc61f36..0082810604 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -14,19 +14,17 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toNonEmptySet +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -38,16 +36,13 @@ import kotlin.test.assertTrue class NodeVaultServiceTest { lateinit var services: MockServices val vaultSvc: VaultService get() = services.vaultService - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProps) database.transaction { services = object : MockServices() { override val vaultService: VaultService = makeVaultService(dataSourceProps) @@ -65,7 +60,7 @@ class NodeVaultServiceTest { @After fun tearDown() { - dataSource.close() + database.close() LogHelper.reset(NodeVaultService::class) } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 7ac25f718e..1ea1148c9b 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -20,8 +20,8 @@ import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.toHexString import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.schemas.CashSchemaV1 import net.corda.schemas.CashSchemaV1.PersistentCashState import net.corda.schemas.CommercialPaperSchemaV1 @@ -35,10 +35,8 @@ import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.junit.* import org.junit.rules.ExpectedException -import java.io.Closeable import java.lang.Thread.sleep import java.math.BigInteger import java.security.KeyPair @@ -53,15 +51,12 @@ class VaultQueryTests { lateinit var services: MockServices val vaultSvc: VaultService get() = services.vaultService val vaultQuerySvc: VaultQueryService get() = services.vaultQueryService - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence @Before fun setUp() { val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProps) database.transaction { val customSchemas = setOf(CommercialPaperSchemaV1, DummyLinearStateSchemaV1) val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas)) @@ -82,7 +77,7 @@ class VaultQueryTests { @After fun tearDown() { - dataSource.close() + database.close() } /** @@ -91,16 +86,14 @@ class VaultQueryTests { @Ignore @Test fun createPersistentTestDb() { - val dataSourceAndDatabase = configureDatabase(makePersistentDataSourceProperties()) - val dataSource = dataSourceAndDatabase.first - val database = dataSourceAndDatabase.second + val database = configureDatabase(makePersistentDataSourceProperties()) setUpDb(database, 5000) - dataSource.close() + database.close() } - private fun setUpDb(_database: Database, delay: Long = 0) { + private fun setUpDb(_database: CordaPersistence, delay: Long = 0) { _database.transaction { diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 4ca05bb8e9..7f18cc6c81 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -13,23 +13,21 @@ import net.corda.core.node.services.VaultService import net.corda.core.node.services.consumedStates import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.configureDatabase import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.testing.LogHelper -import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_KEY import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Before import org.junit.Test -import java.io.Closeable import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -41,17 +39,14 @@ import kotlin.test.assertNull class VaultWithCashTest { lateinit var services: MockServices val vault: VaultService get() = services.vaultService - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence val notaryServices = MockServices(DUMMY_NOTARY_KEY) @Before fun setUp() { LogHelper.setLevel(VaultWithCashTest::class) val dataSourceProps = makeTestDataSourceProperties() - val dataSourceAndDatabase = configureDatabase(dataSourceProps) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(dataSourceProps) database.transaction { services = object : MockServices() { override val vaultService: VaultService = makeVaultService(dataSourceProps) @@ -70,7 +65,7 @@ class VaultWithCashTest { @After fun tearDown() { LogHelper.reset(VaultWithCashTest::class) - dataSource.close() + database.close() } @Test diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt index 8e0c666796..41c45e1a46 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt @@ -5,8 +5,6 @@ import net.corda.core.bufferUntilSubscribed import net.corda.core.tee import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.TransactionManager import org.junit.After import org.junit.Test import rx.Observable @@ -16,13 +14,13 @@ import java.util.* class ObservablesTests { - private fun isInDatabaseTransaction(): Boolean = (TransactionManager.currentOrNull() != null) + private fun isInDatabaseTransaction(): Boolean = (DatabaseTransactionManager.currentOrNull() != null) val toBeClosed = mutableListOf() - fun createDatabase(): Database { - val (closeable, database) = configureDatabase(makeTestDataSourceProperties()) - toBeClosed += closeable + fun createDatabase(): CordaPersistence { + val database = configureDatabase(makeTestDataSourceProperties()) + toBeClosed += database return database } @@ -167,7 +165,7 @@ class ObservablesTests { observableWithDbTx.first().subscribe { undelayedEvent.set(it to isInDatabaseTransaction()) } fun observeSecondEvent(event: Int, future: SettableFuture>) { - future.set(event to if (isInDatabaseTransaction()) StrandLocalTransactionManager.transactionId else null) + future.set(event to if (isInDatabaseTransaction()) DatabaseTransactionManager.transactionId else null) } observableWithDbTx.skip(1).first().subscribe { observeSecondEvent(it, delayedEventFromSecondObserver) } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 9d3035cef3..0c65e25f2f 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -17,19 +17,17 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.testing.LogHelper import net.corda.core.utilities.ProgressTracker import net.corda.irs.flows.RatesFixFlow +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.* import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import java.io.Closeable import java.math.BigDecimal import java.util.function.Predicate import kotlin.test.assertEquals @@ -50,8 +48,7 @@ class NodeInterestRatesTest { val DUMMY_CASH_ISSUER = Party(X500Name("CN=Cash issuer,O=R3,OU=corda,L=London,C=GB"), DUMMY_CASH_ISSUER_KEY.public) lateinit var oracle: NodeInterestRates.Oracle - lateinit var dataSource: Closeable - lateinit var database: Database + lateinit var database: CordaPersistence fun fixCmdFilter(elem: Any): Boolean { return when (elem) { @@ -64,9 +61,7 @@ class NodeInterestRatesTest { @Before fun setUp() { - val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties()) - dataSource = dataSourceAndDatabase.first - database = dataSourceAndDatabase.second + database = configureDatabase(makeTestDataSourceProperties()) database.transaction { oracle = NodeInterestRates.Oracle( MEGA_CORP, @@ -78,7 +73,7 @@ class NodeInterestRatesTest { @After fun tearDown() { - dataSource.close() + database.close() } @Test diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index c4148f403e..e12c56dc83 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -22,7 +22,6 @@ import net.corda.irs.contract.InterestRateSwap import net.corda.irs.flows.FixingFlow import net.corda.jackson.JacksonSupport import net.corda.node.services.identity.InMemoryIdentityService -import net.corda.node.utilities.transaction import net.corda.testing.node.InMemoryMessagingNetwork import rx.Observable import java.security.PublicKey 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 1548e83ef6..55b509c262 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 @@ -19,7 +19,6 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.SimpleNotaryService -import net.corda.node.utilities.transaction import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork import net.corda.testing.node.TestClock diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index b5546e9fc4..2c9601b48f 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -17,12 +17,11 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.trace import net.corda.node.services.messaging.* import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.JDBCHashSet -import net.corda.node.utilities.transaction import net.corda.testing.node.InMemoryMessagingNetwork.InMemoryMessaging import org.apache.activemq.artemis.utils.ReusableLatch import org.bouncycastle.asn1.x500.X500Name -import org.jetbrains.exposed.sql.Database import org.slf4j.LoggerFactory import rx.Observable import rx.subjects.PublishSubject @@ -108,7 +107,7 @@ class InMemoryMessagingNetwork( fun createNode(manuallyPumped: Boolean, executor: AffinityExecutor, advertisedServices: List, - database: Database): Pair> { + database: CordaPersistence): Pair> { check(counter >= 0) { "In memory network stopped: please recreate." } val builder = createNodeWithID(manuallyPumped, counter, executor, advertisedServices, database = database) as Builder counter++ @@ -130,7 +129,7 @@ class InMemoryMessagingNetwork( executor: AffinityExecutor, advertisedServices: List, description: X500Name = X509Utilities.getX509Name("In memory node $id","London","demo@r3.com",null), - database: Database) + database: CordaPersistence) : MessagingServiceBuilder { val peerHandle = PeerHandle(id, description) peersMapping[peerHandle.description] = peerHandle // Assume that the same name - the same entity in MockNetwork. @@ -187,7 +186,7 @@ class InMemoryMessagingNetwork( val id: PeerHandle, val serviceHandles: List, val executor: AffinityExecutor, - val database: Database) : MessagingServiceBuilder { + val database: CordaPersistence) : MessagingServiceBuilder { override fun start(): ListenableFuture { synchronized(this@InMemoryMessagingNetwork) { val node = InMemoryMessaging(manuallyPumped, id, executor, database) @@ -304,7 +303,7 @@ class InMemoryMessagingNetwork( inner class InMemoryMessaging(private val manuallyPumped: Boolean, private val peerHandle: PeerHandle, private val executor: AffinityExecutor, - private val database: Database) : SingletonSerializeAsToken(), MessagingService { + private val database: CordaPersistence) : SingletonSerializeAsToken(), MessagingService { inner class Handler(val topicSession: TopicSession, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index d006e380ac..580ff3072b 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -17,11 +17,9 @@ import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.network.InMemoryNetworkMapCache import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import net.corda.testing.freeLocalHostAndPort -import org.jetbrains.exposed.sql.Database -import java.io.Closeable import java.security.KeyPair import java.security.cert.X509Certificate import kotlin.concurrent.thread @@ -34,8 +32,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort rpcAddress: NetworkHostAndPort = freeLocalHostAndPort(), trustRoot: X509Certificate) : AutoCloseable { - private val databaseWithCloseable: Pair = configureDatabase(config.dataSourceProperties) - val database: Database get() = databaseWithCloseable.second + val database: CordaPersistence = configureDatabase(config.dataSourceProperties) val userService = RPCUserServiceImpl(config.rpcUsers) val monitoringService = MonitoringService(MetricRegistry()) val identity: KeyPair = generateKeyPair() @@ -72,7 +69,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort override fun close() { network.stop() broker.stop() - databaseWithCloseable.first.close() + database.close() executor.shutdownNow() } } From 729eaed3627bf7c9f2ba6db1664e6367c8d57b3e Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Fri, 14 Jul 2017 14:53:09 +0100 Subject: [PATCH 022/197] Update DemoBench packager scripts to fail when packaging fails. (#1023) --- tools/demobench/package-demobench-dmg.sh | 13 +++++++++---- tools/demobench/package-demobench-exe.bat | 10 ++++++++-- tools/demobench/package-demobench-rpm.sh | 12 ++++++++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tools/demobench/package-demobench-dmg.sh b/tools/demobench/package-demobench-dmg.sh index c72c6d3abe..ba0cddaa93 100755 --- a/tools/demobench/package-demobench-dmg.sh +++ b/tools/demobench/package-demobench-dmg.sh @@ -7,7 +7,12 @@ if [ -z "$JAVA_HOME" -o ! -x $JAVA_HOME/bin/java ]; then exit 1 fi -$DIRNAME/../../gradlew -PpackageType=dmg javapackage $* -echo -echo "Wrote installer to '$(find build/javapackage/bundles -type f)'" -echo +if ($DIRNAME/../../gradlew -PpackageType=dmg javapackage $*); then + echo + echo "Wrote installer to '$(find build/javapackage/bundles -type f)'" + echo +else + echo "Failed to create installer." + exit 1 +fi + diff --git a/tools/demobench/package-demobench-exe.bat b/tools/demobench/package-demobench-exe.bat index bb6c21191d..e33d048e5d 100644 --- a/tools/demobench/package-demobench-exe.bat +++ b/tools/demobench/package-demobench-exe.bat @@ -9,12 +9,18 @@ set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage +if "%ERRORLEVEL%" neq "0" goto Fail @echo -@echo "Wrote installer to %DIRNAME%\build\javapackage\bundles\" +@echo Wrote installer to %DIRNAME%\build\javapackage\bundles\ @echo goto end :NoJavaHome -@echo "Please set JAVA_HOME correctly" +@echo Please set JAVA_HOME correctly. +exit /b 1 + +:Fail +@echo Failed to write installer. +exit /b 1 :end diff --git a/tools/demobench/package-demobench-rpm.sh b/tools/demobench/package-demobench-rpm.sh index 3d14661206..5bcc9c2167 100755 --- a/tools/demobench/package-demobench-rpm.sh +++ b/tools/demobench/package-demobench-rpm.sh @@ -7,7 +7,11 @@ if [ -z "$JAVA_HOME" -o ! -x $JAVA_HOME/bin/java ]; then exit 1 fi -$DIRNAME/../../gradlew -PpackageType=rpm javapackage $* -echo -echo "Wrote installer to '$(find $DIRNAME/build/javapackage/bundles -type f)'" -echo +if ($DIRNAME/../../gradlew -PpackageType=rpm javapackage $*); then + echo + echo "Wrote installer to '$(find $DIRNAME/build/javapackage/bundles -type f)'" + echo +else + echo "Failed to create installer." + exit 1 +fi From ae01d658b6ace6de4774a343c0bb5b459b64c7f4 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 13 Jul 2017 17:09:15 +0100 Subject: [PATCH 023/197] Renamed FlowSessionException to UnexpectedFlowEndException --- .../kotlin/net/corda/core/flows/FlowException.kt | 2 +- .../net/corda/core/flows/ContractUpgradeFlowTest.kt | 4 ++-- .../services/statemachine/FlowStateMachineImpl.kt | 6 +++--- .../node/services/statemachine/SessionMessage.kt | 4 ++-- .../node/services/statemachine/FlowFrameworkTests.kt | 12 ++++++------ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt index e527f22c55..4e20320883 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt @@ -25,6 +25,6 @@ open class FlowException(message: String?, cause: Throwable?) : CordaException(m * that we were not expecting), or the other side had an internal error, or the other side terminated when we * were waiting for a response. */ -class FlowSessionException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) { +class UnexpectedFlowEndException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) { constructor(msg: String) : this(msg, null) } \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 889d392ff2..000e81afcc 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -71,7 +71,7 @@ class ContractUpgradeFlowTest { // The request is expected to be rejected because party B hasn't authorised the upgrade yet. val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture mockNet.runNetwork() - assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() } + assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() } // Party B authorise the contract state upgrade. b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef(0), DummyContractV2::class.java) @@ -141,7 +141,7 @@ class ContractUpgradeFlowTest { DummyContractV2::class.java).returnValue mockNet.runNetwork() - assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() } + assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() } // Party B authorise the contract state upgrade. rpcB.authoriseContractUpgrade(btx!!.tx.outRef(0), DummyContractV2::class.java) 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 14f4013ed4..c695424938 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 @@ -255,7 +255,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, state = FlowSessionState.Initiated(peerParty, sessionInitResponse.initiatedSessionId) } else { sessionInitResponse as SessionReject - throw FlowSessionException("Party ${state.sendToParty} rejected session request: ${sessionInitResponse.errorMessage}") + throw UnexpectedFlowEndException("Party ${state.sendToParty} rejected session request: ${sessionInitResponse.errorMessage}") } } @@ -357,7 +357,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, session.erroredEnd(message) } else { val expectedType = receiveRequest.userReceiveType?.name ?: receiveType.simpleName - throw FlowSessionException("Counterparty flow on ${session.state.sendToParty} has completed without " + + throw UnexpectedFlowEndException("Counterparty flow on ${session.state.sendToParty} has completed without " + "sending a $expectedType") } } else { @@ -371,7 +371,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, (end.errorResponse as java.lang.Throwable).fillInStackTrace() throw end.errorResponse } else { - throw FlowSessionException("Counterparty flow on ${state.sendToParty} had an internal error and has terminated") + throw UnexpectedFlowEndException("Counterparty flow on ${state.sendToParty} had an internal error and has terminated") } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt index 9f35c9eace..8b25d0b0b7 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt @@ -2,7 +2,7 @@ package net.corda.node.services.statemachine import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic -import net.corda.core.flows.FlowSessionException +import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.UntrustworthyData @@ -45,7 +45,7 @@ fun ReceivedSessionMessage.checkPayloadIs(type: Class): Untr if (type.isInstance(message.payload)) { return UntrustworthyData(type.cast(message.payload)) } else { - throw FlowSessionException("We were expecting a ${type.name} from $sender but we instead got a " + + throw UnexpectedFlowEndException("We were expecting a ${type.name} from $sender but we instead got a " + "${message.payload.javaClass.name} (${message.payload})") } } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 7526d81740..b4c28dc7c4 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -377,7 +377,7 @@ class FlowFrameworkTests { node2.registerFlowFactory(ReceiveFlow::class) { NoOpFlow() } val resultFuture = node1.services.startFlow(ReceiveFlow(node2.info.legalIdentity)).resultFuture mockNet.runNetwork() - assertThatExceptionOfType(FlowSessionException::class.java).isThrownBy { + assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { resultFuture.getOrThrow() }.withMessageContaining(String::class.java.name) // Make sure the exception message mentions the type the flow was expecting to receive } @@ -400,7 +400,7 @@ class FlowFrameworkTests { Notification.createOnError(erroringFlowFuture.get().exceptionThrown) ) - val receiveFlowException = assertFailsWith(FlowSessionException::class) { + val receiveFlowException = assertFailsWith(UnexpectedFlowEndException::class) { receiveFlowResult.getOrThrow() } assertThat(receiveFlowException.message).doesNotContain("evil bug!") @@ -486,7 +486,7 @@ class FlowFrameworkTests { node1Fiber.resultFuture.getOrThrow() } val node2ResultFuture = node2Fiber.getOrThrow().resultFuture - assertThatExceptionOfType(FlowSessionException::class.java).isThrownBy { + assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { node2ResultFuture.getOrThrow() } @@ -539,7 +539,7 @@ class FlowFrameworkTests { node2.registerFlowFactory(ReceiveFlow::class) { SendFlow(NonSerialisableData(1), it) } val result = node1.services.startFlow(ReceiveFlow(node2.info.legalIdentity)).resultFuture mockNet.runNetwork() - assertThatExceptionOfType(FlowSessionException::class.java).isThrownBy { + assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { result.getOrThrow() } } @@ -581,7 +581,7 @@ class FlowFrameworkTests { } val waiter = node2.services.startFlow(WaitingFlows.Waiter(stx, node1.info.legalIdentity)).resultFuture mockNet.runNetwork() - assertThatExceptionOfType(FlowSessionException::class.java).isThrownBy { + assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { waiter.getOrThrow() } } @@ -643,7 +643,7 @@ class FlowFrameworkTests { track = false) val result = node1.services.startFlow(UpgradedFlow(node2.info.legalIdentity)).resultFuture mockNet.runNetwork() - assertThatExceptionOfType(FlowSessionException::class.java).isThrownBy { + assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { result.getOrThrow() }.withMessageContaining("Version") } From 0bfe1de3ba1320227047538c78a7c64bb8f88ad5 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 14 Jul 2017 15:16:42 +0100 Subject: [PATCH 024/197] Review comments * Rename some functions to more descriptive names * Remove some egregious whitespace * Revert some changes that were a dangling holdover from an aborted refactoring - put it back how it was --- .../carpenter/AMQPSchemaExtensions.kt | 4 ++-- .../serialization/carpenter/ClassCarpenter.kt | 3 +-- .../core/serialization/carpenter/Schema.kt | 18 --------------- .../carpenter/ClassCarpenterTest.kt | 4 ++-- .../carpenter/ClassCarpenterTestUtils.kt | 2 +- ...berCompositeSchemaToClassCarpenterTests.kt | 20 ++++++++--------- .../InheritanceSchemaToClassCarpenterTests.kt | 22 ++++++++----------- 7 files changed, 25 insertions(+), 48 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt index 954cf490b5..b30b2b3c4a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/AMQPSchemaExtensions.kt @@ -19,7 +19,7 @@ fun AMQPSchema.carpenterSchema( /** * if we can load the class then we MUST know about all of it's composite elements */ -private fun CompositeType.validateKnown ( +private fun CompositeType.validatePropertyTypes( classLoaders: List = listOf (ClassLoader.getSystemClassLoader())){ fields.forEach { if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type) @@ -44,7 +44,7 @@ fun CompositeType.carpenterSchema( carpenterSchemas : CarpenterSchemas, force : Boolean = false) { if (classLoaders.exists(name)) { - validateKnown(classLoaders) + validatePropertyTypes(classLoaders) if (!force) return } diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index a7facc0c69..03361446d2 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -201,8 +201,7 @@ class ClassCarpenter { private fun ClassWriter.generateGetters(schema: Schema) { for ((name, type) in schema.fields) { - val opcodes = ACC_PUBLIC - with(visitMethod(opcodes, "get" + name.capitalize(), "()" + type.descriptor, null, null)) { + with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + type.descriptor, null, null)) { type.addNullabilityAnnotation(this) visitCode() visitVarInsn(ALOAD, 0) // Load 'this' diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt index f15eb1d431..5a8d4a9969 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -8,8 +8,6 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Type import java.util.LinkedHashMap -/**********************************************************************************************************************/ - /** * A Schema represents a desired class. */ @@ -36,8 +34,6 @@ abstract class Schema( get() = name.replace(".", "/") } -/**********************************************************************************************************************/ - class ClassSchema( name: String, fields: Map, @@ -45,8 +41,6 @@ class ClassSchema( interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) -/**********************************************************************************************************************/ - class InterfaceSchema( name: String, fields: Map, @@ -54,8 +48,6 @@ class InterfaceSchema( interfaces: List> = emptyList() ) : Schema (name, fields, superclass, interfaces) -/**********************************************************************************************************************/ - object CarpenterSchemaFactory { fun newInstance ( name: String, @@ -68,8 +60,6 @@ object CarpenterSchemaFactory { else ClassSchema (name, fields, superclass, interfaces) } -/**********************************************************************************************************************/ - abstract class Field(val field: Class) { companion object { const val unsetName = "Unset" @@ -107,8 +97,6 @@ abstract class Field(val field: Class) { abstract fun nullTest(mv: MethodVisitor, slot: Int) } -/**********************************************************************************************************************/ - class NonNullableField(field: Class) : Field(field) { override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;" @@ -136,8 +124,6 @@ class NonNullableField(field: Class) : Field(field) { } } -/**********************************************************************************************************************/ - class NullableField(field: Class) : Field(field) { override val nullabilityAnnotation = "Ljavax/annotation/Nullable;" @@ -157,12 +143,8 @@ class NullableField(field: Class) : Field(field) { } } -/**********************************************************************************************************************/ - object FieldFactory { fun newInstance (mandatory: Boolean, name: String, field: Class) = if (mandatory) NonNullableField (name, field) else NullableField (name, field) } - -/**********************************************************************************************************************/ diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index d0d11f7fd8..652a91f9bd 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -483,9 +483,9 @@ class ClassCarpenterTest { @Test fun beanTest() { - val schema = ClassCarpenter.ClassSchema( + val schema = ClassSchema( "pantsPantsPants", - mapOf("a" to ClassCarpenter.NonNullableField(Integer::class.java))) + mapOf("a" to NonNullableField(Integer::class.java))) val clazz = cc.build(schema) val descriptors = Introspector.getBeanInfo(clazz).propertyDescriptors diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt index c4a6cf2119..ac093a70e1 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -13,7 +13,7 @@ fun mangleName(name: String) = "${name}__carpenter" * given a list of class names work through the amqp envelope schema and alter any that * match in the fashion defined above */ -fun Schema.mangleName(names: List): Schema { +fun Schema.mangleNames(names: List): Schema { val newTypes: MutableList = mutableListOf() for (type in types) { diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index a94302b1bb..13deba5c5f 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -89,7 +89,7 @@ class CompositeMembers : AmqpCarpenterBase() { val b = B(A(testA), testB) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b)) - val amqpSchema = obj.envelope.schema.mangleName(listOf (classTestName ("A"))) + val amqpSchema = obj.envelope.schema.mangleNames(listOf (classTestName ("A"))) assert(obj.obj is B) @@ -112,7 +112,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() assertEquals(1, carpenterSchema.size) @@ -140,7 +140,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is B) - val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) val carpenterSchema = amqpSchema.carpenterSchema() // just verify we're in the expected initial state, A is carpentable, B is not because @@ -180,7 +180,7 @@ class CompositeMembers : AmqpCarpenterBase() { @Test(expected = UncarpentableException::class) @Suppress("UNUSED") - fun nestedIsUnkownInherited() { + fun nestedIsUnknownInherited() { val testA = 10 val testB = 20 val testC = 30 @@ -199,7 +199,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @@ -225,7 +225,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val amqpSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) amqpSchema.carpenterSchema() } @@ -251,7 +251,7 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is C) - val carpenterSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) TestMetaCarpenter(carpenterSchema.carpenterSchema()) } @@ -273,13 +273,13 @@ class CompositeMembers : AmqpCarpenterBase() { assert(obj.obj is B) - val carpenterSchema = obj.envelope.schema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B"))) val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema()) assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size) - assertEquals(mangleName(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name) + assertEquals(mangleNames(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name) assertEquals(1, metaCarpenter.schemas.dependencies.size) - assertTrue(mangleName(classTestName("A")) in metaCarpenter.schemas.dependencies) + assertTrue(mangleNames(classTestName("A")) in metaCarpenter.schemas.dependencies) } */ } diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt index eedd4a0937..59051e919c 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -1,7 +1,6 @@ package net.corda.core.serialization.carpenter import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.carpenter.MetaCarpenter import net.corda.core.serialization.carpenter.test.* import net.corda.core.serialization.amqp.* @@ -54,7 +53,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { // it's extremely unlikely we'd need to carpent any classes assertEquals(0, l1.size) - val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"))) val l2 = mangleSchema.carpenterSchema() assertEquals(1, l2.size) @@ -95,9 +94,8 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(0, l1.size) - val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"))) val aName = mangleName(classTestName("A")) - val l2 = mangleSchema.carpenterSchema() assertEquals(1, l2.size) @@ -111,7 +109,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0]) val aBuilder = ClassCarpenter().build(aSchema) - val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ) val j = objJ as J @@ -145,7 +142,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { // pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus // needs some carpentry - val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"))) val l2 = mangleSchema.carpenterSchema() val aName = mangleName(classTestName("A")) @@ -191,7 +188,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { // it's extremely unlikely we'd need to carpent any classes assertEquals(0, l1.size) - val mangleSchema = serSchema.mangleName(listOf(classTestName("A"))) + val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"))) val l2 = mangleSchema.carpenterSchema() val aName = mangleName(classTestName("A")) @@ -239,7 +236,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { // * class B's interface (class IIII) assertEquals(4, serSchema.types.size) - val mangleSchema = serSchema.mangleName(listOf(classTestName("A"), classTestName("B"))) + val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"), classTestName("B"))) val cSchema = mangleSchema.carpenterSchema() val aName = mangleName(classTestName("A")) val bName = mangleName(classTestName("B")) @@ -296,7 +293,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { assertEquals(4, serSchema.types.size) // ignore the return as we expect this to throw - serSchema.mangleName(listOf( + serSchema.mangleNames(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema() } @@ -317,7 +314,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { // * class A's interface (class I) assertEquals(2, serSchema.types.size) - val amqpSchema = serSchema.mangleName(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) + val amqpSchema = serSchema.mangleNames(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I")) val aName = mangleName(classTestName("A")) val iName = mangleName("${this.javaClass.`package`.name}.I") val carpenterSchema = amqpSchema.carpenterSchema() @@ -359,7 +356,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.mangleName(listOf( + val amqpSchema = obj.envelope.schema.mangleNames(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.II")) @@ -411,7 +408,7 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val a = A(testI, testIII) val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a)) - val amqpSchema = obj.envelope.schema.mangleName(listOf( + val amqpSchema = obj.envelope.schema.mangleNames(listOf( classTestName("A"), "${this.javaClass.`package`.name}.I", "${this.javaClass.`package`.name}.III")) @@ -419,7 +416,6 @@ class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() { val aName = mangleName(classTestName("A")) val iName = mangleName("${this.javaClass.`package`.name}.I") val iiiName = mangleName("${this.javaClass.`package`.name}.III") - val carpenterSchema = amqpSchema.carpenterSchema() // Since A depends on III and III extends I we will have to construct them From 42f217f2125c73cc933697d3a723884866509c16 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 14 Jul 2017 16:54:39 +0100 Subject: [PATCH 025/197] Add extension hooks to FinalityFlow Add extension hooks to FinalityFlow to support different behaviours depending on whether the node knows who all of the parties in a transaction are. --- .../net/corda/core/flows/FinalityFlow.kt | 85 +++++++++++++------ .../corda/core/flows/ManualFinalityFlow.kt | 20 +++++ .../net/corda/core/flows/FinalityFlowTests.kt | 56 ++++++++++++ .../core/flows/ManualFinalityFlowTests.kt | 64 ++++++++++++++ docs/source/changelog.rst | 4 + docs/source/release-notes.rst | 3 + 6 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt create mode 100644 core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt create mode 100644 core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index 650ba3e25d..382da66211 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.isFulfilledBy +import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.transactions.LedgerTransaction @@ -32,9 +33,10 @@ import net.corda.core.utilities.toNonEmptySet * @param transactions What to commit. * @param extraRecipients A list of additional participants to inform of the transaction. */ -class FinalityFlow(val transactions: Iterable, +open class FinalityFlow(val transactions: Iterable, val extraRecipients: Set, override val progressTracker: ProgressTracker) : FlowLogic>() { + val extraParticipants: Set = extraRecipients.map { it -> Participant(it, it) }.toSet() constructor(transaction: SignedTransaction, extraParticipants: Set) : this(listOf(transaction), extraParticipants, tracker()) constructor(transaction: SignedTransaction) : this(listOf(transaction), emptySet(), tracker()) constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(listOf(transaction), emptySet(), progressTracker) @@ -50,6 +52,9 @@ class FinalityFlow(val transactions: Iterable, fun tracker() = ProgressTracker(NOTARISING, BROADCASTING) } + open protected val me + get() = serviceHub.myInfo.legalIdentity + @Suspendable @Throws(NotaryException::class) override fun call(): List { @@ -59,34 +64,43 @@ class FinalityFlow(val transactions: Iterable, // Lookup the resolved transactions and use them to map each signed transaction to the list of participants. // Then send to the notary if needed, record locally and distribute. progressTracker.currentStep = NOTARISING - val notarisedTxns = notariseAndRecord(lookupParties(resolveDependenciesOf(transactions))) + val notarisedTxns: List>> = resolveDependenciesOf(transactions) + .map { (stx, ltx) -> Pair(notariseAndRecord(stx), lookupParties(ltx)) } // Each transaction has its own set of recipients, but extra recipients get them all. progressTracker.currentStep = BROADCASTING - val me = serviceHub.myInfo.legalIdentity for ((stx, parties) in notarisedTxns) { - val participants = parties + extraRecipients - me - if (participants.isNotEmpty()) { - subFlow(BroadcastTransactionFlow(stx, participants.toNonEmptySet())) - } + broadcastTransaction(stx, (parties + extraParticipants).filter { it.wellKnown != me }) } return notarisedTxns.map { it.first } } - // TODO: API: Make some of these protected? + /** + * Broadcast a transaction to the participants. By default calls [BroadcastTransactionFlow], however can be + * overridden for more complex transaction delivery protocols (for example where not all parties know each other). + * This implementation will filter out any participants for who there is no well known identity. + * + * @param participants the participants to send the transaction to. This is expected to include extra participants + * and exclude the local node. + */ + @Suspendable + open protected fun broadcastTransaction(stx: SignedTransaction, participants: Iterable) { + val wellKnownParticipants = participants.map { it.wellKnown }.filterNotNull() + if (wellKnownParticipants.isNotEmpty()) { + subFlow(BroadcastTransactionFlow(stx, wellKnownParticipants.toNonEmptySet())) + } + } @Suspendable - private fun notariseAndRecord(stxnsAndParties: List>>): List>> { - return stxnsAndParties.map { (stx, parties) -> - val notarised = if (needsNotarySignature(stx)) { - val notarySignatures = subFlow(NotaryFlow.Client(stx)) - stx + notarySignatures - } else { - stx - } - serviceHub.recordTransactions(notarised) - Pair(notarised, parties) + private fun notariseAndRecord(stx: SignedTransaction): SignedTransaction { + val notarised = if (needsNotarySignature(stx)) { + val notarySignatures = subFlow(NotaryFlow.Client(stx)) + stx + notarySignatures + } else { + stx } + serviceHub.recordTransactions(notarised) + return notarised } private fun needsNotarySignature(stx: SignedTransaction): Boolean { @@ -102,14 +116,31 @@ class FinalityFlow(val transactions: Iterable, return !(notaryKey?.isFulfilledBy(signers) ?: false) } - private fun lookupParties(ltxns: List>): List>> { - return ltxns.map { (stx, ltx) -> - // Calculate who is meant to see the results based on the participants involved. - val keys = ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants } - // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them count as a reason to fail? - val parties = keys.mapNotNull { serviceHub.identityService.partyFromAnonymous(it) }.toSet() - Pair(stx, parties) - } + /** + * Resolve the parties involved in a transaction. + * + * @return the set of participants and their resolved well known identities (where known). + */ + open protected fun lookupParties(ltx: LedgerTransaction): Set { + // Calculate who is meant to see the results based on the participants involved. + return extractParticipants(ltx) + .map(this::partyFromAnonymous) + .toSet() + } + + /** + * Helper function to extract all participants from a ledger transaction. Intended to help implement [lookupParties] + * overriding functions. + */ + protected fun extractParticipants(ltx: LedgerTransaction): List { + return ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants } + } + + /** + * Helper function which wraps [IdentityService.partyFromAnonymous] so it can be called as a lambda function. + */ + protected fun partyFromAnonymous(anon: AbstractParty): Participant { + return Participant(anon, serviceHub.identityService.partyFromAnonymous(anon)) } private fun resolveDependenciesOf(signedTransactions: Iterable): List> { @@ -134,4 +165,6 @@ class FinalityFlow(val transactions: Iterable, stx to ltx } } + + data class Participant(val participant: AbstractParty, val wellKnown: Party?) } diff --git a/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt new file mode 100644 index 0000000000..f91b38b85f --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt @@ -0,0 +1,20 @@ +package net.corda.core.flows + +import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.ProgressTracker + +/** + * Alternative finality flow which only does not attempt to take participants from the transaction, but instead all + * participating parties must be provided manually. + * + * @param transactions What to commit. + * @param extraRecipients A list of additional participants to inform of the transaction. + */ +class ManualFinalityFlow(transactions: Iterable, + recipients: Set, + progressTracker: ProgressTracker) : FinalityFlow(transactions, recipients, progressTracker) { + constructor(transaction: SignedTransaction, extraParticipants: Set) : this(listOf(transaction), extraParticipants, tracker()) + override fun lookupParties(ltx: LedgerTransaction): Set = emptySet() +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt new file mode 100644 index 0000000000..d632137c11 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -0,0 +1,56 @@ +package net.corda.core.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.Amount +import net.corda.core.contracts.GBP +import net.corda.core.contracts.Issued +import net.corda.core.contracts.TransactionType +import net.corda.core.getOrThrow +import net.corda.core.identity.Party +import net.corda.core.transactions.TransactionBuilder +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockServices +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals + +class FinalityFlowTests { + lateinit var mockNet: MockNetwork + lateinit var nodeA: MockNetwork.MockNode + lateinit var nodeB: MockNetwork.MockNode + lateinit var notary: Party + val services = MockServices() + + @Before + fun setup() { + mockNet = MockNetwork() + val nodes = mockNet.createSomeNodes(2) + nodeA = nodes.partyNodes[0] + nodeB = nodes.partyNodes[1] + notary = nodes.notaryNode.info.notaryIdentity + mockNet.runNetwork() + } + + @After + fun tearDown() { + mockNet.stopNodes() + } + + @Test + fun `finalise a simple transaction`() { + val amount = Amount(1000, Issued(nodeA.info.legalIdentity.ref(0), GBP)) + val builder = TransactionBuilder(TransactionType.General, notary) + Cash().generateIssue(builder, amount, nodeB.info.legalIdentity, notary) + val stx = nodeA.services.signInitialTransaction(builder) + val flow = nodeA.services.startFlow(FinalityFlow(stx)) + mockNet.runNetwork() + val result = flow.resultFuture.getOrThrow() + val notarisedTx = result.single() + notarisedTx.verifySignatures() + val transactionSeenByB = nodeB.services.database.transaction { + nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) + } + assertEquals(notarisedTx, transactionSeenByB) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt new file mode 100644 index 0000000000..b96c9585d2 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt @@ -0,0 +1,64 @@ +package net.corda.core.flows + +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.Amount +import net.corda.core.contracts.GBP +import net.corda.core.contracts.Issued +import net.corda.core.contracts.TransactionType +import net.corda.core.getOrThrow +import net.corda.core.identity.Party +import net.corda.core.transactions.TransactionBuilder +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockServices +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ManualFinalityFlowTests { + lateinit var mockNet: MockNetwork + lateinit var nodeA: MockNetwork.MockNode + lateinit var nodeB: MockNetwork.MockNode + lateinit var nodeC: MockNetwork.MockNode + lateinit var notary: Party + val services = MockServices() + + @Before + fun setup() { + mockNet = MockNetwork() + val nodes = mockNet.createSomeNodes(3) + nodeA = nodes.partyNodes[0] + nodeB = nodes.partyNodes[1] + nodeC = nodes.partyNodes[2] + notary = nodes.notaryNode.info.notaryIdentity + mockNet.runNetwork() + } + + @After + fun tearDown() { + mockNet.stopNodes() + } + + @Test + fun `finalise a simple transaction`() { + val amount = Amount(1000, Issued(nodeA.info.legalIdentity.ref(0), GBP)) + val builder = TransactionBuilder(TransactionType.General, notary) + Cash().generateIssue(builder, amount, nodeB.info.legalIdentity, notary) + val stx = nodeA.services.signInitialTransaction(builder) + val flow = nodeA.services.startFlow(ManualFinalityFlow(stx, setOf(nodeC.info.legalIdentity))) + mockNet.runNetwork() + val result = flow.resultFuture.getOrThrow() + val notarisedTx = result.single() + notarisedTx.verifySignatures() + // We override the participants, so node C will get a copy despite not being involved, and B won't + val transactionSeenByB = nodeB.services.database.transaction { + nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) + } + assertNull(transactionSeenByB) + val transactionSeenByC = nodeC.services.database.transaction { + nodeC.services.validatedTransactions.getTransaction(notarisedTx.id) + } + assertEquals(notarisedTx, transactionSeenByC) + } +} \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 97270867b4..346c1f4046 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -34,6 +34,10 @@ UNRELEASED * Moved the core flows previously found in ``net.corda.flows`` into ``net.corda.core.flows``. This is so that all packages in the ``core`` module begin with ``net.corda.core``. +* ``FinalityFlow`` now has can be subclassed, and the ``broadcastTransaction`` and ``lookupParties`` function can be + overriden in order to handle cases where no single transaction participant is aware of all parties, and therefore + the transaction must be relayed between participants rather than sent from a single node. + Milestone 13 ------------ diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index bd89729765..6b5ab654b4 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -6,6 +6,9 @@ Here are release notes for each snapshot release from M9 onwards. Unreleased ---------- +The transaction finalisation flow (``FinalityFlow``) has had hooks for alternative implementations, for example in +scenarios where no single participant in a transaction is aware of the well known identities of all parties. + Milestone 13 ------------ From 2cbdb719c6c28992345aab643866c23930e94cc9 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 12 Jul 2017 10:37:36 +0200 Subject: [PATCH 026/197] Minor: move some test functions for making zips out of the global namespace. --- .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 4 +- core/src/main/kotlin/net/corda/core/Utils.kt | 54 ++++++++++--------- .../corda/attachmentdemo/AttachmentDemo.kt | 5 +- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 182ca10eea..4656c23574 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -5,6 +5,7 @@ import com.google.common.hash.HashingInputStream import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.notUsed import net.corda.contracts.asset.Cash +import net.corda.core.InputStreamAndHash import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.POUNDS import net.corda.core.contracts.SWISS_FRANCS @@ -16,7 +17,6 @@ import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* import net.corda.core.seconds import net.corda.core.utilities.OpaqueBytes -import net.corda.core.sizedInputStreamAndHash import net.corda.core.utilities.loggerFor import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow @@ -78,7 +78,7 @@ class StandaloneCordaRPClientTest { @Test fun `test attachments`() { - val attachment = sizedInputStreamAndHash(attachmentSize) + val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) assertFalse(rpcProxy.attachmentExists(attachment.sha256)) val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) } assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 24d8247df0..a169623d80 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -291,37 +291,39 @@ fun extractZipFile(inputStream: InputStream, toDirectory: Path) { } } -/** - * Get a valid InputStream from an in-memory zip as required for tests. - * Note that a slightly bigger than numOfExpectedBytes size is expected. - */ -@Throws(IllegalArgumentException::class) -fun sizedInputStreamAndHash(numOfExpectedBytes: Int): InputStreamAndHash { - if (numOfExpectedBytes <= 0) throw IllegalArgumentException("A positive number of numOfExpectedBytes is required.") - val baos = ByteArrayOutputStream() - ZipOutputStream(baos).use({ zos -> - val arraySize = 1024 - val bytes = ByteArray(arraySize) - val n = (numOfExpectedBytes - 1) / arraySize + 1 // same as Math.ceil(numOfExpectedBytes/arraySize). - zos.setLevel(Deflater.NO_COMPRESSION) - zos.putNextEntry(ZipEntry("z")) - for (i in 0 until n) { - zos.write(bytes, 0, arraySize) - } - zos.closeEntry() - }) - return getInputStreamAndHashFromOutputStream(baos) -} - /** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */ -fun getInputStreamAndHashFromOutputStream(baos: ByteArrayOutputStream): InputStreamAndHash { - // TODO: Consider converting OutputStream to InputStream without creating a ByteArray, probably using piped streams. +fun ByteArrayOutputStream.getInputStreamAndHash(baos: ByteArrayOutputStream): InputStreamAndHash { val bytes = baos.toByteArray() - // TODO: Consider calculating sha256 on the fly using a DigestInputStream. return InputStreamAndHash(ByteArrayInputStream(bytes), bytes.sha256()) } -data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHash.SHA256) +data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHash.SHA256) { + companion object { + /** + * Get a valid InputStream from an in-memory zip as required for some tests. The zip consists of a single file + * called "z" that contains the given content byte repeated the given number of times. + * Note that a slightly bigger than numOfExpectedBytes size is expected. + */ + @Throws(IllegalArgumentException::class) + @JvmStatic + fun createInMemoryTestZip(numOfExpectedBytes: Int, content: Byte): InputStreamAndHash { + require(numOfExpectedBytes > 0) + val baos = ByteArrayOutputStream() + ZipOutputStream(baos).use { zos -> + val arraySize = 1024 + val bytes = ByteArray(arraySize) { content } + val n = (numOfExpectedBytes - 1) / arraySize + 1 // same as Math.ceil(numOfExpectedBytes/arraySize). + zos.setLevel(Deflater.NO_COMPRESSION) + zos.putNextEntry(ZipEntry("z")) + for (i in 0 until n) { + zos.write(bytes, 0, arraySize) + } + zos.closeEntry() + } + return baos.getInputStreamAndHash(baos) + } + } +} // TODO: Generic csv printing utility for clases. diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 7484959cfd..88ac8380cc 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -3,6 +3,8 @@ package net.corda.attachmentdemo import co.paralleluniverse.fibers.Suspendable import joptsimple.OptionParser import net.corda.client.rpc.CordaRPCClient +import net.corda.core.InputStreamAndHash +import net.corda.core.InputStreamAndHash.Companion.createInMemoryTestZip import net.corda.core.contracts.Contract import net.corda.core.contracts.ContractState import net.corda.core.contracts.TransactionForContract @@ -16,7 +18,6 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startTrackedFlow -import net.corda.core.sizedInputStreamAndHash import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.Emoji import net.corda.core.utilities.NetworkHostAndPort @@ -72,7 +73,7 @@ fun main(args: Array) { /** An in memory test zip attachment of at least numOfClearBytes size, will be used. */ fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K. - val (inputStream, hash) = sizedInputStreamAndHash(numOfClearBytes) + val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0) sender(rpc, inputStream, hash) } From 2fb187ec75bd42309ce85b250bdd4960e4f6334a Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 12 Jul 2017 10:43:00 +0200 Subject: [PATCH 027/197] Docs: Add a note to the troubleshooting page about the -javaagent flag. --- docs/source/troubleshooting.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst index affa9accb5..434cdbe579 100644 --- a/docs/source/troubleshooting.rst +++ b/docs/source/troubleshooting.rst @@ -27,12 +27,23 @@ If you have APT installed and OpenJFX is part of your Unix distribution's packag *************************************************************************** Some of the unit tests, and our serialization framework in general, rely on the constructor parameter names being visible -to Java reflection. Make sure you have specified the `-parameters` option to the Java compiler. We attempt to set this globally +to Java reflection. Make sure you have specified the ``-parameters`` option to the Java compiler. We attempt to set this globally for gradle and IntelliJ, but it's possible this option is not present in your environment for some reason. IDEA issues ----------- +Fiber classes not instrumented +****************************** + +If you run JUnit tests that use flows then IntelliJ will use a default JVM command line of just ``-ea``, which is +insufficient. You will need to open the run config and change it to read ``-ea -javaagent:lib/quasar.jar`` and make +sure the working directory is set to the root of the Corda repository. Alternatively, make sure you have the quasar.jar +file in your application source tree and set the paths appropriately so the JVM can find it. + +You can edit the default JUnit run config in the run configs window to avoid having to do this every time you pick a +new test to run. + No source files are present *************************** From e83deb78ec6eb45afc2e3da4b3c8c9f71a095f64 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 12 Jul 2017 12:18:17 +0200 Subject: [PATCH 028/197] Some small extensions to TestConstants to make setting up a driver-based test with them easier and less boilerplatey. --- .../kotlin/net/corda/testing/TestConstants.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index d2ebdae030..6e8a0dade5 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -2,10 +2,16 @@ package net.corda.testing +import com.google.common.util.concurrent.Futures import net.corda.core.crypto.* import net.corda.core.crypto.testing.DummyPublicKey import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.node.services.ServiceInfo +import net.corda.node.services.transactions.ValidatingNotaryService +import net.corda.nodeapi.User +import net.corda.testing.driver.DriverDSLExposedInterface import org.bouncycastle.asn1.x500.X500Name import java.math.BigInteger import java.security.KeyPair @@ -67,3 +73,50 @@ val DUMMY_CA: CertificateAndKeyPair by lazy { CertificateAndKeyPair(cert, DUMMY_CA_KEY) } +// +// Extensions to the Driver DSL to auto-manufacture nodes by name. +// + +/** + * A simple wrapper for objects provided by the integration test driver DSL. The fields are lazy so + * node construction won't start until you access the members. You can get one of these from the + * [alice], [bob] and [aliceBobAndNotary] functions. + */ +class PredefinedTestNode internal constructor(party: Party, driver: DriverDSLExposedInterface, services: Set) { + val rpcUsers = listOf(User("admin", "admin", setOf("ALL"))) // TODO: Randomize? + val nodeFuture by lazy { driver.startNode(party.name, rpcUsers = rpcUsers, advertisedServices = services) } + val node by lazy { nodeFuture.get()!! } + val rpc by lazy { node.rpcClientToNode() } + + fun useRPC(block: (CordaRPCOps) -> R) = rpc.use(rpcUsers[0].username, rpcUsers[0].password) { block(it.proxy) } +} + +// TODO: Probably we should inject the above keys through the driver to make the nodes use it, rather than have the warnings below. + +/** + * Returns a plain, entirely stock node pre-configured with the [ALICE] identity. Note that a random key will be generated + * for it: you won't have [ALICE_KEY]. + */ +fun DriverDSLExposedInterface.alice(): PredefinedTestNode = PredefinedTestNode(ALICE, this, emptySet()) +/** + * Returns a plain, entirely stock node pre-configured with the [BOB] identity. Note that a random key will be generated + * for it: you won't have [BOB_KEY]. + */ +fun DriverDSLExposedInterface.bob(): PredefinedTestNode = PredefinedTestNode(BOB, this, emptySet()) +/** + * Returns a plain single node notary pre-configured with the [DUMMY_NOTARY] identity. Note that a random key will be generated + * for it: you won't have [DUMMY_NOTARY_KEY]. + */ +fun DriverDSLExposedInterface.notary(): PredefinedTestNode = PredefinedTestNode(DUMMY_NOTARY, this, setOf(ServiceInfo(ValidatingNotaryService.type))) + +/** + * Returns plain, entirely stock nodes pre-configured with the [ALICE], [BOB] and [DUMMY_NOTARY] X.500 names in that + * order. They have been started up in parallel and are now ready to use. + */ +fun DriverDSLExposedInterface.aliceBobAndNotary(): List { + val alice = alice() + val bob = bob() + val notary = notary() + Futures.allAsList(alice.nodeFuture, bob.nodeFuture, notary.nodeFuture).get() + return listOf(alice, bob, notary) +} \ No newline at end of file From a56540a3d63fd327a99560d65ea8dfb25a8cafa7 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 12 Jul 2017 12:18:49 +0200 Subject: [PATCH 029/197] Fix support for large attachments by de-batching tx/attachment fetch. This is a workaround until the upstream Artemis large message streaming bugs are fixed. --- .../corda/core/flows/FetchAttachmentsFlow.kt | 3 +- .../net/corda/core/flows/FetchDataFlow.kt | 24 ++++-- .../corda/core/flows/FetchTransactionsFlow.kt | 2 +- .../statemachine/LargeTransactionsTest.kt | 73 +++++++++++++++++++ .../corda/node/services/CoreFlowHandlers.kt | 10 ++- 5 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt diff --git a/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt index 1eaeff6e56..cdf9f632fb 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchAttachmentsFlow.kt @@ -15,8 +15,7 @@ import net.corda.core.serialization.SerializeAsTokenContext */ @InitiatingFlow class FetchAttachmentsFlow(requests: Set, - otherSide: Party) : FetchDataFlow(requests, otherSide) { - + otherSide: Party) : FetchDataFlow(requests, otherSide, ByteArray::class.java) { override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid) override fun convert(wire: ByteArray): Attachment = FetchedAttachment({ wire }) diff --git a/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt index 28c5fdceb1..bd843fbce0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchDataFlow.kt @@ -27,9 +27,10 @@ import java.util.* * @param T The ultimate type of the data being fetched. * @param W The wire type of the data being fetched, for when it isn't the same as the ultimate type. */ -abstract class FetchDataFlow( +abstract class FetchDataFlow( protected val requests: Set, - protected val otherSide: Party) : FlowLogic>() { + protected val otherSide: Party, + protected val wrapperType: Class) : FlowLogic>() { @CordaSerializable class DownloadedVsRequestedDataMismatch(val requested: SecureHash, val got: SecureHash) : IllegalArgumentException() @@ -54,12 +55,25 @@ abstract class FetchDataFlow( return if (toFetch.isEmpty()) { Result(fromDisk, emptyList()) } else { - logger.trace("Requesting ${toFetch.size} dependency(s) for verification") + logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSide.name}") // TODO: Support "large message" response streaming so response sizes are not limited by RAM. - val maybeItems = sendAndReceive>(otherSide, Request(toFetch)) + // We can then switch to requesting items in large batches to minimise the latency penalty. + // This is blocked by bugs ARTEMIS-1278 and ARTEMIS-1279. For now we limit attachments and txns to 10mb each + // and don't request items in batch, which is a performance loss, but works around the issue. We have + // configured Artemis to not fragment messages up to 10mb so we can send 10mb messages without problems. + // Above that, we start losing authentication data on the message fragments and take exceptions in the + // network layer. + val maybeItems = ArrayList(toFetch.size) + send(otherSide, Request(toFetch)) + for (hash in toFetch) { + // We skip the validation here (with unwrap { it }) because we will do it below in validateFetchResponse. + // The only thing checked is the object type. It is a protocol violation to send results out of order. + maybeItems += receive(wrapperType, otherSide).unwrap { it } + } // Check for a buggy/malicious peer answering with something that we didn't ask for. - val downloaded = validateFetchResponse(maybeItems, toFetch) + val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch) + logger.info("Fetched ${downloaded.size} elements from ${otherSide.name}") maybeWriteToDisk(downloaded) Result(fromDisk, downloaded) } diff --git a/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt index e39e16aa7c..e43a022b7a 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FetchTransactionsFlow.kt @@ -14,7 +14,7 @@ import net.corda.core.transactions.SignedTransaction */ @InitiatingFlow class FetchTransactionsFlow(requests: Set, otherSide: Party) : - FetchDataFlow(requests, otherSide) { + FetchDataFlow(requests, otherSide, SignedTransaction::class.java) { override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt new file mode 100644 index 0000000000..a5c059a498 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -0,0 +1,73 @@ +package net.corda.node.services.statemachine + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.InputStreamAndHash +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.messaging.startFlow +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.unwrap +import net.corda.testing.BOB +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.aliceBobAndNotary +import net.corda.testing.contracts.DummyState +import net.corda.testing.driver.driver +import org.junit.Test +import kotlin.test.assertEquals + +/** + * Check that we can add lots of large attachments to a transaction and that it works OK, e.g. does not hit the + * transaction size limit (which should only consider the hashes). + */ +class LargeTransactionsTest { + @StartableByRPC @InitiatingFlow + class SendLargeTransactionFlow(val hash1: SecureHash, val hash2: SecureHash, val hash3: SecureHash, val hash4: SecureHash) : FlowLogic() { + @Suspendable + override fun call() { + val tx = TransactionBuilder(notary = DUMMY_NOTARY) + .addOutputState(DummyState()) + .addAttachment(hash1) + .addAttachment(hash2) + .addAttachment(hash3) + .addAttachment(hash4) + val stx = serviceHub.signInitialTransaction(tx, serviceHub.legalIdentityKey) + // Send to the other side and wait for it to trigger resolution from us. + sendAndReceive(serviceHub.networkMapCache.getNodeByLegalName(BOB.name)!!.legalIdentity, stx) + } + } + + @InitiatedBy(SendLargeTransactionFlow::class) @Suppress("UNUSED") + class ReceiveLargeTransactionFlow(private val counterParty: Party) : FlowLogic() { + @Suspendable + override fun call() { + val stx = receive(counterParty).unwrap { it } + subFlow(ResolveTransactionsFlow(stx, counterParty)) + // Unblock the other side by sending some dummy object (Unit is fine here as it's a singleton). + send(counterParty, Unit) + } + } + + @Test + fun checkCanSendLargeTransactions() { + // These 4 attachments yield a transaction that's got >10mb attached, so it'd push us over the Artemis + // max message size. + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024*1024*3, 0) + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024*1024*3, 1) + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024*1024*3, 2) + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024*1024*3, 3) + driver(startNodesInProcess = true) { + val (alice, _, _) = aliceBobAndNotary() + alice.useRPC { + val hash1 = it.uploadAttachment(bigFile1.inputStream) + val hash2 = it.uploadAttachment(bigFile2.inputStream) + val hash3 = it.uploadAttachment(bigFile3.inputStream) + val hash4 = it.uploadAttachment(bigFile4.inputStream) + assertEquals(hash1, bigFile1.sha256) + // Should not throw any exceptions. + it.startFlow(::SendLargeTransactionFlow, hash1, hash2, hash3, hash4).returnValue.get() + } + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index 7853a3a04b..f28ce22809 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -31,7 +31,6 @@ class FetchTransactionsHandler(otherParty: Party) : FetchDataHandler(otherParty) { override fun getData(id: SecureHash): ByteArray? { return serviceHub.attachments.openAttachment(id)?.open()?.readBytes() @@ -46,10 +45,13 @@ abstract class FetchDataHandler(val otherParty: Party) : FlowLogic( if (it.hashes.isEmpty()) throw FlowException("Empty hash list") it } - val response = request.hashes.map { - getData(it) ?: throw FetchDataFlow.HashNotFound(it) + // TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer. + // See the discussion in FetchDataFlow. We send each item individually here in a separate asynchronous send + // call, and the other side picks them up with a straight receive call, because we batching would push us over + // the (current) Artemis message size limit. + request.hashes.forEach { + send(otherParty, getData(it) ?: throw FetchDataFlow.HashNotFound(it)) } - send(otherParty, response) } protected abstract fun getData(id: SecureHash): T? From 7aafcf8c57e6f8bc9e4068d071d0d8a514ae0a67 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 14 Jul 2017 20:40:21 +0100 Subject: [PATCH 030/197] Prevent exception causes showing up as null in logs (#1018) * Passing exception messages via constructor instead of overriding toString --- .../core/contracts/TransactionVerification.kt | 45 +++++++++---------- .../kotlin/net/corda/core/flows/NotaryFlow.kt | 10 ++--- .../core/flows/ResolveTransactionsFlowTest.kt | 2 +- .../net/corda/docs/CustomNotaryTutorial.kt | 8 ++-- .../transactions/ValidatingNotaryFlow.kt | 8 ++-- .../ValidatingNotaryServiceTests.kt | 9 ++-- 6 files changed, 39 insertions(+), 43 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt index 6ce17e267a..0b48cae298 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt @@ -93,35 +93,32 @@ data class TransactionForContract(val inputs: List, // DOCEND 3 } -class TransactionResolutionException(val hash: SecureHash) : FlowException() { - override fun toString(): String = "Transaction resolution failure for $hash" -} +class TransactionResolutionException(val hash: SecureHash) : FlowException("Transaction resolution failure for $hash") +class AttachmentResolutionException(val hash: SecureHash) : FlowException("Attachment resolution failure for $hash") -class AttachmentResolutionException(val hash: SecureHash) : FlowException() { - override fun toString(): String = "Attachment resolution failure for $hash" -} +sealed class TransactionVerificationException(val txId: SecureHash, message: String, cause: Throwable?) + : FlowException("$message, transaction: $txId", cause) { -sealed class TransactionVerificationException(val txId: SecureHash, cause: Throwable?) : FlowException(cause) { - class ContractRejection(txId: SecureHash, val contract: Contract, cause: Throwable?) : TransactionVerificationException(txId, cause) - class MoreThanOneNotary(txId: SecureHash) : TransactionVerificationException(txId, null) - class SignersMissing(txId: SecureHash, val missing: List) : TransactionVerificationException(txId, null) { - override fun toString(): String = "Signers missing: ${missing.joinToString()}" - } + class ContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) + : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, contract: $contract", cause) - class DuplicateInputStates(txId: SecureHash, val duplicates: NonEmptySet) : TransactionVerificationException(txId, null) { - override fun toString(): String = "Duplicate inputs: ${duplicates.joinToString()}" - } + class MoreThanOneNotary(txId: SecureHash) + : TransactionVerificationException(txId, "More than one notary", null) - class InvalidNotaryChange(txId: SecureHash) : TransactionVerificationException(txId, null) - class NotaryChangeInWrongTransactionType(txId: SecureHash, val txNotary: Party, val outputNotary: Party) : TransactionVerificationException(txId, null) { - override fun toString(): String { - return "Found unexpected notary change in transaction. Tx notary: $txNotary, found: $outputNotary" - } - } + class SignersMissing(txId: SecureHash, missing: List) + : TransactionVerificationException(txId, "Signers missing: ${missing.joinToString()}", null) - class TransactionMissingEncumbranceException(txId: SecureHash, val missing: Int, val inOut: Direction) : TransactionVerificationException(txId, null) { - override val message: String get() = "Missing required encumbrance $missing in $inOut" - } + class DuplicateInputStates(txId: SecureHash, val duplicates: NonEmptySet) + : TransactionVerificationException(txId, "Duplicate inputs: ${duplicates.joinToString()}", null) + + class InvalidNotaryChange(txId: SecureHash) + : TransactionVerificationException(txId, "Detected a notary change. Outputs must use the same notary as inputs", null) + + class NotaryChangeInWrongTransactionType(txId: SecureHash, txNotary: Party, outputNotary: Party) + : TransactionVerificationException(txId, "Found unexpected notary change in transaction. Tx notary: $txNotary, found: $outputNotary", null) + + class TransactionMissingEncumbranceException(txId: SecureHash, missing: Int, inOut: Direction) + : TransactionVerificationException(txId, "Missing required encumbrance $missing in $inOut", null) @CordaSerializable enum class Direction { diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index a13c3ad650..e452ebc560 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -13,6 +13,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap +import java.security.SignatureException import java.util.function.Predicate object NotaryFlow { @@ -51,8 +52,8 @@ object NotaryFlow { } try { stx.verifySignatures(notaryParty.owningKey) - } catch (ex: SignedTransaction.SignaturesMissingException) { - throw NotaryException(NotaryError.SignaturesMissing(ex)) + } catch (ex: SignatureException) { + throw NotaryException(NotaryError.TransactionInvalid(ex)) } val payload: Any = if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) { @@ -134,10 +135,7 @@ sealed class NotaryError { /** Thrown if the time specified in the [TimeWindow] command is outside the allowed tolerance. */ object TimeWindowInvalid : NotaryError() - data class TransactionInvalid(val msg: String) : NotaryError() - data class SignaturesInvalid(val msg: String) : NotaryError() - - data class SignaturesMissing(val cause: SignedTransaction.SignaturesMissingException) : NotaryError() { + data class TransactionInvalid(val cause: Throwable) : NotaryError() { override fun toString() = cause.toString() } } diff --git a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt index 1e0f222f10..b11d93564f 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt @@ -70,7 +70,7 @@ class ResolveTransactionsFlowTest { val p = ResolveTransactionsFlow(setOf(stx.id), a.info.legalIdentity) val future = b.services.startFlow(p).resultFuture mockNet.runNetwork() - assertFailsWith(SignatureException::class) { future.getOrThrow() } + assertFailsWith(SignedTransaction.SignaturesMissingException::class) { future.getOrThrow() } } @Test diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt index eacbfe96ff..8a4b528d28 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt @@ -61,8 +61,8 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary private fun checkSignatures(stx: SignedTransaction) { try { stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey) - } catch(e: SignedTransaction.SignaturesMissingException) { - throw NotaryException(NotaryError.SignaturesMissing(e)) + } catch(e: SignatureException) { + throw NotaryException(NotaryError.TransactionInvalid(e)) } } @@ -75,8 +75,8 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary return ltx } catch (e: Exception) { throw when (e) { - is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString())) - is SignatureException -> NotaryException(NotaryError.SignaturesInvalid(e.toString())) + is TransactionVerificationException, + is SignatureException -> NotaryException(NotaryError.TransactionInvalid(e)) else -> e } } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index afe9a81d99..daf70b1b24 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -34,8 +34,8 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ private fun checkSignatures(stx: SignedTransaction) { try { stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey) - } catch(e: SignedTransaction.SignaturesMissingException) { - throw NotaryException(NotaryError.SignaturesMissing(e)) + } catch(e: SignatureException) { + throw NotaryException(NotaryError.TransactionInvalid(e)) } } @@ -46,8 +46,8 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ wtx.toLedgerTransaction(serviceHub).verify() } catch (e: Exception) { throw when (e) { - is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString())) - is SignatureException -> NotaryException(NotaryError.SignaturesInvalid(e.toString())) + is TransactionVerificationException, + is SignatureException -> NotaryException(NotaryError.TransactionInvalid(e)) else -> e } } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index f1ca383d4e..ef24dc12b2 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -59,7 +59,8 @@ class ValidatingNotaryServiceTests { val future = runClient(stx) val ex = assertFailsWith(NotaryException::class) { future.getOrThrow() } - assertThat(ex.error).isInstanceOf(NotaryError.SignaturesInvalid::class.java) + val notaryError = ex.error as NotaryError.TransactionInvalid + assertThat(notaryError.cause).isInstanceOf(SignedTransaction.SignaturesMissingException::class.java) } @Test @@ -77,10 +78,10 @@ class ValidatingNotaryServiceTests { val future = runClient(stx) future.getOrThrow() } - val notaryError = ex.error - assertThat(notaryError).isInstanceOf(NotaryError.SignaturesMissing::class.java) + val notaryError = ex.error as NotaryError.TransactionInvalid + assertThat(notaryError.cause).isInstanceOf(SignedTransaction.SignaturesMissingException::class.java) - val missingKeys = (notaryError as NotaryError.SignaturesMissing).cause.missing + val missingKeys = (notaryError.cause as SignedTransaction.SignaturesMissingException).missing assertEquals(setOf(expectedMissingKey), missingKeys) } From 7a06d941c45a1cd1d8db79cca39a73266bea9138 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Fri, 14 Jul 2017 13:17:02 +0100 Subject: [PATCH 031/197] Put Services.kt in clear files, so that at a glance people can see what public services there are. Fix include Correct doc links after code movement --- .../node/services/KeyManagementService.kt | 56 +++++ .../services/TransactionVerifierService.kt | 15 ++ .../core/node/services/VaultQueryService.kt | 140 +++++++++++ .../services/{Services.kt => VaultService.kt} | 227 +----------------- docs/source/api-vault-query.rst | 2 +- docs/source/soft-locking.rst | 4 +- .../services/messaging/P2PMessagingTest.kt | 2 +- .../node/services/api/AbstractNodeService.kt | 1 - .../node/services/api/AcceptsFileUpload.kt | 2 - .../node/services/api/ServiceHubInternal.kt | 26 +- .../keys/E2ETestKeyManagementService.kt | 2 - .../node/services/messaging/Messaging.kt | 2 +- .../messaging/ServiceRequestMessage.kt | 2 +- .../network/InMemoryNetworkMapCache.kt | 3 +- .../services/network/NetworkMapService.kt | 4 +- .../node/messaging/InMemoryMessagingTests.kt | 2 +- .../messaging/ArtemisMessagingTests.kt | 8 +- .../network/AbstractNetworkMapServiceTest.kt | 10 +- .../corda/bank/BankOfCordaRPCClientTest.kt | 3 +- 19 files changed, 255 insertions(+), 256 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt create mode 100644 core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt create mode 100644 core/src/main/kotlin/net/corda/core/node/services/VaultQueryService.kt rename core/src/main/kotlin/net/corda/core/node/services/{Services.kt => VaultService.kt} (59%) diff --git a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt new file mode 100644 index 0000000000..f9799a7f2c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt @@ -0,0 +1,56 @@ +package net.corda.core.node.services + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.crypto.DigitalSignature +import net.corda.core.identity.PartyAndCertificate +import net.corda.flows.AnonymisedIdentity +import java.security.PublicKey + +/** + * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, + * call out to a hardware security module that enforces various auditing and frequency-of-use requirements. + */ +interface KeyManagementService { + /** + * Returns a snapshot of the current signing [PublicKey]s. + * For each of these keys a [PrivateKey] is available, that can be used later for signing. + */ + val keys: Set + + /** + * Generates a new random [KeyPair] and adds it to the internal key storage. Returns the public part of the pair. + */ + @Suspendable + fun freshKey(): PublicKey + + /** + * Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding + * [X509Certificate] and adds it to the identity service. + * + * @param identity identity to generate a key and certificate for. Must be an identity this node has CA privileges for. + * @param revocationEnabled whether to check revocation status of certificates in the certificate path. + * @return X.509 certificate and path to the trust root. + */ + @Suspendable + fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity + + /** + * Filter some keys down to the set that this node owns (has private keys for). + * + * @param candidateKeys keys which this node may own. + */ + fun filterMyKeys(candidateKeys: Iterable): Iterable + + /** + * Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data. + * @param bytes The data to sign over using the chosen key. + * @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity, + * or previously generated via the [freshKey] method. + * If the [PublicKey] is actually a [CompositeKey] the first leaf signing key hosted by the node is used. + * @throws IllegalArgumentException if the input key is not a member of [keys]. + * TODO A full [KeyManagementService] implementation needs to record activity to the [AuditService] and to limit signing to + * appropriately authorised contexts and initiating users. + */ + @Suspendable + fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt new file mode 100644 index 0000000000..fd31e2a55a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt @@ -0,0 +1,15 @@ +package net.corda.core.node.services + +import com.google.common.util.concurrent.ListenableFuture +import net.corda.core.transactions.LedgerTransaction + +/** + * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC. + */ +interface TransactionVerifierService { + /** + * @param transaction The transaction to be verified. + * @return A future that completes successfully if the transaction verified, or sets an exception the verifier threw. + */ + fun verify(transaction: LedgerTransaction): ListenableFuture<*> +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultQueryService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultQueryService.kt new file mode 100644 index 0000000000..48c8f4b3fd --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultQueryService.kt @@ -0,0 +1,140 @@ +package net.corda.core.node.services + +import net.corda.core.contracts.ContractState +import net.corda.core.flows.FlowException +import net.corda.core.messaging.DataFeed +import net.corda.core.node.services.vault.PageSpecification +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.node.services.vault.Sort + +interface VaultQueryService { + + // DOCSTART VaultQueryAPI + /** + * Generic vault query function which takes a [QueryCriteria] object to define filters, + * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), + * and returns a [Vault.Page] object containing the following: + * 1. states as a List of (page number and size defined by [PageSpecification]) + * 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table. + * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1) + * 4. status types used in this query: UNCONSUMED, CONSUMED, ALL + * 5. other results (aggregate functions with/without using value groups) + * + * @throws VaultQueryException if the query cannot be executed for any reason + * (missing criteria or parsing error, paging errors, unsupported query, underlying database error) + * + * Notes + * If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned. + * API users must specify a [PageSpecification] if they are expecting more than [DEFAULT_PAGE_SIZE] results, + * otherwise a [VaultQueryException] will be thrown alerting to this condition. + * It is the responsibility of the API user to request further pages and/or specify a more suitable [PageSpecification]. + */ + @Throws(VaultQueryException::class) + fun _queryBy(criteria: QueryCriteria, + paging: PageSpecification, + sorting: Sort, + contractType: Class): Vault.Page + + /** + * Generic vault query function which takes a [QueryCriteria] object to define filters, + * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), + * and returns a [Vault.PageAndUpdates] object containing + * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]) + * 2) an [Observable] of [Vault.Update] + * + * @throws VaultQueryException if the query cannot be executed for any reason + * + * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function. + * the [QueryCriteria] applies to both snapshot and deltas (streaming updates). + */ + @Throws(VaultQueryException::class) + fun _trackBy(criteria: QueryCriteria, + paging: PageSpecification, + sorting: Sort, + contractType: Class): DataFeed, Vault.Update> + // DOCEND VaultQueryAPI + + // Note: cannot apply @JvmOverloads to interfaces nor interface implementations + // Java Helpers + fun queryBy(contractType: Class): Vault.Page { + return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType) + } + + fun queryBy(contractType: Class, criteria: QueryCriteria): Vault.Page { + return _queryBy(criteria, PageSpecification(), Sort(emptySet()), contractType) + } + + fun queryBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return _queryBy(criteria, paging, Sort(emptySet()), contractType) + } + + fun queryBy(contractType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { + return _queryBy(criteria, PageSpecification(), sorting, contractType) + } + + fun queryBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page { + return _queryBy(criteria, paging, sorting, contractType) + } + + fun trackBy(contractType: Class): DataFeed, Vault.Update> { + return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType) + } + + fun trackBy(contractType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { + return _trackBy(criteria, PageSpecification(), Sort(emptySet()), contractType) + } + + fun trackBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return _trackBy(criteria, paging, Sort(emptySet()), contractType) + } + + fun trackBy(contractType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return _trackBy(criteria, PageSpecification(), sorting, contractType) + } + + fun trackBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed, Vault.Update> { + return _trackBy(criteria, paging, sorting, contractType) + } +} + +inline fun VaultQueryService.queryBy(): Vault.Page { + return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.queryBy(criteria: QueryCriteria): Vault.Page { + return _queryBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.queryBy(criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return _queryBy(criteria, paging, Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.queryBy(criteria: QueryCriteria, sorting: Sort): Vault.Page { + return _queryBy(criteria, PageSpecification(), sorting, T::class.java) +} + +inline fun VaultQueryService.queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page { + return _queryBy(criteria, paging, sorting, T::class.java) +} + +inline fun VaultQueryService.trackBy(): DataFeed, Vault.Update> { + return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.trackBy(criteria: QueryCriteria): DataFeed, Vault.Update> { + return _trackBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.trackBy(criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return _trackBy(criteria, paging, Sort(emptySet()), T::class.java) +} + +inline fun VaultQueryService.trackBy(criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return _trackBy(criteria, PageSpecification(), sorting, T::class.java) +} + +inline fun VaultQueryService.trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed, Vault.Update> { + return _trackBy(criteria, paging, sorting, T::class.java) +} + +class VaultQueryException(description: String) : FlowException("$description") \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt similarity index 59% rename from core/src/main/kotlin/net/corda/core/node/services/Services.kt rename to core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 3b139f4734..3eccef2bb9 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -3,46 +3,23 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture import net.corda.core.contracts.* -import net.corda.core.crypto.composite.CompositeKey -import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.keys import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.DataFeed -import net.corda.core.node.services.vault.PageSpecification -import net.corda.core.node.services.vault.QueryCriteria -import net.corda.core.node.services.vault.Sort -import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE import net.corda.core.serialization.CordaSerializable import net.corda.core.toFuture -import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes -import net.corda.flows.AnonymisedIdentity import rx.Observable import rx.subjects.PublishSubject -import java.io.InputStream import java.security.PublicKey -import java.security.cert.X509Certificate import java.time.Instant import java.util.* -/** - * Session ID to use for services listening for the first message in a session (before a - * specific session ID has been established). - */ -val DEFAULT_SESSION_ID = 0L - -/** - * This file defines various 'services' which are not currently fleshed out. A service is a module that provides - * immutable snapshots of data that may be changing in response to user or network events. - */ - /** * A vault (name may be temporary) wraps a set of states that are useful for us to keep track of, for instance, * because we own them. This class represents an immutable, stable state of a vault: it is guaranteed not to @@ -345,206 +322,4 @@ inline fun VaultService.linearHeadsOfType() = class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) { override fun toString() = "Soft locking error: $message" -} - -interface VaultQueryService { - - // DOCSTART VaultQueryAPI - /** - * Generic vault query function which takes a [QueryCriteria] object to define filters, - * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), - * and returns a [Vault.Page] object containing the following: - * 1. states as a List of (page number and size defined by [PageSpecification]) - * 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table. - * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1) - * 4. status types used in this query: UNCONSUMED, CONSUMED, ALL - * 5. other results (aggregate functions with/without using value groups) - * - * @throws VaultQueryException if the query cannot be executed for any reason - * (missing criteria or parsing error, paging errors, unsupported query, underlying database error) - * - * Notes - * If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned. - * API users must specify a [PageSpecification] if they are expecting more than [DEFAULT_PAGE_SIZE] results, - * otherwise a [VaultQueryException] will be thrown alerting to this condition. - * It is the responsibility of the API user to request further pages and/or specify a more suitable [PageSpecification]. - */ - @Throws(VaultQueryException::class) - fun _queryBy(criteria: QueryCriteria, - paging: PageSpecification, - sorting: Sort, - contractType: Class): Vault.Page - /** - * Generic vault query function which takes a [QueryCriteria] object to define filters, - * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), - * and returns a [Vault.PageAndUpdates] object containing - * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]) - * 2) an [Observable] of [Vault.Update] - * - * @throws VaultQueryException if the query cannot be executed for any reason - * - * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function. - * the [QueryCriteria] applies to both snapshot and deltas (streaming updates). - */ - @Throws(VaultQueryException::class) - fun _trackBy(criteria: QueryCriteria, - paging: PageSpecification, - sorting: Sort, - contractType: Class): DataFeed, Vault.Update> - // DOCEND VaultQueryAPI - - // Note: cannot apply @JvmOverloads to interfaces nor interface implementations - // Java Helpers - fun queryBy(contractType: Class): Vault.Page { - return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType) - } - fun queryBy(contractType: Class, criteria: QueryCriteria): Vault.Page { - return _queryBy(criteria, PageSpecification(), Sort(emptySet()), contractType) - } - fun queryBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { - return _queryBy(criteria, paging, Sort(emptySet()), contractType) - } - fun queryBy(contractType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { - return _queryBy(criteria, PageSpecification(), sorting, contractType) - } - fun queryBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page { - return _queryBy(criteria, paging, sorting, contractType) - } - - fun trackBy(contractType: Class): DataFeed, Vault.Update> { - return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType) - } - fun trackBy(contractType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { - return _trackBy(criteria, PageSpecification(), Sort(emptySet()), contractType) - } - fun trackBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { - return _trackBy(criteria, paging, Sort(emptySet()), contractType) - } - fun trackBy(contractType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { - return _trackBy(criteria, PageSpecification(), sorting, contractType) - } - fun trackBy(contractType: Class, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed, Vault.Update> { - return _trackBy(criteria, paging, sorting, contractType) - } -} - -inline fun VaultQueryService.queryBy(): Vault.Page { - return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.queryBy(criteria: QueryCriteria): Vault.Page { - return _queryBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.queryBy(criteria: QueryCriteria, paging: PageSpecification): Vault.Page { - return _queryBy(criteria, paging, Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.queryBy(criteria: QueryCriteria, sorting: Sort): Vault.Page { - return _queryBy(criteria, PageSpecification(), sorting, T::class.java) -} - -inline fun VaultQueryService.queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page { - return _queryBy(criteria, paging, sorting, T::class.java) -} - -inline fun VaultQueryService.trackBy(): DataFeed, Vault.Update> { - return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.trackBy(criteria: QueryCriteria): DataFeed, Vault.Update> { - return _trackBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.trackBy(criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { - return _trackBy(criteria, paging, Sort(emptySet()), T::class.java) -} - -inline fun VaultQueryService.trackBy(criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { - return _trackBy(criteria, PageSpecification(), sorting, T::class.java) -} - -inline fun VaultQueryService.trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed, Vault.Update> { - return _trackBy(criteria, paging, sorting, T::class.java) -} - -class VaultQueryException(description: String) : FlowException("$description") - -/** - * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, - * call out to a hardware security module that enforces various auditing and frequency-of-use requirements. - */ - -interface KeyManagementService { - /** - * Returns a snapshot of the current signing [PublicKey]s. - * For each of these keys a [PrivateKey] is available, that can be used later for signing. - */ - val keys: Set - - /** - * Generates a new random [KeyPair] and adds it to the internal key storage. Returns the public part of the pair. - */ - @Suspendable - fun freshKey(): PublicKey - - /** - * Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding - * [X509Certificate] and adds it to the identity service. - * - * @param identity identity to generate a key and certificate for. Must be an identity this node has CA privileges for. - * @param revocationEnabled whether to check revocation status of certificates in the certificate path. - * @return X.509 certificate and path to the trust root. - */ - @Suspendable - fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity - - /** - * Filter some keys down to the set that this node owns (has private keys for). - * - * @param candidateKeys keys which this node may own. - */ - fun filterMyKeys(candidateKeys: Iterable): Iterable - - /** - * Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data. - * @param bytes The data to sign over using the chosen key. - * @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity, - * or previously generated via the [freshKey] method. - * If the [PublicKey] is actually a [CompositeKey] the first leaf signing key hosted by the node is used. - * @throws IllegalArgumentException if the input key is not a member of [keys]. - * TODO A full [KeyManagementService] implementation needs to record activity to the [AuditService] and to limit signing to - * appropriately authorised contexts and initiating users. - */ - @Suspendable - fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey -} - -/** - * An interface that denotes a service that can accept file uploads. - */ -// TODO This is no longer used and can be removed -interface FileUploader { - /** - * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent - * back to the user in the response. - */ - fun upload(file: InputStream): String - - /** - * Check if this service accepts this type of upload. For example if you are uploading interest rates this could - * be "my-service-interest-rates". Type here does not refer to file extentions or MIME types. - */ - fun accepts(type: String): Boolean -} - -/** - * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC. - */ -interface TransactionVerifierService { - /** - * @param transaction The transaction to be verified. - * @return A future that completes successfully if the transaction verified, or sets an exception the verifier threw. - */ - fun verify(transaction: LedgerTransaction): ListenableFuture<*> -} +} \ No newline at end of file diff --git a/docs/source/api-vault-query.rst b/docs/source/api-vault-query.rst index 84683b9763..099306bc30 100644 --- a/docs/source/api-vault-query.rst +++ b/docs/source/api-vault-query.rst @@ -11,7 +11,7 @@ Corda provides a number of flexible query mechanisms for accessing the Vault: The majority of query requirements can be satisfied by using the Vault Query API, which is exposed via the ``VaultQueryService`` for use directly by flows: -.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/Services.kt +.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultQueryService.kt :language: kotlin :start-after: DOCSTART VaultQueryAPI :end-before: DOCEND VaultQueryAPI diff --git a/docs/source/soft-locking.rst b/docs/source/soft-locking.rst index 8cf05b3985..885951da98 100644 --- a/docs/source/soft-locking.rst +++ b/docs/source/soft-locking.rst @@ -17,7 +17,7 @@ These states are effectively soft locked until flow termination (exit or error) In addition, the ``VaultService`` exposes a number of functions a developer may use to explicitly reserve, release and query soft locks associated with states as required by their CorDapp application logic: -.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/Services.kt +.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultService.kt :language: kotlin :start-after: DOCSTART SoftLockAPI :end-before: DOCEND SoftLockAPI @@ -25,7 +25,7 @@ query soft locks associated with states as required by their CorDapp application You can also control whether soft locked states are retrieved in general vault queries by setting an optional boolean `includeSoftLockedStates` flag (which is set to *true* by default) -.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/Services.kt +.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/VaultService.kt :language: kotlin :start-after: DOCSTART VaultStatesQuery :end-before: DOCEND VaultStatesQuery diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index d7e0dc2c3e..611e87373e 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -6,12 +6,12 @@ import net.corda.core.* import net.corda.core.crypto.random63BitValue import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.ServiceInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.internal.Node +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.messaging.* import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.SimpleNotaryService diff --git a/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt b/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt index 5c974fcfb1..73acab6e44 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/AbstractNodeService.kt @@ -1,6 +1,5 @@ package net.corda.node.services.api -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize diff --git a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt index a3967ec2ba..14dafbea77 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/AcceptsFileUpload.kt @@ -1,7 +1,5 @@ package net.corda.node.services.api -import net.corda.core.node.services.FileUploader - /** * A service that implements AcceptsFileUpload can have new binary data provided to it via an HTTP upload. */ diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index fedc2c6477..0b909aa84c 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -12,7 +12,6 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.PluginServiceHub -import net.corda.core.node.services.FileUploader import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.TransactionStorage import net.corda.core.serialization.CordaSerializable @@ -24,6 +23,13 @@ import net.corda.node.services.messaging.MessagingService import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.CordaPersistence +import java.io.InputStream + +/** + * Session ID to use for services listening for the first message in a session (before a + * specific session ID has been established). + */ +val DEFAULT_SESSION_ID = 0L interface NetworkMapCacheInternal : NetworkMapCache { /** @@ -62,6 +68,24 @@ sealed class NetworkCacheError : Exception() { class DeregistrationFailed : NetworkCacheError() } +/** + * An interface that denotes a service that can accept file uploads. + */ +// TODO This is no longer used and can be removed +interface FileUploader { + /** + * Accepts the data in the given input stream, and returns some sort of useful return message that will be sent + * back to the user in the response. + */ + fun upload(file: InputStream): String + + /** + * Check if this service accepts this type of upload. For example if you are uploading interest rates this could + * be "my-service-interest-rates". Type here does not refer to file extentions or MIME types. + */ + fun accepts(type: String): Boolean +} + interface ServiceHubInternal : PluginServiceHub { companion object { private val log = loggerFor() diff --git a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt index 0222ba6fa5..cfba3dd792 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt @@ -10,12 +10,10 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.flows.AnonymisedIdentity -import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey -import java.security.cert.CertPath import java.util.* import javax.annotation.concurrent.ThreadSafe diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index c5396a0067..baf8c623d3 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -5,11 +5,11 @@ import com.google.common.util.concurrent.SettableFuture import net.corda.core.catch import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.node.services.api.DEFAULT_SESSION_ID import org.bouncycastle.asn1.x500.X500Name import java.time.Instant import java.util.* diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt index 35ce5218d1..9663a07a2b 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt @@ -3,8 +3,8 @@ package net.corda.node.services.messaging import com.google.common.util.concurrent.ListenableFuture import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.serialization.CordaSerializable +import net.corda.node.services.api.DEFAULT_SESSION_ID /** * Abstract superclass for request messages sent to services which expect a reply. diff --git a/node/src/main/kotlin/net/corda/node/services/network/InMemoryNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/InMemoryNetworkMapCache.kt index 0d3cd6e818..25087c0832 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/InMemoryNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/InMemoryNetworkMapCache.kt @@ -11,14 +11,13 @@ import net.corda.core.messaging.DataFeed import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub -import net.corda.core.node.services.DEFAULT_SESSION_ID -import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.api.NetworkCacheError import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.messaging.MessagingService diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt index d0f51b534e..7ee02601b5 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt @@ -5,15 +5,14 @@ import net.corda.core.ThreadBox import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SignedData import net.corda.core.crypto.isFulfilledBy +import net.corda.core.crypto.random63BitValue import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.ServiceType -import net.corda.core.crypto.random63BitValue import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize @@ -21,6 +20,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.node.services.api.AbstractNodeService +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.MessageHandlerRegistration import net.corda.node.services.messaging.ServiceRequestMessage diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index b935635105..ad09bb67a8 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -1,7 +1,7 @@ package net.corda.node.messaging -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.ServiceInfo +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.messaging.Message import net.corda.node.services.messaging.TopicStringValidator import net.corda.node.services.messaging.createMessage diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 6835133980..59f71acde4 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -6,12 +6,10 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.messaging.RPCOps -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.ALICE -import net.corda.testing.LogHelper import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate @@ -21,11 +19,9 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.testing.freeLocalHostAndPort -import net.corda.testing.freePort +import net.corda.testing.* import net.corda.testing.node.MOCK_VERSION_INFO import net.corda.testing.node.makeTestDataSourceProperties -import net.corda.testing.testNodeConfiguration import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt index 9c6df604e7..549f52e848 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt @@ -4,13 +4,9 @@ import com.google.common.util.concurrent.ListenableFuture import net.corda.core.getOrThrow import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo -import net.corda.core.node.services.DEFAULT_SESSION_ID import net.corda.core.node.services.ServiceInfo import net.corda.core.serialization.deserialize -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.CHARLIE -import net.corda.testing.DUMMY_MAP +import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.send import net.corda.node.services.messaging.sendRequest @@ -26,6 +22,10 @@ import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_ import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.AddOrRemove.ADD import net.corda.node.utilities.AddOrRemove.REMOVE +import net.corda.testing.ALICE +import net.corda.testing.BOB +import net.corda.testing.CHARLIE +import net.corda.testing.DUMMY_MAP import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import org.assertj.core.api.Assertions.assertThat diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 7159c077b4..71e98c6ace 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -7,14 +7,13 @@ import net.corda.core.getOrThrow import net.corda.core.messaging.startFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault -import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.flows.IssuerFlow.IssuanceRequester -import net.corda.testing.driver.driver import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User import net.corda.testing.* +import net.corda.testing.driver.driver import org.junit.Test class BankOfCordaRPCClientTest { From dfbf06a66dbb84b177b0a68c4d050c9e380e7ee1 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 14 Jul 2017 17:50:36 +0100 Subject: [PATCH 032/197] Flow cleanup * Change "for who" to "for whom" * Don't pass parties to FinalityFlow, it can derive them automatically * Create a basket of nodes instead of individually assembling nodes * Switch two party trade flow tests to generate a full anonymous identity --- .../kotlin/net/corda/core/flows/FinalityFlow.kt | 2 +- .../kotlin/net/corda/flows/TwoPartyTradeFlow.kt | 2 +- .../kotlin/net/corda/flows/IssuerFlowTest.kt | 7 ++++--- .../node/messaging/TwoPartyTradeFlowTests.kt | 17 +++++++++++------ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index 382da66211..31e6f2048d 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -78,7 +78,7 @@ open class FinalityFlow(val transactions: Iterable, /** * Broadcast a transaction to the participants. By default calls [BroadcastTransactionFlow], however can be * overridden for more complex transaction delivery protocols (for example where not all parties know each other). - * This implementation will filter out any participants for who there is no well known identity. + * This implementation will filter out any participants for whom there is no well known identity. * * @param participants the participants to send the transaction to. This is expected to include extra participants * and exclude the local node. diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt index 7a529f7237..8d427fd1ef 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt @@ -145,7 +145,7 @@ object TwoPartyTradeFlow { // Notarise and record the transaction. progressTracker.currentStep = RECORDING - return subFlow(FinalityFlow(twiceSignedTx, setOf(otherParty, serviceHub.myInfo.legalIdentity))).single() + return subFlow(FinalityFlow(twiceSignedTx)).single() } @Suspendable diff --git a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt index 48fadb89ee..f16fc7703b 100644 --- a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt @@ -44,9 +44,10 @@ class IssuerFlowTest(val anonymous: Boolean) { @Before fun start() { mockNet = MockNetwork(threadPerNode = true) - notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - bankOfCordaNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) - bankClientNode = mockNet.createPartyNode(notaryNode.network.myAddress, MEGA_CORP.name) + val basketOfNodes = mockNet.createSomeNodes(2) + bankOfCordaNode = basketOfNodes.partyNodes[0] + bankClientNode = basketOfNodes.partyNodes[1] + notaryNode = basketOfNodes.notaryNode } @After diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 43f03c70bd..1c8fa4ddea 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sign +import net.corda.core.crypto.toStringShort import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow @@ -185,6 +186,9 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + + aliceNode.services.identityService.registerIdentity(bobNode.info.legalIdentityAndCert) + bobNode.services.identityService.registerIdentity(aliceNode.info.legalIdentityAndCert) aliceNode.disableDBCloseOnStop() bobNode.disableDBCloseOnStop() @@ -494,11 +498,12 @@ class TwoPartyTradeFlowTests { sellerNode: MockNetwork.MockNode, buyerNode: MockNetwork.MockNode, assetToSell: StateAndRef): RunResult { - sellerNode.services.identityService.registerIdentity(buyerNode.info.legalIdentityAndCert) - buyerNode.services.identityService.registerIdentity(sellerNode.info.legalIdentityAndCert) + val anonymousSeller = sellerNode.services.let { serviceHub -> + serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false) + } val buyerFlows: Observable = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } - val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS) + val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.identity) val sellerResult = sellerNode.services.startFlow(seller).resultFuture return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) } @@ -507,17 +512,17 @@ class TwoPartyTradeFlowTests { class SellerInitiator(val buyer: Party, val notary: NodeInfo, val assetToSell: StateAndRef, - val price: Amount) : FlowLogic() { + val price: Amount, + val me: AbstractParty) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { send(buyer, Pair(notary.notaryIdentity, price)) - val key = serviceHub.keyManagementService.freshKey() return subFlow(Seller( buyer, notary, assetToSell, price, - AnonymousParty(key))) + me)) } } From b7ee7d42a563c991f1c0c8799ff1af6d4e867373 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Thu, 13 Jul 2017 11:17:17 +0100 Subject: [PATCH 033/197] Enable anonymisation in integration testing tutorial Enable anonymisation in integration testing tutorial, and as a requirement fix a bug where the counterparty anonymous identity was not registered by `TransactionKeyFlow`. --- .../kotlin/net/corda/core/flows/TransactionKeyFlow.kt | 2 +- .../net/corda/docs/IntegrationTestingTutorial.kt | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt index b1567f0557..58ff5837df 100644 --- a/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt @@ -31,7 +31,6 @@ class TransactionKeyFlow(val otherSide: Party, override fun call(): LinkedHashMap { progressTracker.currentStep = AWAITING_KEY val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled) - serviceHub.identityService.registerAnonymousIdentity(legalIdentityAnonymous.identity, serviceHub.myInfo.legalIdentity, legalIdentityAnonymous.certPath) // Special case that if we're both parties, a single identity is generated val identities = LinkedHashMap() @@ -39,6 +38,7 @@ class TransactionKeyFlow(val otherSide: Party, identities.put(otherSide, legalIdentityAnonymous) } else { val otherSideAnonymous = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) } + serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous.identity, otherSide, otherSideAnonymous.certPath) identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous) identities.put(otherSide, otherSideAnonymous) } diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index c51e115f09..e0e6f6be4a 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -8,6 +8,7 @@ import net.corda.core.getOrThrow import net.corda.core.messaging.startFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault +import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.testing.ALICE import net.corda.testing.BOB @@ -57,8 +58,9 @@ class IntegrationTestingTutorial { // END 2 // START 3 - val bobVaultUpdates = bobProxy.vaultAndUpdates().second - val aliceVaultUpdates = aliceProxy.vaultAndUpdates().second + val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) + val (_, bobVaultUpdates) = bobProxy.vaultTrackByCriteria(Cash.State::class.java, criteria) + val (_, aliceVaultUpdates) = aliceProxy.vaultTrackByCriteria(Cash.State::class.java, criteria) // END 3 // START 4 @@ -70,8 +72,7 @@ class IntegrationTestingTutorial { i.DOLLARS, issueRef, bob.nodeInfo.legalIdentity, - notary.nodeInfo.notaryIdentity, - false // Not anonymised + notary.nodeInfo.notaryIdentity ).returnValue) } }.forEach(Thread::join) // Ensure the stack of futures is populated. @@ -94,7 +95,7 @@ class IntegrationTestingTutorial { // START 5 for (i in 1..10) { - bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity, false).returnValue.getOrThrow() + bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.getOrThrow() } aliceVaultUpdates.expectEvents { From 5df0de8ff769b90ee1907b6109f57cf705878d1f Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 17 Jul 2017 12:23:14 +0100 Subject: [PATCH 034/197] Adds a tutorial showing multi-party communication. --- docs/source/tut-two-party-contract.rst | 33 ++++ docs/source/tut-two-party-flow.rst | 203 +++++++++++++++++++++ docs/source/tut-two-party-index.rst | 10 + docs/source/tut-two-party-introduction.rst | 25 +++ docs/source/tut-two-party-running.rst | 28 +++ docs/source/tutorials-index.rst | 1 + 6 files changed, 300 insertions(+) create mode 100644 docs/source/tut-two-party-contract.rst create mode 100644 docs/source/tut-two-party-flow.rst create mode 100644 docs/source/tut-two-party-index.rst create mode 100644 docs/source/tut-two-party-introduction.rst create mode 100644 docs/source/tut-two-party-running.rst diff --git a/docs/source/tut-two-party-contract.rst b/docs/source/tut-two-party-contract.rst new file mode 100644 index 0000000000..71f35f60cf --- /dev/null +++ b/docs/source/tut-two-party-contract.rst @@ -0,0 +1,33 @@ +.. highlight:: kotlin +.. raw:: html + + + + +Updating the contract +===================== + +Remember that each state references a contract. The contract imposes constraints on transactions involving that state. +If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid +ledger update. + +We need to modify our contract so that the lender's signature is required in any IOU creation transaction. This will +only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final line of the +``requireThat`` block as follows: + +.. container:: codeset + + .. code-block:: kotlin + + "The borrower and lender must be signers." using (command.signers.containsAll(listOf( + out.borrower.owningKey, out.lender.owningKey))) + + .. code-block:: java + + check.using("The borrower and lender must be signers.", command.getSigners().containsAll( + ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey()))); + +Progress so far +--------------- +Our contract now imposes an additional constraint - the lender must also sign an IOU creation transaction. Next, we +need to update ``IOUFlow`` so that it actually gathers the counterparty's signature as part of the flow. \ No newline at end of file diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst new file mode 100644 index 0000000000..00a2658441 --- /dev/null +++ b/docs/source/tut-two-party-flow.rst @@ -0,0 +1,203 @@ +.. highlight:: kotlin +.. raw:: html + + + + +Updating the flow +================= + +To update the flow, we'll need to do two things: + +* Update the borrower's side of the flow to request the lender's signature +* Create a flow for the lender to run in response to a signature request from the borrower + +Updating the borrower's flow +---------------------------- +In the original CorDapp, we automated the process of notarising a transaction and recording it in every party's vault +by invoking a built-in flow called ``FinalityFlow`` as a subflow. We're going to use another pre-defined flow, called +``CollectSignaturesFlow``, to gather the lender's signature. + +We also need to add the lender's public key to the transaction's command, making the lender one of the required signers +on the transaction. + +In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows: + +.. container:: codeset + + .. code-block:: kotlin + + // We add the items to the builder. + val state = IOUState(iouValue, me, otherParty) + val cmd = Command(IOUContract.Create(), listOf(me.owningKey, otherParty.owningKey)) + txBuilder.withItems(state, cmd) + + // Verifying the transaction. + txBuilder.verify(serviceHub) + + // Signing the transaction. + val signedTx = serviceHub.signInitialTransaction(txBuilder) + + // Obtaining the counterparty's signature + val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx)) + + // Finalising the transaction. + subFlow(FinalityFlow(fullySignedTx)) + + .. code-block:: java + + // We add the items to the builder. + IOUState state = new IOUState(iouValue, me, otherParty); + List requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey()); + Command cmd = new Command(new IOUContract.Create(), requiredSigners); + txBuilder.withItems(state, cmd); + + // Verifying the transaction. + txBuilder.verify(getServiceHub()); + + // Signing the transaction. + final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); + + // Obtaining the counterparty's signature + final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, null)); + + // Finalising the transaction. + subFlow(new FinalityFlow(fullySignedTx)); + +To make the lender a required signer, we simply add the lender's public key to the list of signers on the command. + +``CollectSignaturesFlow``, meanwhile, takes a transaction signed by the flow initiator, and returns a transaction +signed by all the transaction's other required signers. We then pass this fully-signed transaction into +``FinalityFlow``. + +The lender's flow +----------------- +Reorganising our class +^^^^^^^^^^^^^^^^^^^^^^ +Before we define the lender's flow, let's reorganise ``IOUFlow.java``/``IOUFlow.kt`` a little bit: + +* Rename ``IOUFlow`` to ``Initiator`` +* In Java, make the ``Initiator`` class static, rename its constructor to match the new name, and move the definition + inside an enclosing ``IOUFlow`` class +* In Kotlin, move the definition of ``Initiator`` class inside an enclosing ``IOUFlow`` singleton object + +We will end up with the following structure: + +.. container:: codeset + + .. code-block:: kotlin + + object IOUFlow { + @InitiatingFlow + @StartableByRPC + class Initiator(val iouValue: Int, + val otherParty: Party) : FlowLogic() { + + .. code-block:: java + + public class IOUFlow { + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + +Writing the lender's flow +^^^^^^^^^^^^^^^^^^^^^^^^^ +We're now ready to write the lender's flow, which will respond to the borrower's attempt to gather our signature. + +Inside the ``IOUFlow`` class/singleton object, add the following class: + +.. container:: codeset + + .. code-block:: kotlin + + @InitiatedBy(Initiator::class) + class Acceptor(val otherParty: Party) : FlowLogic() { + @Suspendable + override fun call() { + val signTransactionFlow = object : SignTransactionFlow(otherParty) { + override fun checkTransaction(stx: SignedTransaction) = requireThat { + val output = stx.tx.outputs.single().data + "This must be an IOU transaction." using (output is IOUState) + val iou = output as IOUState + "The IOU's value can't be too high." using (iou.value < 100) + } + } + + subFlow(signTransactionFlow) + } + } + + .. code-block:: java + + @InitiatedBy(Initiator.class) + public static class Acceptor extends FlowLogic { + + private final Party otherParty; + + public Acceptor(Party otherParty) { + this.otherParty = otherParty; + } + + @Suspendable + @Override + public Void call() throws FlowException { + class signTxFlow extends SignTransactionFlow { + private signTxFlow(Party otherParty) { + super(otherParty, null); + } + + @Override + protected void checkTransaction(SignedTransaction stx) { + requireThat(require -> { + ContractState output = stx.getTx().getOutputs().get(0).getData(); + require.using("This must be an IOU transaction.", output instanceof IOUState); + IOUState iou = (IOUState) output; + require.using("The IOU's value can't be too high.", iou.getValue() < 100); + return null; + }); + } + } + + subFlow(new signTxFlow(otherParty)); + + return null; + } + } + +As with the ``Initiator``, our ``Acceptor`` flow is a ``FlowLogic`` subclass where we've overridden ``FlowLogic.call``. + +The flow is annotated with ``InitiatedBy(Initiator.class)``, which means that your node will invoke ``Acceptor.call`` +when it receives a message from a instance of ``Initiator`` running on another node. What will this message from the +``Initiator`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that we'll be sent a +``SignedTransaction``, and are expected to send back our signature over that transaction. + +We could handle this manually. However, there is also a pre-defined flow called ``SignTransactionFlow`` that can handle +this process for us automatically. ``SignTransactionFlow`` is an abstract class, and we must subclass it and override +``SignTransactionFlow.checkTransaction``. + +Once we've defined the subclass, we invoke it using ``FlowLogic.subFlow``, and the communication with the borrower's +and the lender's flow is conducted automatically. + +CheckTransactions +~~~~~~~~~~~~~~~~~ +``SignTransactionFlow`` will automatically verify the transaction and its signatures before signing it. However, just +because a transaction is valid doesn't mean we necessarily want to sign. What if we don't want to deal with the +counterparty in question, or the value is too high, or we're not happy with the transaction's structure? + +Overriding ``SignTransactionFlow.checkTransaction`` allows us to define these additional checks. In our case, we are +checking that: + +* The transaction involves an ``IOUState`` - this ensures that ``IOUContract`` will be run to verify the transaction +* The IOU's value is less than some amount (100 in this case) + +If either of these conditions are not met, we will not sign the transaction - even if the transaction and its +signatures are valid. + +Conclusion +---------- +We have now updated our flow to gather the lender's signature as well, in line with the constraints in ``IOUContract``. +We can now run our updated CorDapp, using the instructions :doc:`here `. + +Our CorDapp now requires agreement from both the lender and the borrower before an IOU can be created on the ledger. +This prevents either the lender or the borrower from unilaterally updating the ledger in a way that only benefits +themselves. diff --git a/docs/source/tut-two-party-index.rst b/docs/source/tut-two-party-index.rst new file mode 100644 index 0000000000..27c69f3e33 --- /dev/null +++ b/docs/source/tut-two-party-index.rst @@ -0,0 +1,10 @@ +Two-party flows +=============== + +.. toctree:: + :maxdepth: 1 + + tut-two-party-introduction + tut-two-party-contract + tut-two-party-flow + tut-two-party-running \ No newline at end of file diff --git a/docs/source/tut-two-party-introduction.rst b/docs/source/tut-two-party-introduction.rst new file mode 100644 index 0000000000..46f924583f --- /dev/null +++ b/docs/source/tut-two-party-introduction.rst @@ -0,0 +1,25 @@ +Introduction +============ + +.. note:: This tutorial extends the CorDapp built during the :doc:`Hello, World tutorial `. You can + find the final version of the CorDapp produced in that tutorial + `here `_. + +In the Hello, World tutorial, we built a CorDapp allowing us to model IOUs on ledger. Our CorDapp was made up of three +elements: + +* An ``IOUState``, representing IOUs on the ledger +* An ``IOUContract``, controlling the evolution of IOUs over time +* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger + +However, in our original CorDapp, only the IOU's borrower was required to sign transactions issuing IOUs. The lender +had no say in whether the issuance of the IOU was a valid ledger update or not. + +In this tutorial, we'll update our code so that the borrower requires the lender's agreement before they can issue an +IOU onto the ledger. We'll need to make two changes: + +* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the lender's + signature (as well as the borrower's) to become valid ledger updates +* The ``IOUFlow`` will need to be updated to allow for the gathering of the lender's signature + +We'll start by updating the contract. \ No newline at end of file diff --git a/docs/source/tut-two-party-running.rst b/docs/source/tut-two-party-running.rst new file mode 100644 index 0000000000..a616769fda --- /dev/null +++ b/docs/source/tut-two-party-running.rst @@ -0,0 +1,28 @@ +Running our CorDapp +=================== + + + +Conclusion +---------- +We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our +CorDapp is made up of three key parts: + +* The ``IOUState``, representing IOUs on the ledger +* The ``IOUContract``, controlling the evolution of IOUs over time +* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger. + +Together, these three parts completely determine how IOUs are created and evolved on the ledger. + +Next steps +---------- +You should now be ready to develop your own CorDapps. There's +`a more fleshed-out version of the IOU CorDapp `_ +with an API and web front-end, and a set of example CorDapps in +`the main Corda repo `_, under ``samples``. An explanation of how to run these +samples :doc:`here `. + +As you write CorDapps, you can learn more about the API available :doc:`here `. + +If you get stuck at any point, please reach out on `Slack `_, +`Discourse `_, or `Stack Overflow `_. \ No newline at end of file diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst index bd92064680..faaa498335 100644 --- a/docs/source/tutorials-index.rst +++ b/docs/source/tutorials-index.rst @@ -5,6 +5,7 @@ Tutorials :maxdepth: 1 hello-world-index + tut-two-party-index tutorial-contract tutorial-contract-clauses tutorial-test-dsl From b37c73827f5d471ba61165829a0e5491dbd20e6d Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 17 Jul 2017 15:42:08 +0100 Subject: [PATCH 035/197] Splits verifySigs into verifySigsExcept and verifyRequiredSigs to clarify usage. --- .../flows/AbstractStateReplacementFlow.kt | 4 +- .../corda/core/flows/CollectSignaturesFlow.kt | 6 +-- .../net/corda/core/flows/FinalityFlow.kt | 2 +- .../kotlin/net/corda/core/flows/NotaryFlow.kt | 2 +- .../core/flows/ResolveTransactionsFlow.kt | 2 +- .../core/transactions/SignedTransaction.kt | 37 +++++++++++-------- .../corda/core/contracts/TransactionTests.kt | 28 +++++++------- .../core/flows/CollectSignaturesFlowTests.kt | 4 +- .../net/corda/core/flows/FinalityFlowTests.kt | 2 +- .../core/flows/ManualFinalityFlowTests.kt | 2 +- .../TransactionSerializationTests.kt | 6 +-- docs/source/api-transactions.rst | 19 +++++----- .../java/net/corda/docs/FlowCookbookJava.java | 4 +- .../net/corda/docs/CustomNotaryTutorial.kt | 2 +- .../kotlin/net/corda/docs/FlowCookbook.kt | 4 +- .../corda/docs/FxTransactionBuildTutorial.kt | 4 +- .../docs/WorkflowTransactionBuildTutorial.kt | 4 +- .../source/tutorial-building-transactions.rst | 4 +- .../corda/contracts/asset/ObligationTests.kt | 4 +- .../transactions/ValidatingNotaryFlow.kt | 2 +- 20 files changed, 75 insertions(+), 67 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt index b34ab2a65b..774514f819 100644 --- a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt @@ -167,14 +167,14 @@ abstract class AbstractStateReplacementFlow { val mySignature = sign(stx) val swapSignatures = sendAndReceive>(otherSide, mySignature) - // TODO: This step should not be necessary, as signatures are re-checked in verifySignatures. + // TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures. val allSignatures = swapSignatures.unwrap { signatures -> signatures.forEach { it.verify(stx.id) } signatures } val finalTx = stx + allSignatures - finalTx.verifySignatures() + finalTx.verifyRequiredSignatures() serviceHub.recordTransactions(finalTx) } 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 1e49baed1c..5fc859f0f6 100644 --- a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt @@ -86,7 +86,7 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction, } // The signatures must be valid and the transaction must be valid. - partiallySignedTx.verifySignatures(*notSigned.toTypedArray()) + partiallySignedTx.verifySignaturesExcept(*notSigned.toTypedArray()) partiallySignedTx.tx.toLedgerTransaction(serviceHub).verify() // Determine who still needs to sign. @@ -105,7 +105,7 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction, // Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures. progressTracker.currentStep = VERIFYING - if (notaryKey != null) stx.verifySignatures(notaryKey) else stx.verifySignatures() + if (notaryKey != null) stx.verifySignaturesExcept(notaryKey) else stx.verifyRequiredSignatures() return stx } @@ -223,7 +223,7 @@ abstract class SignTransactionFlow(val otherParty: Party, val signed = stx.sigs.map { it.by } val allSigners = stx.tx.mustSign val notSigned = allSigners - signed - stx.verifySignatures(*notSigned.toTypedArray()) + stx.verifySignaturesExcept(*notSigned.toTypedArray()) } /** diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index 31e6f2048d..d4a819724f 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -159,7 +159,7 @@ open class FinalityFlow(val transactions: Iterable, return sorted.map { stx -> val notary = stx.tx.notary // The notary signature(s) are allowed to be missing but no others. - val wtx = if (notary != null) stx.verifySignatures(notary.owningKey) else stx.verifySignatures() + val wtx = if (notary != null) stx.verifySignaturesExcept(notary.owningKey) else stx.verifyRequiredSignatures() val ltx = wtx.toLedgerTransaction(augmentedLookup) ltx.verify() stx to ltx diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index e452ebc560..4d64302f5b 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -51,7 +51,7 @@ object NotaryFlow { "Input states must have the same Notary" } try { - stx.verifySignatures(notaryParty.owningKey) + stx.verifySignaturesExcept(notaryParty.owningKey) } catch (ex: SignatureException) { throw NotaryException(NotaryError.TransactionInvalid(ex)) } diff --git a/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt index c1b2045476..325802548c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ResolveTransactionsFlow.kt @@ -119,7 +119,7 @@ class ResolveTransactionsFlow(private val txHashes: Set, // be a clearer API if we do that. But for consistency with the other c'tor we currently do not. // // If 'stx' is set, then 'wtx' is the contents (from the c'tor). - val wtx = stx?.verifySignatures() ?: wtx + val wtx = stx?.verifyRequiredSignatures() ?: wtx wtx?.let { fetchMissingAttachments(listOf(it)) val ltx = it.toLedgerTransaction(serviceHub) diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index 89f7e082b5..a0b2b0a654 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -54,6 +54,17 @@ data class SignedTransaction(val txBits: SerializedBytes, class SignaturesMissingException(val missing: NonEmptySet, val descriptions: List, override val id: SecureHash) : NamedByHash, SignatureException("Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}") + /** + * Verifies the signatures on this transaction and throws if any are missing. In this context, "verifying" means + * checking they are valid signatures and that their public keys are in the contained transactions + * [BaseTransaction.mustSign] property. + * + * @throws SignatureException if any signatures are invalid or unrecognised. + * @throws SignaturesMissingException if any signatures should have been present but were not. + */ + @Throws(SignatureException::class) + fun verifyRequiredSignatures() = verifySignaturesExcept() + /** * Verifies the signatures on this transaction and throws if any are missing which aren't passed as parameters. * In this context, "verifying" means checking they are valid signatures and that their public keys are in @@ -68,18 +79,14 @@ data class SignedTransaction(val txBits: SerializedBytes, */ // DOCSTART 2 @Throws(SignatureException::class) - fun verifySignatures(vararg allowedToBeMissing: PublicKey): WireTransaction { + fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey): WireTransaction { // DOCEND 2 // Embedded WireTransaction is not deserialised until after we check the signatures. checkSignaturesAreValid() - val missing = getMissingSignatures() - if (missing.isNotEmpty()) { - val allowed = allowedToBeMissing.toSet() - val needed = missing - allowed - if (needed.isNotEmpty()) + val needed = getMissingSignatures() - allowedToBeMissing + if (needed.isNotEmpty()) throw SignaturesMissingException(needed.toNonEmptySet(), getMissingKeyDescriptions(needed), id) - } check(tx.id == id) return tx } @@ -88,7 +95,7 @@ data class SignedTransaction(val txBits: SerializedBytes, * Mathematically validates the signatures that are present on this transaction. This does not imply that * the signatures are by the right keys, or that there are sufficient signatures, just that they aren't * corrupt. If you use this function directly you'll need to do the other checks yourself. Probably you - * want [verifySignatures] instead. + * want [verifySignaturesExcept] instead. * * @throws SignatureException if a signature fails to verify. */ @@ -136,8 +143,8 @@ data class SignedTransaction(val txBits: SerializedBytes, operator fun plus(sigList: Collection) = withAdditionalSignatures(sigList) /** - * Checks the transaction's signatures are valid, optionally calls [verifySignatures] to check - * all required signatures are present, and then calls [WireTransaction.toLedgerTransaction] + * Checks the transaction's signatures are valid, optionally calls [verifyRequiredSignatures] to + * check all required signatures are present, and then calls [WireTransaction.toLedgerTransaction] * with the passed in [ServiceHub] to resolve the dependencies, returning an unverified * LedgerTransaction. * @@ -154,14 +161,14 @@ data class SignedTransaction(val txBits: SerializedBytes, @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction { checkSignaturesAreValid() - if (checkSufficientSignatures) verifySignatures() + if (checkSufficientSignatures) verifyRequiredSignatures() return tx.toLedgerTransaction(services) } /** - * Checks the transaction's signatures are valid, optionally calls [verifySignatures] to check - * all required signatures are present, calls [WireTransaction.toLedgerTransaction] with the - * passed in [ServiceHub] to resolve the dependencies and return an unverified + * Checks the transaction's signatures are valid, optionally calls [verifyRequiredSignatures] + * to check all required signatures are present, calls [WireTransaction.toLedgerTransaction] + * with the passed in [ServiceHub] to resolve the dependencies and return an unverified * LedgerTransaction, then verifies the LedgerTransaction. * * @throws AttachmentResolutionException if a required attachment was not found in storage. @@ -173,7 +180,7 @@ data class SignedTransaction(val txBits: SerializedBytes, @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) { checkSignaturesAreValid() - if (checkSufficientSignatures) verifySignatures() + if (checkSufficientSignatures) verifyRequiredSignatures() tx.toLedgerTransaction(services).verify() } diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt index d4308446e2..400fc7fff7 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt @@ -46,18 +46,18 @@ class TransactionTests { ) assertEquals( setOf(compKey, DUMMY_KEY_2.public), - assertFailsWith { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing + assertFailsWith { makeSigned(wtx, DUMMY_KEY_1).verifyRequiredSignatures() }.missing ) assertEquals( setOf(compKey, DUMMY_KEY_2.public), - assertFailsWith { makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures() }.missing + assertFailsWith { makeSigned(wtx, DUMMY_KEY_1, ak).verifyRequiredSignatures() }.missing ) - makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk).verifySignatures() - makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ck).verifySignatures() - makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk, ck).verifySignatures() - makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak).verifySignatures(compKey) - makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures(compKey, DUMMY_KEY_2.public) // Mixed allowed to be missing. + makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk).verifyRequiredSignatures() + makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ck).verifyRequiredSignatures() + makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk, ck).verifyRequiredSignatures() + makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak).verifySignaturesExcept(compKey) + makeSigned(wtx, DUMMY_KEY_1, ak).verifySignaturesExcept(compKey, DUMMY_KEY_2.public) // Mixed allowed to be missing. } @Test @@ -72,25 +72,25 @@ class TransactionTests { type = TransactionType.General, timeWindow = null ) - assertFailsWith { makeSigned(wtx).verifySignatures() } + assertFailsWith { makeSigned(wtx).verifyRequiredSignatures() } assertEquals( setOf(DUMMY_KEY_1.public), - assertFailsWith { makeSigned(wtx, DUMMY_KEY_2).verifySignatures() }.missing + assertFailsWith { makeSigned(wtx, DUMMY_KEY_2).verifyRequiredSignatures() }.missing ) assertEquals( setOf(DUMMY_KEY_2.public), - assertFailsWith { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing + assertFailsWith { makeSigned(wtx, DUMMY_KEY_1).verifyRequiredSignatures() }.missing ) assertEquals( setOf(DUMMY_KEY_2.public), - assertFailsWith { makeSigned(wtx, DUMMY_CASH_ISSUER_KEY).verifySignatures(DUMMY_KEY_1.public) }.missing + assertFailsWith { makeSigned(wtx, DUMMY_CASH_ISSUER_KEY).verifySignaturesExcept(DUMMY_KEY_1.public) }.missing ) - makeSigned(wtx, DUMMY_KEY_1).verifySignatures(DUMMY_KEY_2.public) - makeSigned(wtx, DUMMY_KEY_2).verifySignatures(DUMMY_KEY_1.public) + makeSigned(wtx, DUMMY_KEY_1).verifySignaturesExcept(DUMMY_KEY_2.public) + makeSigned(wtx, DUMMY_KEY_2).verifySignaturesExcept(DUMMY_KEY_1.public) - makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2).verifySignatures() + makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2).verifyRequiredSignatures() } @Test diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index f62050a35a..a322952e49 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -141,7 +141,7 @@ class CollectSignaturesFlowTests { val flow = a.services.startFlow(TestFlowTwo.Initiator(state)) mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() - result.verifySignatures() + result.verifyRequiredSignatures() println(result.tx) println(result.sigs) } @@ -153,7 +153,7 @@ class CollectSignaturesFlowTests { val flow = a.services.startFlow(CollectSignaturesFlow(ptx)) mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() - result.verifySignatures() + result.verifyRequiredSignatures() println(result.tx) println(result.sigs) } diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index d632137c11..1e4ee26c3a 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -47,7 +47,7 @@ class FinalityFlowTests { mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() val notarisedTx = result.single() - notarisedTx.verifySignatures() + notarisedTx.verifyRequiredSignatures() val transactionSeenByB = nodeB.services.database.transaction { nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) } diff --git a/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt index b96c9585d2..0b1be4e0d2 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt @@ -50,7 +50,7 @@ class ManualFinalityFlowTests { mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() val notarisedTx = result.single() - notarisedTx.verifySignatures() + notarisedTx.verifyRequiredSignatures() // We override the participants, so node C will get a copy despite not being involved, and B won't val transactionSeenByB = nodeB.services.database.transaction { nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 4e34ebfb07..9456e8f21f 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -64,12 +64,12 @@ class TransactionSerializationTests { val stx = notaryServices.addSignature(ptx) // Now check that the signature we just made verifies. - stx.verifySignatures() + stx.verifyRequiredSignatures() // Corrupt the data and ensure the signature catches the problem. stx.id.bytes[5] = stx.id.bytes[5].inc() assertFailsWith(SignatureException::class) { - stx.verifySignatures() + stx.verifyRequiredSignatures() } } @@ -92,7 +92,7 @@ class TransactionSerializationTests { val dummyServices = MockServices(DUMMY_KEY_2) val stx2 = dummyServices.addSignature(ptx2) - stx.copy(sigs = stx2.sigs).verifySignatures() + stx.copy(sigs = stx2.sigs).verifyRequiredSignatures() } } diff --git a/docs/source/api-transactions.rst b/docs/source/api-transactions.rst index a9a62e89e8..3fe7d89643 100644 --- a/docs/source/api-transactions.rst +++ b/docs/source/api-transactions.rst @@ -382,10 +382,11 @@ A ``SignedTransaction`` is a combination of: :start-after: DOCSTART 1 :end-before: DOCEND 1 -Before adding our signature to the transaction, we'll want to verify both the transaction itself and its signatures. +Before adding our signature to the transaction, we'll want to verify both the transaction's contents and the +transaction's signatures. -Verifying the transaction -^^^^^^^^^^^^^^^^^^^^^^^^^ +Verifying the transaction's contents +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't currently have in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow called ``ResolveTransactionsFlow``. See :doc:`api-flows` for more details. @@ -460,10 +461,10 @@ the contract. Here's an example of how we might do this: :end-before: DOCEND 34 :dedent: 12 -Verifying the signatures -^^^^^^^^^^^^^^^^^^^^^^^^ -We also need to verify the signatures over the transaction to prevent tampering. We do this using -``SignedTransaction.verifySignatures``: +Verifying the transaction's signatures +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +We also need to verify that the transaction has all the required signatures, and that these signatures are valid, to +prevent tampering. We do this using ``SignedTransaction.verifyRequiredSignatures``: .. container:: codeset @@ -479,8 +480,8 @@ We also need to verify the signatures over the transaction to prevent tampering. :end-before: DOCEND 35 :dedent: 12 -Optionally, we can pass ``verifySignatures`` a ``vararg`` of the public keys for which the signatures are allowed -to be missing: +Alternatively, we can use ``SignedTransaction.verifySignaturesExcept``, which takes a ``vararg`` of the public keys for +which the signatures are allowed to be missing: .. container:: codeset diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index fec84eb069..2abc9a3316 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -474,14 +474,14 @@ public class FlowCookbookJava { // We can verify that a transaction has all the required // signatures, and that they're all valid, by running: // DOCSTART 35 - fullySignedTx.verifySignatures(); + fullySignedTx.verifyRequiredSignatures(); // DOCEND 35 // If the transaction is only partially signed, we have to pass in // a list of the public keys corresponding to the missing // signatures, explicitly telling the system not to check them. // DOCSTART 36 - onceSignedTx.verifySignatures(counterpartyPubKey); + onceSignedTx.verifySignaturesExcept(counterpartyPubKey); // DOCEND 36 // We can also choose to only check the signatures that are diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt index 8a4b528d28..0d791a0d29 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt @@ -60,7 +60,7 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary private fun checkSignatures(stx: SignedTransaction) { try { - stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey) + stx.verifySignaturesExcept(serviceHub.myInfo.notaryIdentity.owningKey) } catch(e: SignatureException) { throw NotaryException(NotaryError.TransactionInvalid(e)) } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index a5a0150b22..6640084b68 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -457,14 +457,14 @@ object FlowCookbook { // We can verify that a transaction has all the required // signatures, and that they're all valid, by running: // DOCSTART 35 - fullySignedTx.verifySignatures() + fullySignedTx.verifyRequiredSignatures() // DOCEND 35 // If the transaction is only partially signed, we have to pass in // a list of the public keys corresponding to the missing // signatures, explicitly telling the system not to check them. // DOCSTART 36 - onceSignedTx.verifySignatures(counterpartyPubKey) + onceSignedTx.verifySignaturesExcept(counterpartyPubKey) // DOCEND 36 // We can also choose to only check the signatures that are diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt index b684d9e2eb..bfb7407a51 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt @@ -159,7 +159,7 @@ class ForeignExchangeFlow(val tradeId: String, val allPartySignedTx = sendAndReceive(remoteRequestWithNotary.owner, signedTransaction).unwrap { val withNewSignature = signedTransaction + it // check all signatures are present except the notary - withNewSignature.verifySignatures(withNewSignature.tx.notary!!.owningKey) + withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey) // This verifies that the transaction is contract-valid, even though it is missing signatures. // In a full solution there would be states tracking the trade request which @@ -234,7 +234,7 @@ class ForeignExchangeRemoteFlow(val source: Party) : FlowLogic() { val proposedTrade = sendAndReceive(source, ourResponse).unwrap { val wtx = it.tx // check all signatures are present except our own and the notary - it.verifySignatures(ourKey, wtx.notary!!.owningKey) + it.verifySignaturesExcept(ourKey, wtx.notary!!.owningKey) // We need to fetch their complete input states and dependencies so that verify can operate checkDependencies(it) diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index ffff6d14cb..a19e6f7645 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -197,7 +197,7 @@ class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : Flow val agreedTx = selfSignedTx + it // Receive back their signature and confirm that it is for an unmodified transaction // Also that the only missing signature is from teh Notary - agreedTx.verifySignatures(notary.owningKey) + agreedTx.verifySignaturesExcept(notary.owningKey) // Recheck the data of the transaction. Note we run toLedgerTransaction on the WireTransaction // as we do not have all the signature. agreedTx.tx.toLedgerTransaction(serviceHub).verify() @@ -226,7 +226,7 @@ class RecordCompletionFlow(val source: Party) : FlowLogic() { // First we receive the verdict transaction signed by their single key val completeTx = receive(source).unwrap { // Check the transaction is signed apart from our own key and the notary - val wtx = it.verifySignatures(serviceHub.myInfo.legalIdentity.owningKey, it.tx.notary!!.owningKey) + val wtx = it.verifySignaturesExcept(serviceHub.myInfo.legalIdentity.owningKey, it.tx.notary!!.owningKey) // Check the transaction data is correctly formed wtx.toLedgerTransaction(serviceHub).verify() // Confirm that this is the expected type of transaction diff --git a/docs/source/tutorial-building-transactions.rst b/docs/source/tutorial-building-transactions.rst index 0b6da351b2..31fcb12cde 100644 --- a/docs/source/tutorial-building-transactions.rst +++ b/docs/source/tutorial-building-transactions.rst @@ -270,8 +270,8 @@ apply any new signatures to its original proposal to ensure the contents of the transaction has not been altered by the remote parties. The typical code therefore checks the received ``SignedTransaction`` -using the ``verifySignatures`` method, but excluding itself, the notary -and any other parties yet to apply their signature. The contents of the +using the ``verifySignaturesExcept`` method, excluding itself, the +notary and any other parties yet to apply their signature. The contents of the ``WireTransaction`` inside the ``SignedTransaction`` should be fully verified further by expanding with ``toLedgerTransaction`` and calling ``verify``. Further context specific and business checks should then be diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index 269234eb3b..131a30036a 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -291,7 +291,7 @@ class ObligationTests { assertEquals(1, stx.tx.outputs.size) assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.DEFAULTED), stx.tx.outputs[0].data) - stx.verifySignatures() + stx.verifyRequiredSignatures() // And set it back stateAndRef = stx.tx.outRef>(0) @@ -302,7 +302,7 @@ class ObligationTests { stx = notaryServices.addSignature(ptx) assertEquals(1, stx.tx.outputs.size) assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.NORMAL), stx.tx.outputs[0].data) - stx.verifySignatures() + stx.verifyRequiredSignatures() } /** Test generating a transaction to settle an obligation. */ diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index daf70b1b24..60688e14d5 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -33,7 +33,7 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ private fun checkSignatures(stx: SignedTransaction) { try { - stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey) + stx.verifySignaturesExcept(serviceHub.myInfo.notaryIdentity.owningKey) } catch(e: SignatureException) { throw NotaryException(NotaryError.TransactionInvalid(e)) } From 195189070a4d563dba24c8007c723a56445cc5a2 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 17 Jul 2017 13:38:19 +0100 Subject: [PATCH 036/197] Moved the Path extension methods that were in Utils.kt into the internal package --- .../corda/kotlin/rpc/ValidateClasspathTest.kt | 2 +- .../net/corda/core/internal/package-info.java | 4 ++ core/src/main/kotlin/net/corda/core/Utils.kt | 44 ++---------------- .../net/corda/core/internal/InternalUtils.kt | 46 +++++++++++++++++++ .../corda/core/crypto/CompositeKeyTests.kt | 2 +- .../corda/core/crypto/X509UtilitiesTest.kt | 2 +- .../nodeapi/ArtemisMessagingComponent.kt | 2 +- .../corda/nodeapi/config/SSLConfiguration.kt | 2 +- .../corda/nodeapi/config/ConfigParsingTest.kt | 2 +- .../kotlin/net/corda/node/BootTests.kt | 2 +- .../node/services/BFTNotaryServiceTests.kt | 2 +- .../messaging/MQSecurityAsNodeTest.kt | 6 +-- .../main/kotlin/net/corda/node/ArgsParser.kt | 2 +- .../net/corda/node/internal/AbstractNode.kt | 1 + .../net/corda/node/internal/NodeStartup.kt | 2 + .../node/services/config/ConfigUtilities.kt | 8 ++-- .../messaging/ArtemisMessagingServer.kt | 1 + .../persistence/NodeAttachmentService.kt | 2 +- .../services/transactions/BFTSMaRtConfig.kt | 2 +- .../net/corda/node/shell/InteractiveShell.kt | 3 ++ .../corda/node/utilities/KeyStoreUtilities.kt | 6 +-- .../registration/NetworkRegistrationHelper.kt | 1 + .../node/CordappScanningNodeProcessTest.kt | 6 +-- .../kotlin/net/corda/node/ArgsParserTest.kt | 2 +- .../persistence/NodeAttachmentStorageTest.kt | 8 ++-- .../services/transactions/PathManagerTests.kt | 2 +- .../NetworkisRegistrationHelperTest.kt | 2 +- .../kotlin/net/corda/attachmentdemo/Main.kt | 2 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 2 +- .../corda/notarydemo/RaftNotaryCordform.kt | 2 +- .../corda/notarydemo/SingleNotaryCordform.kt | 2 +- .../example/OGSwapPricingCcpExample.kt | 4 +- .../test/kotlin/net/corda/traderdemo/Main.kt | 2 +- .../net/corda/smoketesting/NodeProcess.kt | 4 +- .../net/corda/testing/driver/DriverTests.kt | 6 +-- .../net/corda/testing/ProjectStructure.kt | 4 +- .../kotlin/net/corda/testing/RPCDriver.kt | 2 +- .../kotlin/net/corda/testing/driver/Driver.kt | 1 + .../corda/testing/driver/ProcessUtilities.kt | 4 +- .../kotlin/net/corda/testing/node/MockNode.kt | 3 ++ .../net/corda/testing/node/NodeBasedTest.kt | 2 + .../net/corda/demobench/explorer/Explorer.kt | 6 +-- .../net/corda/demobench/views/NodeTabView.kt | 8 ++-- .../corda/demobench/model/NodeConfigTest.kt | 2 +- .../net/corda/explorer/model/SettingsModel.kt | 6 +-- .../corda/explorer/model/SettingsModelTest.kt | 2 +- .../net/corda/verifier/VerifierDriver.kt | 2 +- .../kotlin/net/corda/verifier/Verifier.kt | 2 +- .../net/corda/webserver/WebArgsParser.kt | 2 +- .../kotlin/net/corda/webserver/WebServer.kt | 2 +- 50 files changed, 133 insertions(+), 103 deletions(-) create mode 100644 core/src/main/java/net/corda/core/internal/package-info.java create mode 100644 core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt index ecc534ca8a..f79e97641a 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/ValidateClasspathTest.kt @@ -1,6 +1,6 @@ package net.corda.kotlin.rpc -import net.corda.core.div +import net.corda.core.internal.div import org.junit.Test import java.io.File import java.nio.file.Path diff --git a/core/src/main/java/net/corda/core/internal/package-info.java b/core/src/main/java/net/corda/core/internal/package-info.java new file mode 100644 index 0000000000..aa06d1bace --- /dev/null +++ b/core/src/main/java/net/corda/core/internal/package-info.java @@ -0,0 +1,4 @@ +/** + * WARNING: This is an internal package and not part of the public API. Do not use anything found here or in any sub-package. + */ +package net.corda.core.internal; \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index a169623d80..bf38a24d07 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -9,6 +9,9 @@ import com.google.common.util.concurrent.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.FlowException +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.write import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger import rx.Observable @@ -17,10 +20,8 @@ import rx.subjects.PublishSubject import rx.subjects.UnicastSubject import java.io.* import java.math.BigDecimal -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets.UTF_8 -import java.nio.file.* -import java.nio.file.attribute.FileAttribute +import java.nio.file.Files +import java.nio.file.Path import java.time.Duration import java.time.temporal.Temporal import java.util.concurrent.CompletableFuture @@ -28,7 +29,6 @@ import java.util.concurrent.ExecutionException import java.util.concurrent.Future import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock -import java.util.stream.Stream import java.util.zip.Deflater import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -107,40 +107,6 @@ fun ListenableFuture.toObservable(): Observable { } } -/** Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform separator problems. */ -operator fun Path.div(other: String): Path = resolve(other) -operator fun String.div(other: String): Path = Paths.get(this) / other - -fun Path.createDirectory(vararg attrs: FileAttribute<*>): Path = Files.createDirectory(this, *attrs) -fun Path.createDirectories(vararg attrs: FileAttribute<*>): Path = Files.createDirectories(this, *attrs) -fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options) -fun Path.copyToDirectory(targetDir: Path, vararg options: CopyOption): Path { - require(targetDir.isDirectory()) { "$targetDir is not a directory" } - val targetFile = targetDir.resolve(fileName) - Files.copy(this, targetFile, *options) - return targetFile -} -fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options) -fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) -fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options) -val Path.size: Long get() = Files.size(this) -inline fun Path.list(block: (Stream) -> R): R = Files.list(this).use(block) -fun Path.deleteIfExists(): Boolean = Files.deleteIfExists(this) -fun Path.readAll(): ByteArray = Files.readAllBytes(this) -inline fun Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = Files.newInputStream(this, *options).use(block) -inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = emptyArray(), block: (OutputStream) -> Unit) { - if (createDirs) { - normalize().parent?.createDirectories() - } - Files.newOutputStream(this, *options).use(block) -} - -inline fun Path.readLines(charset: Charset = UTF_8, block: (Stream) -> R): R = Files.lines(this, charset).use(block) -fun Path.readAllLines(charset: Charset = UTF_8): List = Files.readAllLines(this, charset) -fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) - -fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) - // Simple infix function to add back null safety that the JDK lacks: timeA until timeB infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt new file mode 100644 index 0000000000..45c6629d62 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -0,0 +1,46 @@ +package net.corda.core.internal + +import java.io.InputStream +import java.io.OutputStream +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.* +import java.nio.file.attribute.FileAttribute +import java.util.stream.Stream + +/** + * Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform + * separator problems. + */ +operator fun Path.div(other: String): Path = resolve(other) +operator fun String.div(other: String): Path = Paths.get(this) / other + +fun Path.createDirectory(vararg attrs: FileAttribute<*>): Path = Files.createDirectory(this, *attrs) +fun Path.createDirectories(vararg attrs: FileAttribute<*>): Path = Files.createDirectories(this, *attrs) +fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options) +fun Path.copyToDirectory(targetDir: Path, vararg options: CopyOption): Path { + require(targetDir.isDirectory()) { "$targetDir is not a directory" } + val targetFile = targetDir.resolve(fileName) + Files.copy(this, targetFile, *options) + return targetFile +} +fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options) +fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) +fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options) +inline val Path.size: Long get() = Files.size(this) +inline fun Path.list(block: (Stream) -> R): R = Files.list(this).use(block) +fun Path.deleteIfExists(): Boolean = Files.deleteIfExists(this) +fun Path.readAll(): ByteArray = Files.readAllBytes(this) +inline fun Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = Files.newInputStream(this, *options).use(block) +inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = emptyArray(), block: (OutputStream) -> Unit) { + if (createDirs) { + normalize().parent?.createDirectories() + } + Files.newOutputStream(this, *options).use(block) +} + +inline fun Path.readLines(charset: Charset = UTF_8, block: (Stream) -> R): R = Files.lines(this, charset).use(block) +fun Path.readAllLines(charset: Charset = UTF_8): List = Files.readAllLines(this, charset) +fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) + +fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index c0816b10ed..d0d773951c 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -3,7 +3,7 @@ package net.corda.core.crypto import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.composite.CompositeSignature import net.corda.core.crypto.composite.CompositeSignaturesWithKeys -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.node.utilities.loadKeyStore diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt index e2247edb7d..1b6e651035 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt @@ -4,7 +4,7 @@ import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.toTypedArray import net.corda.node.services.config.createKeystoreForCordaNode import net.corda.node.utilities.* diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt index a37a841f83..f1ad6582a1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt @@ -6,7 +6,7 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceType -import net.corda.core.read +import net.corda.core.internal.read import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.NetworkHostAndPort diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt index 13a70eb517..6fb82e508f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.config -import net.corda.core.div +import net.corda.core.internal.div import java.nio.file.Path interface SSLConfiguration { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt index a3abc82492..1d7b061fad 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/config/ConfigParsingTest.kt @@ -4,7 +4,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory.empty import com.typesafe.config.ConfigRenderOptions.defaults import com.typesafe.config.ConfigValueFactory -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.getTestX509Name import org.assertj.core.api.Assertions.assertThat diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index b5366a7ace..3048c19417 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -1,7 +1,7 @@ package net.corda.node import co.paralleluniverse.fibers.Suspendable -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.getOrThrow diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index f361a57a2e..5889c4d269 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -6,7 +6,7 @@ import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash import net.corda.core.crypto.composite.CompositeKey -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryFlow diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index a5abf5f336..6d3dbd452d 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -1,11 +1,11 @@ package net.corda.services.messaging -import net.corda.core.copyTo -import net.corda.core.createDirectories +import net.corda.core.internal.copyTo +import net.corda.core.internal.createDirectories import net.corda.core.crypto.CertificateType import net.corda.core.crypto.Crypto import net.corda.core.crypto.X509Utilities -import net.corda.core.exists +import net.corda.core.internal.exists import net.corda.node.utilities.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt index 6168e4b99b..1527beec44 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt @@ -2,7 +2,7 @@ package net.corda.node import joptsimple.OptionParser import joptsimple.util.EnumConverter -import net.corda.core.div +import net.corda.core.internal.div import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.FullNodeConfiguration import net.corda.nodeapi.config.parseAs diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 2420a736bd..3557eff7e7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -14,6 +14,7 @@ import net.corda.core.crypto.composite.CompositeKey import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.* import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index f819f8fa31..33bdffcadc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -6,6 +6,8 @@ import joptsimple.OptionException import net.corda.core.* import net.corda.core.crypto.commonName import net.corda.core.crypto.orgName +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.node.VersionInfo import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.Emoji diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 882de35155..b1217548d0 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -4,11 +4,11 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions -import net.corda.core.copyTo -import net.corda.core.createDirectories +import net.corda.core.internal.copyTo +import net.corda.core.internal.createDirectories import net.corda.core.crypto.* -import net.corda.core.div -import net.corda.core.exists +import net.corda.core.internal.div +import net.corda.core.internal.exists import net.corda.core.utilities.loggerFor import net.corda.node.utilities.* import net.corda.nodeapi.config.SSLConfiguration diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index c300cfd69a..e84db12439 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -7,6 +7,7 @@ import net.corda.core.* import net.corda.core.crypto.* import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache.MapChange diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index bbb4ffc340..9021f45af6 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -9,7 +9,7 @@ import com.google.common.io.CountingInputStream import net.corda.core.contracts.AbstractAttachment import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash -import net.corda.core.isDirectory +import net.corda.core.internal.isDirectory import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* import net.corda.core.utilities.loggerFor diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt index 72c0212eb2..28f6375c60 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt @@ -1,6 +1,6 @@ package net.corda.node.services.transactions -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index 553890b946..b9ed79ce82 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -13,6 +13,9 @@ import net.corda.core.* import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.write import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.StateMachineUpdate import net.corda.core.utilities.Emoji diff --git a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt index f4a26d5b28..161a4f8142 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt @@ -3,9 +3,9 @@ package net.corda.node.utilities import net.corda.core.crypto.CertificateAndKeyPair import net.corda.core.crypto.Crypto import net.corda.core.crypto.cert -import net.corda.core.exists -import net.corda.core.read -import net.corda.core.write +import net.corda.core.internal.exists +import net.corda.core.internal.read +import net.corda.core.internal.write import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.path.CertPath import java.io.IOException diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index b362153d6d..f9942475fc 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.core.crypto.cert +import net.corda.core.internal.* import net.corda.node.services.config.NodeConfiguration import net.corda.node.utilities.* import org.bouncycastle.cert.path.CertPath diff --git a/node/src/smoke-test/kotlin/net/corda/node/CordappScanningNodeProcessTest.kt b/node/src/smoke-test/kotlin/net/corda/node/CordappScanningNodeProcessTest.kt index 87b8faad10..6ddb4dc4e1 100644 --- a/node/src/smoke-test/kotlin/net/corda/node/CordappScanningNodeProcessTest.kt +++ b/node/src/smoke-test/kotlin/net/corda/node/CordappScanningNodeProcessTest.kt @@ -1,8 +1,8 @@ package net.corda.node -import net.corda.core.copyToDirectory -import net.corda.core.createDirectories -import net.corda.core.div +import net.corda.core.internal.copyToDirectory +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.nodeapi.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess diff --git a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt index 2316f31e92..94b09c11c5 100644 --- a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt +++ b/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt @@ -1,7 +1,7 @@ package net.corda.node import joptsimple.OptionException -import net.corda.core.div +import net.corda.core.internal.div import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.Test diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index 5c33a9707a..c24f89cd2d 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -5,11 +5,11 @@ import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.read -import net.corda.core.readAll +import net.corda.core.internal.read +import net.corda.core.internal.readAll import net.corda.testing.LogHelper -import net.corda.core.write -import net.corda.core.writeLines +import net.corda.core.internal.write +import net.corda.core.internal.writeLines import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.persistence.schemas.requery.AttachmentEntity import net.corda.node.services.transactions.PersistentUniquenessProvider diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PathManagerTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PathManagerTests.kt index baec774fa0..834126f17d 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PathManagerTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PathManagerTests.kt @@ -1,6 +1,6 @@ package net.corda.node.services.transactions -import net.corda.core.exists +import net.corda.core.internal.exists import org.junit.Test import java.nio.file.Files import kotlin.test.assertFailsWith diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index c08e5bc72b..95580f6d82 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -4,7 +4,7 @@ import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.mock import net.corda.core.crypto.* -import net.corda.core.exists +import net.corda.core.internal.exists import net.corda.core.toTypedArray import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt index 4528c419c0..bd586c7542 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt @@ -1,6 +1,6 @@ package net.corda.attachmentdemo -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 3f6af7fc9f..e9d72c01e6 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -1,6 +1,6 @@ package net.corda.notarydemo -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.BOB diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 10a94ea6a4..f0f5279def 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -1,7 +1,7 @@ package net.corda.notarydemo import net.corda.core.crypto.appendToCommonName -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.BOB diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index bdd1c21093..567bfe03f5 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -1,6 +1,6 @@ package net.corda.notarydemo -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.BOB diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/example/OGSwapPricingCcpExample.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/example/OGSwapPricingCcpExample.kt index 3966488829..4c4821d4f5 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/example/OGSwapPricingCcpExample.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/example/OGSwapPricingCcpExample.kt @@ -32,8 +32,8 @@ import com.opengamma.strata.product.common.BuySell import com.opengamma.strata.product.swap.type.FixedIborSwapConventions import com.opengamma.strata.report.ReportCalculationResults import com.opengamma.strata.report.trade.TradeReport -import net.corda.core.div -import net.corda.core.exists +import net.corda.core.internal.div +import net.corda.core.internal.exists import java.nio.file.Path import java.nio.file.Paths import java.time.LocalDate diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index 544055d907..d376d21ca8 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -1,6 +1,6 @@ package net.corda.traderdemo -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B diff --git a/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index f7a56f2be9..6ab3de90d7 100644 --- a/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -2,8 +2,8 @@ package net.corda.smoketesting import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.createDirectories -import net.corda.core.div +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import java.nio.file.Path diff --git a/test-utils/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/test-utils/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index a6ba60fc47..8beae0b433 100644 --- a/test-utils/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/test-utils/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -1,11 +1,11 @@ package net.corda.testing.driver import com.google.common.util.concurrent.ListenableFuture -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.getOrThrow -import net.corda.core.list +import net.corda.core.internal.list import net.corda.core.node.services.ServiceInfo -import net.corda.core.readLines +import net.corda.core.internal.readLines import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR diff --git a/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt b/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt index 3e866fdf4e..1809d4dc16 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/ProjectStructure.kt @@ -1,7 +1,7 @@ package net.corda.testing -import net.corda.core.div -import net.corda.core.isDirectory +import net.corda.core.internal.div +import net.corda.core.internal.isDirectory import java.nio.file.Path import java.nio.file.Paths diff --git a/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt b/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt index af6844273b..ff78e3deeb 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -7,7 +7,7 @@ import net.corda.client.mock.int import net.corda.client.mock.string import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.map import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index 809f3fd4b6..bf7d4c5a94 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -15,6 +15,7 @@ import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName import net.corda.core.identity.Party +import net.corda.core.internal.div import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceInfo diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt index 7ac9eedf94..c020d830a1 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt @@ -1,7 +1,7 @@ package net.corda.testing.driver -import net.corda.core.div -import net.corda.core.exists +import net.corda.core.internal.div +import net.corda.core.internal.exists import java.io.File.pathSeparator import java.nio.file.Path diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt index aaf1abb4ad..e511175760 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -11,6 +11,9 @@ import net.corda.core.crypto.cert import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.createDirectories +import net.corda.core.internal.createDirectory +import net.corda.core.internal.div import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt b/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt index 81b11bd7fc..7aa63a557f 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt @@ -7,6 +7,8 @@ import net.corda.core.* import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType import net.corda.core.utilities.WHITESPACE diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt index 52f76905be..3bf445b827 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt @@ -1,8 +1,8 @@ package net.corda.demobench.explorer -import net.corda.core.createDirectories -import net.corda.core.div -import net.corda.core.list +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.list import net.corda.core.utilities.loggerFor import net.corda.demobench.model.JVMConfig import net.corda.demobench.model.NodeConfig diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index 0049c9e194..cf9822f969 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -15,14 +15,14 @@ import javafx.scene.layout.Priority import javafx.stage.FileChooser import javafx.util.StringConverter import net.corda.core.crypto.commonName -import net.corda.core.div -import net.corda.core.exists +import net.corda.core.internal.div +import net.corda.core.internal.exists import net.corda.core.node.CityDatabase import net.corda.core.node.WorldMapLocation -import net.corda.core.readAllLines +import net.corda.core.internal.readAllLines import net.corda.core.utilities.normaliseLegalName import net.corda.core.utilities.validateLegalName -import net.corda.core.writeLines +import net.corda.core.internal.writeLines import net.corda.demobench.model.* import net.corda.demobench.ui.CloseableTab import org.controlsfx.control.CheckListView diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index 904a90f717..6fee64384a 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigValueFactory -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.DUMMY_NOTARY import net.corda.node.internal.NetworkMapInfo diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/model/SettingsModel.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/model/SettingsModel.kt index 2ee2e62814..2e85b434d8 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/model/SettingsModel.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/model/SettingsModel.kt @@ -5,9 +5,9 @@ import javafx.beans.Observable import javafx.beans.property.ObjectProperty import javafx.beans.property.SimpleObjectProperty import net.corda.core.contracts.currency -import net.corda.core.createDirectories -import net.corda.core.div -import net.corda.core.exists +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.exists import tornadofx.* import java.nio.file.Files import java.nio.file.Path diff --git a/tools/explorer/src/test/kotlin/net/corda/explorer/model/SettingsModelTest.kt b/tools/explorer/src/test/kotlin/net/corda/explorer/model/SettingsModelTest.kt index 5d78027bf0..d09cde66cb 100644 --- a/tools/explorer/src/test/kotlin/net/corda/explorer/model/SettingsModelTest.kt +++ b/tools/explorer/src/test/kotlin/net/corda/explorer/model/SettingsModelTest.kt @@ -1,6 +1,6 @@ package net.corda.explorer.model -import net.corda.core.div +import net.corda.core.internal.div import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 66315907aa..870500cb15 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -8,7 +8,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.commonName -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.map import net.corda.core.crypto.random63BitValue import net.corda.core.transactions.LedgerTransaction diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index b8df7f891b..f5c54a013a 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -3,7 +3,7 @@ package net.corda.verifier import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt index 539b812a44..d78f9087a7 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt @@ -6,7 +6,7 @@ import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions import joptsimple.OptionParser import joptsimple.util.EnumConverter -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.utilities.loggerFor import org.slf4j.event.Level import java.io.PrintStream diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt index 4f7a072466..99420e2abb 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt @@ -3,7 +3,7 @@ package net.corda.webserver import com.typesafe.config.ConfigException -import net.corda.core.div +import net.corda.core.internal.div import net.corda.core.rootCause import net.corda.webserver.internal.NodeWebServer import org.slf4j.LoggerFactory From c62387f3f6cc70bfb0e755ead3fccab0f557e1d7 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 17 Jul 2017 10:18:33 +0100 Subject: [PATCH 037/197] Fixed TransientProperty so that it actually works during (de)serialisation --- core/src/main/kotlin/net/corda/core/Utils.kt | 11 ---- .../core/serialization/CordaClassResolver.kt | 65 ++++++++++--------- .../net/corda/core/utilities/KotlinUtils.kt | 29 ++++++++- .../corda/core/utilities/ProgressTracker.kt | 4 +- .../corda/core/utilities/KotlinUtilsTest.kt | 58 +++++++++++++++++ .../kotlin/net/corda/irs/flows/FixingFlow.kt | 4 +- 6 files changed, 124 insertions(+), 47 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index bf38a24d07..3ee55d50ba 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -35,7 +35,6 @@ import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import kotlin.concurrent.withLock import kotlin.reflect.KClass -import kotlin.reflect.KProperty val Int.days: Duration get() = Duration.ofDays(this.toLong()) @Suppress("unused") // It's here for completeness @@ -215,17 +214,7 @@ class ThreadBox(val content: T, val lock: ReentrantLock = ReentrantLock() @CordaSerializable abstract class RetryableException(message: String) : FlowException(message) -/** - * A simple wrapper that enables the use of Kotlin's "val x by TransientProperty { ... }" syntax. Such a property - * will not be serialized to disk, and if it's missing (or the first time it's accessed), the initializer will be - * used to set it up. Note that the initializer will be called with the TransientProperty object locked. - */ -class TransientProperty(private val initializer: () -> T) { - @Transient private var v: T? = null - @Synchronized - operator fun getValue(thisRef: Any?, property: KProperty<*>) = v ?: initializer().also { v = it } -} /** * Given a path to a zip file, extracts it to the given directory. diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt index 11f450cac9..864167b91e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt @@ -3,12 +3,13 @@ package net.corda.core.serialization import com.esotericsoftware.kryo.* import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.Util import net.corda.core.node.AttachmentsClassLoader import net.corda.core.utilities.loggerFor import java.io.PrintWriter -import java.lang.reflect.Modifier +import java.lang.reflect.Modifier.isAbstract import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths @@ -52,18 +53,16 @@ class CordaClassResolver(val whitelist: ClassWhitelist, val amqpEnabled: Boolean } private fun checkClass(type: Class<*>): Registration? { - /** If call path has disabled whitelisting (see [CordaKryo.register]), just return without checking. */ + // If call path has disabled whitelisting (see [CordaKryo.register]), just return without checking. if (!whitelistEnabled) return null // Allow primitives, abstracts and interfaces - if (type.isPrimitive || type == Any::class.java || Modifier.isAbstract(type.modifiers) || type == String::class.java) return null + if (type.isPrimitive || type == Any::class.java || isAbstract(type.modifiers) || type == String::class.java) return null // If array, recurse on element type - if (type.isArray) { - return checkClass(type.componentType) - } - if (!type.isEnum && Enum::class.java.isAssignableFrom(type)) { - // Specialised enum entry, so just resolve the parent Enum type since cannot annotate the specialised entry. - return checkClass(type.superclass) - } + if (type.isArray) return checkClass(type.componentType) + // Specialised enum entry, so just resolve the parent Enum type since cannot annotate the specialised entry. + if (!type.isEnum && Enum::class.java.isAssignableFrom(type)) return checkClass(type.superclass) + // Kotlin lambdas require some special treatment + if (kotlin.jvm.internal.Lambda::class.java.isAssignableFrom(type)) return null // It's safe to have the Class already, since Kryo loads it with initialisation off. // If we use a whitelist with blacklisting capabilities, whitelist.hasListed(type) may throw an IllegalStateException if input class is blacklisted. // Thus, blacklisting precedes annotation checking. @@ -74,34 +73,40 @@ class CordaClassResolver(val whitelist: ClassWhitelist, val amqpEnabled: Boolean } override fun registerImplicit(type: Class<*>): Registration { - val hasAnnotation = checkForAnnotation(type) // If something is not annotated, or AMQP is disabled, we stay serializing with Kryo. This will typically be the // case for flow checkpoints (ignoring all cases where AMQP is disabled) since our top level messaging data structures // are annotated and once we enter AMQP serialisation we stay with it for the entire object subgraph. - if (!hasAnnotation || !amqpEnabled) { - val objectInstance = try { - type.kotlin.objectInstance - } catch (t: Throwable) { - // objectInstance will throw if the type is something like a lambda - null - } - // We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent. - val references = kryo.references - try { - kryo.references = true - val serializer = if (objectInstance != null) KotlinObjectSerializer(objectInstance) else kryo.getDefaultSerializer(type) - return register(Registration(type, serializer, NAME.toInt())) - } finally { - kryo.references = references - } - } else { + if (checkForAnnotation(type) && amqpEnabled) { // Build AMQP serializer return register(Registration(type, KryoAMQPSerializer, NAME.toInt())) } + + val objectInstance = try { + type.kotlin.objectInstance + } catch (t: Throwable) { + null // objectInstance will throw if the type is something like a lambda + } + + // We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent. + val references = kryo.references + try { + kryo.references = true + val serializer = if (objectInstance != null) { + KotlinObjectSerializer(objectInstance) + } else if (kotlin.jvm.internal.Lambda::class.java.isAssignableFrom(type)) { + // Kotlin lambdas extend this class and any captured variables are stored in synthentic fields + FieldSerializer(kryo, type).apply { setIgnoreSyntheticFields(false) } + } else { + kryo.getDefaultSerializer(type) + } + return register(Registration(type, serializer, NAME.toInt())) + } finally { + kryo.references = references + } } - // Trivial Serializer which simply returns the given instance which we already know is a Kotlin object - private class KotlinObjectSerializer(val objectInstance: Any) : Serializer() { + // Trivial Serializer which simply returns the given instance, which we already know is a Kotlin object + private class KotlinObjectSerializer(private val objectInstance: Any) : Serializer() { override fun read(kryo: Kryo, input: Input, type: Class): Any = objectInstance override fun write(kryo: Kryo, output: Output, obj: Any) = Unit } diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index 19e1a90e06..6df50156d3 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -1,7 +1,9 @@ package net.corda.core.utilities +import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger import org.slf4j.LoggerFactory +import kotlin.reflect.KProperty /** * Get the [Logger] for a class using the syntax @@ -20,5 +22,30 @@ inline fun Logger.debug(msg: () -> String) { if (isDebugEnabled) debug(msg()) } +/** + * A simple wrapper that enables the use of Kotlin's `val x by transient { ... }` syntax. Such a property + * will not be serialized, and if it's missing (or the first time it's accessed), the initializer will be + * used to set it up. + */ +@Suppress("DEPRECATION") +fun transient(initializer: () -> T) = TransientProperty(initializer) + +@Deprecated("Use transient") +@CordaSerializable +class TransientProperty(private val initialiser: () -> T) { + @Transient private var initialised = false + @Transient private var value: T? = null + + @Suppress("UNCHECKED_CAST") + @Synchronized + operator fun getValue(thisRef: Any?, property: KProperty<*>): T { + if (!initialised) { + value = initialiser() + initialised = true + } + return value as T + } +} + /** @see NonEmptySet.copyOf */ -fun Collection.toNonEmptySet(): NonEmptySet = NonEmptySet.copyOf(this) \ No newline at end of file +fun Collection.toNonEmptySet(): NonEmptySet = NonEmptySet.copyOf(this) diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index fc788eb10c..6cd9526795 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -1,10 +1,8 @@ package net.corda.core.utilities -import net.corda.core.TransientProperty import net.corda.core.serialization.CordaSerializable import rx.Observable import rx.Subscription -import rx.subjects.BehaviorSubject import rx.subjects.PublishSubject import java.util.* @@ -76,7 +74,7 @@ class ProgressTracker(vararg steps: Step) { val steps = arrayOf(UNSTARTED, *steps, DONE) // This field won't be serialized. - private val _changes by TransientProperty { PublishSubject.create() } + private val _changes by transient { PublishSubject.create() } @CordaSerializable private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?) diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt new file mode 100644 index 0000000000..b58c74a51f --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt @@ -0,0 +1,58 @@ +package net.corda.core.utilities + +import net.corda.core.crypto.random63BitValue +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class KotlinUtilsTest { + @Test + fun `transient property which is null`() { + val test = NullTransientProperty() + test.transientValue + test.transientValue + assertThat(test.evalCount).isEqualTo(1) + } + + @Test + fun `transient property with non-capturing lamba`() { + val original = NonCapturingTransientProperty() + val originalVal = original.transientVal + val copy = original.serialize().deserialize() + val copyVal = copy.transientVal + assertThat(copyVal).isNotEqualTo(originalVal) + assertThat(copy.transientVal).isEqualTo(copyVal) + } + + @Test + fun `transient property with capturing lamba`() { + val original = CapturingTransientProperty("Hello") + val originalVal = original.transientVal + val copy = original.serialize().deserialize() + val copyVal = copy.transientVal + assertThat(copyVal).isNotEqualTo(originalVal) + assertThat(copy.transientVal).isEqualTo(copyVal) + assertThat(copy.transientVal).startsWith("Hello") + } + + private class NullTransientProperty { + var evalCount = 0 + val transientValue by transient { + evalCount++ + null + } + } + + @CordaSerializable + private class NonCapturingTransientProperty { + val transientVal by transient { random63BitValue() } + } + + @CordaSerializable + private class CapturingTransientProperty(prefix: String) { + private val seed = random63BitValue() + val transientVal by transient { prefix + seed + random63BitValue() } + } +} \ No newline at end of file diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt index 57110870aa..9b8a11cd96 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt @@ -3,7 +3,6 @@ package net.corda.irs.flows import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.Fix import net.corda.contracts.FixableDealState -import net.corda.core.TransientProperty import net.corda.core.contracts.* import net.corda.core.crypto.toBase58String import net.corda.core.flows.FlowLogic @@ -17,6 +16,7 @@ import net.corda.core.seconds import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.transient import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.trace import net.corda.flows.TwoPartyDealFlow @@ -103,7 +103,7 @@ object FixingFlow { override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() { @Suppress("UNCHECKED_CAST") - internal val dealToFix: StateAndRef by TransientProperty { + internal val dealToFix: StateAndRef by transient { val state = serviceHub.loadState(payload.ref) as TransactionState StateAndRef(state, payload.ref) } From 8596f1cc17a607b3472dc2fcb08d466621005e4d Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Mon, 17 Jul 2017 17:56:02 +0100 Subject: [PATCH 038/197] AttachmentClassLoader isn't directly exposed on the api, so move together with serialisation. (#1060) --- .../core/{node => serialization}/AttachmentsClassLoader.kt | 3 +-- .../kotlin/net/corda/core/serialization/CordaClassResolver.kt | 1 - core/src/main/kotlin/net/corda/core/serialization/Kryo.kt | 1 - .../net/corda/core/serialization/CordaClassResolverTests.kt | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) rename core/src/main/kotlin/net/corda/core/{node => serialization}/AttachmentsClassLoader.kt (98%) diff --git a/core/src/main/kotlin/net/corda/core/node/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/AttachmentsClassLoader.kt similarity index 98% rename from core/src/main/kotlin/net/corda/core/node/AttachmentsClassLoader.kt rename to core/src/main/kotlin/net/corda/core/serialization/AttachmentsClassLoader.kt index bd76fd8bbc..d179ab6e25 100644 --- a/core/src/main/kotlin/net/corda/core/node/AttachmentsClassLoader.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/AttachmentsClassLoader.kt @@ -1,8 +1,7 @@ -package net.corda.core.node +package net.corda.core.serialization import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash -import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.FileNotFoundException diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt index 864167b91e..2ab25299fb 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaClassResolver.kt @@ -6,7 +6,6 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.Util -import net.corda.core.node.AttachmentsClassLoader import net.corda.core.utilities.loggerFor import java.io.PrintWriter import java.lang.reflect.Modifier.isAbstract diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index 27b03c66ac..8db0fdbd8a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -11,7 +11,6 @@ import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.composite.CompositeKey import net.corda.core.identity.Party -import net.corda.core.node.AttachmentsClassLoader import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.LazyPool import net.corda.core.utilities.OpaqueBytes diff --git a/core/src/test/kotlin/net/corda/core/serialization/CordaClassResolverTests.kt b/core/src/test/kotlin/net/corda/core/serialization/CordaClassResolverTests.kt index 46d4aa7499..a3eb22da91 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/CordaClassResolverTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/CordaClassResolverTests.kt @@ -5,7 +5,6 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.MapReferenceResolver import net.corda.core.node.AttachmentClassLoaderTests -import net.corda.core.node.AttachmentsClassLoader import net.corda.core.node.services.AttachmentStorage import net.corda.testing.node.MockAttachmentStorage import org.junit.Rule From 65ce5fec4bab82a26147b66323f2a0e5e52cb7dd Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 17 Jul 2017 18:20:02 +0100 Subject: [PATCH 039/197] Fixed Vault Query over RPC using custom attributes. (#1066) * Implemented Kryo custom serializers for Field and KProperty types. * Adjusted KPropertySerializer to use kotlin member properties upon read() due to failing RPC tests. Added additional Kotlin and Java tests (CordaRPCClient, StandaaloneCordaRPCClient) Annotated schemas to be CordaSerializable (required when referencing as KProperty in custom queries). Cleanup some outstanding compiler warnings. * Added client RPC Java integration and smoke tests to build. * Clean up compiler warnings in Java tests. * Fixed incorrect assertion expectation. * Backed out Field and KProperty custom serializers. * Backed out Field and KProperty custom serializers. * Store VaultQueryCustom custom column references as name and class (from Java Field and Kotlin KProperty1 types respectively). Custom serialization of Field and KProperty type no longer required. * Removed blank lines as per RP review comments. --- client/rpc/build.gradle | 10 ++ .../client/rpc/CordaRPCJavaClientTest.java | 94 ++++++++++++++++++ .../corda/client/rpc/CordaRPCClientTest.kt | 32 ++++-- .../rpc/StandaloneCordaRPCJavaClientTest.java | 99 +++++++++++++++++++ .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 48 +++++++-- .../node/services/vault/QueryCriteriaUtils.kt | 51 ++++------ .../net/corda/core/schemas/PersistentTypes.kt | 3 +- .../kotlin/net/corda/schemas/CashSchemaV1.kt | 2 + .../corda/schemas/CommercialPaperSchemaV1.kt | 2 + node/build.gradle | 10 ++ .../vault/HibernateQueryCriteriaParser.kt | 12 ++- .../corda/node/services/vault/VaultSchema.kt | 2 + 12 files changed, 315 insertions(+), 50 deletions(-) create mode 100644 client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java create mode 100644 client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index b2ab10dff4..6ee29ab387 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -24,6 +24,11 @@ sourceSets { runtimeClasspath += main.output + test.output srcDir file('src/integration-test/kotlin') } + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + } } smokeTest { kotlin { @@ -33,6 +38,11 @@ sourceSets { runtimeClasspath += main.output srcDir file('src/smoke-test/kotlin') } + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/java') + } } } diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java new file mode 100644 index 0000000000..a09af59e85 --- /dev/null +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -0,0 +1,94 @@ +package net.corda.client.rpc; + +import com.google.common.util.concurrent.*; +import net.corda.client.rpc.internal.*; +import net.corda.contracts.asset.*; +import net.corda.core.contracts.*; +import net.corda.core.messaging.*; +import net.corda.core.node.services.*; +import net.corda.core.node.services.vault.*; +import net.corda.core.utilities.*; +import net.corda.flows.*; +import net.corda.node.internal.*; +import net.corda.node.services.transactions.*; +import net.corda.nodeapi.*; +import net.corda.schemas.*; +import net.corda.testing.node.*; +import org.junit.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; + +import static kotlin.test.AssertionsKt.*; +import static net.corda.client.rpc.CordaRPCClientConfiguration.*; +import static net.corda.node.services.RPCUserServiceKt.*; +import static net.corda.testing.TestConstants.*; + +public class CordaRPCJavaClientTest extends NodeBasedTest { + private List perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); + private Set permSet = new HashSet<>(perms); + private User rpcUser = new User("user1", "test", permSet); + + private Node node; + private CordaRPCClient client; + private RPCClient.RPCConnection connection = null; + private CordaRPCOps rpcProxy; + + private void login(String username, String password) { + connection = client.start(username, password); + rpcProxy = connection.getProxy(); + } + + @Before + public void setUp() throws ExecutionException, InterruptedException { + Set services = new HashSet<>(Collections.singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null))); + ListenableFuture nodeFuture = startNode(getALICE().getName(), 1, services, Arrays.asList(rpcUser), Collections.emptyMap()); + node = nodeFuture.get(); + client = new CordaRPCClient(node.getConfiguration().getRpcAddress(), null, getDefault()); + } + + @After + public void done() throws IOException { + connection.close(); + } + + @Test + public void testLogin() { + login(rpcUser.getUsername(), rpcUser.getPassword()); + } + + @Test + public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { + login(rpcUser.getUsername(), rpcUser.getPassword()); + + Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); + + FlowHandle flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, + dollars123, OpaqueBytes.of("1".getBytes()), + node.info.getLegalIdentity(), node.info.getLegalIdentity()); + System.out.println("Started issuing cash, waiting on result"); + flowHandle.getReturnValue().get(); + + Amount balance = getBalance(Currency.getInstance("USD")); + System.out.print("Balance: " + balance + "\n"); + + assertEquals(dollars123, balance, "matching"); + } + + private Amount getBalance(Currency currency) throws NoSuchFieldException { + Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + @SuppressWarnings("unchecked") + QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies)); + + Vault.Page results = rpcProxy.vaultQueryByCriteria(sumCriteria, Cash.State.class); + if (results.getOtherResults().isEmpty()) { + return new Amount<>(0L, currency); + } else { + Assert.assertNotNull(results.getOtherResults()); + Long quantity = (Long) results.getOtherResults().get(0); + return new Amount<>(quantity, currency); + } + } +} diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 40a179fd29..2aa147d3a9 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -1,13 +1,17 @@ package net.corda.client.rpc +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.Amount import net.corda.core.contracts.DOLLARS +import net.corda.core.contracts.USD +import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator import net.corda.core.getOrThrow import net.corda.core.messaging.* import net.corda.core.node.services.ServiceInfo -import net.corda.core.crypto.random63BitValue +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.node.services.vault.builder import net.corda.core.utilities.OpaqueBytes -import net.corda.testing.ALICE import net.corda.flows.CashException import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow @@ -15,10 +19,13 @@ import net.corda.node.internal.Node import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.nodeapi.User +import net.corda.schemas.CashSchemaV1 +import net.corda.testing.ALICE import net.corda.testing.node.NodeBasedTest import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After +import org.junit.Assert import org.junit.Before import org.junit.Test import java.util.* @@ -117,10 +124,23 @@ class CordaRPCClientTest : NodeBasedTest() { println("Started issuing cash, waiting on result") flowHandle.returnValue.get() - val finishCash = proxy.getCashBalances() - println("Cash Balances: $finishCash") - assertEquals(1, finishCash.size) - assertEquals(123.DOLLARS, finishCash.get(Currency.getInstance("USD"))) + val cashDollars = getBalance(USD, proxy) + println("Balance: $cashDollars") + assertEquals(123.DOLLARS, cashDollars) + } + + private fun getBalance(currency: Currency, proxy: CordaRPCOps): Amount { + val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() } + val sumCriteria = QueryCriteria.VaultCustomQueryCriteria(sum) + + val results = proxy.vaultQueryBy(sumCriteria) + if (results.otherResults.isEmpty()) { + return Amount(0L, currency) + } else { + Assert.assertNotNull(results.otherResults) + val quantity = results.otherResults[0] as Long + return Amount(quantity, currency) + } } @Test diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java new file mode 100644 index 0000000000..8039adeadf --- /dev/null +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -0,0 +1,99 @@ +package net.corda.java.rpc; + +import net.corda.client.rpc.*; +import net.corda.contracts.asset.*; +import net.corda.core.contracts.*; +import net.corda.core.messaging.*; +import net.corda.core.node.*; +import net.corda.core.node.services.*; +import net.corda.core.node.services.vault.*; +import net.corda.core.utilities.*; +import net.corda.flows.*; +import net.corda.nodeapi.*; +import net.corda.schemas.*; +import net.corda.smoketesting.*; +import org.bouncycastle.asn1.x500.*; +import org.junit.*; + +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import static kotlin.test.AssertionsKt.assertEquals; + +public class StandaloneCordaRPCJavaClientTest { + private List perms = Collections.singletonList("ALL"); + private Set permSet = new HashSet<>(perms); + private User rpcUser = new User("user1", "test", permSet); + + private AtomicInteger port = new AtomicInteger(15000); + + private NodeProcess notary; + private CordaRPCOps rpcProxy; + private CordaRPCConnection connection; + private NodeInfo notaryNode; + + private NodeConfig notaryConfig = new NodeConfig( + new X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), + port.getAndIncrement(), + port.getAndIncrement(), + port.getAndIncrement(), + Collections.singletonList("corda.notary.validating"), + Arrays.asList(rpcUser), + null + ); + + @Before + public void setUp() { + notary = new NodeProcess.Factory().create(notaryConfig); + connection = notary.connect(); + rpcProxy = connection.getProxy(); + notaryNode = fetchNotaryIdentity(); + } + + @After + public void done() { + try { + connection.close(); + } finally { + notary.close(); + } + } + + private NodeInfo fetchNotaryIdentity() { + DataFeed, NetworkMapCache.MapChange> nodeDataFeed = rpcProxy.networkMapFeed(); + return nodeDataFeed.getSnapshot().get(0); + } + + @Test + public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { + Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); + + FlowHandle flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, + dollars123, OpaqueBytes.of("1".getBytes()), + notaryNode.getLegalIdentity(), notaryNode.getLegalIdentity()); + System.out.println("Started issuing cash, waiting on result"); + flowHandle.getReturnValue().get(); + + Amount balance = getBalance(Currency.getInstance("USD")); + System.out.print("Balance: " + balance + "\n"); + + assertEquals(dollars123, balance, "matching"); + } + + private Amount getBalance(Currency currency) throws NoSuchFieldException { + Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + @SuppressWarnings("unchecked") + QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies)); + + Vault.Page results = rpcProxy.vaultQueryByCriteria(sumCriteria, Cash.State.class); + if (results.getOtherResults().isEmpty()) { + return new Amount<>(0L, currency); + } else { + Assert.assertNotNull(results.getOtherResults()); + Long quantity = (Long) results.getOtherResults().get(0); + return new Amount<>(quantity, currency); + } + } +} diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 4656c23574..429d1fd169 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -6,9 +6,7 @@ import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.notUsed import net.corda.contracts.asset.Cash import net.corda.core.InputStreamAndHash -import net.corda.core.contracts.DOLLARS -import net.corda.core.contracts.POUNDS -import net.corda.core.contracts.SWISS_FRANCS +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.getOrThrow import net.corda.core.messaging.* @@ -21,6 +19,7 @@ import net.corda.core.utilities.loggerFor import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow import net.corda.nodeapi.User +import net.corda.schemas.CashSchemaV1 import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess import org.apache.commons.io.output.NullOutputStream @@ -35,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals +import kotlin.test.assertTrue class StandaloneCordaRPClientTest { private companion object { @@ -177,10 +177,44 @@ class StandaloneCordaRPClientTest { assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100 // Check that this cash exists in the vault - val cashBalance = rpcProxy.getCashBalances() - log.info("Cash Balances: $cashBalance") - assertEquals(1, cashBalance.size) - assertEquals(629.POUNDS, cashBalance[Currency.getInstance("GBP")]) + val cashBalances = rpcProxy.getCashBalances() + log.info("Cash Balances: $cashBalances") + assertEquals(1, cashBalances.size) + assertEquals(629.POUNDS, cashBalances[Currency.getInstance("GBP")]) + } + + @Test + fun `test cash balances`() { + val startCash = rpcProxy.getCashBalances() + assertTrue(startCash.isEmpty(), "Should not start with any cash") + + val flowHandle = rpcProxy.startFlow(::CashIssueFlow, + 629.DOLLARS, OpaqueBytes.of(0), + notaryNode.legalIdentity, notaryNode.legalIdentity + ) + println("Started issuing cash, waiting on result") + flowHandle.returnValue.get() + + val balance = getBalance(USD) + println("Balance: " + balance) + assertEquals(629.DOLLARS, balance) + } + + private fun getBalance(currency: Currency): Amount { + val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() } + val sumCriteria = QueryCriteria.VaultCustomQueryCriteria(sum) + + val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(currency.currencyCode) } + val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex) + + val results = rpcProxy.vaultQueryBy(sumCriteria.and(ccyCriteria)) + if (results.otherResults.isEmpty()) { + return Amount(0L, currency) + } else { + @Suppress("UNCHECKED_CAST") + val quantity = results.otherResults[0] as Long + return Amount(quantity, currency) + } } private fun fetchNotaryIdentity(): NodeInfo { diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 92817ac424..44de37183f 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -6,7 +6,7 @@ import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import java.lang.reflect.Field import kotlin.reflect.KProperty1 -import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaGetter @CordaSerializable enum class BinaryLogicalOperator { @@ -66,9 +66,9 @@ sealed class CriteriaExpression { } @CordaSerializable -sealed class Column { - data class Java(val field: Field) : Column() - data class Kotlin(val property: KProperty1) : Column() +class Column(val name: String, val declaringClass: Class<*>) { + constructor(field: Field) : this(field.name, field.declaringClass) + constructor(property: KProperty1) : this(property.name, property.javaGetter!!.declaringClass) } @CordaSerializable @@ -92,19 +92,8 @@ fun resolveEnclosingObjectFromExpression(expression: CriteriaExpression resolveEnclosingObjectFromColumn(column: Column): Class { - return when (column) { - is Column.Java -> column.field.declaringClass as Class - is Column.Kotlin -> column.property.javaField!!.declaringClass as Class - } -} - -fun getColumnName(column: Column): String { - return when (column) { - is Column.Java -> column.field.name - is Column.Kotlin -> column.property.name - } -} +fun resolveEnclosingObjectFromColumn(column: Column): Class = column.declaringClass as Class +fun getColumnName(column: Column): String = column.name /** * Pagination and Ordering @@ -210,14 +199,14 @@ sealed class SortAttribute { object Builder { fun > compare(operator: BinaryComparisonOperator, value: R) = ColumnPredicate.BinaryComparison(operator, value) - fun KProperty1.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column.Kotlin(this), predicate) + fun KProperty1.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) - fun Field.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column.Java(this), predicate) + fun Field.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) - fun KProperty1.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) - = CriteriaExpression.AggregateFunctionExpression(Column.Kotlin(this), predicate, groupByColumns, orderBy) - fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) - = CriteriaExpression.AggregateFunctionExpression(Column.Java(this), predicate, groupByColumns, orderBy) + fun KProperty1.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) + = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) + fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) + = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) fun > KProperty1.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) fun > Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) @@ -264,34 +253,34 @@ object Builder { /** aggregate functions */ fun KProperty1.sum(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.sum(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) @JvmStatic fun Field.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) fun KProperty1.avg(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.avg(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.min(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.min(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.max(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.max(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column(it) }, orderBy) } inline fun builder(block: Builder.() -> A) = block(Builder) diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index 36a847eec3..a2c2d00e8f 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -3,6 +3,7 @@ package net.corda.core.schemas import io.requery.Persistable import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef +import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.toHexString import java.io.Serializable import javax.persistence.Column @@ -49,7 +50,7 @@ open class MappedSchema(schemaFamily: Class<*>, * A super class for all mapped states exported to a schema that ensures the [StateRef] appears on the database row. The * [StateRef] will be set to the correct value by the framework (there's no need to set during mapping generation by the state itself). */ -@MappedSuperclass open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : StatePersistable +@MappedSuperclass @CordaSerializable open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : StatePersistable /** * Embedded [StateRef] representation used in state mapping. diff --git a/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt b/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt index e2ede7a16e..8e11e1f8c8 100644 --- a/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import javax.persistence.* /** @@ -13,6 +14,7 @@ object CashSchema * First version of a cash contract ORM schema that maps all fields of the [Cash] contract state as it stood * at the time of writing. */ +@CordaSerializable object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity @Table(name = "contract_cash_states", diff --git a/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt b/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt index 2b24f6be8b..98d600ed09 100644 --- a/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -17,6 +18,7 @@ object CommercialPaperSchema * First version of a commercial paper contract ORM schema that maps all fields of the [CommercialPaper] contract state * as it stood at the time of writing. */ +@CordaSerializable object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity @Table(name = "cp_states", diff --git a/node/build.gradle b/node/build.gradle index 74d9bf04e7..b389dfb409 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -41,6 +41,11 @@ sourceSets { runtimeClasspath += main.output + test.output srcDir file('src/integration-test/kotlin') } + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + } resources { srcDir file('src/integration-test/resources') } @@ -53,6 +58,11 @@ sourceSets { runtimeClasspath += main.output srcDir file('src/smoke-test/kotlin') } + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/java') + } } } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index 6ad1d928f5..6f383f81c2 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -74,8 +74,8 @@ class HibernateQueryCriteriaParser(val contractType: Class, val timeCondition = criteria.timeCondition val timeInstantType = timeCondition!!.type val timeColumn = when (timeInstantType) { - QueryCriteria.TimeInstantType.RECORDED -> Column.Kotlin(VaultSchemaV1.VaultStates::recordedTime) - QueryCriteria.TimeInstantType.CONSUMED -> Column.Kotlin(VaultSchemaV1.VaultStates::consumedTime) + QueryCriteria.TimeInstantType.RECORDED -> Column(VaultSchemaV1.VaultStates::recordedTime) + QueryCriteria.TimeInstantType.CONSUMED -> Column(VaultSchemaV1.VaultStates::consumedTime) } val expression = CriteriaExpression.ColumnPredicateExpression(timeColumn, timeCondition.predicate) predicateSet.add(parseExpression(vaultStates, expression) as Predicate) @@ -93,9 +93,10 @@ class HibernateQueryCriteriaParser(val contractType: Class, } } is ColumnPredicate.BinaryComparison -> { - column as Path?> @Suppress("UNCHECKED_CAST") val literal = columnPredicate.rightLiteral as Comparable? + @Suppress("UNCHECKED_CAST") + column as Path?> when (columnPredicate.operator) { BinaryComparisonOperator.GREATER_THAN -> criteriaBuilder.greaterThan(column, literal) BinaryComparisonOperator.GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal) @@ -104,6 +105,7 @@ class HibernateQueryCriteriaParser(val contractType: Class, } } is ColumnPredicate.Likeness -> { + @Suppress("UNCHECKED_CAST") column as Path when (columnPredicate.operator) { LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral) @@ -190,8 +192,8 @@ class HibernateQueryCriteriaParser(val contractType: Class, // add optional group by clauses expression.groupByColumns?.let { columns -> val groupByExpressions = - columns.map { column -> - val path = root.get(getColumnName(column)) + columns.map { _column -> + val path = root.get(getColumnName(_column)) aggregateExpressions.add(path) path } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index 5ef516971a..af52eb645f 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -6,6 +6,7 @@ import net.corda.core.node.services.Vault import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import java.time.Instant import java.util.* @@ -19,6 +20,7 @@ object VaultSchema /** * First version of the Vault ORM schema */ +@CordaSerializable object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, version = 1, mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java, CommonSchemaV1.Party::class.java)) { @Entity From 561a329064a98d415e5a1fdceb9c581ba1019302 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 17 Jul 2017 20:24:50 +0100 Subject: [PATCH 040/197] Moved the various extension methods for creating Durations from Utils.kt to KotlinUtils.kt --- .../client/jfx/model/NodeMonitorModel.kt | 2 +- .../net/corda/client/rpc/RPCStabilityTests.kt | 4 +- .../corda/client/rpc/internal/RPCClient.kt | 4 +- .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 2 +- .../corda/client/rpc/RPCConcurrencyTests.kt | 2 +- .../corda/client/rpc/RPCPerformanceTests.kt | 5 +-- core/src/main/kotlin/net/corda/core/Utils.kt | 8 ---- .../net/corda/core/crypto/X509Utilities.kt | 4 +- .../core/node/services/TimeWindowChecker.kt | 2 +- .../net/corda/core/utilities/KotlinUtils.kt | 37 +++++++++++++++++++ .../node/services/TimeWindowCheckerTests.kt | 2 +- .../TransactionSerializationTests.kt | 2 +- .../kotlin/net/corda/docs/FlowCookbook.kt | 8 ++-- .../docs/WorkflowTransactionBuildTutorial.kt | 6 +-- .../net/corda/contracts/asset/Obligation.kt | 8 ++-- .../net/corda/flows/TwoPartyDealFlow.kt | 2 +- .../net/corda/flows/TwoPartyTradeFlow.kt | 2 +- .../corda/contracts/CommercialPaperTests.kt | 4 +- .../corda/contracts/asset/ObligationTests.kt | 10 ++--- .../net/corda/node/NodePerformanceTests.kt | 2 +- .../services/messaging/P2PMessagingTest.kt | 1 + .../services/messaging/P2PSecurityTest.kt | 2 +- .../kotlin/net/corda/node/internal/Node.kt | 6 +-- .../net/corda/node/services/keys/KMSUtils.kt | 6 +-- .../messaging/ArtemisMessagingServer.kt | 4 +- .../node/services/messaging/RPCServer.kt | 2 +- .../registration/NetworkRegistrationHelper.kt | 2 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 2 +- .../corda/node/services/NotaryChangeTests.kt | 2 +- .../events/NodeSchedulerServiceTest.kt | 2 +- .../transactions/NotaryServiceTests.kt | 2 +- .../node/services/vault/VaultQueryTests.kt | 4 +- .../corda/node/utilities/ClockUtilsTest.kt | 36 +++++++++--------- .../net/corda/node/utilities/FiberBoxTest.kt | 20 +++++----- .../kotlin/net/corda/irs/IRSDemoTest.kt | 10 ++--- .../kotlin/net/corda/irs/flows/FixingFlow.kt | 2 +- .../net/corda/irs/utilities/OracleUtils.kt | 8 +++- .../kotlin/net/corda/irs/contract/IRSTests.kt | 2 +- .../net/corda/vega/flows/StateRevisionFlow.kt | 2 +- .../net/corda/traderdemo/TraderDemoTest.kt | 2 +- .../net/corda/traderdemo/flow/SellerFlow.kt | 4 +- .../testing/TransactionDSLInterpreter.kt | 2 +- .../net/corda/demobench/web/WebServer.kt | 2 +- 43 files changed, 136 insertions(+), 105 deletions(-) diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index e2b134bc8c..944c49643c 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -10,7 +10,7 @@ import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.messaging.StateMachineUpdate import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.Vault -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import rx.Observable diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index ac524995c7..bf20920824 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -12,8 +12,8 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.future import net.corda.core.getOrThrow import net.corda.core.messaging.RPCOps -import net.corda.core.millis -import net.corda.core.seconds +import net.corda.core.utilities.millis +import net.corda.core.utilities.seconds import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.Try import net.corda.node.services.messaging.RPCServerConfiguration diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index a792c24faa..059f5d2be7 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -2,9 +2,9 @@ package net.corda.client.rpc.internal import net.corda.core.logElapsedTime import net.corda.core.messaging.RPCOps -import net.corda.core.minutes +import net.corda.core.utilities.minutes import net.corda.core.crypto.random63BitValue -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 429d1fd169..b8dfe87614 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -13,7 +13,7 @@ import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor import net.corda.flows.CashIssueFlow diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt index fb283773d1..f146014a76 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCConcurrencyTests.kt @@ -3,7 +3,7 @@ package net.corda.client.rpc import net.corda.client.rpc.internal.RPCClientConfiguration import net.corda.core.future import net.corda.core.messaging.RPCOps -import net.corda.core.millis +import net.corda.core.utilities.millis import net.corda.core.crypto.random63BitValue import net.corda.core.serialization.CordaSerializable import net.corda.node.services.messaging.RPCServerConfiguration diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt index 362f001232..5684d53ca6 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt @@ -3,12 +3,11 @@ package net.corda.client.rpc import com.google.common.base.Stopwatch import net.corda.client.rpc.internal.RPCClientConfiguration import net.corda.core.messaging.RPCOps -import net.corda.core.minutes -import net.corda.core.seconds +import net.corda.core.utilities.minutes +import net.corda.core.utilities.seconds import net.corda.core.utilities.div import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.testing.RPCDriverExposedDSLInterface -import net.corda.testing.driver.ShutdownManager import net.corda.testing.measure import net.corda.testing.performance.startPublishingFixedRateInjector import net.corda.testing.performance.startReporter diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 3ee55d50ba..36a70a937f 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -36,14 +36,6 @@ import java.util.zip.ZipOutputStream import kotlin.concurrent.withLock import kotlin.reflect.KClass -val Int.days: Duration get() = Duration.ofDays(this.toLong()) -@Suppress("unused") // It's here for completeness -val Int.hours: Duration get() = Duration.ofHours(this.toLong()) -val Int.minutes: Duration get() = Duration.ofMinutes(this.toLong()) -val Int.seconds: Duration get() = Duration.ofSeconds(this.toLong()) -val Int.millis: Duration get() = Duration.ofMillis(this.toLong()) - - // TODO: Review by EOY2016 if we ever found these utilities helpful. val Int.bd: BigDecimal get() = BigDecimal(this) val Double.bd: BigDecimal get() = BigDecimal(this) diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index ec20ada436..db7717aa33 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,5 +1,7 @@ package net.corda.core.crypto +import net.corda.core.utilities.days +import net.corda.core.utilities.millis import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder @@ -33,7 +35,7 @@ object X509Utilities { val CORDA_CLIENT_TLS = "cordaclienttls" val CORDA_CLIENT_CA = "cordaclientca" - private val DEFAULT_VALIDITY_WINDOW = Pair(Duration.ofMillis(0), Duration.ofDays(365 * 10)) + private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** * Helper function to return the latest out of an instant and an optional date. */ diff --git a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt index b06ee522af..771a821ce4 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt @@ -1,7 +1,7 @@ package net.corda.core.node.services import net.corda.core.contracts.TimeWindow -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.until import java.time.Clock import java.time.Duration diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index 6df50156d3..3d55db4c9c 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -3,8 +3,15 @@ package net.corda.core.utilities import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.time.Duration import kotlin.reflect.KProperty +// +// READ ME FIRST: +// This is a collection of public utilities useful only for Kotlin code. If you're looking to add a public utility that +// is also relevant to Java then put it in Utils.kt. +// + /** * Get the [Logger] for a class using the syntax * @@ -22,6 +29,36 @@ inline fun Logger.debug(msg: () -> String) { if (isDebugEnabled) debug(msg()) } +/** + * Extension method for easier construction of [Duration]s in terms of integer days: `val twoDays = 2.days`. + * @see Duration.ofDays + */ +inline val Int.days: Duration get() = Duration.ofDays(toLong()) + +/** + * Extension method for easier construction of [Duration]s in terms of integer hours: `val twoHours = 2.hours`. + * @see Duration.ofHours + */ +inline val Int.hours: Duration get() = Duration.ofHours(toLong()) + +/** + * Extension method for easier construction of [Duration]s in terms of integer minutes: `val twoMinutes = 2.minutes`. + * @see Duration.ofMinutes + */ +inline val Int.minutes: Duration get() = Duration.ofMinutes(toLong()) + +/** + * Extension method for easier construction of [Duration]s in terms of integer seconds: `val twoSeconds = 2.seconds`. + * @see Duration.ofSeconds + */ +inline val Int.seconds: Duration get() = Duration.ofSeconds(toLong()) + +/** + * Extension method for easier construction of [Duration]s in terms of integer milliseconds: `val twoMillis = 2.millis`. + * @see Duration.ofMillis + */ +inline val Int.millis: Duration get() = Duration.ofMillis(toLong()) + /** * A simple wrapper that enables the use of Kotlin's `val x by transient { ... }` syntax. Such a property * will not be serialized, and if it's missing (or the first time it's accessed), the initializer will be diff --git a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt index 9b174d6b41..3b18f549b2 100644 --- a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt @@ -1,7 +1,7 @@ package net.corda.core.node.services import net.corda.core.contracts.TimeWindow -import net.corda.core.seconds +import net.corda.core.utilities.seconds import org.junit.Test import java.time.Clock import java.time.Instant diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 9456e8f21f..5ac91f2695 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -3,7 +3,7 @@ package net.corda.core.serialization import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.TransactionBuilder import net.corda.testing.* import net.corda.testing.node.MockServices diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 6640084b68..bd6eb2a93d 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -20,13 +20,13 @@ import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step import net.corda.core.utilities.UntrustworthyData +import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap import net.corda.testing.DUMMY_PUBKEY_1 import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import org.bouncycastle.asn1.x500.X500Name import java.security.PublicKey -import java.time.Duration import java.time.Instant // We group our two flows inside a singleton object to indicate that they work @@ -280,11 +280,11 @@ object FlowCookbook { // We can also define a time window as an ``Instant`` +/- a time // tolerance (e.g. 30 seconds): // DOCSTART 42 - val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(Instant.now(), Duration.ofSeconds(30)) + val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(Instant.now(), 30.seconds) // DOCEND 42 // Or as a start-time plus a duration: // DOCSTART 43 - val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(Instant.now(), Duration.ofSeconds(30)) + val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(Instant.now(), 30.seconds) // DOCEND 43 /**----------------------- @@ -326,7 +326,7 @@ object FlowCookbook { // DOCEND 44 // Or as a start time plus a duration (e.g. 45 seconds): // DOCSTART 45 - regTxBuilder.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(45)) + regTxBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds) // DOCEND 45 /**---------------------- diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index a19e6f7645..16a226e4ea 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -15,9 +15,9 @@ import net.corda.core.node.ServiceHub import net.corda.core.node.services.linearHeadsOfType import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap import java.security.PublicKey -import java.time.Duration // DOCSTART 1 // Helper method to locate the latest Vault version of a LinearState from a possibly out of date StateRef @@ -123,7 +123,7 @@ class SubmitTradeApprovalFlow(val tradeId: String, // Create the TransactionBuilder and populate with the new state. val tx = TransactionType.General.Builder(notary) .withItems(tradeProposal, Command(TradeApprovalContract.Commands.Issue(), listOf(tradeProposal.source.owningKey))) - tx.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(60)) + tx.setTimeWindow(serviceHub.clock.instant(), 60.seconds) // We can automatically sign as there is no untrusted data. val signedTx = serviceHub.signInitialTransaction(tx) // Notarise and distribute. @@ -184,7 +184,7 @@ class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : Flow newState, Command(TradeApprovalContract.Commands.Completed(), listOf(serviceHub.myInfo.legalIdentity.owningKey, latestRecord.state.data.source.owningKey))) - tx.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(60)) + tx.setTimeWindow(serviceHub.clock.instant(), 60.seconds) // We can sign this transaction immediately as we have already checked all the fields and the decision // is ultimately a manual one from the caller. // As a SignedTransaction we can pass the data around certain that it cannot be modified, diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt index dbc7cf54dc..9399aeba7a 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt @@ -10,15 +10,15 @@ import net.corda.core.contracts.* import net.corda.core.contracts.clauses.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.crypto.testing.NULL_PARTY +import net.corda.core.crypto.random63BitValue import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party -import net.corda.core.crypto.random63BitValue import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.Emoji import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.seconds import org.bouncycastle.asn1.x500.X500Name import java.math.BigInteger import java.security.PublicKey @@ -202,7 +202,7 @@ class Obligation

: Contract { "amount in settle command ${command.value.amount} matches settled total $totalAmountSettled" using (command.value.amount == totalAmountSettled) "signatures are present from all obligors" using command.signers.containsAll(requiredSigners) "there are no zero sized inputs" using inputs.none { it.amount.quantity == 0L } - "at obligor ${obligor} the obligations after settlement balance" using + "at obligor $obligor the obligations after settlement balance" using (inputAmount == outputAmount + Amount(totalPenniesSettled, groupingKey)) } return setOf(command.value) @@ -264,7 +264,7 @@ class Obligation

: Contract { /** When the contract must be settled by. */ val dueBefore: Instant, - val timeTolerance: Duration = Duration.ofSeconds(30) + val timeTolerance: Duration = 30.seconds ) { val product: P get() = acceptableIssuedProducts.map { it.product }.toSet().single() diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt index c421a58b57..ad16e4241d 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt @@ -12,7 +12,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceType -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt index 8d427fd1ef..1290536405 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt @@ -8,7 +8,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder diff --git a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt index e5428add4d..893d8ce3e4 100644 --- a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt @@ -3,12 +3,12 @@ package net.corda.contracts import net.corda.contracts.asset.* import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.core.contracts.* -import net.corda.core.days +import net.corda.core.utilities.days import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.node.utilities.configureDatabase import net.corda.testing.* diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index 131a30036a..aee082b27f 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -6,16 +6,16 @@ import net.corda.contracts.asset.Obligation.Lifecycle import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.testing.NULL_PARTY -import net.corda.core.hours import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.days +import net.corda.core.utilities.hours import net.corda.testing.* import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices import org.junit.Test -import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* @@ -272,7 +272,7 @@ class ObligationTests { @Test fun `generate set lifecycle`() { // We don't actually verify the states, this is just here to make things look sensible - val dueBefore = TEST_TX_TIME - Duration.ofDays(7) + val dueBefore = TEST_TX_TIME - 7.days // Generate a transaction issuing the obligation var tx = TransactionType.General.Builder(null).apply { @@ -534,8 +534,8 @@ class ObligationTests { } // Try defaulting an obligation due in the future - val pastTestTime = TEST_TX_TIME - Duration.ofDays(7) - val futureTestTime = TEST_TX_TIME + Duration.ofDays(7) + val pastTestTime = TEST_TX_TIME - 7.days + val futureTestTime = TEST_TX_TIME + 7.days transaction("Settlement") { input(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime) output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) } diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index c22c69ec34..00421c1ce2 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -7,7 +7,7 @@ import net.corda.core.contracts.DOLLARS import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.messaging.startFlow -import net.corda.core.minutes +import net.corda.core.utilities.minutes import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.div diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 611e87373e..d357b21cb0 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -10,6 +10,7 @@ import net.corda.core.node.services.ServiceInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.seconds import net.corda.node.internal.Node import net.corda.node.services.api.DEFAULT_SESSION_ID import net.corda.node.services.messaging.* diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index 4aeb1a4e24..07049024b0 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -6,7 +6,7 @@ import net.corda.core.crypto.cert import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow import net.corda.core.node.NodeInfo -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.utilities.NonEmptySet import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.config.configureWithDevSSLCertificate diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 1e5c973df5..bdd44c655e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -8,11 +8,7 @@ import net.corda.core.* import net.corda.core.messaging.RPCOps import net.corda.core.node.ServiceHub import net.corda.core.node.services.ServiceInfo -import net.corda.core.seconds -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.parseNetworkHostAndPort -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.VersionInfo import net.corda.node.serialization.NodeClock import net.corda.node.services.RPCUserService diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index 481e9e8246..54a23918da 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -4,17 +4,15 @@ import net.corda.core.crypto.* import net.corda.core.identity.AnonymousParty import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService +import net.corda.core.utilities.days import net.corda.flows.AnonymisedIdentity -import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PublicKey import java.security.Security -import java.security.cert.CertPath import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.time.Duration -import java.util.* /** * Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding @@ -33,7 +31,7 @@ fun freshCertificate(identityService: IdentityService, issuerSigner: ContentSigner, revocationEnabled: Boolean = false): AnonymisedIdentity { val issuerCertificate = issuer.certificate - val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate) + val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate) val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window) val certFactory = CertificateFactory.getInstance("X509") val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index e84db12439..221460f952 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -11,9 +11,7 @@ import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache.MapChange -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 71ee340dbb..8548520ba7 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -14,7 +14,7 @@ import com.google.common.collect.SetMultimap import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.core.crypto.random63BitValue import net.corda.core.messaging.RPCOps -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.serialization.KryoPoolWithContext import net.corda.core.utilities.* import net.corda.node.services.RPCUserService diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index f9942475fc..55ea786b23 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -1,6 +1,5 @@ package net.corda.node.utilities.registration -import net.corda.core.* import net.corda.core.crypto.CertificateType import net.corda.core.crypto.Crypto import net.corda.core.crypto.X509Utilities @@ -9,6 +8,7 @@ import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.core.crypto.cert import net.corda.core.internal.* +import net.corda.core.utilities.seconds import net.corda.node.services.config.NodeConfiguration import net.corda.node.utilities.* import org.bouncycastle.cert.path.CertPath diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 1c8fa4ddea..1cd53227da 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -8,7 +8,6 @@ import net.corda.core.contracts.* import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sign -import net.corda.core.crypto.toStringShort import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow @@ -27,6 +26,7 @@ import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.days import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap import net.corda.flows.TwoPartyTradeFlow.Buyer diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index dedb3bd5c3..cfd8a53088 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -6,7 +6,7 @@ import net.corda.core.crypto.generateKeyPair import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.WireTransaction import net.corda.core.flows.NotaryChangeFlow import net.corda.core.flows.StateReplacementException 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 8d21979c6f..abce8ae027 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 @@ -1,7 +1,7 @@ package net.corda.node.services.events import net.corda.core.contracts.* -import net.corda.core.days +import net.corda.core.utilities.days import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRefFactory diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index fbc80fd8db..0edfe85802 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -10,7 +10,7 @@ import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryFlow import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.node.internal.AbstractNode import net.corda.node.services.network.NetworkMapService diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 1ea1148c9b..af9e8c009b 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -8,12 +8,12 @@ import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.core.contracts.* import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.toBase58String -import net.corda.core.days +import net.corda.core.utilities.days import net.corda.core.identity.Party import net.corda.core.node.services.* import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.* -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.NonEmptySet diff --git a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt index 38a5c91d67..81b290f178 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt @@ -6,6 +6,8 @@ import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand import com.google.common.util.concurrent.SettableFuture import net.corda.core.getOrThrow +import net.corda.core.utilities.hours +import net.corda.core.utilities.minutes import net.corda.testing.node.TestClock import org.junit.After import org.junit.Before @@ -44,7 +46,7 @@ class ClockUtilsTest { @Test fun `test waiting negative time for a deadline`() { - assertFalse(stoppedClock.awaitWithDeadline(stoppedClock.instant().minus(Duration.ofHours(1))), "Should have reached deadline") + assertFalse(stoppedClock.awaitWithDeadline(stoppedClock.instant().minus(1.hours)), "Should have reached deadline") } @Test @@ -56,13 +58,13 @@ class ClockUtilsTest { @Test fun `test waiting negative time for a deadline with incomplete future`() { val future = SettableFuture.create() - assertFalse(stoppedClock.awaitWithDeadline(stoppedClock.instant().minus(Duration.ofHours(1)), future), "Should have reached deadline") + assertFalse(stoppedClock.awaitWithDeadline(stoppedClock.instant().minus(1.hours), future), "Should have reached deadline") } @Test fun `test waiting for a deadline with future completed before wait`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val future = SettableFuture.create() completeNow(future) assertTrue(stoppedClock.awaitWithDeadline(advancedClock.instant(), future), "Should not have reached deadline") @@ -70,7 +72,7 @@ class ClockUtilsTest { @Test fun `test waiting for a deadline with future completed after wait`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val future = SettableFuture.create() completeAfterWaiting(future) assertTrue(stoppedClock.awaitWithDeadline(advancedClock.instant(), future), "Should not have reached deadline") @@ -78,38 +80,38 @@ class ClockUtilsTest { @Test fun `test waiting for a deadline with clock advance`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) - advanceClockAfterWait(testClock, Duration.ofHours(1)) + advanceClockAfterWait(testClock, 1.hours) assertFalse(testClock.awaitWithDeadline(advancedClock.instant()), "Should have reached deadline") } @Test fun `test waiting for a deadline with clock advance and incomplete future`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = SettableFuture.create() - advanceClockAfterWait(testClock, Duration.ofHours(1)) + advanceClockAfterWait(testClock, 1.hours) assertFalse(testClock.awaitWithDeadline(advancedClock.instant(), future), "Should have reached deadline") } @Test fun `test waiting for a deadline with clock advance and complete future`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(2)) + val advancedClock = Clock.offset(stoppedClock, 2.hours) val testClock = TestClock(stoppedClock) val future = SettableFuture.create() - advanceClockAfterWait(testClock, Duration.ofHours(1)) + advanceClockAfterWait(testClock, 1.hours) completeAfterWaiting(future) assertTrue(testClock.awaitWithDeadline(advancedClock.instant(), future), "Should not have reached deadline") } @Test fun `test waiting for a deadline with multiple clock advance and incomplete future`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = SettableFuture.create() for (advance in 1..6) { - advanceClockAfterWait(testClock, Duration.ofMinutes(10)) + advanceClockAfterWait(testClock, 10.minutes) } assertFalse(testClock.awaitWithDeadline(advancedClock.instant(), future), "Should have reached deadline") } @@ -126,7 +128,7 @@ class ClockUtilsTest { } val testClock = TestClock(stoppedClock) - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(10)) + val advancedClock = Clock.offset(stoppedClock, 10.hours) try { testClock.awaitWithDeadline(advancedClock.instant(), SettableFuture.create()) @@ -138,7 +140,7 @@ class ClockUtilsTest { @Test @Suspendable fun `test waiting for a deadline with multiple clock advance and incomplete JDK8 future on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = CompletableFuture() val scheduler = FiberExecutorScheduler("test", executor) @@ -151,7 +153,7 @@ class ClockUtilsTest { while (fiber.state != Strand.State.TIMED_WAITING) { Strand.sleep(1) } - testClock.advanceBy(Duration.ofMinutes(10)) + testClock.advanceBy(10.minutes) }).start() } assertFalse(future.getOrThrow(), "Should have reached deadline") @@ -160,7 +162,7 @@ class ClockUtilsTest { @Test @Suspendable fun `test waiting for a deadline with multiple clock advance and incomplete Guava future on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = SettableFuture.create() val scheduler = FiberExecutorScheduler("test", executor) @@ -173,7 +175,7 @@ class ClockUtilsTest { while (fiber.state != Strand.State.TIMED_WAITING) { Strand.sleep(1) } - testClock.advanceBy(Duration.ofMinutes(10)) + testClock.advanceBy(10.minutes) }).start() } assertFalse(future.getOrThrow(), "Should have reached deadline") diff --git a/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt b/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt index 80211f61aa..25fc475071 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt @@ -5,6 +5,8 @@ import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand import net.corda.core.RetryableException import net.corda.core.getOrThrow +import net.corda.core.utilities.hours +import net.corda.core.utilities.minutes import net.corda.testing.node.TestClock import org.junit.After import org.junit.Before @@ -50,7 +52,7 @@ class FiberBoxTest { @Test fun `readWithDeadline with no wait`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) mutex.write { integer = 1 } assertEquals(1, mutex.readWithDeadline(realClock, advancedClock.instant()) { integer }) @@ -58,7 +60,7 @@ class FiberBoxTest { @Test fun `readWithDeadline with stopped clock and background write`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) assertEquals(1, mutex.readWithDeadline(stoppedClock, advancedClock.instant()) { backgroundWrite() @@ -68,22 +70,22 @@ class FiberBoxTest { @Test(expected = TestRetryableException::class) fun `readWithDeadline with clock advanced`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) assertEquals(1, mutex.readWithDeadline(testClock, advancedClock.instant()) { - backgroundAdvanceClock(testClock, Duration.ofHours(1)) + backgroundAdvanceClock(testClock, 1.hours) if (integer == 1) 0 else throw TestRetryableException("Not 1") }) } @Test fun `readWithDeadline with clock advanced 5x and background write`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) assertEquals(5, mutex.readWithDeadline(testClock, advancedClock.instant()) { - backgroundAdvanceClock(testClock, Duration.ofMinutes(10)) + backgroundAdvanceClock(testClock, 10.minutes) backgroundWrite() if (integer == 5) 5 else throw TestRetryableException("Not 5") }) @@ -97,7 +99,7 @@ class FiberBoxTest { @Test(expected = TestRetryableException::class) @Suspendable fun `readWithDeadline with clock advanced on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = CompletableFuture() val scheduler = FiberExecutorScheduler("test", executor) @@ -116,7 +118,7 @@ class FiberBoxTest { while (fiber.state != Strand.State.TIMED_WAITING) { Strand.sleep(1) } - testClock.advanceBy(Duration.ofMinutes(10)) + testClock.advanceBy(10.minutes) }).start() } assertEquals(2, future.getOrThrow()) @@ -130,7 +132,7 @@ class FiberBoxTest { @Test @Suspendable fun `readWithDeadline with background write on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, Duration.ofHours(1)) + val advancedClock = Clock.offset(stoppedClock, 1.hours) val testClock = TestClock(stoppedClock) val future = CompletableFuture() val scheduler = FiberExecutorScheduler("test", executor) 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 ac93b3ec0b..66dce242fe 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 @@ -6,15 +6,16 @@ import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.core.toFuture import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY +import net.corda.core.utilities.seconds import net.corda.irs.api.NodeInterestRates import net.corda.irs.contract.InterestRateSwap import net.corda.irs.utilities.uploadFile import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B +import net.corda.testing.DUMMY_NOTARY import net.corda.testing.IntegrationTestCategory import net.corda.testing.driver.driver import net.corda.testing.http.HttpApi @@ -25,13 +26,12 @@ import rx.Observable import java.net.URL import java.time.Duration import java.time.LocalDate -import java.time.temporal.ChronoUnit class IRSDemoTest : IntegrationTestCategory { val rpcUser = User("user", "password", emptySet()) val currentDate: LocalDate = LocalDate.now() val futureDate: LocalDate = currentDate.plusMonths(6) - val maxWaitTime: Duration = Duration.of(60, ChronoUnit.SECONDS) + val maxWaitTime: Duration = 60.seconds @Test fun `runs IRS demo`() { diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt index 9b8a11cd96..1a0f55c814 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt @@ -12,7 +12,7 @@ import net.corda.core.flows.SchedulableFlow import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceType -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt index 54413553a8..bb370c9a36 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt @@ -1,7 +1,11 @@ package net.corda.irs.utilities import net.corda.core.contracts.TimeWindow -import java.time.* +import net.corda.core.utilities.hours +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZonedDateTime /** * This whole file exists as short cuts to get demos working. In reality we'd have static data and/or rules engine @@ -16,5 +20,5 @@ fun suggestInterestRateAnnouncementTimeWindow(index: String, source: String, dat // Here we apply a blanket announcement time of 11:45 London irrespective of source or index val time = LocalTime.of(11, 45) val zoneId = ZoneId.of("Europe/London") - return TimeWindow.fromStartAndDuration(ZonedDateTime.of(date, time, zoneId).toInstant(), Duration.ofHours(24)) + return TimeWindow.fromStartAndDuration(ZonedDateTime.of(date, time, zoneId).toInstant(), 24.hours) } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index fc01eddcaa..576dee021e 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -2,7 +2,7 @@ package net.corda.irs.contract import net.corda.contracts.* import net.corda.core.contracts.* -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY_KEY diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt index 343282440c..61684381f3 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt @@ -2,7 +2,7 @@ package net.corda.vega.flows import net.corda.core.contracts.StateAndRef import net.corda.core.identity.Party -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.flows.AbstractStateReplacementFlow import net.corda.core.flows.StateReplacementException import net.corda.vega.contracts.RevisionedState diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index a0198451a9..ddd7127651 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -4,7 +4,7 @@ import com.google.common.util.concurrent.Futures import net.corda.client.rpc.CordaRPCClient import net.corda.core.contracts.DOLLARS import net.corda.core.getOrThrow -import net.corda.core.millis +import net.corda.core.utilities.millis import net.corda.core.node.services.ServiceInfo import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index ca738a7bdb..ed1b130b41 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -5,7 +5,7 @@ import net.corda.contracts.CommercialPaper import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash -import net.corda.core.days +import net.corda.core.utilities.days import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatingFlow @@ -14,7 +14,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.flows.TwoPartyTradeFlow diff --git a/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt b/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt index cb973e022f..92b9604350 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt @@ -4,7 +4,7 @@ import net.corda.core.contracts.* import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party -import net.corda.core.seconds +import net.corda.core.utilities.seconds import net.corda.core.transactions.TransactionBuilder import java.security.PublicKey import java.time.Duration diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt index aad88033bc..b948a7580e 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt @@ -4,7 +4,7 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.RateLimiter import com.google.common.util.concurrent.SettableFuture import net.corda.core.catch -import net.corda.core.minutes +import net.corda.core.utilities.minutes import net.corda.core.until import net.corda.core.utilities.loggerFor import net.corda.demobench.model.NodeConfig From 0ccfae252fdabd166f2be00f84df73ade08f2195 Mon Sep 17 00:00:00 2001 From: Clinton Date: Tue, 18 Jul 2017 12:34:56 +0100 Subject: [PATCH 041/197] POMs generated by publishing are now correct. (#1055) * POMs generated by publishing are now correct. The publish extension now requires an explicit call to configure the publishing instead of waiting until after evaluation. This prevents evaluation order issues with the artifact renaming code that causes the POM to have the original, incorrect, artifact names. * Fixed new test compile issues caused by removal of some dependencies in test utils that caused webserver code to be automatically included in any project also compiling test utils. --- client/jackson/build.gradle | 2 +- client/jfx/build.gradle | 2 +- client/mock/build.gradle | 2 +- client/rpc/build.gradle | 2 +- constants.properties | 2 +- cordform-common/build.gradle | 4 ++++ core/build.gradle | 2 +- finance/build.gradle | 2 +- gradle-plugins/cordformation/build.gradle | 4 ++++ gradle-plugins/publish-utils/README.rst | 2 +- .../plugins/ProjectPublishExtension.groovy | 15 +++++++++++++-- .../net/corda/plugins/PublishTasks.groovy | 18 ++++++------------ gradle-plugins/quasar-utils/build.gradle | 4 ++++ node-api/build.gradle | 2 +- node-schemas/build.gradle | 4 ++-- node/build.gradle | 2 +- node/capsule/build.gradle | 2 +- .../network/AbstractNetworkMapServiceTest.kt | 7 ++++--- samples/attachment-demo/build.gradle | 1 + samples/bank-of-corda-demo/build.gradle | 1 + settings.gradle | 2 +- test-common/build.gradle | 2 +- test-utils/build.gradle | 4 +--- verifier/build.gradle | 2 +- webserver/build.gradle | 2 +- webserver/webcapsule/build.gradle | 3 +-- 26 files changed, 56 insertions(+), 39 deletions(-) diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle index 234f7e1ae0..1457f01bb4 100644 --- a/client/jackson/build.gradle +++ b/client/jackson/build.gradle @@ -31,5 +31,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle index 5d12e01f56..9c70ee7a04 100644 --- a/client/jfx/build.gradle +++ b/client/jfx/build.gradle @@ -62,5 +62,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/client/mock/build.gradle b/client/mock/build.gradle index d709d4c911..0c4a2efbb0 100644 --- a/client/mock/build.gradle +++ b/client/mock/build.gradle @@ -30,5 +30,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index 6ee29ab387..61159ac74d 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -92,5 +92,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/constants.properties b/constants.properties index 6993620217..c2aea318ca 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=0.13.2 +gradlePluginsVersion=0.13.3 kotlinVersion=1.1.1 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/cordform-common/build.gradle b/cordform-common/build.gradle index 340a4b6ec6..82274e0d09 100644 --- a/cordform-common/build.gradle +++ b/cordform-common/build.gradle @@ -17,3 +17,7 @@ dependencies { // Bouncy Castle: for X.500 distinguished name manipulation compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" } + +publish { + name project.name +} \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index e7dc4fa641..564780c011 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -98,5 +98,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/finance/build.gradle b/finance/build.gradle index 3e4cae5813..48fa690b36 100644 --- a/finance/build.gradle +++ b/finance/build.gradle @@ -49,5 +49,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 72c778d692..390ea7c8ba 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -61,3 +61,7 @@ jar { rename { 'net/corda/plugins/runnodes.jar' } } } + +publish { + name project.name +} diff --git a/gradle-plugins/publish-utils/README.rst b/gradle-plugins/publish-utils/README.rst index 3ca1c2febf..d1657ee5fe 100644 --- a/gradle-plugins/publish-utils/README.rst +++ b/gradle-plugins/publish-utils/README.rst @@ -76,8 +76,8 @@ The project configuration block has the following structure: .. code-block:: text publish { - name = 'non-default-project-name' disableDefaultJar = false // set to true to disable the default JAR being created (e.g. when creating a fat JAR) + name 'non-default-project-name' // Always put this last because it causes configuration to happen } **Artifacts** diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy index de5c4dfb90..ee978bdbb8 100644 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy +++ b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy @@ -1,14 +1,25 @@ package net.corda.plugins class ProjectPublishExtension { + private PublishTasks task + + void setPublishTask(PublishTasks task) { + this.task = task + } + /** - * Use a different name from the current project name for publishing + * Use a different name from the current project name for publishing. + * Set this after all other settings that need to be configured */ - String name + void name(String name) { + task.setPublishName(name) + } + /** * True when we do not want to publish default Java components */ Boolean disableDefaultJar = false + /** * True if publishing a WAR instead of a JAR. Forces disableDefaultJAR to "true" when true */ diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy index f5221ff037..f25e07da6a 100644 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy +++ b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy @@ -23,24 +23,17 @@ class PublishTasks implements Plugin { void apply(Project project) { this.project = project + this.publishName = project.name createTasks() createExtensions() createConfigurations() - - project.afterEvaluate { - configurePublishingName() - checkAndConfigurePublishing() - } } - void configurePublishingName() { - if(publishConfig.name != null) { - project.logger.info("Changing publishing name for ${project.name} to ${publishConfig.name}") - publishName = publishConfig.name - } else { - publishName = project.name - } + void setPublishName(String publishName) { + project.logger.info("Changing publishing name from ${project.name} to ${publishName}") + this.publishName = publishName + checkAndConfigurePublishing() } void checkAndConfigurePublishing() { @@ -157,6 +150,7 @@ class PublishTasks implements Plugin { project.extensions.create("bintrayConfig", BintrayConfigExtension) } publishConfig = project.extensions.create("publish", ProjectPublishExtension) + publishConfig.setPublishTask(this) } void createConfigurations() { diff --git a/gradle-plugins/quasar-utils/build.gradle b/gradle-plugins/quasar-utils/build.gradle index e4bba19bcf..7829d47d75 100644 --- a/gradle-plugins/quasar-utils/build.gradle +++ b/gradle-plugins/quasar-utils/build.gradle @@ -12,3 +12,7 @@ dependencies { compile gradleApi() compile localGroovy() } + +publish { + name project.name +} diff --git a/node-api/build.gradle b/node-api/build.gradle index 6bb83dcbf6..c2ba65f9c3 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -38,5 +38,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/node-schemas/build.gradle b/node-schemas/build.gradle index 88a9102a8b..7f015714a1 100644 --- a/node-schemas/build.gradle +++ b/node-schemas/build.gradle @@ -1,7 +1,7 @@ +apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' apply plugin: 'idea' -apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'com.jfrog.artifactory' description 'Corda node database schemas' @@ -33,5 +33,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/node/build.gradle b/node/build.gradle index b389dfb409..ffbe9df98b 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -223,5 +223,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } \ No newline at end of file diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index fac1f538f5..12de5f404e 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -67,6 +67,6 @@ artifacts { } publish { - name = 'corda' disableDefaultJar = true + name 'corda' } diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt index 549f52e848..1559a7ea9a 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt @@ -30,13 +30,14 @@ import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x500.X500Name -import org.eclipse.jetty.util.BlockingArrayQueue import org.junit.After import org.junit.Before import org.junit.Test import java.math.BigInteger import java.security.KeyPair import java.time.Instant +import java.util.* +import java.util.concurrent.LinkedBlockingQueue abstract class AbstractNetworkMapServiceTest { lateinit var mockNet: MockNetwork @@ -222,9 +223,9 @@ abstract class AbstractNetworkMapServiceTest return response } - private fun MockNode.subscribe(): List { + private fun MockNode.subscribe(): Queue { val request = SubscribeRequest(true, network.myAddress) - val updates = BlockingArrayQueue() + val updates = LinkedBlockingQueue() services.networkService.addMessageHandler(PUSH_TOPIC, DEFAULT_SESSION_ID) { message, _ -> updates += message.data.deserialize() } diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index 55be04795d..49d27cbf00 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -29,6 +29,7 @@ dependencies { compile project(path: ":node:capsule", configuration: 'runtimeArtifacts') compile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') compile project(':core') + compile project(':webserver') compile project(':test-utils') } diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index fd957bbcdc..fce442611c 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -32,6 +32,7 @@ dependencies { compile project(':client:jfx') compile project(':client:rpc') compile project(':finance') + compile project(':webserver') compile project(':test-utils') // Javax is required for webapis diff --git a/settings.gradle b/settings.gradle index edfef044a8..fef72becbe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,4 +34,4 @@ include 'samples:network-visualiser' include 'samples:simm-valuation-demo' include 'samples:notary-demo' include 'samples:bank-of-corda-demo' -include 'cordform-common' +include 'cordform-common' \ No newline at end of file diff --git a/test-common/build.gradle b/test-common/build.gradle index 3a955f1691..11574b8cb6 100644 --- a/test-common/build.gradle +++ b/test-common/build.gradle @@ -5,5 +5,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/test-utils/build.gradle b/test-utils/build.gradle index f6801db7ef..c1ac72f456 100644 --- a/test-utils/build.gradle +++ b/test-utils/build.gradle @@ -30,10 +30,8 @@ sourceSets { dependencies { compile project(':test-common') - compile project(':finance') compile project(':core') compile project(':node') - compile project(':webserver') compile project(':client:mock') compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" @@ -67,5 +65,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/verifier/build.gradle b/verifier/build.gradle index e0035ffa33..41ba1988a5 100644 --- a/verifier/build.gradle +++ b/verifier/build.gradle @@ -75,6 +75,6 @@ artifacts { } publish { - name = 'corda-verifier' disableDefaultJar = true + name 'corda-verifier' } diff --git a/webserver/build.gradle b/webserver/build.gradle index 0868a6ec8a..05c2daf185 100644 --- a/webserver/build.gradle +++ b/webserver/build.gradle @@ -72,5 +72,5 @@ jar { } publish { - name = jar.baseName + name jar.baseName } diff --git a/webserver/webcapsule/build.gradle b/webserver/webcapsule/build.gradle index 9cfc1f8e0c..9f5fa8df55 100644 --- a/webserver/webcapsule/build.gradle +++ b/webserver/webcapsule/build.gradle @@ -57,7 +57,6 @@ artifacts { } publish { - name = 'corda-webserver' - publishWar = false // TODO: Use WAR instead of JAR disableDefaultJar = true + name 'corda-webserver' } From b0c299707b3c19c975567e33b3eca213c6a5810e Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 18 Jul 2017 11:41:25 +0100 Subject: [PATCH 042/197] Moved DeclaredField utility to internal and expanded its use --- core/src/main/kotlin/net/corda/core/Utils.kt | 19 ------------- .../core/crypto/composite/CompositeKey.kt | 2 +- .../net/corda/core/internal/InternalUtils.kt | 28 ++++++++++++++++++- .../corda/core/crypto/CompositeKeyTests.kt | 7 ++--- .../kotlin/net/corda/node/SerialFilter.kt | 8 +++--- .../statemachine/FlowStateMachineImpl.kt | 8 ++++-- .../statemachine/StateMachineManager.kt | 12 +++++--- .../node/services/transactions/BFTSMaRt.kt | 2 +- 8 files changed, 49 insertions(+), 37 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 36a70a937f..3be1a00cc3 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -34,7 +34,6 @@ import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import kotlin.concurrent.withLock -import kotlin.reflect.KClass // TODO: Review by EOY2016 if we ever found these utilities helpful. val Int.bd: BigDecimal get() = BigDecimal(this) @@ -338,21 +337,3 @@ fun Class.checkNotUnorderedHashMap() { fun Class<*>.requireExternal(msg: String = "Internal class") = require(!name.startsWith("net.corda.node.") && !name.contains(".internal.")) { "$msg: $name" } - -interface DeclaredField { - companion object { - inline fun Any?.declaredField(clazz: KClass<*>, name: String): DeclaredField = declaredField(clazz.java, name) - inline fun Any.declaredField(name: String): DeclaredField = declaredField(javaClass, name) - inline fun Any?.declaredField(clazz: Class<*>, name: String): DeclaredField { - val javaField = clazz.getDeclaredField(name).apply { isAccessible = true } - val receiver = this - return object : DeclaredField { - override var value - get() = javaField.get(receiver) as T - set(value) = javaField.set(receiver, value) - } - } - } - - var value: T -} diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt index 51a13a076a..9e865ff098 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt @@ -59,7 +59,7 @@ class CompositeKey private constructor(val threshold: Int, children: List = children.sorted() init { // TODO: replace with the more extensive, but slower, checkValidity() test. diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 45c6629d62..3e14f250a5 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -2,11 +2,13 @@ package net.corda.core.internal import java.io.InputStream import java.io.OutputStream +import java.lang.reflect.Field import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.* import java.nio.file.attribute.FileAttribute import java.util.stream.Stream +import kotlin.reflect.KClass /** * Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform @@ -43,4 +45,28 @@ inline fun Path.readLines(charset: Charset = UTF_8, block: (Stream) fun Path.readAllLines(charset: Charset = UTF_8): List = Files.readAllLines(this, charset) fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) -fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) \ No newline at end of file +fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) + +/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ +fun Class<*>.staticField(name: String): DeclaredField = DeclaredField(this, name, null) +/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [KClass]. */ +fun KClass<*>.staticField(name: String): DeclaredField = DeclaredField(java, name, null) +/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) instance field of the receiver object. */ +fun Any.declaredField(name: String): DeclaredField = DeclaredField(javaClass, name, this) +/** + * Returns a [DeclaredField] wrapper around the (possibly non-public) instance field of the receiver object, but declared + * in its superclass [clazz]. + */ +fun Any.declaredField(clazz: KClass<*>, name: String): DeclaredField = DeclaredField(clazz.java, name, this) + +/** + * A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's + * visibility. + */ +class DeclaredField(clazz: Class<*>, name: String, private val receiver: Any?) { + private val javaField = clazz.getDeclaredField(name).apply { isAccessible = true } + var value: T + @Suppress("UNCHECKED_CAST") + get() = javaField.get(receiver) as T + set(value) = javaField.set(receiver, value) +} diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index d0d773951c..9498f15b03 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -1,8 +1,10 @@ package net.corda.core.crypto import net.corda.core.crypto.composite.CompositeKey +import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight import net.corda.core.crypto.composite.CompositeSignature import net.corda.core.crypto.composite.CompositeSignaturesWithKeys +import net.corda.core.internal.declaredField import net.corda.core.internal.div import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes @@ -233,10 +235,7 @@ class CompositeKeyTests { // We will create a graph cycle between key5 and key3. Key5 has already a reference to key3 (via key4). // To create a cycle, we add a reference (child) from key3 to key5. // Children list is immutable, so reflection is used to inject key5 as an extra NodeAndWeight child of key3. - val field = key3.javaClass.getDeclaredField("children") - field.isAccessible = true - val fixedChildren = key3.children.plus(CompositeKey.NodeAndWeight(key5, 1)) - field.set(key3, fixedChildren) + key3.declaredField>("children").value = key3.children + NodeAndWeight(key5, 1) /* A view of the example graph cycle. * diff --git a/node/src/main/kotlin/net/corda/node/SerialFilter.kt b/node/src/main/kotlin/net/corda/node/SerialFilter.kt index ef6ebe138d..9998eacd7b 100644 --- a/node/src/main/kotlin/net/corda/node/SerialFilter.kt +++ b/node/src/main/kotlin/net/corda/node/SerialFilter.kt @@ -1,7 +1,7 @@ package net.corda.node -import net.corda.core.DeclaredField -import net.corda.core.DeclaredField.Companion.declaredField +import net.corda.core.internal.DeclaredField +import net.corda.core.internal.staticField import net.corda.node.internal.Node import java.lang.reflect.Method import java.lang.reflect.Proxy @@ -32,8 +32,8 @@ internal object SerialFilter { undecided = statusEnum.getField("UNDECIDED").get(null) rejected = statusEnum.getField("REJECTED").get(null) val configClass = Class.forName("${filterInterface.name}\$Config") - serialFilterLock = declaredField(configClass, "serialFilterLock").value - serialFilterField = declaredField(configClass, "serialFilter") + serialFilterLock = configClass.staticField("serialFilterLock").value + serialFilterField = configClass.staticField("serialFilter") } internal fun install(acceptClass: (Class<*>) -> Boolean) { 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 c695424938..75d9d4e867 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 @@ -6,19 +6,21 @@ import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture -import net.corda.core.DeclaredField.Companion.declaredField import net.corda.core.abbreviate import net.corda.core.crypto.SecureHash import net.corda.core.crypto.random63BitValue import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.staticField import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.* import net.corda.node.services.api.FlowAppAuditEvent import net.corda.node.services.api.FlowPermissionAuditEvent import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.utilities.* +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.DatabaseTransaction +import net.corda.node.utilities.DatabaseTransactionManager import org.slf4j.Logger import org.slf4j.LoggerFactory import java.sql.Connection @@ -34,7 +36,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, override val flowInitiator: FlowInitiator) : Fiber(id.toString(), scheduler), FlowStateMachine { companion object { // Used to work around a small limitation in Quasar. - private val QUASAR_UNBLOCKER = declaredField(Fiber::class, "SERIALIZER_BLOCKER").value + private val QUASAR_UNBLOCKER = Fiber::class.staticField("SERIALIZER_BLOCKER").value /** * Return the current [FlowStateMachineImpl] or null if executing outside of one. 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 2d72684e41..1fa7885055 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 @@ -5,6 +5,7 @@ import co.paralleluniverse.fibers.FiberExecutorScheduler import co.paralleluniverse.io.serialization.kryo.KryoSerializer import co.paralleluniverse.strands.Strand import com.codahale.metrics.Gauge +import com.esotericsoftware.kryo.ClassResolver import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.Serializer @@ -24,6 +25,7 @@ import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party +import net.corda.core.internal.declaredField import net.corda.core.messaging.DataFeed import net.corda.core.serialization.* import net.corda.core.then @@ -37,7 +39,10 @@ import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession -import net.corda.node.utilities.* +import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.bufferUntilDatabaseCommit +import net.corda.node.utilities.wrapWithDatabaseTransaction import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import rx.Observable @@ -83,10 +88,9 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, private val quasarKryoPool = KryoPool.Builder { val serializer = Fiber.getFiberSerializer(false) as KryoSerializer val classResolver = makeNoWhitelistClassResolver().apply { setKryo(serializer.kryo) } - // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that - val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true } serializer.kryo.apply { - field.set(this, classResolver) + // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that + declaredField(Kryo::class, "classResolver").value = classResolver DefaultKryoCustomizer.customize(this) addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index 3846c81a0e..a7119c3e73 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -11,7 +11,6 @@ import bftsmart.tom.core.messages.TOMMessage import bftsmart.tom.server.defaultservices.DefaultRecoverable import bftsmart.tom.server.defaultservices.DefaultReplier import bftsmart.tom.util.Extractor -import net.corda.core.DeclaredField.Companion.declaredField import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.DigitalSignature @@ -21,6 +20,7 @@ import net.corda.core.crypto.sign import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.identity.Party +import net.corda.core.internal.declaredField import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.serialization.CordaSerializable From 2778e294f3e7f6e8597a76215e6b5e43bb2b8722 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 18 Jul 2017 13:01:20 +0100 Subject: [PATCH 043/197] FiberBox and RetryableException removed as they're not used --- core/src/main/kotlin/net/corda/core/Utils.kt | 13 -- .../net/corda/node/utilities/FiberBox.kt | 76 -------- .../net/corda/node/utilities/FiberBoxTest.kt | 169 ------------------ .../net/corda/irs/api/NodeInterestRates.kt | 4 +- 4 files changed, 2 insertions(+), 260 deletions(-) delete mode 100644 node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt delete mode 100644 node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 3be1a00cc3..44b4ebf5a3 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -8,11 +8,9 @@ import com.google.common.io.ByteStreams import com.google.common.util.concurrent.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.flows.FlowException import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.write -import net.corda.core.serialization.CordaSerializable import org.slf4j.Logger import rx.Observable import rx.Observer @@ -196,17 +194,6 @@ class ThreadBox(val content: T, val lock: ReentrantLock = ReentrantLock() fun checkNotLocked() = check(!lock.isHeldByCurrentThread) } -/** - * This represents a transient exception or condition that might no longer be thrown if the operation is re-run or called - * again. - * - * We avoid the use of the word transient here to hopefully reduce confusion with the term in relation to (Java) serialization. - */ -@CordaSerializable -abstract class RetryableException(message: String) : FlowException(message) - - - /** * Given a path to a zip file, extracts it to the given directory. */ diff --git a/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt b/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt deleted file mode 100644 index d930fe206d..0000000000 --- a/node/src/main/kotlin/net/corda/node/utilities/FiberBox.kt +++ /dev/null @@ -1,76 +0,0 @@ -package net.corda.node.utilities - -import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.concurrent.ReentrantLock -import com.google.common.util.concurrent.SettableFuture -import net.corda.core.RetryableException -import java.time.Clock -import java.time.Instant -import java.util.concurrent.Future -import java.util.concurrent.locks.Lock -import kotlin.concurrent.withLock - -// TODO: We should consider using a Semaphore or CountDownLatch here to make it a little easier to understand, but it seems as though the current version of Quasar does not support suspending on either of their implementations. - -/** - * Modelled on [net.corda.core.ThreadBox], but with support for waiting that is compatible with Quasar [Fiber]s and [MutableClock]s. - * - * It supports 3 main operations, all of which operate in a similar context to the [locked] method - * of [net.corda.core.ThreadBox]. i.e. in the context of the content. - * * [read] operations which acquire the associated lock but do not notify any waiters (see [readWithDeadline]) - * and is a direct equivalent of [net.corda.core.ThreadBox.locked]. - * * [write] operations which are the same as [read] operations but additionally notify any waiters that the content may have changed. - * * [readWithDeadline] operations acquire the lock and are evaluated repeatedly until they no longer throw any subclass - * of [RetryableException]. Between iterations it will wait until woken by a [write] or the deadline is reached. It will eventually - * re-throw a [RetryableException] if the deadline passes without any successful iterations. - * - * The construct also supports [MutableClock]s so it can cope with artificial progress towards the deadline, for simulations - * or testing. - * - * Currently this is intended for use within a node as a simplified way for Oracles to implement subscriptions for changing - * data by running a flow internally to implement the request handler which can then - * effectively relinquish control until the data becomes available. This isn't the most scalable design and is intended - * to be temporary. In addition, it's enitrely possible to envisage a time when we want public [net.corda.core.flows.FlowLogic] - * implementations to be able to wait for some condition to become true outside of message send/receive. At that point - * we may revisit this implementation and indeed the whole model for this, when we understand that requirement more fully. - */ -// TODO This is no longer used and can be removed -class FiberBox(private val content: T, private val lock: Lock = ReentrantLock()) { - private var mutated: SettableFuture? = null - - @Suppress("UNUSED_VALUE") // This is here due to the compiler thinking ourMutated is not used - @Suspendable - fun readWithDeadline(clock: Clock, deadline: Instant, body: T.() -> R): R { - var ex: Exception - var ourMutated: Future? = null - do { - lock.lock() - try { - if (mutated == null || mutated!!.isDone) { - mutated = SettableFuture.create() - } - ourMutated = mutated - return body(content) - } catch(e: RetryableException) { - ex = e - } finally { - lock.unlock() - } - } while (clock.awaitWithDeadline(deadline, ourMutated!!) && clock.instant().isBefore(deadline)) - throw ex - } - - @Suspendable - fun read(body: T.() -> R): R = lock.withLock { body(content) } - - @Suspendable - fun write(body: T.() -> R): R { - lock.lock() - try { - return body(content) - } finally { - mutated?.set(true) - lock.unlock() - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt b/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt deleted file mode 100644 index 25fc475071..0000000000 --- a/node/src/test/kotlin/net/corda/node/utilities/FiberBoxTest.kt +++ /dev/null @@ -1,169 +0,0 @@ -package net.corda.node.utilities - -import co.paralleluniverse.fibers.FiberExecutorScheduler -import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.Strand -import net.corda.core.RetryableException -import net.corda.core.getOrThrow -import net.corda.core.utilities.hours -import net.corda.core.utilities.minutes -import net.corda.testing.node.TestClock -import org.junit.After -import org.junit.Before -import org.junit.Test -import java.time.Clock -import java.time.Duration -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import kotlin.test.assertEquals - -class FiberBoxTest { - - class Content { - var integer: Int = 0 - } - - class TestRetryableException(message: String) : RetryableException(message) - - lateinit var mutex: FiberBox - lateinit var realClock: Clock - lateinit var stoppedClock: Clock - lateinit var executor: ExecutorService - - @Before - fun setup() { - mutex = FiberBox(Content()) - realClock = Clock.systemUTC() - stoppedClock = Clock.fixed(realClock.instant(), realClock.zone) - executor = Executors.newSingleThreadExecutor() - } - - @After - fun teardown() { - executor.shutdown() - } - - @Test - fun `write and read`() { - mutex.write { integer = 1 } - assertEquals(1, mutex.read { integer }) - } - - @Test - fun `readWithDeadline with no wait`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - - mutex.write { integer = 1 } - assertEquals(1, mutex.readWithDeadline(realClock, advancedClock.instant()) { integer }) - } - - @Test - fun `readWithDeadline with stopped clock and background write`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - - assertEquals(1, mutex.readWithDeadline(stoppedClock, advancedClock.instant()) { - backgroundWrite() - if (integer == 1) 1 else throw TestRetryableException("Not 1") - }) - } - - @Test(expected = TestRetryableException::class) - fun `readWithDeadline with clock advanced`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - val testClock = TestClock(stoppedClock) - - assertEquals(1, mutex.readWithDeadline(testClock, advancedClock.instant()) { - backgroundAdvanceClock(testClock, 1.hours) - if (integer == 1) 0 else throw TestRetryableException("Not 1") - }) - } - - @Test - fun `readWithDeadline with clock advanced 5x and background write`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - val testClock = TestClock(stoppedClock) - - assertEquals(5, mutex.readWithDeadline(testClock, advancedClock.instant()) { - backgroundAdvanceClock(testClock, 10.minutes) - backgroundWrite() - if (integer == 5) 5 else throw TestRetryableException("Not 5") - }) - } - - /** - * If this test seems to hang and throw an NPE, then likely that quasar suspendables scanner has not been - * run on core module (in IntelliJ, open gradle side tab and run: - * r3prototyping -> core -> Tasks -> other -> quasarScan - */ - @Test(expected = TestRetryableException::class) - @Suspendable - fun `readWithDeadline with clock advanced on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - val testClock = TestClock(stoppedClock) - val future = CompletableFuture() - val scheduler = FiberExecutorScheduler("test", executor) - val fiber = scheduler.newFiber(@Suspendable { - try { - future.complete(mutex.readWithDeadline(testClock, advancedClock.instant()) { - if (integer == 1) 1 else throw TestRetryableException("Not 1") - }) - } catch(e: Exception) { - future.completeExceptionally(e) - } - }).start() - for (advance in 1..6) { - scheduler.newFiber(@Suspendable { - // Wait until fiber is waiting - while (fiber.state != Strand.State.TIMED_WAITING) { - Strand.sleep(1) - } - testClock.advanceBy(10.minutes) - }).start() - } - assertEquals(2, future.getOrThrow()) - } - - /** - * If this test seems to hang and throw an NPE, then likely that quasar suspendables scanner has not been - * run on core module (in IntelliJ, open gradle side tab and run: - * r3prototyping -> core -> Tasks -> other -> quasarScan - */ - @Test - @Suspendable - fun `readWithDeadline with background write on Fibers`() { - val advancedClock = Clock.offset(stoppedClock, 1.hours) - val testClock = TestClock(stoppedClock) - val future = CompletableFuture() - val scheduler = FiberExecutorScheduler("test", executor) - val fiber = scheduler.newFiber(@Suspendable { - try { - future.complete(mutex.readWithDeadline(testClock, advancedClock.instant()) { - if (integer == 1) 1 else throw TestRetryableException("Not 1") - }) - } catch(e: Exception) { - future.completeExceptionally(e) - } - }).start() - scheduler.newFiber(@Suspendable { - // Wait until fiber is waiting - while (fiber.state != Strand.State.TIMED_WAITING) { - Strand.sleep(1) - } - mutex.write { integer = 1 } - }).start() - assertEquals(1, future.getOrThrow()) - } - - private fun backgroundWrite() { - executor.execute { - mutex.write { integer += 1 } - } - } - - private fun backgroundAdvanceClock(clock: TestClock, duration: Duration) { - executor.execute { - clock.advanceBy(duration) - } - } -} diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 33604a7454..01bcebfb17 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -8,12 +8,12 @@ import net.corda.contracts.Tenor import net.corda.contracts.math.CubicSplineInterpolator import net.corda.contracts.math.Interpolator import net.corda.contracts.math.InterpolatorFactory -import net.corda.core.RetryableException import net.corda.core.ThreadBox import net.corda.core.contracts.Command import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTreeException import net.corda.core.crypto.keys +import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.StartableByRPC @@ -192,7 +192,7 @@ object NodeInterestRates { } // TODO: can we split into two? Fix not available (retryable/transient) and unknown (permanent) - class UnknownFix(val fix: FixOf) : RetryableException("Unknown fix: $fix") + class UnknownFix(val fix: FixOf) : FlowException("Unknown fix: $fix") // Upload the raw fix data via RPC. In a real system the oracle data would be taken from a database. @StartableByRPC From f571aeb6a7e9e2be185ae1150af928558bf520ad Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Tue, 18 Jul 2017 16:54:32 +0100 Subject: [PATCH 044/197] Remove createTwoNodes() Remove `createTwoNodes()` from mock network as its behaviour is inconsistent with creating a set of nodes. `createSomeNodes()` is generally a better fit and creates a network map and notary, and registers all nodes. Where that's not the intention, it's acceptable to manually create each node. --- .../net/corda/flows/CashExitFlowTests.kt | 6 ++-- .../net/corda/flows/CashIssueFlowTests.kt | 6 ++-- .../net/corda/flows/CashPaymentFlowTests.kt | 8 ++--- .../corda/node/messaging/AttachmentTests.kt | 8 +++-- .../network/AbstractNetworkMapServiceTest.kt | 8 ++--- .../network/InMemoryNetworkMapCacheTest.kt | 8 +++-- .../persistence/DataVendingServiceTests.kt | 8 +++-- .../statemachine/FlowFrameworkTests.kt | 22 ++++++++++---- .../kotlin/net/corda/testing/node/MockNode.kt | 29 +++---------------- 9 files changed, 51 insertions(+), 52 deletions(-) diff --git a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt index b5ff01bff8..9ed4a335ed 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt @@ -26,9 +26,9 @@ class CashExitFlowTests { @Before fun start() { - val nodes = mockNet.createTwoNodes() - notaryNode = nodes.first - bankOfCordaNode = nodes.second + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] notary = notaryNode.info.notaryIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity diff --git a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt index db183cabff..36f0edddd4 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt @@ -24,9 +24,9 @@ class CashIssueFlowTests { @Before fun start() { - val nodes = mockNet.createTwoNodes() - notaryNode = nodes.first - bankOfCordaNode = nodes.second + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] notary = notaryNode.info.notaryIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity diff --git a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt index 59261e384e..8dee2c15b8 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashPaymentFlowTests.kt @@ -31,14 +31,12 @@ class CashPaymentFlowTests { @Before fun start() { - val nodes = mockNet.createTwoNodes() - notaryNode = nodes.first - bankOfCordaNode = nodes.second + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] notary = notaryNode.info.notaryIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity - notaryNode.services.identityService.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert) - bankOfCordaNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert) val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, bankOfCorda, notary)).resultFuture diff --git a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt index 8789df1724..bdba7b2e77 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt @@ -55,7 +55,9 @@ class AttachmentTests { @Test fun `download and store`() { - val (n0, n1) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(2) + val n0 = nodes.partyNodes[0] + val n1 = nodes.partyNodes[1] // Insert an attachment into node zero's store directly. val id = n0.database.transaction { @@ -84,7 +86,9 @@ class AttachmentTests { @Test fun `missing`() { - val (n0, n1) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(2) + val n0 = nodes.partyNodes[0] + val n1 = nodes.partyNodes[1] // Get node one to fetch a non-existent attachment. val hash = SecureHash.randomSHA256() diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt index 1559a7ea9a..6b6d20da42 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt @@ -1,6 +1,7 @@ package net.corda.node.services.network import com.google.common.util.concurrent.ListenableFuture +import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo @@ -19,6 +20,7 @@ import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC +import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.AddOrRemove.ADD import net.corda.node.utilities.AddOrRemove.REMOVE @@ -51,10 +53,8 @@ abstract class AbstractNetworkMapServiceTest @Before fun setup() { mockNet = MockNetwork(defaultFactory = nodeFactory) - mockNet.createTwoNodes(firstNodeName = DUMMY_MAP.name, secondNodeName = ALICE.name).apply { - mapServiceNode = first - alice = second - } + mapServiceNode = mockNet.createNode(null, -1, nodeFactory, true, DUMMY_MAP.name, null, BigInteger.valueOf(random63BitValue()), ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)) + alice = mockNet.createNode(mapServiceNode.network.myAddress, -1, nodeFactory, true, ALICE.name) mockNet.runNetwork() lastSerial = System.currentTimeMillis() } diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt index a77ae0e1ef..4ac1ff00cc 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt @@ -21,7 +21,9 @@ class InMemoryNetworkMapCacheTest { @Test fun registerWithNetwork() { - val (n0, n1) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(1) + val n0 = nodes.mapNode + val n1 = nodes.partyNodes[0] val future = n1.services.networkMapCache.addMapService(n1.network, n0.network.myAddress, false, null) mockNet.runNetwork() future.getOrThrow() @@ -48,7 +50,9 @@ class InMemoryNetworkMapCacheTest { @Test fun `getNodeByLegalIdentity`() { - val (n0, n1) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(1) + val n0 = nodes.mapNode + val n1 = nodes.partyNodes[0] val node0Cache: NetworkMapCache = n0.services.networkMapCache val expected = n1.info diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt index ab29d87541..a26802a529 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt @@ -42,7 +42,9 @@ class DataVendingServiceTests { @Test fun `notify of transaction`() { - val (vaultServiceNode, registerNode) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(2) + val vaultServiceNode = nodes.partyNodes[0] + val registerNode = nodes.partyNodes[1] val beneficiary = vaultServiceNode.info.legalIdentity val deposit = registerNode.info.legalIdentity.ref(1) mockNet.runNetwork() @@ -70,7 +72,9 @@ class DataVendingServiceTests { */ @Test fun `notify failure`() { - val (vaultServiceNode, registerNode) = mockNet.createTwoNodes() + val nodes = mockNet.createSomeNodes(2) + val vaultServiceNode = nodes.partyNodes[0] + val registerNode = nodes.partyNodes[1] val beneficiary = vaultServiceNode.info.legalIdentity val deposit = MEGA_CORP.ref(1) mockNet.runNetwork() diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index b4c28dc7c4..3c3b87c44f 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -31,6 +31,7 @@ import net.corda.core.utilities.unwrap import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.checkpoints import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.testing.* @@ -71,19 +72,27 @@ class FlowFrameworkTests { @Before fun start() { - val nodes = mockNet.createTwoNodes() - node1 = nodes.first - node2 = nodes.second + node1 = mockNet.createNode(advertisedServices = ServiceInfo(NetworkMapService.type)) + node2 = mockNet.createNode(networkMapAddress = node1.network.myAddress) + // We intentionally create our own notary and ignore the one provided by the network val notaryKeyPair = generateKeyPair() val notaryService = ServiceInfo(ValidatingNotaryService.type, getTestX509Name("notary-service-2000")) val overrideServices = mapOf(Pair(notaryService, notaryKeyPair)) // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing // service addressing. - notary1 = mockNet.createNotaryNode(networkMapAddr = node1.network.myAddress, overrideServices = overrideServices, serviceName = notaryService.name) - notary2 = mockNet.createNotaryNode(networkMapAddr = node1.network.myAddress, overrideServices = overrideServices, serviceName = notaryService.name) + notary1 = mockNet.createNotaryNode(networkMapAddress = node1.network.myAddress, overrideServices = overrideServices, serviceName = notaryService.name) + notary2 = mockNet.createNotaryNode(networkMapAddress = node1.network.myAddress, overrideServices = overrideServices, serviceName = notaryService.name) mockNet.messagingNetwork.receivedMessages.toSessionTransfers().forEach { sessionTransfers += it } mockNet.runNetwork() + + // We don't create a network map, so manually handle registrations + val nodes = listOf(node1, node2, notary1, notary2) + nodes.forEach { node -> + nodes.map { it.services.myInfo.legalIdentityAndCert }.forEach { identity -> + node.services.identityService.registerIdentity(identity) + } + } } @After @@ -327,8 +336,9 @@ class FlowFrameworkTests { anonymous = false)) // We pay a couple of times, the notary picking should go round robin for (i in 1..3) { - node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false)) + val flow = node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false)) mockNet.runNetwork() + flow.resultFuture.getOrThrow() } val endpoint = mockNet.messagingNetwork.endpoint(notary1.network.myAddress as InMemoryMessagingNetwork.PeerHandle)!! val party1Info = notary1.services.networkMapCache.getPartyInfo(notary1.info.notaryIdentity)!! diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt index e511175760..28e856a203 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -331,27 +331,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } - // TODO: Move this to using createSomeNodes which doesn't conflate network services with network users. - /** - * Sets up a two node network, in which the first node runs network map and notary services and the other - * doesn't. - */ - fun createTwoNodes(firstNodeName: X500Name? = null, - secondNodeName: X500Name? = null, - nodeFactory: Factory = defaultFactory, - notaryKeyPair: KeyPair? = null): Pair { - require(nodes.isEmpty()) - val notaryServiceInfo = ServiceInfo(SimpleNotaryService.type) - val notaryOverride = if (notaryKeyPair != null) - mapOf(Pair(notaryServiceInfo, notaryKeyPair)) - else - null - return Pair( - createNode(null, -1, nodeFactory, true, firstNodeName, notaryOverride, BigInteger.valueOf(random63BitValue()), ServiceInfo(NetworkMapService.type), notaryServiceInfo), - createNode(nodes[0].network.myAddress, -1, nodeFactory, true, secondNodeName) - ) - } - /** * A bundle that separates the generic user nodes and service-providing nodes. A real network might not be so * clearly separated, but this is convenient for testing. @@ -384,18 +363,18 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, return BasketOfNodes(nodes, notaryNode, mapNode) } - fun createNotaryNode(networkMapAddr: SingleMessageRecipient? = null, + fun createNotaryNode(networkMapAddress: SingleMessageRecipient? = null, legalName: X500Name? = null, overrideServices: Map? = null, serviceName: X500Name? = null): MockNode { - return createNode(networkMapAddr, -1, defaultFactory, true, legalName, overrideServices, BigInteger.valueOf(random63BitValue()), + return createNode(networkMapAddress, -1, defaultFactory, true, legalName, overrideServices, BigInteger.valueOf(random63BitValue()), ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type, serviceName)) } - fun createPartyNode(networkMapAddr: SingleMessageRecipient, + fun createPartyNode(networkMapAddress: SingleMessageRecipient, legalName: X500Name? = null, overrideServices: Map? = null): MockNode { - return createNode(networkMapAddr, -1, defaultFactory, true, legalName, overrideServices) + return createNode(networkMapAddress, -1, defaultFactory, true, legalName, overrideServices) } @Suppress("unused") // This is used from the network visualiser tool. From 5501d74d292793399ca79596c939200090fe9a8c Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Tue, 18 Jul 2017 16:22:20 +0100 Subject: [PATCH 045/197] Remove NotaryChangeTxBuilder from examples - it's being deprecated --- .../java/net/corda/docs/FlowCookbookJava.java | 24 ++++++++----------- .../kotlin/net/corda/docs/FlowCookbook.kt | 24 ++++++++----------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index 2abc9a3316..4b5b2b7e41 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableSet; import net.corda.contracts.asset.Cash; import net.corda.core.contracts.*; import net.corda.core.contracts.TransactionType.General; -import net.corda.core.contracts.TransactionType.NotaryChange; import net.corda.core.crypto.DigitalSignature; import net.corda.core.crypto.SecureHash; import net.corda.core.flows.*; @@ -311,16 +310,13 @@ public class FlowCookbookJava { ------------------------*/ progressTracker.setCurrentStep(TX_BUILDING); - // There are two types of transaction (notary-change and general), - // and therefore two types of transaction builder: // DOCSTART 19 - TransactionBuilder notaryChangeTxBuilder = new TransactionBuilder(NotaryChange.INSTANCE, specificNotary); - TransactionBuilder regTxBuilder = new TransactionBuilder(General.INSTANCE, specificNotary); + TransactionBuilder txBuilder = new TransactionBuilder(General.INSTANCE, specificNotary); // DOCEND 19 // We add items to the transaction builder using ``TransactionBuilder.withItems``: // DOCSTART 27 - regTxBuilder.withItems( + txBuilder.withItems( // Inputs, as ``StateRef``s that reference to the outputs of previous transactions ourStateAndRef, // Outputs, as ``ContractState``s @@ -332,20 +328,20 @@ public class FlowCookbookJava { // We can also add items using methods for the individual components: // DOCSTART 28 - regTxBuilder.addInputState(ourStateAndRef); - regTxBuilder.addOutputState(ourOutput); - regTxBuilder.addCommand(ourCommand); - regTxBuilder.addAttachment(ourAttachment); + txBuilder.addInputState(ourStateAndRef); + txBuilder.addOutputState(ourOutput); + txBuilder.addCommand(ourCommand); + txBuilder.addAttachment(ourAttachment); // DOCEND 28 // There are several ways of setting the transaction's time-window. // We can set a time-window directly: // DOCSTART 44 - regTxBuilder.setTimeWindow(ourTimeWindow); + txBuilder.setTimeWindow(ourTimeWindow); // DOCEND 44 // Or as a start time plus a duration (e.g. 45 seconds): // DOCSTART 45 - regTxBuilder.setTimeWindow(Instant.now(), Duration.ofSeconds(45)); + txBuilder.setTimeWindow(Instant.now(), Duration.ofSeconds(45)); // DOCEND 45 /*----------------------- @@ -356,12 +352,12 @@ public class FlowCookbookJava { // We finalise the transaction by signing it, // converting it into a ``SignedTransaction``. // DOCSTART 29 - SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(regTxBuilder); + SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(txBuilder); // DOCEND 29 // We can also sign the transaction using a different public key: // DOCSTART 30 PublicKey otherKey = getServiceHub().getKeyManagementService().freshKey(); - SignedTransaction onceSignedTx2 = getServiceHub().signInitialTransaction(regTxBuilder, otherKey); + SignedTransaction onceSignedTx2 = getServiceHub().signInitialTransaction(txBuilder, otherKey); // DOCEND 30 // If instead this was a ``SignedTransaction`` that we'd received diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index bd6eb2a93d..88d9e05d6b 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.asset.Cash import net.corda.core.contracts.* import net.corda.core.contracts.TransactionType.General -import net.corda.core.contracts.TransactionType.NotaryChange import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.flows.* @@ -292,16 +291,13 @@ object FlowCookbook { -----------------------**/ progressTracker.currentStep = TX_BUILDING - // There are two types of transaction (notary-change and general), - // and therefore two types of transaction builder: // DOCSTART 19 - val notaryChangeTxBuilder: TransactionBuilder = TransactionBuilder(NotaryChange, specificNotary) - val regTxBuilder: TransactionBuilder = TransactionBuilder(General, specificNotary) + val txBuilder: TransactionBuilder = TransactionBuilder(General, specificNotary) // DOCEND 19 // We add items to the transaction builder using ``TransactionBuilder.withItems``: // DOCSTART 27 - regTxBuilder.withItems( + txBuilder.withItems( // Inputs, as ``StateRef``s that reference the outputs of previous transactions ourStateAndRef, // Outputs, as ``ContractState``s @@ -313,20 +309,20 @@ object FlowCookbook { // We can also add items using methods for the individual components: // DOCSTART 28 - regTxBuilder.addInputState(ourStateAndRef) - regTxBuilder.addOutputState(ourOutput) - regTxBuilder.addCommand(ourCommand) - regTxBuilder.addAttachment(ourAttachment) + txBuilder.addInputState(ourStateAndRef) + txBuilder.addOutputState(ourOutput) + txBuilder.addCommand(ourCommand) + txBuilder.addAttachment(ourAttachment) // DOCEND 28 // There are several ways of setting the transaction's time-window. // We can set a time-window directly: // DOCSTART 44 - regTxBuilder.setTimeWindow(ourTimeWindow) + txBuilder.setTimeWindow(ourTimeWindow) // DOCEND 44 // Or as a start time plus a duration (e.g. 45 seconds): // DOCSTART 45 - regTxBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds) + txBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds) // DOCEND 45 /**---------------------- @@ -337,12 +333,12 @@ object FlowCookbook { // We finalise the transaction by signing it, converting it into a // ``SignedTransaction``. // DOCSTART 29 - val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder) + val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(txBuilder) // DOCEND 29 // We can also sign the transaction using a different public key: // DOCSTART 30 val otherKey: PublicKey = serviceHub.keyManagementService.freshKey() - val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder, otherKey) + val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(txBuilder, otherKey) // DOCEND 30 // If instead this was a ``SignedTransaction`` that we'd received From cb306a22fe8ae57b9a1d9995c0dea00eec3823b3 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Tue, 18 Jul 2017 16:27:56 +0100 Subject: [PATCH 046/197] Modify NodeVaultServiceTest to not access makeUpdate() directly --- .../services/vault/NodeVaultServiceTest.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 0082810604..9a6e3be062 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -25,6 +25,7 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before import org.junit.Test +import rx.observers.TestSubscriber import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -443,6 +444,10 @@ class NodeVaultServiceTest { @Test fun `make update`() { val service = (services.vaultService as NodeVaultService) + val vaultSubscriber = TestSubscriber().apply { + service.updates.subscribe(this) + } + val anonymousIdentity = services.keyManagementService.freshKeyAndCert(services.myInfo.legalIdentityAndCert, false) val thirdPartyIdentity = AnonymousParty(generateKeyPair().public) val amount = Amount(1000, Issued(BOC.ref(1), GBP)) @@ -454,21 +459,18 @@ class NodeVaultServiceTest { }.toWireTransaction() val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0)) - database.transaction { - val expected = Vault.Update(emptySet(), setOf(cashState), null) - val actual = service.makeUpdate(issueTx, setOf(anonymousIdentity.identity.owningKey)) - assertEquals(expected, actual) - services.vaultService.notify(issueTx) - } + database.transaction { service.notify(issueTx) } + val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null) database.transaction { val moveTx = TransactionBuilder(TransactionType.General, services.myInfo.legalIdentity).apply { - services.vaultService.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity) + service.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity) }.toWireTransaction() - - val expected = Vault.Update(setOf(cashState), emptySet(), null) - val actual = service.makeUpdate(moveTx, setOf(anonymousIdentity.identity.owningKey)) - assertEquals(expected, actual) + service.notify(moveTx) } + val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null) + + val observedUpdates = vaultSubscriber.onNextEvents + assertEquals(observedUpdates, listOf(expectedIssueUpdate, expectedMoveUpdate)) } } From 8a2074eeeef09e13e55fa96070653ce4d2eb9455 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Wed, 19 Jul 2017 12:33:57 +0300 Subject: [PATCH 047/197] Remove tolerance from TimeWindowChecker (#1047) --- .../core/node/services/TimeWindowChecker.kt | 14 ++++------- .../node/services/TimeWindowCheckerTests.kt | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt index 771a821ce4..35296f06a6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt @@ -1,26 +1,22 @@ package net.corda.core.node.services import net.corda.core.contracts.TimeWindow -import net.corda.core.utilities.seconds -import net.corda.core.until import java.time.Clock -import java.time.Duration /** - * Checks if the given time-window falls within the allowed tolerance interval. + * Checks if the current instant provided by the input clock falls within the provided time-window. */ -class TimeWindowChecker(val clock: Clock = Clock.systemUTC(), - val tolerance: Duration = 30.seconds) { +class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) { fun isValid(timeWindow: TimeWindow): Boolean { - val untilTime = timeWindow.untilTime val fromTime = timeWindow.fromTime + val untilTime = timeWindow.untilTime val now = clock.instant() // We don't need to test for (fromTime == null && untilTime == null) or backwards bounds because the TimeWindow // constructor already checks that. - if (untilTime != null && untilTime until now > tolerance) return false - if (fromTime != null && now until fromTime > tolerance) return false + if (fromTime != null && now < fromTime) return false + if (untilTime != null && now > untilTime) return false return true } } diff --git a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt index 3b18f549b2..0546ae22a8 100644 --- a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt @@ -11,23 +11,29 @@ import kotlin.test.assertTrue class TimeWindowCheckerTests { val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) - val timeWindowChecker = TimeWindowChecker(clock, tolerance = 30.seconds) + val timeWindowChecker = TimeWindowChecker(clock) @Test fun `should return true for valid time-window`() { val now = clock.instant() - val timeWindowPast = TimeWindow.between(now - 60.seconds, now - 29.seconds) - val timeWindowFuture = TimeWindow.between(now + 29.seconds, now + 60.seconds) - assertTrue { timeWindowChecker.isValid(timeWindowPast) } - assertTrue { timeWindowChecker.isValid(timeWindowFuture) } + val timeWindowBetween = TimeWindow.between(now - 10.seconds, now + 10.seconds) + val timeWindowFromOnly = TimeWindow.fromOnly(now - 10.seconds) + val timeWindowUntilOnly = TimeWindow.untilOnly(now + 10.seconds) + assertTrue { timeWindowChecker.isValid(timeWindowBetween) } + assertTrue { timeWindowChecker.isValid(timeWindowFromOnly) } + assertTrue { timeWindowChecker.isValid(timeWindowUntilOnly) } } @Test fun `should return false for invalid time-window`() { val now = clock.instant() - val timeWindowPast = TimeWindow.between(now - 60.seconds, now - 31.seconds) - val timeWindowFuture = TimeWindow.between(now + 31.seconds, now + 60.seconds) - assertFalse { timeWindowChecker.isValid(timeWindowPast) } - assertFalse { timeWindowChecker.isValid(timeWindowFuture) } + val timeWindowBetweenPast = TimeWindow.between(now - 10.seconds, now - 2.seconds) + val timeWindowBetweenFuture = TimeWindow.between(now + 2.seconds, now + 10.seconds) + val timeWindowFromOnlyFuture = TimeWindow.fromOnly(now + 10.seconds) + val timeWindowUntilOnlyPast = TimeWindow.untilOnly(now - 10.seconds) + assertFalse { timeWindowChecker.isValid(timeWindowBetweenPast) } + assertFalse { timeWindowChecker.isValid(timeWindowBetweenFuture) } + assertFalse { timeWindowChecker.isValid(timeWindowFromOnlyFuture) } + assertFalse { timeWindowChecker.isValid(timeWindowUntilOnlyPast) } } } From 264b9316e384c83c2af36ecea6e2303bfa80e32f Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 19 Jul 2017 11:14:48 +0100 Subject: [PATCH 048/197] Store notaries's identity composite key in keystore (#1036) * * Store composite key in keystore from file for notaries's identity. * Some refactoring. * * Addressed PR issues * * Remove unintended format changes * * Fixed failing test due to getting keys from wrong keystore --- .../messaging/MQSecurityAsNodeTest.kt | 5 +- .../net/corda/node/internal/AbstractNode.kt | 137 ++++++++---------- .../node/services/config/ConfigUtilities.kt | 4 +- .../corda/node/utilities/KeyStoreUtilities.kt | 54 ++++++- .../registration/NetworkRegistrationHelper.kt | 2 +- 5 files changed, 114 insertions(+), 88 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index 6d3dbd452d..59649518bd 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -22,7 +22,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints -import org.bouncycastle.cert.path.CertPath import org.junit.Test import java.nio.file.Files @@ -118,7 +117,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { X509Utilities.CORDA_CLIENT_CA, clientKey.private, keyPass, - CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert))) + arrayOf(clientCACert, intermediateCA.certificate, rootCACert)) clientCAKeystore.save(nodeKeystore, keyStorePassword) val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) @@ -126,7 +125,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass, - CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))) + arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert)) tlsKeystore.save(sslKeystore, keyStorePassword) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3557eff7e7..fdc15af0d2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -8,9 +8,9 @@ import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.SettableFuture import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult -import net.corda.core.* import net.corda.core.crypto.* import net.corda.core.crypto.composite.CompositeKey +import net.corda.core.flatMap import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -28,7 +28,10 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.toNonEmptySet -import net.corda.flows.* +import net.corda.flows.CashExitFlow +import net.corda.flows.CashIssueFlow +import net.corda.flows.CashPaymentFlow +import net.corda.flows.IssuerFlow import net.corda.node.services.* import net.corda.node.services.api.* import net.corda.node.services.config.NodeConfiguration @@ -69,15 +72,14 @@ import rx.Observable import java.io.IOException import java.lang.reflect.InvocationTargetException import java.lang.reflect.Modifier.* -import java.math.BigInteger import java.net.JarURLConnection import java.net.URI import java.nio.file.Path import java.nio.file.Paths import java.security.KeyPair -import java.security.KeyStore import java.security.KeyStoreException -import java.security.cert.* +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate import java.time.Clock import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -709,63 +711,62 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, // the legal name is actually validated in some way. // TODO: Integrate with Key management service? - val certFactory = CertificateFactory.getInstance("X509") val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val privateKeyAlias = "$serviceId-private-key" - val privKeyFile = configuration.baseDirectory / privateKeyAlias - val pubIdentityFile = configuration.baseDirectory / "$serviceId-public" - val certificateAndKeyPair = keyStore.certificateAndKeyPair(privateKeyAlias) - val identityCertPathAndKey: Pair = if (certificateAndKeyPair != null) { - val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - val (cert, keyPair) = certificateAndKeyPair - // Get keys from keystore. - val loadedServiceName = cert.subject - if (loadedServiceName != serviceName) { - throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" + - "$serviceName vs $loadedServiceName") - } - val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath) - Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair) - } else if (privKeyFile.exists()) { + val compositeKeyAlias = "$serviceId-composite-key" + + if (!keyStore.containsAlias(privateKeyAlias)) { + val privKeyFile = configuration.baseDirectory / privateKeyAlias + val pubIdentityFile = configuration.baseDirectory / "$serviceId-public" + // TODO: Remove use of [ServiceIdentityGenerator.generateToDisk]. // Get keys from key file. - // TODO: this is here to smooth out the key storage transition, remove this in future release. - // Check that the identity in the config file matches the identity file we have stored to disk. - // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed - // things up for us. - val myIdentity = pubIdentityFile.readAll().deserialize() - if (myIdentity.name != serviceName) - throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" + - "$serviceName vs ${myIdentity.name}") - // Load the private key. - val keyPair = privKeyFile.readAll().deserialize() - if (myIdentity.owningKey !is CompositeKey) { // TODO: Support case where owningKey is a composite key. - keyStore.save(serviceName, privateKeyAlias, keyPair) + // TODO: this is here to smooth out the key storage transition, remove this migration in future release. + if (privKeyFile.exists()) { + migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, privateKeyAlias, compositeKeyAlias) + } else { + log.info("$privateKeyAlias not found in keystore ${configuration.nodeKeystore}, generating fresh key!") + keyStore.saveNewKeyPair(serviceName, privateKeyAlias, generateKeyPair()) } - val dummyCaKey = entropyToKeyPair(BigInteger.valueOf(111)) - val dummyCa = CertificateAndKeyPair( - X509Utilities.createSelfSignedCACertificate(X500Name("CN=Dummy CA,OU=Corda,O=R3 Ltd,L=London,C=GB"), dummyCaKey), - dummyCaKey) - val partyAndCertificate = getTestPartyAndCertificate(myIdentity, dummyCa) - // Sanity check the certificate and path - val validatorParameters = PKIXParameters(setOf(TrustAnchor(dummyCa.certificate.cert, null))) - val validator = CertPathValidator.getInstance("PKIX") - validatorParameters.isRevocationEnabled = false - validator.validate(partyAndCertificate.certPath, validatorParameters) as PKIXCertPathValidatorResult - Pair(partyAndCertificate, keyPair) - } else { - val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!! - // Create new keys and store in keystore. - log.info("Identity key not found, generating fresh key!") - val keyPair: KeyPair = generateKeyPair() - val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public) - val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath) - keyStore.save(serviceName, privateKeyAlias, keyPair) - require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" } - Pair(PartyAndCertificate(serviceName, keyPair.public, cert, certPath), keyPair) } - partyKeys += identityCertPathAndKey.second - return identityCertPathAndKey + + val (cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias) + + // Get keys from keystore. + val loadedServiceName = cert.subject + if (loadedServiceName != serviceName) + throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:$serviceName vs $loadedServiceName") + + val certPath = CertificateFactory.getInstance("X509").generateCertPath(keyStore.getCertificateChain(privateKeyAlias).toList()) + // Use composite key instead if exists + // TODO: Use configuration to indicate composite key should be used instead of public key for the identity. + val publicKey = if (keyStore.containsAlias(compositeKeyAlias)) { + Crypto.toSupportedPublicKey(keyStore.getCertificate(compositeKeyAlias).publicKey) + } else { + keyPair.public + } + + partyKeys += keyPair + return Pair(PartyAndCertificate(loadedServiceName, publicKey, cert, certPath), keyPair) + } + + private fun migrateKeysFromFile(keyStore: KeyStoreWrapper, serviceName: X500Name, + pubIdentityFile: Path, privKeyFile: Path, + privateKeyAlias: String, compositeKeyAlias: String) { + log.info("Migrating $privateKeyAlias from file to keystore...") + val myIdentity = pubIdentityFile.readAll().deserialize() + // Check that the identity in the config file matches the identity file we have stored to disk. + // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed + // things up for us. + if (myIdentity.name != serviceName) + throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:$serviceName vs ${myIdentity.name}") + // Load the private key. + val keyPair = privKeyFile.readAll().deserialize() + keyStore.saveNewKeyPair(serviceName, privateKeyAlias, keyPair) + // Store composite key separately. + if (myIdentity.owningKey is CompositeKey) { + keyStore.savePublicKey(serviceName, compositeKeyAlias, myIdentity.owningKey) + } + log.info("Finish migrating $privateKeyAlias from file to keystore.") } private fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair): PartyAndCertificate { @@ -801,10 +802,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, override val keyManagementService by lazy { makeKeyManagementService(identityService) } override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch) } override val identityService by lazy { - val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) + val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) + val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) makeIdentityService( - keyStoreWrapper.keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate, - keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA), + trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).cert, + caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA), info.legalIdentityAndCert) } override val attachments: AttachmentStorage get() = this@AbstractNode.attachments @@ -836,18 +838,3 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } } - -private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) { - constructor(storePath: Path, storePassword: String) : this(loadKeyStore(storePath, storePassword), storePath, storePassword) - - fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? { - return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null - } - - fun save(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) { - val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword) - val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public).cert - keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), arrayOf(cert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))) - keyStore.save(storePath, storePassword) - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index b1217548d0..76d7ec5792 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -98,7 +98,7 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path, X509Utilities.CORDA_CLIENT_CA, clientKey.private, keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) + arrayOf(clientCACert, intermediateCACert, rootCACert)) clientCAKeystore.save(clientCAKeystorePath, storePassword) val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword) @@ -106,6 +106,6 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path, X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) + arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert)) tlsKeystore.save(sslKeyStorePath, storePassword) } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt index 161a4f8142..b429f2e09e 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt @@ -1,19 +1,19 @@ package net.corda.node.utilities -import net.corda.core.crypto.CertificateAndKeyPair -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.cert +import net.corda.core.crypto.* import net.corda.core.internal.exists import net.corda.core.internal.read import net.corda.core.internal.write +import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.cert.X509CertificateHolder -import org.bouncycastle.cert.path.CertPath import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.nio.file.Path import java.security.* +import java.security.cert.CertPath import java.security.cert.Certificate +import java.security.cert.CertificateFactory val KEYSTORE_TYPE = "JKS" @@ -77,8 +77,8 @@ fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { * but for SSL purposes this is recommended. * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. */ -fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: CertPath) { - addOrReplaceKey(alias, key, password, chain.certificates.map { it.cert }.toTypedArray()) +fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array) { + addOrReplaceKey(alias, key, password, chain.map { it.cert }.toTypedArray()) } /** @@ -89,7 +89,7 @@ fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain * but for SSL purposes this is recommended. * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. */ -fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array) { +fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array) { if (containsAlias(alias)) { this.deleteEntry(alias) } @@ -168,3 +168,43 @@ fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey { val key = getKey(alias, keyPass) as PrivateKey return Crypto.toSupportedPrivateKey(key) } + +class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) { + private val keyStore = storePath.read { loadKeyStore(it, storePassword) } + + private fun createCertificate(serviceName: X500Name, pubKey: PublicKey): CertPath { + val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + // Assume key password = store password. + val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + // Create new keys and store in keystore. + val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey) + val certPath = CertificateFactory.getInstance("X509").generateCertPath(listOf(cert.cert) + clientCertPath) + require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" } + return certPath + } + + fun saveNewKeyPair(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) { + val certPath = createCertificate(serviceName, keyPair.public) + // Assume key password = store password. + keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray()) + keyStore.save(storePath, storePassword) + } + + fun savePublicKey(serviceName: X500Name, pubKeyAlias: String, pubKey: PublicKey) { + val certPath = createCertificate(serviceName, pubKey) + // Assume key password = store password. + keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first()) + keyStore.save(storePath, storePassword) + } + + // Delegate methods to keystore. Sadly keystore doesn't have an interface. + fun containsAlias(alias: String) = keyStore.containsAlias(alias) + + fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias) + + fun getCertificateChain(alias: String): Array = keyStore.getCertificateChain(alias) + + fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias) + + fun certificateAndKeyPair(alias: String) = keyStore.getCertificateAndKeyPair(alias, storePassword) +} diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 55ea786b23..dde44de66e 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -49,7 +49,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) // Save to the key store. caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), - CertPath(arrayOf(selfSignCert))) + arrayOf(selfSignCert)) caKeyStore.save(config.nodeKeystore, keystorePassword) } val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) From 298287fe28f419b12fb87431c6468caef40c86a1 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 19 Jul 2017 13:34:28 +0100 Subject: [PATCH 049/197] Expand documentation on obligation contract Expand documentation on obligation contract and add an example issuance function for cash obligations. --- .../net/corda/contracts/asset/Obligation.kt | 37 +++++++++++++++++++ .../corda/contracts/asset/ObligationTests.kt | 5 ++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt index 9399aeba7a..ec2643b9fd 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt @@ -254,6 +254,12 @@ class Obligation

: Contract { * Subset of state, containing the elements specified when issuing a new settlement contract. * * @param P the product the obligation is for payment of. + * @param acceptableContracts is the contract types that can be accepted, such as cash. + * @param acceptableIssuedProducts is the assets which are acceptable forms of payment (i.e. GBP issued by the Bank + * of England). + * @param dueBefore when payment is due by. + * @param timeTolerance tolerance value on [dueBefore], to handle clock skew between distributed systems. Generally + * this would be about 30 seconds. */ @CordaSerializable data class Terms

( @@ -467,8 +473,39 @@ class Obligation

: Contract { generateExitCommand = { amount -> Commands.Exit(amount) } ) + /** + * Puts together an issuance transaction for the specified currency obligation amount that starts out being owned by + * the given pubkey. + * + * @param tx transaction builder to add states and commands to. + * @param obligor the party who is expected to pay some currency amount to fulfil the obligation (also the owner of + * the obligation). + * @param amount currency amount the obligor is expected to pay. + * @param dueBefore the date on which the obligation is due. The default time tolerance is used (currently this is + * 30 seconds). + * @param beneficiary the party the obligor is expected to pay. + * @param notary the notary for this transaction's outputs. + */ + fun generateCashIssue(tx: TransactionBuilder, + obligor: AbstractParty, + amount: Amount>, + dueBefore: Instant, + beneficiary: AbstractParty, + notary: Party) { + val issuanceDef = Terms(NonEmptySet.of(Cash().legalContractReference), NonEmptySet.of(amount.token), dueBefore) + OnLedgerAsset.generateIssue(tx, TransactionState(State(Lifecycle.NORMAL, obligor, issuanceDef, amount.quantity, beneficiary), notary), Commands.Issue()) + } + /** * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey. + * + * @param tx transaction builder to add states and commands to. + * @param obligor the party who is expected to pay some amount to fulfil the obligation. + * @param issuanceDef the terms of the obligation, including which contracts and underlying assets are acceptable + * forms of payment. + * @param pennies the quantity of the asset (in the smallest normal unit of measurement) owed. + * @param beneficiary the party the obligor is expected to pay. + * @param notary the notary for this transaction's outputs. */ fun generateIssue(tx: TransactionBuilder, obligor: AbstractParty, diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index aee082b27f..fe3113b2af 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -274,9 +274,10 @@ class ObligationTests { // We don't actually verify the states, this is just here to make things look sensible val dueBefore = TEST_TX_TIME - 7.days - // Generate a transaction issuing the obligation + // Generate a transaction issuing the obligation. var tx = TransactionType.General.Builder(null).apply { - Obligation().generateIssue(this, MINI_CORP, megaCorpDollarSettlement.copy(dueBefore = dueBefore), 100.DOLLARS.quantity, + val amount = Amount(100, Issued(defaultIssuer, USD)) + Obligation().generateCashIssue(this, ALICE, amount, dueBefore, beneficiary = MINI_CORP, notary = DUMMY_NOTARY) } var stx = miniCorpServices.signInitialTransaction(tx) From c4c551dbd27674cc58c6b2fc9f003fc99552200d Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 19 Jul 2017 14:03:34 +0100 Subject: [PATCH 050/197] Confidential identities API improvements * Registering anonymous identities now takes in AnonymisedIdentity * AnonymousParty.toString() now uses toStringShort() to match other toString() functions * Add verifyAnonymousIdentity() function to verify without storing an identity * Replace pathForAnonymous() with anonymousFromKey() which matches actual use-cases better * Add unit test for fetching the anonymous identity from a key * Update verifyAnonymousIdentity() function signature to match registerAnonymousIdentity() * Rename AnonymisedIdentity to AnonymousPartyAndPath * Remove certificate from AnonymousPartyAndPath as it's not actually used. * Rename registerAnonymousIdentity() to verifyAndRegisterAnonymousIdentity() --- .../corda/core/flows/TransactionKeyFlow.kt | 16 +++--- .../corda/core/identity/AnonymisedIdentity.kt | 16 ------ .../net/corda/core/identity/AnonymousParty.kt | 3 +- .../core/identity/AnonymousPartyAndPath.kt | 45 ++++++++++++++++ .../core/identity/PartyAndCertificate.kt | 7 +++ .../core/node/services/IdentityService.kt | 48 +++++++++++++++-- .../node/services/KeyManagementService.kt | 4 +- .../core/flows/TransactionKeyFlowTests.kt | 20 ++++---- .../kotlin/net/corda/flows/CashExitFlow.kt | 2 +- .../kotlin/net/corda/flows/CashIssueFlow.kt | 5 +- .../kotlin/net/corda/flows/CashPaymentFlow.kt | 5 +- .../corda/node/services/CoreFlowHandlers.kt | 7 ++- .../identity/InMemoryIdentityService.kt | 51 ++++++++++++------- .../keys/E2ETestKeyManagementService.kt | 4 +- .../net/corda/node/services/keys/KMSUtils.kt | 13 +++-- .../keys/PersistentKeyManagementService.kt | 4 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 2 +- .../network/InMemoryIdentityServiceTests.kt | 45 ++++++++++++---- .../services/vault/NodeVaultServiceTest.kt | 4 +- .../net/corda/testing/node/MockServices.kt | 4 +- 20 files changed, 209 insertions(+), 96 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt create mode 100644 core/src/main/kotlin/net/corda/core/identity/AnonymousPartyAndPath.kt diff --git a/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt index 58ff5837df..21f11c8fba 100644 --- a/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/TransactionKeyFlow.kt @@ -1,10 +1,10 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap -import net.corda.flows.AnonymisedIdentity /** * Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction. @@ -14,31 +14,31 @@ import net.corda.flows.AnonymisedIdentity @InitiatingFlow class TransactionKeyFlow(val otherSide: Party, val revocationEnabled: Boolean, - override val progressTracker: ProgressTracker) : FlowLogic>() { + override val progressTracker: ProgressTracker) : FlowLogic>() { constructor(otherSide: Party) : this(otherSide, false, tracker()) companion object { object AWAITING_KEY : ProgressTracker.Step("Awaiting key") fun tracker() = ProgressTracker(AWAITING_KEY) - fun validateIdentity(otherSide: Party, anonymousOtherSide: AnonymisedIdentity): AnonymisedIdentity { - require(anonymousOtherSide.certificate.subject == otherSide.name) + fun validateIdentity(otherSide: Party, anonymousOtherSide: AnonymousPartyAndPath): AnonymousPartyAndPath { + require(anonymousOtherSide.name == otherSide.name) return anonymousOtherSide } } @Suspendable - override fun call(): LinkedHashMap { + override fun call(): LinkedHashMap { progressTracker.currentStep = AWAITING_KEY val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled) // Special case that if we're both parties, a single identity is generated - val identities = LinkedHashMap() + val identities = LinkedHashMap() if (otherSide == serviceHub.myInfo.legalIdentity) { identities.put(otherSide, legalIdentityAnonymous) } else { - val otherSideAnonymous = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) } - serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous.identity, otherSide, otherSideAnonymous.certPath) + val otherSideAnonymous = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) } + serviceHub.identityService.verifyAndRegisterAnonymousIdentity(otherSideAnonymous, otherSide) identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous) identities.put(otherSide, otherSideAnonymous) } diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt deleted file mode 100644 index 0048917443..0000000000 --- a/core/src/main/kotlin/net/corda/core/identity/AnonymisedIdentity.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.corda.flows - -import net.corda.core.identity.AnonymousParty -import net.corda.core.serialization.CordaSerializable -import org.bouncycastle.cert.X509CertificateHolder -import java.security.PublicKey -import java.security.cert.CertPath - -@CordaSerializable -data class AnonymisedIdentity( - val certPath: CertPath, - val certificate: X509CertificateHolder, - val identity: AnonymousParty) { - constructor(certPath: CertPath, certificate: X509CertificateHolder, identity: PublicKey) - : this(certPath, certificate, AnonymousParty(identity)) -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt index 33ffffb19b..b2264a1c6f 100644 --- a/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt +++ b/core/src/main/kotlin/net/corda/core/identity/AnonymousParty.kt @@ -2,6 +2,7 @@ package net.corda.core.identity import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.toBase58String +import net.corda.core.crypto.toStringShort import net.corda.core.utilities.OpaqueBytes import org.bouncycastle.asn1.x500.X500Name import java.security.PublicKey @@ -13,7 +14,7 @@ import java.security.PublicKey class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) { // Use the key as the bulk of the toString(), but include a human readable identifier as well, so that [Party] // can put in the key and actual name - override fun toString() = "${owningKey.toBase58String()} " + override fun toString() = "${owningKey.toStringShort()} " override fun nameOrNull(): X500Name? = null diff --git a/core/src/main/kotlin/net/corda/core/identity/AnonymousPartyAndPath.kt b/core/src/main/kotlin/net/corda/core/identity/AnonymousPartyAndPath.kt new file mode 100644 index 0000000000..2119e7cfa1 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/identity/AnonymousPartyAndPath.kt @@ -0,0 +1,45 @@ +package net.corda.core.identity + +import net.corda.core.crypto.subject +import net.corda.core.serialization.CordaSerializable +import org.bouncycastle.asn1.x500.X500Name +import java.security.PublicKey +import java.security.cert.CertPath +import java.security.cert.X509Certificate + +/** + * A pair of an anonymous party and the certificate path to prove it is owned by a well known identity. This class + * does not validate the certificate path matches the party, and should not be trusted without being verified, for example + * using [IdentityService.verifyAnonymousIdentity]. + * + * Although similar to [PartyAndCertificate], the two distinct types exist in order to minimise risk of mixing up + * confidential and well known identities. In contrast to [PartyAndCertificate] equality tests are based on the anonymous + * party's key rather than name, which is the appropriate test for confidential parties but not for well known identities. + */ +@CordaSerializable +data class AnonymousPartyAndPath( + val party: AnonymousParty, + val certPath: CertPath) { + constructor(party: PublicKey, certPath: CertPath) + : this(AnonymousParty(party), certPath) + init { + require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" } + } + + /** + * Get the X.500 name of the certificate for the anonymous party. + * + * @return the X.500 name if the anonymous party's certificate is an X.509 certificate, or null otherwise. + */ + val name: X500Name? + get() = (certPath.certificates.first() as? X509Certificate)?.subject + + override fun equals(other: Any?): Boolean { + return if (other is AnonymousPartyAndPath) + party == other.party + else + false + } + + override fun hashCode(): Int = party.hashCode() +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt index 12557d562e..d1742b569b 100644 --- a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt +++ b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt @@ -29,5 +29,12 @@ data class PartyAndCertificate(val party: Party, } override fun hashCode(): Int = party.hashCode() + /** + * Convert this party and certificate into an anomymised identity. This exists primarily for example cases which + * want to use well known identities as if they're anonymous identities. + */ + fun toAnonymisedIdentity(): AnonymousPartyAndPath { + return AnonymousPartyAndPath(party.owningKey, certPath) + } override fun toString(): String = party.toString() } diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index 3e35feb6f2..aacd0f6daf 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.* +import net.corda.core.identity.AnonymousPartyAndPath import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException @@ -29,15 +30,39 @@ interface IdentityService { fun registerIdentity(party: PartyAndCertificate) /** - * Verify and then store an identity. + * Verify and then store an anonymous identity. * - * @param anonymousParty a party representing a legal entity in a transaction. - * @param path certificate path from the trusted root to the party. + * @param anonymousIdentity an anonymised identity representing a legal entity in a transaction. + * @param party well known party the anonymised party must represent. * @throws IllegalArgumentException if the certificate path is invalid, or if there is already an existing * certificate chain for the anonymous party. */ @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - fun registerAnonymousIdentity(anonymousParty: AnonymousParty, party: Party, path: CertPath) + @Deprecated("Use verifyAndRegisterAnonymousIdentity() instead, which is the same function with a better name") + fun registerAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate + + /** + * Verify and then store an anonymous identity. + * + * @param anonymousIdentity an anonymised identity representing a legal entity in a transaction. + * @param wellKnownIdentity well known party the anonymised party must represent. + * @throws IllegalArgumentException if the certificate path is invalid, or if there is already an existing + * certificate chain for the anonymous party. + */ + @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) + fun verifyAndRegisterAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, wellKnownIdentity: Party): PartyAndCertificate + + /** + * Verify an anonymous identity. + * + * @param anonymousParty a party representing a legal entity in a transaction. + * @param party well known party the anonymised party must represent. + * @param path certificate path from the trusted root to the party. + * @return the full well known identity. + * @throws IllegalArgumentException if the certificate path is invalid. + */ + @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) + fun verifyAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate /** * Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with @@ -54,6 +79,19 @@ interface IdentityService { */ fun getAllIdentities(): Iterable + /** + * Get the certificate and path for a previously registered anonymous identity. These are used to prove an anonmyous + * identity is owned by a well known identity. + */ + fun anonymousFromKey(owningKey: PublicKey): AnonymousPartyAndPath? + + /** + * Get the certificate and path for a well known identity's owning key. + * + * @return the party and certificate, or null if unknown. + */ + fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? + /** * Get the certificate and path for a well known identity. * @@ -68,6 +106,7 @@ interface IdentityService { fun partyFromKey(key: PublicKey): Party? @Deprecated("Use partyFromX500Name or partiesFromName") fun partyFromName(name: String): Party? + fun partyFromX500Name(principal: X500Name): Party? /** @@ -98,6 +137,7 @@ interface IdentityService { /** * Get the certificate chain showing an anonymous party is owned by the given party. */ + @Deprecated("Use anonymousFromKey instead, which provides more detail and takes in a more relevant input", replaceWith = ReplaceWith("anonymousFromKey(anonymousParty.owningKey)")) fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? /** diff --git a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt index f9799a7f2c..a2e49b0407 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt @@ -2,8 +2,8 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.DigitalSignature +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate -import net.corda.flows.AnonymisedIdentity import java.security.PublicKey /** @@ -32,7 +32,7 @@ interface KeyManagementService { * @return X.509 certificate and path to the trust root. */ @Suspendable - fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity + fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath /** * Filter some keys down to the set that this node owns (has private keys for). 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 4bd6152651..b91e1a2744 100644 --- a/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt @@ -2,8 +2,8 @@ package net.corda.core.flows import net.corda.core.getOrThrow import net.corda.core.identity.AbstractParty +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party -import net.corda.flows.AnonymisedIdentity import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY @@ -49,22 +49,22 @@ class TransactionKeyFlowTests { val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob)) // Get the results - val actual: Map = requesterFlow.resultFuture.getOrThrow().toMap() + val actual: Map = requesterFlow.resultFuture.getOrThrow().toMap() assertEquals(2, actual.size) // Verify that the generated anonymous identities do not match the well known identities val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException() val bobAnonymousIdentity = actual[bob] ?: throw IllegalStateException() - assertNotEquals(alice, aliceAnonymousIdentity.identity) - assertNotEquals(bob, bobAnonymousIdentity.identity) + assertNotEquals(alice, aliceAnonymousIdentity.party) + assertNotEquals(bob, bobAnonymousIdentity.party) // Verify that the anonymous identities look sane - assertEquals(alice.name, aliceAnonymousIdentity.certificate.subject) - assertEquals(bob.name, bobAnonymousIdentity.certificate.subject) + assertEquals(alice.name, aliceAnonymousIdentity.name) + assertEquals(bob.name, bobAnonymousIdentity.name) // Verify that the nodes have the right anonymous identities - assertTrue { aliceAnonymousIdentity.identity.owningKey in aliceNode.services.keyManagementService.keys } - assertTrue { bobAnonymousIdentity.identity.owningKey in bobNode.services.keyManagementService.keys } - assertFalse { aliceAnonymousIdentity.identity.owningKey in bobNode.services.keyManagementService.keys } - assertFalse { bobAnonymousIdentity.identity.owningKey in aliceNode.services.keyManagementService.keys } + assertTrue { aliceAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys } + assertTrue { bobAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } + assertFalse { aliceAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } + assertFalse { bobAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys } } } diff --git a/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt index 9a363fffc2..9d0437c7e5 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashExitFlow.kt @@ -8,8 +8,8 @@ import net.corda.core.contracts.TransactionType import net.corda.core.contracts.issuedBy import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import java.util.* diff --git a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt index 0bcaa5fed8..9c4525a814 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashIssueFlow.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.issuedBy import net.corda.core.flows.FinalityFlow import net.corda.core.flows.StartableByRPC import net.corda.core.flows.TransactionKeyFlow +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes @@ -45,9 +46,9 @@ class CashIssueFlow(val amount: Amount, val txIdentities = if (anonymous) { subFlow(TransactionKeyFlow(recipient)) } else { - emptyMap() + emptyMap() } - val anonymousRecipient = txIdentities[recipient]?.identity ?: recipient + val anonymousRecipient = txIdentities[recipient]?.party ?: recipient progressTracker.currentStep = GENERATING_TX val builder: TransactionBuilder = TransactionType.General.Builder(notary = notary) val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef) diff --git a/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt index f7bc488b5c..803e2da094 100644 --- a/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/CashPaymentFlow.kt @@ -6,6 +6,7 @@ import net.corda.core.contracts.InsufficientBalanceException import net.corda.core.contracts.TransactionType import net.corda.core.flows.StartableByRPC import net.corda.core.flows.TransactionKeyFlow +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker @@ -38,9 +39,9 @@ open class CashPaymentFlow( val txIdentities = if (anonymous) { subFlow(TransactionKeyFlow(recipient)) } else { - emptyMap() + emptyMap() } - val anonymousRecipient = txIdentities.get(recipient)?.identity ?: recipient + val anonymousRecipient = txIdentities.get(recipient)?.party ?: recipient progressTracker.currentStep = GENERATING_TX val builder: TransactionBuilder = TransactionType.General.Builder(null as Party?) // TODO: Have some way of restricting this to states the caller controls diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index f28ce22809..ce50c1e021 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -7,11 +7,11 @@ import net.corda.core.contracts.UpgradedContract import net.corda.core.contracts.requireThat import net.corda.core.crypto.SecureHash import net.corda.core.flows.* +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap -import net.corda.flows.* /** * This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple @@ -138,10 +138,9 @@ class TransactionKeyHandler(val otherSide: Party, val revocationEnabled: Boolean val revocationEnabled = false progressTracker.currentStep = SENDING_KEY val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled) - val otherSideAnonymous = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { TransactionKeyFlow.validateIdentity(otherSide, it) } - val (certPath, theirCert, txIdentity) = otherSideAnonymous + val otherSideAnonymous = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { TransactionKeyFlow.validateIdentity(otherSide, it) } // Validate then store their identity so that we can prove the key in the transaction is owned by the // counterparty. - serviceHub.identityService.registerAnonymousIdentity(txIdentity, otherSide, certPath) + serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous, otherSide) } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 4438f2e9c5..bbdf812c5e 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -5,10 +5,7 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.cert import net.corda.core.crypto.subject import net.corda.core.crypto.toStringShort -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.Party -import net.corda.core.identity.PartyAndCertificate +import net.corda.core.identity.* import net.corda.core.node.services.IdentityService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor @@ -21,8 +18,6 @@ import java.security.cert.* import java.util.* import java.util.concurrent.ConcurrentHashMap import javax.annotation.concurrent.ThreadSafe -import javax.security.auth.x500.X500Principal -import kotlin.collections.ArrayList /** * Simple identity service which caches parties and provides functionality for efficient lookup. @@ -50,14 +45,16 @@ class InMemoryIdentityService(identities: Iterable = emptyS private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) private val keyToParties = ConcurrentHashMap() private val principalToParties = ConcurrentHashMap() - private val partyToPath = ConcurrentHashMap() + private val partyToPath = ConcurrentHashMap>() init { val caCertificatesWithRoot: Set = caCertificates.toSet() + trustRoot caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot)) keyToParties.putAll(identities.associateBy { it.owningKey } ) principalToParties.putAll(identities.associateBy { it.name }) - partyToPath.putAll(certPaths) + certPaths.forEach { (party, path) -> + partyToPath.put(party, Pair(path, X509CertificateHolder(path.certificates.first().encoded))) + } } // TODO: Check the certificate validation logic @@ -70,15 +67,23 @@ class InMemoryIdentityService(identities: Iterable = emptyS log.trace { "Registering identity $party" } require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" } - partyToPath[party.party] = party.certPath + partyToPath[party.party] = Pair(party.certPath, party.certificate) keyToParties[party.owningKey] = party principalToParties[party.name] = party } + override fun anonymousFromKey(owningKey: PublicKey): AnonymousPartyAndPath? { + val anonymousParty = AnonymousParty(owningKey) + val path = partyToPath[anonymousParty] + return path?.let { it -> + AnonymousPartyAndPath(anonymousParty, it.first) + } + } + override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey] override fun certificateFromParty(party: Party): PartyAndCertificate? = principalToParties[party.name] // We give the caller a copy of the data set to avoid any locking problems - override fun getAllIdentities(): Iterable = ArrayList(keyToParties.values) + override fun getAllIdentities(): Iterable = java.util.ArrayList(keyToParties.values) override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party @Deprecated("Use partyFromX500Name") @@ -115,7 +120,7 @@ class InMemoryIdentityService(identities: Iterable = emptyS @Throws(IdentityService.UnknownAnonymousPartyException::class) override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) { - val path = partyToPath[anonymousParty] ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}") + val path = partyToPath[anonymousParty]?.first ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}") require(path.certificates.size > 1) { "Certificate path must contain at least two certificates" } val actual = path.certificates[1] require(actual is X509Certificate && actual.publicKey == party.owningKey) { "Next certificate in the path must match the party key ${party.owningKey.toStringShort()}." } @@ -123,22 +128,30 @@ class InMemoryIdentityService(identities: Iterable = emptyS require(target is X509Certificate && target.publicKey == anonymousParty.owningKey) { "Certificate path starts with a certificate for the anonymous party" } } - override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty] + override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty]?.first + + override fun registerAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate = verifyAndRegisterAnonymousIdentity(anonymousIdentity, party) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - override fun registerAnonymousIdentity(anonymousParty: AnonymousParty, party: Party, path: CertPath) { + override fun verifyAndRegisterAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, wellKnownIdentity: Party): PartyAndCertificate { + val fullParty = verifyAnonymousIdentity(anonymousIdentity, wellKnownIdentity) + val certificate = X509CertificateHolder(anonymousIdentity.certPath.certificates.first().encoded) + log.trace { "Registering identity $fullParty" } + + partyToPath[anonymousIdentity.party] = Pair(anonymousIdentity.certPath, certificate) + keyToParties[anonymousIdentity.party.owningKey] = fullParty + return fullParty + } + + override fun verifyAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate { + val (anonymousParty, path) = anonymousIdentity val fullParty = certificateFromParty(party) ?: throw IllegalArgumentException("Unknown identity ${party.name}") require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } // Validate the chain first, before we do anything clever with it validateCertificatePath(anonymousParty, path) val subjectCertificate = path.certificates.first() require(subjectCertificate is X509Certificate && subjectCertificate.subject == fullParty.name) { "Subject of the transaction certificate must match the well known identity" } - - log.trace { "Registering identity $fullParty" } - - partyToPath[anonymousParty] = path - keyToParties[anonymousParty.owningKey] = fullParty - principalToParties[fullParty.name] = fullParty + return fullParty } /** diff --git a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt index cfba3dd792..cd09d34e3c 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt @@ -5,11 +5,11 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.keys import net.corda.core.crypto.sign +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.flows.AnonymisedIdentity import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PrivateKey @@ -56,7 +56,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService, return keyPair.public } - override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity { + override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath { return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) } diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index 54a23918da..9b50ac1572 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -1,11 +1,10 @@ package net.corda.node.services.keys import net.corda.core.crypto.* -import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.utilities.days -import net.corda.flows.AnonymisedIdentity import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PublicKey @@ -29,16 +28,16 @@ fun freshCertificate(identityService: IdentityService, subjectPublicKey: PublicKey, issuer: PartyAndCertificate, issuerSigner: ContentSigner, - revocationEnabled: Boolean = false): AnonymisedIdentity { + revocationEnabled: Boolean = false): AnonymousPartyAndPath { val issuerCertificate = issuer.certificate val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate) val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window) val certFactory = CertificateFactory.getInstance("X509") val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) - identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey), - issuer.party, - ourCertPath) - return AnonymisedIdentity(ourCertPath, issuerCertificate, subjectPublicKey) + val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath) + identityService.verifyAndRegisterAnonymousIdentity(anonymisedIdentity, + issuer.party) + return anonymisedIdentity } fun getSigner(issuerKeyPair: KeyPair): ContentSigner { diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 359239bc0f..19108a422c 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -5,11 +5,11 @@ import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.keys import net.corda.core.crypto.sign +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.flows.AnonymisedIdentity import net.corda.node.utilities.* import org.bouncycastle.operator.ContentSigner import org.jetbrains.exposed.sql.ResultRow @@ -71,7 +71,7 @@ class PersistentKeyManagementService(val identityService: IdentityService, return keyPair.public } - override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity { + override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath { return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 1cd53227da..36d03e3343 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -503,7 +503,7 @@ class TwoPartyTradeFlowTests { } val buyerFlows: Observable = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } - val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.identity) + val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.party) val sellerResult = sellerNode.services.startFlow(seller).resultFuture return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt index 053d85bfaa..602481e6c6 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt @@ -1,11 +1,11 @@ package net.corda.node.services.network import net.corda.core.crypto.* +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService -import net.corda.flows.AnonymisedIdentity import net.corda.node.services.identity.InMemoryIdentityService import net.corda.testing.* import org.bouncycastle.asn1.x500.X500Name @@ -93,6 +93,29 @@ class InMemoryIdentityServiceTests { } } + /** + * Generate a pair of certificate paths from a root CA, down to a transaction key, store and verify the associations. + * Also checks that incorrect associations are rejected. + */ + @Test + fun `get anonymous identity by key`() { + val trustRoot = DUMMY_CA + val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot) + val (bob, bobTxIdentity) = createParty(ALICE.name, trustRoot) + + // Now we have identities, construct the service and let it know about both + val service = InMemoryIdentityService(setOf(alice), emptyMap(), trustRoot.certificate.cert) + service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party) + + var actual = service.anonymousFromKey(aliceTxIdentity.party.owningKey) + assertEquals(aliceTxIdentity, actual!!) + + assertNull(service.anonymousFromKey(bobTxIdentity.party.owningKey)) + service.verifyAndRegisterAnonymousIdentity(bobTxIdentity, bob.party) + actual = service.anonymousFromKey(bobTxIdentity.party.owningKey) + assertEquals(bobTxIdentity, actual!!) + } + /** * Generate a pair of certificate paths from a root CA, down to a transaction key, store and verify the associations. * Also checks that incorrect associations are rejected. @@ -113,35 +136,35 @@ class InMemoryIdentityServiceTests { // Now we have identities, construct the service and let it know about both val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert) - service.registerAnonymousIdentity(aliceTxIdentity.identity, alice.party, aliceTxIdentity.certPath) + service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party) - val anonymousBob = AnonymousParty(bobTxKey.public) - service.registerAnonymousIdentity(anonymousBob, bob.party, bobCertPath) + val anonymousBob = AnonymousPartyAndPath(AnonymousParty(bobTxKey.public),bobCertPath) + service.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party) // Verify that paths are verified - service.assertOwnership(alice.party, aliceTxIdentity.identity) - service.assertOwnership(bob.party, anonymousBob) + service.assertOwnership(alice.party, aliceTxIdentity.party) + service.assertOwnership(bob.party, anonymousBob.party) assertFailsWith { - service.assertOwnership(alice.party, anonymousBob) + service.assertOwnership(alice.party, anonymousBob.party) } assertFailsWith { - service.assertOwnership(bob.party, aliceTxIdentity.identity) + service.assertOwnership(bob.party, aliceTxIdentity.party) } assertFailsWith { val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded) - service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.identity) + service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.party) } } - private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair { + private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair { val certFactory = CertificateFactory.getInstance("X509") val issuerKeyPair = generateKeyPair() val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca) val txKey = Crypto.generateKeyPair() val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public) val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) - return Pair(issuer, AnonymisedIdentity(txCertPath, txCert, AnonymousParty(txKey.public))) + return Pair(issuer, AnonymousPartyAndPath(AnonymousParty(txKey.public), txCertPath)) } /** diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 9a6e3be062..2647bbbd89 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -431,7 +431,7 @@ class NodeVaultServiceTest { assertTrue { service.isRelevant(wellKnownCash, services.keyManagementService.keys) } val anonymousIdentity = services.keyManagementService.freshKeyAndCert(services.myInfo.legalIdentityAndCert, false) - val anonymousCash = Cash.State(amount, anonymousIdentity.identity) + val anonymousCash = Cash.State(amount, anonymousIdentity.party) assertTrue { service.isRelevant(anonymousCash, services.keyManagementService.keys) } val thirdPartyIdentity = AnonymousParty(generateKeyPair().public) @@ -455,7 +455,7 @@ class NodeVaultServiceTest { // Issue then move some cash val issueTx = TransactionBuilder(TransactionType.General, services.myInfo.legalIdentity).apply { Cash().generateIssue(this, - amount, anonymousIdentity.identity, services.myInfo.legalIdentity) + amount, anonymousIdentity.party, services.myInfo.legalIdentity) }.toWireTransaction() val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0)) diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt index dd6a1caffa..91ccf67286 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -3,6 +3,7 @@ package net.corda.testing.node import net.corda.core.contracts.Attachment import net.corda.core.crypto.* import net.corda.core.flows.StateMachineRunId +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo @@ -12,7 +13,6 @@ import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NonEmptySet -import net.corda.flows.AnonymisedIdentity import net.corda.node.VersionInfo import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.WritableTransactionStorage @@ -105,7 +105,7 @@ class MockKeyManagementService(val identityService: IdentityService, override fun filterMyKeys(candidateKeys: Iterable): Iterable = candidateKeys.filter { it in this.keys } - override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity { + override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath { return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) } From 070f0c040e7825d3a435be00157daf199a2d8717 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 18 Jul 2017 13:14:11 +0100 Subject: [PATCH 051/197] Moved a bunch of stuff which shouldn't be public out of core.utilities and into core.internal --- .../corda/client/rpc/internal/RPCClientProxyHandler.kt | 3 +++ .../kotlin/net/corda/client/rpc/RPCPerformanceTests.kt | 2 +- core/src/main/kotlin/net/corda/core/Utils.kt | 6 ------ .../net/corda/core/{utilities => internal}/Emoji.kt | 9 ++++++--- .../net/corda/core/{utilities => internal}/LazyPool.kt | 3 +-- .../corda/core/{utilities => internal}/LazyStickyPool.kt | 2 +- .../net/corda/core/{utilities => internal}/LifeCycle.kt | 2 +- .../src/main/kotlin/net/corda/core/serialization/Kryo.kt | 2 +- .../net/corda/core/transactions/WireTransaction.kt | 2 +- .../net/corda/core/flows/ContractUpgradeFlowTest.kt | 2 +- .../main/kotlin/net/corda/contracts/CommercialPaper.kt | 2 +- .../kotlin/net/corda/contracts/CommercialPaperLegacy.kt | 2 +- .../src/main/kotlin/net/corda/contracts/asset/Cash.kt | 2 +- .../main/kotlin/net/corda/contracts/asset/Obligation.kt | 2 +- .../kotlin/net/corda/contracts/DummyFungibleContract.kt | 2 +- .../kotlin/net/corda/node/NodePerformanceTests.kt | 2 +- .../main/kotlin/net/corda/node/internal/NodeStartup.kt | 2 +- .../net/corda/node/services/messaging/RPCServer.kt | 2 ++ .../main/kotlin/net/corda/node/shell/InteractiveShell.kt | 2 +- .../net/corda/node/utilities/ANSIProgressRenderer.kt | 2 +- .../kotlin/net/corda/attachmentdemo/AttachmentDemo.kt | 2 +- .../kotlin/net/corda/traderdemo/TraderDemoClientApi.kt | 2 +- .../main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt | 2 +- .../kotlin/net/corda/testing/performance/Injectors.kt | 1 - .../main/kotlin/net/corda/testing/performance}/Rate.kt | 8 ++++---- 25 files changed, 34 insertions(+), 34 deletions(-) rename core/src/main/kotlin/net/corda/core/{utilities => internal}/Emoji.kt (94%) rename core/src/main/kotlin/net/corda/core/{utilities => internal}/LazyPool.kt (96%) rename core/src/main/kotlin/net/corda/core/{utilities => internal}/LazyStickyPool.kt (98%) rename core/src/main/kotlin/net/corda/core/{utilities => internal}/LifeCycle.kt (97%) rename {core/src/main/kotlin/net/corda/core/utilities => test-utils/src/main/kotlin/net/corda/testing/performance}/Rate.kt (71%) 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 e83363b7fc..e0cb142d82 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 @@ -14,6 +14,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.core.ThreadBox import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow +import net.corda.core.internal.LazyPool +import net.corda.core.internal.LazyStickyPool +import net.corda.core.internal.LifeCycle import net.corda.core.messaging.RPCOps import net.corda.core.serialization.KryoPoolWithContext import net.corda.core.utilities.* diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt index 5684d53ca6..b9d64a3cab 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt @@ -5,7 +5,7 @@ import net.corda.client.rpc.internal.RPCClientConfiguration import net.corda.core.messaging.RPCOps import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds -import net.corda.core.utilities.div +import net.corda.testing.performance.div import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.testing.RPCDriverExposedDSLInterface import net.corda.testing.measure diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 44b4ebf5a3..4654d4cd4d 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -310,12 +310,6 @@ private class ObservableToFuture(observable: Observable) : AbstractFuture< /** Return the sum of an Iterable of [BigDecimal]s. */ fun Iterable.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + b } -fun codePointsString(vararg codePoints: Int): String { - val builder = StringBuilder() - codePoints.forEach { builder.append(Character.toChars(it)) } - return builder.toString() -} - fun Class.checkNotUnorderedHashMap() { if (HashMap::class.java.isAssignableFrom(this) && !LinkedHashMap::class.java.isAssignableFrom(this)) { throw NotSerializableException("Map type $this is unstable under iteration. Suggested fix: use LinkedHashMap instead.") diff --git a/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt b/core/src/main/kotlin/net/corda/core/internal/Emoji.kt similarity index 94% rename from core/src/main/kotlin/net/corda/core/utilities/Emoji.kt rename to core/src/main/kotlin/net/corda/core/internal/Emoji.kt index f2a6a6b566..25a09dec00 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt +++ b/core/src/main/kotlin/net/corda/core/internal/Emoji.kt @@ -1,6 +1,4 @@ -package net.corda.core.utilities - -import net.corda.core.codePointsString +package net.corda.core.internal /** * A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal. @@ -79,4 +77,9 @@ object Emoji { } } + private fun codePointsString(vararg codePoints: Int): String { + val builder = StringBuilder() + codePoints.forEach { builder.append(Character.toChars(it)) } + return builder.toString() + } } diff --git a/core/src/main/kotlin/net/corda/core/utilities/LazyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt similarity index 96% rename from core/src/main/kotlin/net/corda/core/utilities/LazyPool.kt rename to core/src/main/kotlin/net/corda/core/internal/LazyPool.kt index 2649924aa1..3e4e3a526d 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/LazyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyPool.kt @@ -1,7 +1,6 @@ -package net.corda.core.utilities +package net.corda.core.internal import java.util.concurrent.ConcurrentLinkedQueue -import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.Semaphore /** diff --git a/core/src/main/kotlin/net/corda/core/utilities/LazyStickyPool.kt b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt similarity index 98% rename from core/src/main/kotlin/net/corda/core/utilities/LazyStickyPool.kt rename to core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt index f44723b6b8..6746989291 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/LazyStickyPool.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LazyStickyPool.kt @@ -1,4 +1,4 @@ -package net.corda.core.utilities +package net.corda.core.internal import java.util.* import java.util.concurrent.LinkedBlockingQueue diff --git a/core/src/main/kotlin/net/corda/core/utilities/LifeCycle.kt b/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt similarity index 97% rename from core/src/main/kotlin/net/corda/core/utilities/LifeCycle.kt rename to core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt index bc73e9f51a..96786ea3e9 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/LifeCycle.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LifeCycle.kt @@ -1,4 +1,4 @@ -package net.corda.core.utilities +package net.corda.core.internal import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.withLock diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index 8db0fdbd8a..f91119f9e8 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -12,7 +12,7 @@ import net.corda.core.crypto.* import net.corda.core.crypto.composite.CompositeKey import net.corda.core.identity.Party import net.corda.core.transactions.WireTransaction -import net.corda.core.utilities.LazyPool +import net.corda.core.internal.LazyPool import net.corda.core.utilities.OpaqueBytes import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey 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 684ad86f9b..af16bd728a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -13,7 +13,7 @@ import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.p2PKryo import net.corda.core.serialization.serialize -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 000e81afcc..e891f4677c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -11,7 +11,7 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.OpaqueBytes import net.corda.flows.CashIssueFlow import net.corda.node.internal.CordaRPCOpsImpl diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt index f3d2fcdb2e..e175b4c5b1 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt @@ -18,7 +18,7 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.schemas.CommercialPaperSchemaV1 import java.time.Instant import java.util.* diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt index 0c3811556b..e263e1077c 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt @@ -9,7 +9,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.VaultService import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import java.time.Instant import java.util.* diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt index f47fb51ec1..1e47041843 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt @@ -20,7 +20,7 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.schemas.CashSchemaV1 import org.bouncycastle.asn1.x500.X500Name import java.math.BigInteger diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt index ec2643b9fd..f07f06ffea 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt @@ -16,7 +16,7 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.seconds import org.bouncycastle.asn1.x500.X500Name diff --git a/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt index e73cce41ce..c30e73135d 100644 --- a/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt @@ -16,7 +16,7 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.schemas.SampleCashSchemaV1 import net.corda.schemas.SampleCashSchemaV2 import net.corda.schemas.SampleCashSchemaV3 diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 00421c1ce2..f6f56fc5d9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -10,7 +10,7 @@ import net.corda.core.messaging.startFlow import net.corda.core.utilities.minutes import net.corda.core.node.services.ServiceInfo import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.div +import net.corda.testing.performance.div import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow import net.corda.node.services.startFlowPermission diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 33bdffcadc..e42b76c68c 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -10,7 +10,7 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.node.VersionInfo import net.corda.core.node.services.ServiceInfo -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.loggerFor import net.corda.node.ArgsParser import net.corda.node.CmdLineOptions diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 8548520ba7..cb42b35110 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -13,6 +13,8 @@ import com.google.common.collect.Multimaps import com.google.common.collect.SetMultimap import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.core.crypto.random63BitValue +import net.corda.core.internal.LazyStickyPool +import net.corda.core.internal.LifeCycle import net.corda.core.messaging.RPCOps import net.corda.core.utilities.seconds import net.corda.core.serialization.KryoPoolWithContext diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index b9ed79ce82..945a5318f9 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -18,7 +18,7 @@ import net.corda.core.internal.div import net.corda.core.internal.write import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.StateMachineUpdate -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.loggerFor import net.corda.jackson.JacksonSupport import net.corda.jackson.StringToMethodCallParser diff --git a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt index e8b6de4269..8339d0ae22 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt @@ -1,6 +1,6 @@ package net.corda.node.utilities -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.ProgressTracker import net.corda.node.utilities.ANSIProgressRenderer.progressTracker import org.apache.logging.log4j.LogManager diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 88ac8380cc..0d42bf06ce 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -19,7 +19,7 @@ import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startTrackedFlow import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.ProgressTracker import net.corda.testing.DUMMY_BANK_B diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt index 7f2ddbee4e..062a2fc426 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemoClientApi.kt @@ -13,7 +13,7 @@ import net.corda.core.getOrThrow import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.loggerFor import net.corda.flows.IssuerFlow.IssuanceRequester import net.corda.testing.BOC diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt index 5dbdcbf7ef..aebcef0c28 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt @@ -9,7 +9,7 @@ import net.corda.core.flows.InitiatedBy import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.Emoji +import net.corda.core.internal.Emoji import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import net.corda.flows.TwoPartyTradeFlow diff --git a/test-utils/src/main/kotlin/net/corda/testing/performance/Injectors.kt b/test-utils/src/main/kotlin/net/corda/testing/performance/Injectors.kt index 5690d0dba9..c57e9a165f 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/performance/Injectors.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/performance/Injectors.kt @@ -3,7 +3,6 @@ package net.corda.testing.performance import com.codahale.metrics.Gauge import com.codahale.metrics.MetricRegistry import com.google.common.base.Stopwatch -import net.corda.core.utilities.Rate import net.corda.testing.driver.ShutdownManager import java.time.Duration import java.util.* diff --git a/core/src/main/kotlin/net/corda/core/utilities/Rate.kt b/test-utils/src/main/kotlin/net/corda/testing/performance/Rate.kt similarity index 71% rename from core/src/main/kotlin/net/corda/core/utilities/Rate.kt rename to test-utils/src/main/kotlin/net/corda/testing/performance/Rate.kt index 1936a27fa3..8a01bd4c27 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/Rate.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/performance/Rate.kt @@ -1,4 +1,4 @@ -package net.corda.core.utilities +package net.corda.testing.performance import java.time.Duration import java.time.temporal.ChronoUnit @@ -21,9 +21,9 @@ data class Rate( /** * Converts the number of events to the given unit. */ - operator fun times(inUnit: TimeUnit): Long { - return inUnit.convert(numberOfEvents, perTimeUnit) - } + operator fun times(inUnit: TimeUnit): Long = inUnit.convert(numberOfEvents, perTimeUnit) + + override fun toString(): String = "$numberOfEvents / ${perTimeUnit.name.dropLast(1).toLowerCase()}" // drop the "s" at the end } operator fun Long.div(timeUnit: TimeUnit) = Rate(this, timeUnit) From 080e9670a180ce980d9b58edd2a204b0c0c4ffab Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 19 Jul 2017 14:39:31 +0100 Subject: [PATCH 052/197] Fix to allow classes with char members to deserialize --- .../serialization/amqp/PropertySerializer.kt | 25 +++++++++++--- .../amqp/DeserializeSimpleTypesTests.kt | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeSimpleTypesTests.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt index 4020ca5cc5..66f15ba7b2 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt @@ -55,8 +55,10 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re companion object { fun make(name: String, readMethod: Method, resolvedType: Type, factory: SerializerFactory): PropertySerializer { if (SerializerFactory.isPrimitive(resolvedType)) { - // This is a little inefficient for performance since it does a runtime check of type. We could do build time check with lots of subclasses here. - return AMQPPrimitivePropertySerializer(name, readMethod, resolvedType) + return when(resolvedType) { + Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod) + else -> AMQPPrimitivePropertySerializer(name, readMethod, resolvedType) + } } else { return DescribedTypePropertySerializer(name, readMethod, resolvedType) { factory.get(null, resolvedType) } } @@ -86,10 +88,9 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re } /** - * A property serializer for an AMQP primitive type (Int, String, etc). + * A property serializer for most AMQP primitive type (Int, String, etc). */ class AMQPPrimitivePropertySerializer(name: String, readMethod: Method, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) { - override fun writeClassInfo(output: SerializationOutput) {} override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? { @@ -105,5 +106,21 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re } } } + + /** + * A property serializer for the AMQP char type, needed as a specialisation as the underlaying + * value of the character is stored in numeric ASCII form and on deserialisation requires explicit + * casting back to a char otherwise it's treated as an Integer and TypeMismatchs occur + */ + class AMQPCharPropertySerializer(name: String, readMethod: Method) : + PropertySerializer(name, readMethod, Char::class.java) { + override fun writeClassInfo(output: SerializationOutput) {} + + override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput) = (obj as Int).toChar() + + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) { + data.putObject(readMethod.invoke(obj)) + } + } } diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeSimpleTypesTests.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeSimpleTypesTests.kt new file mode 100644 index 0000000000..d9bdcaf1e9 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeSimpleTypesTests.kt @@ -0,0 +1,34 @@ +package net.corda.core.serialization.amqp + +import org.junit.Test +import java.io.ByteArrayOutputStream +import kotlin.test.assertEquals + +/** + * Prior to certain fixes being made within the [PropertySerializaer] classes these simple + * deserialization operations would've blown up with type mismatch errors where the deserlized + * char property of the class would've been treated as an Integer and given to the constructor + * as such + */ +class DeserializeSimpleTypesTests { + @Test + fun testChar() { + data class C(val c: Char) + val c = C('c') + val serialisedC = SerializationOutput().serialize(c) + val deserializedC = DeserializationInput().deserialize(serialisedC) + + assertEquals(c.c, deserializedC.c) + } + + @Test + fun testCharacter() { + data class C(val c: Character) + val c = C(Character ('c')) + val serialisedC = SerializationOutput().serialize(c) + val deserializedC = DeserializationInput().deserialize(serialisedC) + + assertEquals(c.c, deserializedC.c) + } +} + From 97e0c3c4c2a421276f90b7a0041c63e59f8e2797 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 19 Jul 2017 15:19:50 +0100 Subject: [PATCH 053/197] Review Comments --- .../corda/core/serialization/amqp/PropertySerializer.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt index 66f15ba7b2..0211af8541 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/PropertySerializer.kt @@ -108,9 +108,9 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re } /** - * A property serializer for the AMQP char type, needed as a specialisation as the underlaying - * value of the character is stored in numeric ASCII form and on deserialisation requires explicit - * casting back to a char otherwise it's treated as an Integer and TypeMismatchs occur + * A property serializer for the AMQP char type, needed as a specialisation as the underlying + * value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit + * casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs */ class AMQPCharPropertySerializer(name: String, readMethod: Method) : PropertySerializer(name, readMethod, Char::class.java) { @@ -119,7 +119,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method, val re override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput) = (obj as Int).toChar() override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) { - data.putObject(readMethod.invoke(obj)) + data.putChar((readMethod.invoke(obj) as Char).toInt()) } } } From 9d3ad5fe060a521957f4a243db8de6a5de4df55c Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 19 Jul 2017 16:00:14 +0100 Subject: [PATCH 054/197] Remove dummy cash issuer from two party trade flows Switch to using a dedicated bank node in two party trade flows so that when KYC checks are enforced, all nodes on the network know who the issuer is. --- .../node/messaging/TwoPartyTradeFlowTests.kt | 110 ++++++++++-------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 36d03e3343..e261576ccd 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -2,27 +2,33 @@ package net.corda.node.messaging import co.paralleluniverse.fibers.Suspendable import net.corda.contracts.CommercialPaper -import net.corda.contracts.asset.* -import net.corda.core.* +import net.corda.contracts.asset.CASH +import net.corda.contracts.asset.Cash +import net.corda.contracts.asset.`issued by` +import net.corda.contracts.asset.`owned by` import net.corda.core.contracts.* import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sign import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.StateMachineRunId +import net.corda.core.getOrThrow import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.map import net.corda.core.messaging.DataFeed import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.Vault +import net.corda.core.rootCause import net.corda.core.serialization.serialize +import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction @@ -51,7 +57,6 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.math.BigInteger import java.security.KeyPair -import java.security.PublicKey import java.util.* import java.util.concurrent.Future import java.util.jar.JarOutputStream @@ -89,10 +94,12 @@ class TwoPartyTradeFlowTests { mockNet = MockNetwork(false, true) ledger { - val basketOfNodes = mockNet.createSomeNodes(2) + val basketOfNodes = mockNet.createSomeNodes(3) val notaryNode = basketOfNodes.notaryNode val aliceNode = basketOfNodes.partyNodes[0] val bobNode = basketOfNodes.partyNodes[1] + val bankNode = basketOfNodes.partyNodes[2] + val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) aliceNode.disableDBCloseOnStop() bobNode.disableDBCloseOnStop() @@ -102,11 +109,11 @@ class TwoPartyTradeFlowTests { } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, aliceNode.info.legalIdentity, - 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second + fillUpForSeller(false, issuer, aliceNode.info.legalIdentity, + 1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), null, notaryNode.info.notaryIdentity).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, "alice's paper".outputStateAndRef()) @@ -137,6 +144,8 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val cpIssuer = bankNode.info.legalIdentity.ref(1, 2, 3) aliceNode.disableDBCloseOnStop() bobNode.disableDBCloseOnStop() @@ -146,11 +155,11 @@ class TwoPartyTradeFlowTests { } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, aliceNode.info.legalIdentity, - 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second + fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity, + 1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), null, notaryNode.info.notaryIdentity).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) val cashLockId = UUID.randomUUID() bobNode.database.transaction { @@ -186,6 +195,8 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val cpIssuer = bankNode.info.legalIdentity.ref(1, 2, 3) aliceNode.services.identityService.registerIdentity(bobNode.info.legalIdentityAndCert) bobNode.services.identityService.registerIdentity(aliceNode.info.legalIdentityAndCert) @@ -201,10 +212,10 @@ class TwoPartyTradeFlowTests { bobNode.services.fillWithSomeTestCash(2000.DOLLARS, outputNotary = notaryNode.info.notaryIdentity) } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, aliceNode.info.legalIdentity, - 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second + fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity, + 1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), null, notaryNode.info.notaryIdentity).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) val aliceFuture = runBuyerAndSeller(notaryNode, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult // Everything is on this thread so we can now step through the flow one step at a time. @@ -305,6 +316,8 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) + val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) ledger(aliceNode.services) { @@ -319,16 +332,14 @@ class TwoPartyTradeFlowTests { attachment(ByteArrayInputStream(stream.toByteArray())) } - val extraKey = bobNode.services.keyManagementService.keys.single() - val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(extraKey), - DUMMY_CASH_ISSUER.party, + val bobsFakeCash = fillUpForBuyer(false, issuer, AnonymousParty(bobNode.info.legalIdentity.owningKey), notaryNode.info.notaryIdentity).second - val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, extraKey, DUMMY_CASH_ISSUER_KEY.public, MEGA_CORP_PUBKEY) + val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, aliceNode.info.legalIdentity, - 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second + fillUpForSeller(false, issuer, aliceNode.info.legalIdentity, + 1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), attachmentID, notaryNode.info.notaryIdentity).second } - val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) mockNet.runNetwork() // Clear network map registration messages @@ -404,6 +415,8 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) + val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) ledger(aliceNode.services) { @@ -419,17 +432,16 @@ class TwoPartyTradeFlowTests { } val bobsKey = bobNode.services.keyManagementService.keys.single() - val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(bobsKey), - DUMMY_CASH_ISSUER.party, + val bobsFakeCash = fillUpForBuyer(false, issuer, AnonymousParty(bobsKey), notaryNode.info.notaryIdentity).second - insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, DUMMY_CASH_ISSUER_KEY.public, MEGA_CORP_PUBKEY) + insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, aliceNode.info.legalIdentity, - 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second + fillUpForSeller(false, issuer, aliceNode.info.legalIdentity, + 1200.DOLLARS `issued by` bankNode.info.legalIdentity.ref(0), attachmentID, notaryNode.info.notaryIdentity).second } - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) mockNet.runNetwork() // Clear network map registration messages @@ -503,7 +515,7 @@ class TwoPartyTradeFlowTests { } val buyerFlows: Observable = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } - val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.party) + val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller) val sellerResult = sellerNode.services.startFlow(seller).resultFuture return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) } @@ -513,7 +525,7 @@ class TwoPartyTradeFlowTests { val notary: NodeInfo, val assetToSell: StateAndRef, val price: Amount, - val me: AbstractParty) : FlowLogic() { + val me: AnonymousPartyAndPath) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { send(buyer, Pair(notary.notaryIdentity, price)) @@ -522,7 +534,7 @@ class TwoPartyTradeFlowTests { notary, assetToSell, price, - me)) + me.party)) } } @@ -546,19 +558,20 @@ class TwoPartyTradeFlowTests { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) - val issuer = MEGA_CORP.ref(1, 2, 3) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) val bobsBadCash = bobNode.database.transaction { - fillUpForBuyer(bobError, bobNode.info.legalIdentity, DUMMY_CASH_ISSUER.party, + fillUpForBuyer(bobError, issuer, bobNode.info.legalIdentity, notaryNode.info.notaryIdentity).second } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(aliceError, aliceNode.info.legalIdentity, + fillUpForSeller(aliceError, issuer, aliceNode.info.legalIdentity, 1200.DOLLARS `issued by` issuer, null, notaryNode.info.notaryIdentity).second } - insertFakeTransactions(bobsBadCash, bobNode, notaryNode, DUMMY_CASH_ISSUER_KEY.public, MEGA_CORP_PUBKEY) - insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, MEGA_CORP_PUBKEY) + insertFakeTransactions(bobsBadCash, bobNode, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) mockNet.runNetwork() // Clear network map registration messages @@ -582,8 +595,8 @@ class TwoPartyTradeFlowTests { private fun insertFakeTransactions( wtxToSign: List, node: AbstractNode, - notaryNode: MockNetwork.MockNode, - vararg extraKeys: PublicKey): Map { + notaryNode: AbstractNode, + vararg extraSigningNodes: AbstractNode): Map { val signed = wtxToSign.map { val bits = it.serialize() @@ -591,14 +604,8 @@ class TwoPartyTradeFlowTests { val sigs = mutableListOf() sigs.add(node.services.keyManagementService.sign(id.bytes, node.services.legalIdentityKey)) sigs.add(notaryNode.services.keyManagementService.sign(id.bytes, notaryNode.services.notaryIdentityKey)) - for (extraKey in extraKeys) { - if (extraKey == DUMMY_CASH_ISSUER_KEY.public) { - sigs.add(DUMMY_CASH_ISSUER_KEY.sign(id.bytes)) - } else if (extraKey == MEGA_CORP_PUBKEY) { - sigs.add(MEGA_CORP_KEY.sign(id.bytes)) - } else { - sigs.add(node.services.keyManagementService.sign(id.bytes, extraKey)) - } + extraSigningNodes.forEach { currentNode -> + sigs.add(currentNode.services.keyManagementService.sign(id.bytes, currentNode.info.legalIdentity.owningKey)) } SignedTransaction(bits, sigs) } @@ -614,10 +621,10 @@ class TwoPartyTradeFlowTests { private fun LedgerDSL.fillUpForBuyer( withError: Boolean, + issuer: PartyAndReference, owner: AbstractParty, - issuer: AbstractParty, notary: Party): Pair, List> { - val interimOwner = MEGA_CORP + val interimOwner = issuer.party // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she // wants to sell to Bob. val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { @@ -625,10 +632,10 @@ class TwoPartyTradeFlowTests { output("elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } output("elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } if (!withError) { - command(issuer.owningKey) { Cash.Commands.Issue() } + command(issuer.party.owningKey) { Cash.Commands.Issue() } } else { // Put a broken command on so at least a signature is created - command(issuer.owningKey) { Cash.Commands.Move() } + command(issuer.party.owningKey) { Cash.Commands.Move() } } timeWindow(TEST_TX_TIME) if (withError) { @@ -660,15 +667,16 @@ class TwoPartyTradeFlowTests { private fun LedgerDSL.fillUpForSeller( withError: Boolean, + issuer: PartyAndReference, owner: AbstractParty, amount: Amount>, attachmentID: SecureHash?, notary: Party): Pair, List> { val ap = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { output("alice's paper", notary = notary) { - CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days) + CommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days) } - command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() } + command(issuer.party.owningKey) { CommercialPaper.Commands.Issue() } if (!withError) timeWindow(time = TEST_TX_TIME) if (attachmentID != null) From 0e0b99eaf09a5ae5243bd1311fc608254b31ee17 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 19 Jul 2017 16:34:56 +0100 Subject: [PATCH 055/197] * Remove Kryo from ServiceIdentityGenerator (#1083) * Store encoded private, public and composite key to file instead of Party and Key using Kryo. --- .../net/corda/node/internal/AbstractNode.kt | 19 ++++++++----------- .../utilities/ServiceIdentityGenerator.kt | 12 +++++------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index fdc15af0d2..c76566a993 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -718,11 +718,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, if (!keyStore.containsAlias(privateKeyAlias)) { val privKeyFile = configuration.baseDirectory / privateKeyAlias val pubIdentityFile = configuration.baseDirectory / "$serviceId-public" + val compositeKeyFile = configuration.baseDirectory / compositeKeyAlias // TODO: Remove use of [ServiceIdentityGenerator.generateToDisk]. // Get keys from key file. // TODO: this is here to smooth out the key storage transition, remove this migration in future release. if (privKeyFile.exists()) { - migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, privateKeyAlias, compositeKeyAlias) + migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, compositeKeyFile, privateKeyAlias, compositeKeyAlias) } else { log.info("$privateKeyAlias not found in keystore ${configuration.nodeKeystore}, generating fresh key!") keyStore.saveNewKeyPair(serviceName, privateKeyAlias, generateKeyPair()) @@ -750,21 +751,17 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private fun migrateKeysFromFile(keyStore: KeyStoreWrapper, serviceName: X500Name, - pubIdentityFile: Path, privKeyFile: Path, + pubKeyFile: Path, privKeyFile: Path, compositeKeyFile:Path, privateKeyAlias: String, compositeKeyAlias: String) { log.info("Migrating $privateKeyAlias from file to keystore...") - val myIdentity = pubIdentityFile.readAll().deserialize() // Check that the identity in the config file matches the identity file we have stored to disk. - // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed - // things up for us. - if (myIdentity.name != serviceName) - throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:$serviceName vs ${myIdentity.name}") // Load the private key. - val keyPair = privKeyFile.readAll().deserialize() - keyStore.saveNewKeyPair(serviceName, privateKeyAlias, keyPair) + val publicKey = Crypto.decodePublicKey(pubKeyFile.readAll()) + val privateKey = Crypto.decodePrivateKey(privKeyFile.readAll()) + keyStore.saveNewKeyPair(serviceName, privateKeyAlias, KeyPair(publicKey, privateKey)) // Store composite key separately. - if (myIdentity.owningKey is CompositeKey) { - keyStore.savePublicKey(serviceName, compositeKeyAlias, myIdentity.owningKey) + if (compositeKeyFile.exists()) { + keyStore.savePublicKey(serviceName, compositeKeyAlias, Crypto.decodePublicKey(compositeKeyFile.readAll())) } log.info("Finish migrating $privateKeyAlias from file to keystore.") } diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 2f2db9d09b..43daa96cbc 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -3,8 +3,6 @@ package net.corda.node.utilities import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.Party -import net.corda.core.serialization.serialize -import net.corda.core.serialization.storageKryo import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import org.bouncycastle.asn1.x500.X500Name @@ -33,16 +31,16 @@ object ServiceIdentityGenerator { val keyPairs = (1..dirs.size).map { generateKeyPair() } val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) // Avoid adding complexity! This class is a hack that needs to stay runnable in the gradle environment. - val notaryParty = Party(serviceName, notaryKey) - val notaryPartyBytes = notaryParty.serialize() val privateKeyFile = "$serviceId-private-key" val publicKeyFile = "$serviceId-public" + val compositeKeyFile = "$serviceId-composite-key" keyPairs.zip(dirs) { keyPair, dir -> Files.createDirectories(dir) - notaryPartyBytes.writeToFile(dir.resolve(publicKeyFile)) + Files.write(dir.resolve(compositeKeyFile), notaryKey.encoded) // Use storageKryo as our whitelist is not available in the gradle build environment: - keyPair.serialize(storageKryo()).writeToFile(dir.resolve(privateKeyFile)) + Files.write(dir.resolve(privateKeyFile), keyPair.private.encoded) + Files.write(dir.resolve(publicKeyFile), keyPair.public.encoded) } - return notaryParty + return Party(serviceName, notaryKey) } } From 0fdd4ae6c67d69ae696d4466e73f212a239fd602 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Wed, 19 Jul 2017 13:06:43 +0100 Subject: [PATCH 056/197] Remove TransactionForContract and just use LedgerTransaction Fix minor doc error Get rid of @deprecated method that was only present for technical reasons. Minor fix and include in changelog --- .../net/corda/core/contracts/Structures.kt | 10 +- .../corda/core/contracts/TransactionTypes.kt | 5 +- .../core/contracts/TransactionVerification.kt | 128 ------------------ .../TransactionVerificationException.kt | 42 ++++++ .../net/corda/core/contracts/clauses/AllOf.kt | 4 +- .../net/corda/core/contracts/clauses/AnyOf.kt | 4 +- .../corda/core/contracts/clauses/Clause.kt | 6 +- .../core/contracts/clauses/ClauseVerifier.kt | 8 +- .../corda/core/contracts/clauses/FilterOn.kt | 4 +- .../contracts/clauses/FirstComposition.kt | 4 +- .../corda/core/contracts/clauses/FirstOf.kt | 4 +- .../contracts/clauses/GroupClauseVerifier.kt | 6 +- .../corda/core/flows/ContractUpgradeFlow.kt | 5 +- .../core/transactions/LedgerTransaction.kt | 64 +++++++-- .../contracts/TransactionEncumbranceTests.kt | 5 +- .../core/contracts/clauses/AllOfTests.kt | 7 +- .../core/contracts/clauses/AnyOfTests.kt | 9 +- .../core/contracts/clauses/ClauseTestUtils.kt | 6 +- .../contracts/clauses/VerifyClausesTests.kt | 13 +- .../core/flows/ContractUpgradeFlowTest.kt | 3 +- .../core/node/AttachmentClassLoaderTests.kt | 3 +- .../net/corda/core/node/VaultUpdateTests.kt | 3 +- .../TransactionSerializationTests.kt | 5 +- .../amqp/SerializationOutputTests.kt | 10 +- docs/source/api-contracts.rst | 43 +++--- docs/source/changelog.rst | 7 + docs/source/clauses.rst | 12 +- .../docs/WorkflowTransactionBuildTutorial.kt | 5 +- docs/source/hello-world-contract.rst | 12 +- docs/source/tutorial-contract-clauses.rst | 12 +- docs/source/tutorial-contract.rst | 12 +- .../contracts/universal/UniversalContract.kt | 33 ++--- .../corda/contracts/AnotherDummyContract.kt | 6 +- .../corda/contracts/JavaCommercialPaper.java | 22 +-- .../net/corda/contracts/CommercialPaper.kt | 15 +- .../corda/contracts/CommercialPaperLegacy.kt | 5 +- .../kotlin/net/corda/contracts/asset/Cash.kt | 5 +- .../contracts/asset/CommodityContract.kt | 5 +- .../net/corda/contracts/asset/Obligation.kt | 17 +-- .../clause/AbstractConserveAmount.kt | 3 +- .../corda/contracts/clause/AbstractIssue.kt | 3 +- .../kotlin/net/corda/contracts/clause/Net.kt | 3 +- .../contracts/clause/NoZeroSizedOutputs.kt | 3 +- .../corda/contracts/DummyFungibleContract.kt | 9 +- .../services/vault/schemas/VaultSchemaTest.kt | 6 +- .../corda/attachmentdemo/AttachmentDemo.kt | 9 +- .../main/kotlin/net/corda/irs/contract/IRS.kt | 19 ++- .../net/corda/vega/contracts/OGTrade.kt | 9 +- .../net/corda/vega/contracts/PortfolioSwap.kt | 11 +- .../corda/testing/AlwaysSucceedContract.kt | 4 +- .../corda/testing/contracts/DummyContract.kt | 3 +- .../testing/contracts/DummyContractV2.kt | 7 +- .../testing/contracts/DummyDealContract.kt | 6 +- .../testing/contracts/DummyLinearContract.kt | 8 +- 54 files changed, 339 insertions(+), 333 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt create mode 100644 core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index 0740a4a7e4..b4801a4efa 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -6,7 +6,11 @@ import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.serialization.* +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.MissingAttachmentsException +import net.corda.core.serialization.SerializeAsTokenContext +import net.corda.core.serialization.serialize +import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.OpaqueBytes import java.io.FileNotFoundException import java.io.IOException @@ -205,7 +209,7 @@ interface LinearState : ContractState { */ @CordaSerializable class ClauseVerifier : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -399,7 +403,7 @@ interface Contract { * existing contract code. */ @Throws(IllegalArgumentException::class) - fun verify(tx: TransactionForContract) + fun verify(tx: LedgerTransaction) /** * Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt index c5af7e80d8..d22eadb11f 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt @@ -122,12 +122,11 @@ sealed class TransactionType { * If any contract fails to verify, the whole transaction is considered to be invalid. */ private fun verifyContracts(tx: LedgerTransaction) { - val ctx = tx.toTransactionForContract() // TODO: This will all be replaced in future once the sandbox and contract constraints work is done. - val contracts = (ctx.inputs.map { it.contract } + ctx.outputs.map { it.contract }).toSet() + val contracts = (tx.inputs.map { it.state.data.contract } + tx.outputs.map { it.data.contract }).toSet() for (contract in contracts) { try { - contract.verify(ctx) + contract.verify(tx) } catch(e: Throwable) { throw TransactionVerificationException.ContractRejection(tx.id, contract, e) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt deleted file mode 100644 index 0b48cae298..0000000000 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt +++ /dev/null @@ -1,128 +0,0 @@ -package net.corda.core.contracts - -import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowException -import net.corda.core.identity.Party -import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.NonEmptySet -import java.security.PublicKey -import java.util.* - -// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts. - -/** - * A transaction to be passed as input to a contract verification function. Defines helper methods to - * simplify verification logic in contracts. - */ -// DOCSTART 1 -data class TransactionForContract(val inputs: List, - val outputs: List, - val attachments: List, - val commands: List>, - val origHash: SecureHash, - val inputNotary: Party? = null, - val timeWindow: TimeWindow? = null) { -// DOCEND 1 - override fun hashCode() = origHash.hashCode() - override fun equals(other: Any?) = other is TransactionForContract && other.origHash == origHash - - /** - * Given a type and a function that returns a grouping key, associates inputs and outputs together so that they - * can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement - * equals and hashCode). - * - * The purpose of this function is to simplify the writing of verification logic for transactions that may contain - * similar but unrelated state evolutions which need to be checked independently. Consider a transaction that - * simultaneously moves both dollars and euros (e.g. is an atomic FX trade). There may be multiple dollar inputs and - * multiple dollar outputs, depending on things like how fragmented the owner's vault is and whether various privacy - * techniques are in use. The quantity of dollars on the output side must sum to the same as on the input side, to - * ensure no money is being lost track of. This summation and checking must be repeated independently for each - * currency. To solve this, you would use groupStates with a type of Cash.State and a selector that returns the - * currency field: the resulting list can then be iterated over to perform the per-currency calculation. - */ - // DOCSTART 2 - fun groupStates(ofType: Class, selector: (T) -> K): List> { - val inputs = inputs.filterIsInstance(ofType) - val outputs = outputs.filterIsInstance(ofType) - - val inGroups: Map> = inputs.groupBy(selector) - val outGroups: Map> = outputs.groupBy(selector) - - @Suppress("DEPRECATION") - return groupStatesInternal(inGroups, outGroups) - } - // DOCEND 2 - - /** See the documentation for the reflection-based version of [groupStates] */ - inline fun groupStates(selector: (T) -> K): List> { - val inputs = inputs.filterIsInstance() - val outputs = outputs.filterIsInstance() - - val inGroups: Map> = inputs.groupBy(selector) - val outGroups: Map> = outputs.groupBy(selector) - - @Suppress("DEPRECATION") - return groupStatesInternal(inGroups, outGroups) - } - - @Deprecated("Do not use this directly: exposed as public only due to function inlining") - fun groupStatesInternal(inGroups: Map>, outGroups: Map>): List> { - val result = ArrayList>() - - for ((k, v) in inGroups.entries) - result.add(InOutGroup(v, outGroups[k] ?: emptyList(), k)) - for ((k, v) in outGroups.entries) { - if (inGroups[k] == null) - result.add(InOutGroup(emptyList(), v, k)) - } - - return result - } - - /** Utilities for contract writers to incorporate into their logic. */ - - /** - * A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated - * using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated - * state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add - * up on both sides of the transaction, but the values must be summed independently per currency. Grouping can - * be used to simplify this logic. - */ - // DOCSTART 3 - data class InOutGroup(val inputs: List, val outputs: List, val groupingKey: K) - // DOCEND 3 -} - -class TransactionResolutionException(val hash: SecureHash) : FlowException("Transaction resolution failure for $hash") -class AttachmentResolutionException(val hash: SecureHash) : FlowException("Attachment resolution failure for $hash") - -sealed class TransactionVerificationException(val txId: SecureHash, message: String, cause: Throwable?) - : FlowException("$message, transaction: $txId", cause) { - - class ContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) - : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, contract: $contract", cause) - - class MoreThanOneNotary(txId: SecureHash) - : TransactionVerificationException(txId, "More than one notary", null) - - class SignersMissing(txId: SecureHash, missing: List) - : TransactionVerificationException(txId, "Signers missing: ${missing.joinToString()}", null) - - class DuplicateInputStates(txId: SecureHash, val duplicates: NonEmptySet) - : TransactionVerificationException(txId, "Duplicate inputs: ${duplicates.joinToString()}", null) - - class InvalidNotaryChange(txId: SecureHash) - : TransactionVerificationException(txId, "Detected a notary change. Outputs must use the same notary as inputs", null) - - class NotaryChangeInWrongTransactionType(txId: SecureHash, txNotary: Party, outputNotary: Party) - : TransactionVerificationException(txId, "Found unexpected notary change in transaction. Tx notary: $txNotary, found: $outputNotary", null) - - class TransactionMissingEncumbranceException(txId: SecureHash, missing: Int, inOut: Direction) - : TransactionVerificationException(txId, "Missing required encumbrance $missing in $inOut", null) - - @CordaSerializable - enum class Direction { - INPUT, - OUTPUT - } -} diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt new file mode 100644 index 0000000000..6f42461e5c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -0,0 +1,42 @@ +package net.corda.core.contracts + +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowException +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.NonEmptySet +import java.security.PublicKey + +class TransactionResolutionException(val hash: SecureHash) : FlowException("Transaction resolution failure for $hash") +class AttachmentResolutionException(val hash: SecureHash) : FlowException("Attachment resolution failure for $hash") + +sealed class TransactionVerificationException(val txId: SecureHash, message: String, cause: Throwable?) + : FlowException("$message, transaction: $txId", cause) { + + class ContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) + : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, contract: $contract", cause) + + class MoreThanOneNotary(txId: SecureHash) + : TransactionVerificationException(txId, "More than one notary", null) + + class SignersMissing(txId: SecureHash, missing: List) + : TransactionVerificationException(txId, "Signers missing: ${missing.joinToString()}", null) + + class DuplicateInputStates(txId: SecureHash, val duplicates: NonEmptySet) + : TransactionVerificationException(txId, "Duplicate inputs: ${duplicates.joinToString()}", null) + + class InvalidNotaryChange(txId: SecureHash) + : TransactionVerificationException(txId, "Detected a notary change. Outputs must use the same notary as inputs", null) + + class NotaryChangeInWrongTransactionType(txId: SecureHash, txNotary: Party, outputNotary: Party) + : TransactionVerificationException(txId, "Found unexpected notary change in transaction. Tx notary: $txNotary, found: $outputNotary", null) + + class TransactionMissingEncumbranceException(txId: SecureHash, missing: Int, inOut: Direction) + : TransactionVerificationException(txId, "Missing required encumbrance $missing in $inOut", null) + + @CordaSerializable + enum class Direction { + INPUT, + OUTPUT + } +} diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/AllOf.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/AllOf.kt index 6fcc9df40b..cb338ec6d1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/AllOf.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/AllOf.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import java.util.* /** @@ -24,7 +24,7 @@ open class AllOf(firstClause: Claus return clauses } - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/AnyOf.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/AnyOf.kt index ceb732bea2..ee4b1d57dd 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/AnyOf.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/AnyOf.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import java.util.* /** @@ -18,7 +18,7 @@ open class AnyOf(vararg rawCl return matched } - override fun verify(tx: TransactionForContract, inputs: List, outputs: List, commands: List>, groupingKey: K?): Set { + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: K?): Set { return matchedClauses(commands).flatMapTo(HashSet()) { clause -> clause.verify(tx, inputs, outputs, commands, groupingKey) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/Clause.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/Clause.kt index 00409ce2c3..d003a186ed 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/Clause.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/Clause.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.loggerFor /** @@ -48,14 +48,14 @@ abstract class Clause { * @param commands commands which are relevant to this clause. By default this is the set passed into [verifyClause], * but may be further reduced by clauses such as [GroupClauseVerifier]. * @param groupingKey a grouping key applied to states and commands, where applicable. Taken from - * [TransactionForContract.InOutGroup]. + * [LedgerTransaction.InOutGroup]. * @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a * later clause. This would normally be all commands matching "requiredCommands" for this clause, but some * verify() functions may do further filtering on possible matches, and return a subset. This may also include * commands that were not required (for example the Exit command for fungible assets is optional). */ @Throws(IllegalStateException::class) - abstract fun verify(tx: TransactionForContract, + abstract fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt index 30fadda3ed..db970d5f07 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt @@ -5,7 +5,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction /** * Verify a transaction against the given list of clauses. @@ -15,15 +15,15 @@ import net.corda.core.contracts.TransactionForContract * @param commands commands extracted from the transaction, which are relevant to the * clauses. */ -fun verifyClause(tx: TransactionForContract, +fun verifyClause(tx: LedgerTransaction, clause: Clause, commands: List>) { if (Clause.log.isTraceEnabled) { clause.getExecutionPath(commands).forEach { - Clause.log.trace("Tx ${tx.origHash} clause: $clause") + Clause.log.trace("Tx ${tx.id} clause: $clause") } } - val matchedCommands = clause.verify(tx, tx.inputs, tx.outputs, commands, null) + val matchedCommands = clause.verify(tx, tx.inputs.map { it.state.data }, tx.outputs.map { it.data }, commands, null) check(matchedCommands.containsAll(commands.map { it.value })) { "The following commands were not matched at the end of execution: " + (commands - matchedCommands) } } diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/FilterOn.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/FilterOn.kt index e34f313443..e7112ab6bc 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/FilterOn.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/FilterOn.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction /** * Filter the states that are passed through to the wrapped clause, to restrict them to a specific type. @@ -16,7 +16,7 @@ class FilterOn(val clause: Claus override fun getExecutionPath(commands: List>): List> = clause.getExecutionPath(commands) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstComposition.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstComposition.kt index 2edea14625..f52b61195a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstComposition.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstComposition.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import java.util.* /** @@ -19,7 +19,7 @@ class FirstComposition(firstClause: clauses.addAll(remainingClauses) } - override fun verify(tx: TransactionForContract, inputs: List, outputs: List, commands: List>, groupingKey: K?): Set { + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: K?): Set { val clause = matchedClauses(commands).singleOrNull() ?: throw IllegalStateException("No delegate clause matched in first composition") return clause.verify(tx, inputs, outputs, commands, groupingKey) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstOf.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstOf.kt index 43a495b026..2de9fc2b98 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstOf.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/FirstOf.kt @@ -3,7 +3,7 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.loggerFor import java.util.* @@ -33,7 +33,7 @@ class FirstOf(firstClause: Clause, outputs: List, commands: List>, groupingKey: K?): Set { + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: K?): Set { return matchedClause(commands).verify(tx, inputs, outputs, commands, groupingKey) } diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/GroupClauseVerifier.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/GroupClauseVerifier.kt index f8634a812a..45ce9610e3 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/GroupClauseVerifier.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/GroupClauseVerifier.kt @@ -3,16 +3,16 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import java.util.* abstract class GroupClauseVerifier(val clause: Clause) : Clause() { - abstract fun groupStates(tx: TransactionForContract): List> + abstract fun groupStates(tx: LedgerTransaction): List> override fun getExecutionPath(commands: List>): List> = clause.getExecutionPath(commands) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index bc1ced6793..9ec39b7093 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -1,6 +1,7 @@ package net.corda.core.flows import net.corda.core.contracts.* +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import java.security.PublicKey @@ -21,9 +22,9 @@ class ContractUpgradeFlow outRef(index: Int) = StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) - // TODO: Remove this concept. - // There isn't really a good justification for hiding this data from the contract, it's just a backwards compat hack. - /** Strips the transaction down to a form that is usable by the contract verify functions */ - fun toTransactionForContract(): TransactionForContract { - return TransactionForContract(inputs.map { it.state.data }, outputs.map { it.data }, attachments, commands, id, - inputs.map { it.state.notary }.singleOrNull(), timeWindow) - } - /** * Verifies this transaction and throws an exception if not valid, depending on the type. For general transactions: * @@ -88,4 +83,57 @@ class LedgerTransaction( result = 31 * result + id.hashCode() return result } + + /** + * Given a type and a function that returns a grouping key, associates inputs and outputs together so that they + * can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement + * equals and hashCode). + * + * The purpose of this function is to simplify the writing of verification logic for transactions that may contain + * similar but unrelated state evolutions which need to be checked independently. Consider a transaction that + * simultaneously moves both dollars and euros (e.g. is an atomic FX trade). There may be multiple dollar inputs and + * multiple dollar outputs, depending on things like how fragmented the owner's vault is and whether various privacy + * techniques are in use. The quantity of dollars on the output side must sum to the same as on the input side, to + * ensure no money is being lost track of. This summation and checking must be repeated independently for each + * currency. To solve this, you would use groupStates with a type of Cash.State and a selector that returns the + * currency field: the resulting list can then be iterated over to perform the per-currency calculation. + */ + // DOCSTART 2 + fun groupStates(ofType: Class, selector: (T) -> K): List> { + val inputs = inputs.map { it.state.data }.filterIsInstance(ofType) + val outputs = outputs.map { it.data }.filterIsInstance(ofType) + + val inGroups: Map> = inputs.groupBy(selector) + val outGroups: Map> = outputs.groupBy(selector) + + val result = ArrayList>() + + for ((k, v) in inGroups.entries) + result.add(InOutGroup(v, outGroups[k] ?: emptyList(), k)) + for ((k, v) in outGroups.entries) { + if (inGroups[k] == null) + result.add(InOutGroup(emptyList(), v, k)) + } + + return result + } + // DOCEND 2 + + /** See the documentation for the reflection-based version of [groupStates] */ + inline fun groupStates(noinline selector: (T) -> K): List> { + return groupStates(T::class.java, selector) + } + + /** Utilities for contract writers to incorporate into their logic. */ + + /** + * A set of related inputs and outputs that are connected by some common attributes. An InOutGroup is calculated + * using [groupStates] and is useful for handling cases where a transaction may contain similar but unrelated + * state evolutions, for example, a transaction that moves cash in two different currencies. The numbers must add + * up on both sides of the transaction, but the values must be summed independently per currency. Grouping can + * be used to simplify this logic. + */ + // DOCSTART 3 + data class InOutGroup(val inputs: List, val outputs: List, val groupingKey: K) + // DOCEND 3 } diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt index c13ebbae09..9e6be70bec 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt @@ -3,6 +3,7 @@ package net.corda.core.contracts import net.corda.contracts.asset.Cash import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction import net.corda.testing.MEGA_CORP import net.corda.testing.MINI_CORP import net.corda.testing.ledger @@ -28,8 +29,8 @@ class TransactionEncumbranceTests { class DummyTimeLock : Contract { override val legalContractReference = SecureHash.sha256("DummyTimeLock") - override fun verify(tx: TransactionForContract) { - val timeLockInput = tx.inputs.filterIsInstance().singleOrNull() ?: return + override fun verify(tx: LedgerTransaction) { + val timeLockInput = tx.inputs.map { it.state.data }.filterIsInstance().singleOrNull() ?: return val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window") requireThat { "the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom) diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt index 7e2636bcfc..234bc05642 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/AllOfTests.kt @@ -2,8 +2,9 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData -import net.corda.core.contracts.TransactionForContract +import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertEquals @@ -15,7 +16,7 @@ class AllOfTests { fun minimal() { val counter = AtomicInteger(0) val clause = AllOf(matchedClause(counter), matchedClause(counter)) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of two clauses @@ -25,7 +26,7 @@ class AllOfTests { @Test fun `not all match`() { val clause = AllOf(matchedClause(), unmatchedClause()) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) assertFailsWith { verifyClause(tx, clause, emptyList>()) } } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt index fa7d6be9a8..e599515bb0 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/AnyOfTests.kt @@ -2,8 +2,9 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData -import net.corda.core.contracts.TransactionForContract +import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertEquals @@ -14,7 +15,7 @@ class AnyOfTests { fun minimal() { val counter = AtomicInteger(0) val clause = AnyOf(matchedClause(counter), matchedClause(counter)) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of two clauses @@ -25,7 +26,7 @@ class AnyOfTests { fun `not all match`() { val counter = AtomicInteger(0) val clause = AnyOf(matchedClause(counter), unmatchedClause(counter)) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) verifyClause(tx, clause, emptyList>()) // Check that we've run the verify() function of one clause @@ -36,7 +37,7 @@ class AnyOfTests { fun `none match`() { val counter = AtomicInteger(0) val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter)) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) assertFailsWith(IllegalArgumentException::class) { verifyClause(tx, clause, emptyList>()) } diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/ClauseTestUtils.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/ClauseTestUtils.kt index a21e6d2b08..fb0f023f3c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/ClauseTestUtils.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/ClauseTestUtils.kt @@ -3,12 +3,12 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract +import net.corda.core.transactions.LedgerTransaction import java.util.concurrent.atomic.AtomicInteger internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause() { override val requiredCommands: Set> = emptySet() - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set { @@ -20,7 +20,7 @@ internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause() { override val requiredCommands: Set> = setOf(object : CommandData {}.javaClass) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set { diff --git a/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt b/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt index 4627f1baa2..ddb196b3d9 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/clauses/VerifyClausesTests.kt @@ -3,9 +3,10 @@ package net.corda.core.contracts.clauses import net.corda.core.contracts.AuthenticatedObject import net.corda.core.contracts.CommandData import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionForContract -import net.corda.testing.contracts.DummyContract +import net.corda.core.contracts.TransactionType import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction +import net.corda.testing.contracts.DummyContract import org.junit.Test import kotlin.test.assertFailsWith @@ -17,25 +18,25 @@ class VerifyClausesTests { @Test fun minimal() { val clause = object : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set = emptySet() } - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) verifyClause(tx, clause, emptyList>()) } @Test fun errorSuperfluousCommands() { val clause = object : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set = emptySet() } val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create()) - val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), listOf(command), SecureHash.randomSHA256()) + val tx = LedgerTransaction(emptyList(), emptyList(), listOf(command), emptyList(), SecureHash.randomSHA256(), null, emptyList(), null, TransactionType.General) // The clause is matched, but doesn't mark the command as consumed, so this should error assertFailsWith { verifyClause(tx, clause, listOf(command)) } } diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index e891f4677c..acac7c2574 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -10,6 +10,7 @@ import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.node.services.unconsumedStates +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.internal.Emoji import net.corda.core.utilities.OpaqueBytes @@ -206,7 +207,7 @@ class ContractUpgradeFlowTest { override fun upgrade(state: Cash.State) = CashV2.State(state.amount.times(1000), listOf(state.owner)) - override fun verify(tx: TransactionForContract) {} + override fun verify(tx: LedgerTransaction) {} // Dummy Cash contract for testing. override val legalContractReference = SecureHash.sha256("") diff --git a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt index e2af5fe66b..efe0a254c0 100644 --- a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt @@ -9,6 +9,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.testing.DUMMY_NOTARY import net.corda.testing.MEGA_CORP @@ -58,7 +59,7 @@ class AttachmentClassLoaderTests { class Create : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Always accepts. } diff --git a/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt b/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt index a2f2e81716..e63804a74a 100644 --- a/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/VaultUpdateTests.kt @@ -4,6 +4,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.node.services.Vault +import net.corda.core.transactions.LedgerTransaction import net.corda.testing.DUMMY_NOTARY import org.junit.Test import kotlin.test.assertEquals @@ -13,7 +14,7 @@ class VaultUpdateTests { object DummyContract : Contract { - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { } override val legalContractReference: SecureHash = SecureHash.sha256("") diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 5ac91f2695..729ff16822 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -3,8 +3,9 @@ package net.corda.core.serialization import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty -import net.corda.core.utilities.seconds +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.seconds import net.corda.testing.* import net.corda.testing.node.MockServices import org.junit.Before @@ -20,7 +21,7 @@ class TransactionSerializationTests { class TestCash : Contract { override val legalContractReference = SecureHash.sha256("TestCash") - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { } data class State( diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt index f1e9df1707..13dc2b22fb 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/SerializationOutputTests.kt @@ -1,6 +1,10 @@ package net.corda.core.serialization.amqp -import net.corda.core.contracts.* +import net.corda.core.CordaRuntimeException +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty @@ -8,7 +12,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.EmptyWhitelist import net.corda.core.serialization.KryoAMQPSerializer import net.corda.core.serialization.amqp.SerializerFactory.Companion.isPrimitive -import net.corda.core.CordaRuntimeException +import net.corda.core.transactions.LedgerTransaction import net.corda.nodeapi.RPCException import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_PUBKEY @@ -505,7 +509,7 @@ class SerializationOutputTests { } object FooContract : Contract { - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { } diff --git a/docs/source/api-contracts.rst b/docs/source/api-contracts.rst index c011a098bd..6a03f5451e 100644 --- a/docs/source/api-contracts.rst +++ b/docs/source/api-contracts.rst @@ -22,19 +22,19 @@ The ``Contract`` interface is defined as follows: Where: -* ``verify(tx: TransactionForContract)`` determines whether transactions involving states which reference this + * ``verify(tx: LedgerTransaction)`` determines whether transactions involving states which reference this contract type are valid * ``legalContractReference`` is the hash of the legal prose contract that ``verify`` seeks to express in code verify() -------- -``verify()`` is a method that doesn't return anything and takes a ``TransactionForContract`` as a parameter. It +``verify()`` is a method that doesn't return anything and takes a ``LedgerTransaction`` as a parameter. It either throws an exception if the transaction is considered invalid, or returns normally if the transaction is considered valid. ``verify()`` is executed in a sandbox. It does not have access to the enclosing scope, and is not able to access -the network or perform any other I/O. It only has access to the properties defined on ``TransactionForContract`` when +the network or perform any other I/O. It only has access to the properties defined on ``LedgerTransaction`` when establishing whether a transaction is valid. The two simplest ``verify`` functions are the one that accepts all transactions, and the one that rejects all @@ -46,14 +46,14 @@ Here is the ``verify`` that accepts all transactions: .. sourcecode:: kotlin - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Always accepts! } .. sourcecode:: java @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { // Always accepts! } @@ -63,39 +63,40 @@ And here is the ``verify`` that rejects all transactions: .. sourcecode:: kotlin - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { throw IllegalArgumentException("Always rejects!") } .. sourcecode:: java @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { throw new IllegalArgumentException("Always rejects!"); } -TransactionForContract +LedgerTransaction ^^^^^^^^^^^^^^^^^^^^^^ -The ``TransactionForContract`` object passed into ``verify()`` represents the full set of information available to +The ``LedgerTransaction`` object passed into ``verify()`` represents the full set of information available to ``verify()`` when deciding whether to accept or reject the transaction. It has the following properties: .. container:: codeset - .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt + .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt :language: kotlin :start-after: DOCSTART 1 :end-before: DOCEND 1 Where: -* ``inputs`` is a list of the transaction's inputs -* ``outputs`` is a list of the transaction's outputs -* ``attachments`` is a list of the transaction's attachments -* ``commands`` is a list of the transaction's commands, and their associated signatures -* ``origHash`` is the transaction's hash -* ``inputNotary`` is the transaction's notary -* ``timestamp`` is the transaction's timestamp + * ``inputs`` is a list of the transaction's inputs' +* ``outputs`` is a list of the transaction's outputs' +* ``attachments`` is a list of the transaction's attachments' +* ``commands`` is a list of the transaction's commands, and their associated signatures' +* ``id`` is the transaction's merkle root hash' +* ``notary`` is the transaction's notary. If there are inputs these must have the same notary on their source transactions. +* ``timeWindow`` is the transaction's timestamp and defines the acceptable delay for notarisation. +* ``type`` is the class of Transaction. Normal ledger data transactions are ``TransactionType.General``, but migration of states to a new notary uses ``TransactionType.NotaryChange``. requireThat() ^^^^^^^^^^^^^ @@ -134,7 +135,7 @@ exception will cause the transaction to be rejected. Commands ^^^^^^^^ -``TransactionForContract`` contains the commands as a list of ``AuthenticatedObject`` instances. +``LedgerTransaction`` contains the commands as a list of ``AuthenticatedObject`` instances. ``AuthenticatedObject`` pairs an object with a list of signers. In this case, ``AuthenticatedObject`` pairs a command with a list of the entities that are required to sign a transaction where this command is present: @@ -175,7 +176,7 @@ execution of ``verify()``: class Transfer : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand() when (command.value) { @@ -200,7 +201,7 @@ execution of ``verify()``: } @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { final AuthenticatedObject command = requireSingleCommand(tx.getCommands(), Commands.class); if (command.getValue() instanceof Commands.Issue) { @@ -228,7 +229,7 @@ We can imagine that we would like to verify the USD states and the GBP states se :scale: 20 :align: center -``TransactionForContract`` provides a ``groupStates`` method to allow you to group states in this way: +``LedgerTransaction`` provides a ``groupStates`` method to allow you to group states in this way: .. container:: codeset diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 346c1f4046..6faaf0b64f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -38,6 +38,13 @@ UNRELEASED overriden in order to handle cases where no single transaction participant is aware of all parties, and therefore the transaction must be relayed between participants rather than sent from a single node. +* ``TransactionForContract`` has been removed and all usages of this class have been replaced with usage of + ``LedgerTransaction``. In particular ``Contract.verify`` and the ``Clauses`` API have been changed and now take a + ``LedgerTransaction`` as passed in parameter. The prinicpal consequence of this is that the types of the input and output + collections on the transaction object have changed, so it may be necessary to ``map`` down to the ``ContractState`` + sub-properties in existing code. + It is intended that new helper methods will be added shortly to the API to reduce the impact of these changes. + Milestone 13 ------------ diff --git a/docs/source/clauses.rst b/docs/source/clauses.rst index fd8e514aff..d19552987c 100644 --- a/docs/source/clauses.rst +++ b/docs/source/clauses.rst @@ -18,7 +18,7 @@ Let's take a look at a simplified structure of the ``Clause`` class: open val requiredCommands: Set> = emptySet() @Throws(IllegalStateException::class) - abstract fun verify(tx: TransactionForContract, + abstract fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -47,7 +47,7 @@ An example ``verify`` from ``Obligation`` contract: .. sourcecode:: kotlin - override fun verify(tx: TransactionForContract) = verifyClause(tx, FirstOf( + override fun verify(tx: LedgerTransaction) = verifyClause(tx, FirstOf( Clauses.Net(), Clauses.Group

() ), tx.commands.select()) @@ -112,7 +112,7 @@ Example from ``CommercialPaper.kt``: Redeem(), Move(), Issue())) { - override fun groupStates(tx: TransactionForContract): List>> + override fun groupStates(tx: LedgerTransaction): List>> = tx.groupStates> { it.token } } @@ -183,11 +183,11 @@ grouped input and output states with a grouping key used for each group. Example ) ) ) { - override fun groupStates(tx: TransactionForContract): List, Issued>>> + override fun groupStates(tx: LedgerTransaction): List, Issued>>> = tx.groupStates, Issued>> { it.amount.token } } -Usually it's convenient to use ``groupStates`` function defined on ``TransactionForContract`` class. Which given a type and a +Usually it's convenient to use ``groupStates`` function defined on ``LedgerTransaction`` class. Which given a type and a selector function, that returns a grouping key, associates inputs and outputs together so that they can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement equals and hashCode). @@ -232,7 +232,7 @@ Example from ``CommercialPaper.kt``: { token -> map { Amount(it.faceValue.quantity, it.token) }.sumOrZero(token) }) { override val requiredCommands: Set> = setOf(Commands.Issue::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index 16a226e4ea..38168a6351 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -14,6 +14,7 @@ import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.linearHeadsOfType import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap @@ -69,7 +70,7 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash * The verify method locks down the allowed transactions to contain just a single proposal being * created/modified and the only modification allowed is to the state field. */ - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand() require(tx.timeWindow?.midpoint != null) { "must have a time-window" } when (command.value) { @@ -78,7 +79,7 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash "Issue of new WorkflowContract must not include any inputs" using (tx.inputs.isEmpty()) "Issue of new WorkflowContract must be in a unique transaction" using (tx.outputs.size == 1) } - val issued = tx.outputs[0] as TradeApprovalContract.State + val issued = tx.outputs[0].data as TradeApprovalContract.State requireThat { "Issue requires the source Party as signer" using (command.signers.contains(issued.source.owningKey)) "Initial Issue state must be NEW" using (issued.state == WorkflowState.NEW) diff --git a/docs/source/hello-world-contract.rst b/docs/source/hello-world-contract.rst index de523ddf8b..31bebc2495 100644 --- a/docs/source/hello-world-contract.rst +++ b/docs/source/hello-world-contract.rst @@ -36,7 +36,7 @@ Just as every Corda state must implement the ``ContractState`` interface, every interface Contract { // Implements the contract constraints in code. @Throws(IllegalArgumentException::class) - fun verify(tx: TransactionForContract) + fun verify(tx: LedgerTransaction) // Expresses the contract constraints as legal prose. val legalContractReference: SecureHash @@ -94,7 +94,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi // Our Create command. class Create : CommandData - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand() requireThat { @@ -103,7 +103,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi "There should be one output state of type IOUState." using (tx.outputs.size == 1) // IOU-specific constraints. - val out = tx.outputs.single() as IOUState + val out = tx.outputs.single().data as IOUState "The IOU's value must be non-negative." using (out.value > 0) "The lender and the borrower cannot be the same entity." using (out.lender != out.borrower) @@ -125,7 +125,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi import net.corda.core.contracts.AuthenticatedObject; import net.corda.core.contracts.CommandData; import net.corda.core.contracts.Contract; - import net.corda.core.contracts.TransactionForContract; + import net.corda.core.transactions.LedgerTransaction; import net.corda.core.crypto.SecureHash; import net.corda.core.identity.Party; @@ -137,7 +137,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi public static class Create implements CommandData {} @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { final AuthenticatedObject command = requireSingleCommand(tx.getCommands(), Create.class); requireThat(check -> { @@ -146,7 +146,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1); // IOU-specific constraints. - final IOUState out = (IOUState) tx.getOutputs().get(0); + final IOUState out = (IOUState) tx.getOutputs().getData().get(0); final Party lender = out.getLender(); final Party borrower = out.getBorrower(); check.using("The IOU's value must be non-negative.",out.getValue() > 0); diff --git a/docs/source/tutorial-contract-clauses.rst b/docs/source/tutorial-contract-clauses.rst index cb6ea53f46..c8b3ca98cd 100644 --- a/docs/source/tutorial-contract-clauses.rst +++ b/docs/source/tutorial-contract-clauses.rst @@ -68,7 +68,7 @@ We start by defining the ``CommercialPaper`` class. As in the previous tutorial, class CommercialPaper : Contract { override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper") - override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select()) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), tx.commands.select()) interface Commands : CommandData { data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands @@ -85,7 +85,7 @@ We start by defining the ``CommercialPaper`` class. As in the previous tutorial, } @Override - public void verify(@NotNull TransactionForContract tx) throws IllegalArgumentException { + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx)); } @@ -128,7 +128,7 @@ and is included in the ``CommercialPaper.kt`` code. override val requiredCommands: Set> get() = setOf(Commands.Move::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -158,7 +158,7 @@ and is included in the ``CommercialPaper.kt`` code. @NotNull @Override - public Set verify(@NotNull TransactionForContract tx, + public Set verify(@NotNull LedgerTransaction tx, @NotNull List inputs, @NotNull List outputs, @NotNull List> commands, @@ -229,7 +229,7 @@ its subclauses (wrapped move, issue, redeem). "Any" in this case means that it w Redeem(), Move(), Issue())) { - override fun groupStates(tx: TransactionForContract): List>> + override fun groupStates(tx: LedgerTransaction): List>> = tx.groupStates> { it.token } } @@ -246,7 +246,7 @@ its subclauses (wrapped move, issue, redeem). "Any" in this case means that it w @NotNull @Override - public List> groupStates(@NotNull TransactionForContract tx) { + public List> groupStates(@NotNull LedgerTransaction tx) { return tx.groupStates(State.class, State::withoutOwner); } } diff --git a/docs/source/tutorial-contract.rst b/docs/source/tutorial-contract.rst index b41f65aa25..154dfef8da 100644 --- a/docs/source/tutorial-contract.rst +++ b/docs/source/tutorial-contract.rst @@ -61,7 +61,7 @@ Kotlin syntax works. class CommercialPaper : Contract { override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper"); - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { TODO() } } @@ -75,7 +75,7 @@ Kotlin syntax works. } @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { throw new UnsupportedOperationException(); } } @@ -298,7 +298,7 @@ run two contracts one time each: Cash and CommercialPaper. .. sourcecode:: kotlin - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. val groups = tx.groupStates(State::withoutOwner) @@ -309,7 +309,7 @@ run two contracts one time each: Cash and CommercialPaper. .. sourcecode:: java @Override - public void verify(TransactionForContract tx) { + public void verify(LedgerTransaction tx) { List> groups = tx.groupStates(State.class, State::withoutOwner); AuthenticatedObject cmd = requireSingleCommand(tx.getCommands(), Commands.class); @@ -356,7 +356,7 @@ inputs e.g. because she received the dollars in two payments. The input and outp the cash smart contract must consider the pounds and the dollars separately because they are not fungible: they cannot be merged together. So we have two groups: A and B. -The ``TransactionForContract.groupStates`` method handles this logic for us: firstly, it selects only states of the +The ``LedgerTransaction.groupStates`` method handles this logic for us: firstly, it selects only states of the given type (as the transaction may include other types of state, such as states representing bond ownership, or a multi-sig state) and then it takes a function that maps a state to a grouping key. All states that share the same key are grouped together. In the case of the cash example above, the grouping key would be the currency. @@ -791,7 +791,7 @@ The time-lock contract mentioned above can be implemented very simply: class TestTimeLock : Contract { ... - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { val time = tx.timestamp.before ?: throw IllegalStateException(...) ... requireThat { diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt index 74e3b0b00c..33f94b3bc4 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt @@ -6,6 +6,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import java.math.BigDecimal import java.time.Instant @@ -37,14 +38,14 @@ class UniversalContract : Contract { class Split(val ratio: BigDecimal) : Commands } - fun eval(@Suppress("UNUSED_PARAMETER") tx: TransactionForContract, expr: Perceivable): Instant? = when (expr) { + fun eval(@Suppress("UNUSED_PARAMETER") tx: LedgerTransaction, expr: Perceivable): Instant? = when (expr) { is Const -> expr.value is StartDate -> null is EndDate -> null else -> throw Error("Unable to evaluate") } - fun eval(tx: TransactionForContract, expr: Perceivable): Boolean = when (expr) { + fun eval(tx: LedgerTransaction, expr: Perceivable): Boolean = when (expr) { is PerceivableAnd -> eval(tx, expr.left) && eval(tx, expr.right) is PerceivableOr -> eval(tx, expr.left) || eval(tx, expr.right) is Const -> expr.value @@ -57,7 +58,7 @@ class UniversalContract : Contract { else -> throw NotImplementedError("eval - Boolean - " + expr.javaClass.name) } - fun eval(tx: TransactionForContract, expr: Perceivable): BigDecimal = + fun eval(tx: LedgerTransaction, expr: Perceivable): BigDecimal = when (expr) { is Const -> expr.value is UnaryPlus -> { @@ -94,7 +95,7 @@ class UniversalContract : Contract { else -> throw NotImplementedError("eval - BigDecimal - " + expr.javaClass.name) } - fun validateImmediateTransfers(tx: TransactionForContract, arrangement: Arrangement): Arrangement = when (arrangement) { + fun validateImmediateTransfers(tx: LedgerTransaction, arrangement: Arrangement): Arrangement = when (arrangement) { is Obligation -> { val amount = eval(tx, arrangement.amount) requireThat { "transferred quantity is non-negative" using (amount >= BigDecimal.ZERO) } @@ -179,7 +180,7 @@ class UniversalContract : Contract { else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { requireThat { "transaction has a single command".using(tx.commands.size == 1) @@ -191,7 +192,7 @@ class UniversalContract : Contract { when (value) { is Commands.Action -> { - val inState = tx.inputs.single() as State + val inState = tx.inputs.single().state.data as State val arr = when (inState.details) { is Actions -> inState.details is RollOut -> reduceRollOut(inState.details) @@ -221,7 +222,7 @@ class UniversalContract : Contract { when (tx.outputs.size) { 1 -> { - val outState = tx.outputs.single() as State + val outState = tx.outputs.single().data as State requireThat { "output state must match action result state" using (arrangement.equals(outState.details)) "output state must match action result state" using (rest == zero) @@ -229,7 +230,7 @@ class UniversalContract : Contract { } 0 -> throw IllegalArgumentException("must have at least one out state") else -> { - val allContracts = And(tx.outputs.map { (it as State).details }.toSet()) + val allContracts = And(tx.outputs.map { (it.data as State).details }.toSet()) requireThat { "output states must match action result state" using (arrangement.equals(allContracts)) @@ -239,15 +240,15 @@ class UniversalContract : Contract { } } is Commands.Issue -> { - val outState = tx.outputs.single() as State + val outState = tx.outputs.single().data as State requireThat { "the transaction is signed by all liable parties" using (liableParties(outState.details).all { it in cmd.signers }) "the transaction has no input states" using tx.inputs.isEmpty() } } is Commands.Move -> { - val inState = tx.inputs.single() as State - val outState = tx.outputs.single() as State + val inState = tx.inputs.single().state.data as State + val outState = tx.outputs.single().data as State requireThat { "the transaction is signed by all liable parties" using (liableParties(outState.details).all { it in cmd.signers }) @@ -256,13 +257,13 @@ class UniversalContract : Contract { } } is Commands.Fix -> { - val inState = tx.inputs.single() as State + val inState = tx.inputs.single().state.data as State val arr = when (inState.details) { is Actions -> inState.details is RollOut -> reduceRollOut(inState.details) else -> throw IllegalArgumentException("Unexpected arrangement, " + tx.inputs.single()) } - val outState = tx.outputs.single() as State + val outState = tx.outputs.single().data as State val unusedFixes = value.fixes.map { it.of }.toMutableSet() val expectedArr = replaceFixing(tx, arr, @@ -279,7 +280,7 @@ class UniversalContract : Contract { } @Suppress("UNCHECKED_CAST") - fun replaceFixing(tx: TransactionForContract, perceivable: Perceivable, + fun replaceFixing(tx: LedgerTransaction, perceivable: Perceivable, fixings: Map, unusedFixings: MutableSet): Perceivable = when (perceivable) { is Const -> perceivable @@ -299,11 +300,11 @@ class UniversalContract : Contract { else -> throw NotImplementedError("replaceFixing - " + perceivable.javaClass.name) } - fun replaceFixing(tx: TransactionForContract, arr: Action, + fun replaceFixing(tx: LedgerTransaction, arr: Action, fixings: Map, unusedFixings: MutableSet) = Action(arr.name, replaceFixing(tx, arr.condition, fixings, unusedFixings), replaceFixing(tx, arr.arrangement, fixings, unusedFixings)) - fun replaceFixing(tx: TransactionForContract, arr: Arrangement, + fun replaceFixing(tx: LedgerTransaction, arr: Arrangement, fixings: Map, unusedFixings: MutableSet): Arrangement = when (arr) { is Zero -> arr diff --git a/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt b/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt index ae94bab519..9d5e4d8bdd 100644 --- a/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt +++ b/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt @@ -1,11 +1,11 @@ package net.corda.contracts.isolated import net.corda.core.contracts.* -import net.corda.core.identity.Party import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import java.security.PublicKey // The dummy contract doesn't do anything useful. It exists for testing purposes. @@ -22,7 +22,7 @@ class AnotherDummyContract : Contract, net.corda.core.node.DummyContractBackdoor class Create : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Always accepts. } diff --git a/finance/src/main/java/net/corda/contracts/JavaCommercialPaper.java b/finance/src/main/java/net/corda/contracts/JavaCommercialPaper.java index df6b29e5be..3ab60ffdf9 100644 --- a/finance/src/main/java/net/corda/contracts/JavaCommercialPaper.java +++ b/finance/src/main/java/net/corda/contracts/JavaCommercialPaper.java @@ -7,7 +7,6 @@ import kotlin.Pair; import kotlin.Unit; import net.corda.contracts.asset.CashKt; import net.corda.core.contracts.*; -import net.corda.core.contracts.TransactionForContract.InOutGroup; import net.corda.core.contracts.clauses.AnyOf; import net.corda.core.contracts.clauses.Clause; import net.corda.core.contracts.clauses.ClauseVerifier; @@ -18,6 +17,7 @@ import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AnonymousParty; import net.corda.core.identity.Party; import net.corda.core.node.services.VaultService; +import net.corda.core.transactions.LedgerTransaction; import net.corda.core.transactions.TransactionBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -153,7 +153,7 @@ public class JavaCommercialPaper implements Contract { @NotNull @Override - public List> groupStates(@NotNull TransactionForContract tx) { + public List> groupStates(@NotNull LedgerTransaction tx) { return tx.groupStates(State.class, State::withoutOwner); } } @@ -168,11 +168,11 @@ public class JavaCommercialPaper implements Contract { @NotNull @Override - public Set verify(@NotNull TransactionForContract tx, + public Set verify(@NotNull LedgerTransaction tx, @NotNull List inputs, @NotNull List outputs, @NotNull List> commands, - State groupingKey) { + State groupingKey) { AuthenticatedObject cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class); // There should be only a single input due to aggregation above State input = Iterables.getOnlyElement(inputs); @@ -200,11 +200,11 @@ public class JavaCommercialPaper implements Contract { @NotNull @Override - public Set verify(@NotNull TransactionForContract tx, + public Set verify(@NotNull LedgerTransaction tx, @NotNull List inputs, @NotNull List outputs, @NotNull List> commands, - State groupingKey) { + State groupingKey) { AuthenticatedObject cmd = requireSingleCommand(tx.getCommands(), Commands.Redeem.class); // There should be only a single input due to aggregation above @@ -217,7 +217,7 @@ public class JavaCommercialPaper implements Contract { Instant time = null == timeWindow ? null : timeWindow.getUntilTime(); - Amount> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner()); + Amount> received = CashKt.sumCashBy(tx.getOutputs().stream().map(TransactionState::getData).collect(Collectors.toList()), input.getOwner()); requireThat(require -> { require.using("must be timestamped", timeWindow != null); @@ -243,11 +243,11 @@ public class JavaCommercialPaper implements Contract { @NotNull @Override - public Set verify(@NotNull TransactionForContract tx, + public Set verify(@NotNull LedgerTransaction tx, @NotNull List inputs, @NotNull List outputs, @NotNull List> commands, - State groupingKey) { + State groupingKey) { AuthenticatedObject cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class); State output = Iterables.getOnlyElement(outputs); TimeWindow timeWindowCommand = tx.getTimeWindow(); @@ -293,7 +293,7 @@ public class JavaCommercialPaper implements Contract { } @NotNull - private List> extractCommands(@NotNull TransactionForContract tx) { + private List> extractCommands(@NotNull LedgerTransaction tx) { return tx.getCommands() .stream() .filter((AuthenticatedObject command) -> command.getValue() instanceof Commands) @@ -302,7 +302,7 @@ public class JavaCommercialPaper implements Contract { } @Override - public void verify(@NotNull TransactionForContract tx) throws IllegalArgumentException { + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx)); } diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt index e175b4c5b1..c0b31b72c2 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt @@ -9,14 +9,15 @@ import net.corda.core.contracts.clauses.Clause import net.corda.core.contracts.clauses.GroupClauseVerifier import net.corda.core.contracts.clauses.verifyClause import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.toBase58String import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.VaultService -import net.corda.core.crypto.random63BitValue import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.internal.Emoji import net.corda.schemas.CommercialPaperSchemaV1 @@ -57,7 +58,7 @@ class CommercialPaper : Contract { val maturityDate: Instant ) - override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select()) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), tx.commands.select()) data class State( val issuance: PartyAndReference, @@ -112,7 +113,7 @@ class CommercialPaper : Contract { Redeem(), Move(), Issue())) { - override fun groupStates(tx: TransactionForContract): List>> + override fun groupStates(tx: LedgerTransaction): List>> = tx.groupStates> { it.token } } @@ -121,7 +122,7 @@ class CommercialPaper : Contract { { token -> map { Amount(it.faceValue.quantity, it.token) }.sumOrZero(token) }) { override val requiredCommands: Set> = setOf(Commands.Issue::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -140,7 +141,7 @@ class CommercialPaper : Contract { class Move : Clause>() { override val requiredCommands: Set> = setOf(Commands.Move::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -160,7 +161,7 @@ class CommercialPaper : Contract { class Redeem : Clause>() { override val requiredCommands: Set> = setOf(Commands.Redeem::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -171,7 +172,7 @@ class CommercialPaper : Contract { val timeWindow = tx.timeWindow val input = inputs.single() - val received = tx.outputs.sumCashBy(input.owner) + val received = tx.outputs.map { it.data }.sumCashBy(input.owner) val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window") requireThat { "the paper must have matured" using (time >= input.maturityDate) diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt index e263e1077c..5648959ac6 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.testing.NULL_PARTY import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.VaultService +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.internal.Emoji import java.time.Instant @@ -54,7 +55,7 @@ class CommercialPaperLegacy : Contract { class Issue : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. val groups = tx.groupStates(State::withoutOwner) @@ -80,7 +81,7 @@ class CommercialPaperLegacy : Contract { is Commands.Redeem -> { // Redemption of the paper requires movement of on-ledger cash. val input = inputs.single() - val received = tx.outputs.sumCashBy(input.owner) + val received = tx.outputs.map { it.data }.sumCashBy(input.owner) val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window") requireThat { "the paper must have matured" using (time >= input.maturityDate) diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt index 1e47041843..9b0ca94da2 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt @@ -19,6 +19,7 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.internal.Emoji import net.corda.schemas.CashSchemaV1 @@ -72,7 +73,7 @@ class Cash : OnLedgerAsset() { ConserveAmount()) ) ) { - override fun groupStates(tx: TransactionForContract): List>> + override fun groupStates(tx: LedgerTransaction): List>> = tx.groupStates> { it.amount.token } } @@ -173,7 +174,7 @@ class Cash : OnLedgerAsset() { override fun generateIssueCommand() = Commands.Issue() override fun generateMoveCommand() = Commands.Move() - override fun verify(tx: TransactionForContract) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), extractCommands(tx.commands)) } diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/CommodityContract.kt b/finance/src/main/kotlin/net/corda/contracts/asset/CommodityContract.kt index efa22e2264..e6697f2564 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/CommodityContract.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/CommodityContract.kt @@ -13,6 +13,7 @@ import net.corda.core.crypto.newSecureRandom import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import java.util.* @@ -69,7 +70,7 @@ class CommodityContract : OnLedgerAsset> { it.amount.token } } @@ -137,7 +138,7 @@ class CommodityContract : OnLedgerAsset>) : Commands, FungibleAsset.Commands.Exit } - override fun verify(tx: TransactionForContract) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), extractCommands(tx.commands)) override fun extractCommands(commands: Collection>): List> diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt index f07f06ffea..b0b06973bb 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt @@ -15,6 +15,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.internal.Emoji import net.corda.core.utilities.NonEmptySet @@ -74,7 +75,7 @@ class Obligation

: Contract { ) ) ) { - override fun groupStates(tx: TransactionForContract): List, Issued>>> + override fun groupStates(tx: LedgerTransaction): List, Issued>>> = tx.groupStates, Issued>> { it.amount.token } } @@ -97,7 +98,7 @@ class Obligation

: Contract { val lifecycleClause = Clauses.VerifyLifecycle() override fun toString(): String = "Net obligations" - override fun verify(tx: TransactionForContract, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set { + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set { lifecycleClause.verify(tx, inputs, outputs, commands, groupingKey) return super.verify(tx, inputs, outputs, commands, groupingKey) } @@ -109,7 +110,7 @@ class Obligation

: Contract { class SetLifecycle

: Clause, Commands, Issued>>() { override val requiredCommands: Set> = setOf(Commands.SetLifecycle::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List>, outputs: List>, commands: List>, @@ -128,7 +129,7 @@ class Obligation

: Contract { */ class Settle

: Clause, Commands, Issued>>() { override val requiredCommands: Set> = setOf(Commands.Settle::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List>, outputs: List>, commands: List>, @@ -158,7 +159,7 @@ class Obligation

: Contract { // Move (signed by B) // // That would pass this check. Ensuring they do not is best addressed in the transaction generation stage. - val assetStates = tx.outputs.filterIsInstance>() + val assetStates = tx.outputs.map { it.data }.filterIsInstance>() val acceptableAssetStates = assetStates // TODO: This filter is nonsense, because it just checks there is an asset contract loaded, we need to // verify the asset contract is the asset contract we expect. @@ -216,7 +217,7 @@ class Obligation

: Contract { * non-standard lifecycle states on input/output. */ class VerifyLifecycle : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -385,7 +386,7 @@ class Obligation

: Contract { data class Exit

(override val amount: Amount>>) : Commands, FungibleAsset.Commands.Exit> } - override fun verify(tx: TransactionForContract) = verifyClause(tx, FirstOf( + override fun verify(tx: LedgerTransaction) = verifyClause(tx, FirstOf( Clauses.Net(), Clauses.Group

() ), tx.commands.select()) @@ -396,7 +397,7 @@ class Obligation

: Contract { @VisibleForTesting private fun verifySetLifecycleCommand(inputs: List>>, outputs: List>>, - tx: TransactionForContract, + tx: LedgerTransaction, setLifecycleCommand: AuthenticatedObject) { // Default must not change anything except lifecycle, so number of inputs and outputs must match // exactly. diff --git a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt index f2fa484632..0dd239b484 100644 --- a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt +++ b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractConserveAmount.kt @@ -4,6 +4,7 @@ import net.corda.contracts.asset.OnLedgerAsset import net.corda.core.contracts.* import net.corda.core.contracts.clauses.Clause import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.loggerFor import java.security.PublicKey @@ -37,7 +38,7 @@ abstract class AbstractConserveAmount, C : CommandData, T : generateExitCommand: (Amount>) -> CommandData): Set = OnLedgerAsset.generateExit(tx, amountIssued, assetStates, deriveState, generateMoveCommand, generateExitCommand) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractIssue.kt b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractIssue.kt index b437daa907..ccf37384b0 100644 --- a/finance/src/main/kotlin/net/corda/contracts/clause/AbstractIssue.kt +++ b/finance/src/main/kotlin/net/corda/contracts/clause/AbstractIssue.kt @@ -2,6 +2,7 @@ package net.corda.contracts.clause import net.corda.core.contracts.* import net.corda.core.contracts.clauses.Clause +import net.corda.core.transactions.LedgerTransaction /** * Standard issue clause for contracts that issue fungible assets. @@ -17,7 +18,7 @@ abstract class AbstractIssue( val sum: List.() -> Amount>, val sumOrZero: List.(token: Issued) -> Amount> ) : Clause>() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/finance/src/main/kotlin/net/corda/contracts/clause/Net.kt b/finance/src/main/kotlin/net/corda/contracts/clause/Net.kt index 5c791b1a84..fedf3b6b4f 100644 --- a/finance/src/main/kotlin/net/corda/contracts/clause/Net.kt +++ b/finance/src/main/kotlin/net/corda/contracts/clause/Net.kt @@ -9,6 +9,7 @@ import net.corda.contracts.asset.sumAmountsDue import net.corda.core.contracts.* import net.corda.core.contracts.clauses.Clause import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction import java.security.PublicKey /** @@ -49,7 +50,7 @@ open class NetClause : Clause( override val requiredCommands: Set> = setOf(Obligation.Commands.Net::class.java) @Suppress("ConvertLambdaToReference") - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/finance/src/main/kotlin/net/corda/contracts/clause/NoZeroSizedOutputs.kt b/finance/src/main/kotlin/net/corda/contracts/clause/NoZeroSizedOutputs.kt index 3987ce4ec5..9fcafebe3a 100644 --- a/finance/src/main/kotlin/net/corda/contracts/clause/NoZeroSizedOutputs.kt +++ b/finance/src/main/kotlin/net/corda/contracts/clause/NoZeroSizedOutputs.kt @@ -2,13 +2,14 @@ package net.corda.contracts.clause import net.corda.core.contracts.* import net.corda.core.contracts.clauses.Clause +import net.corda.core.transactions.LedgerTransaction /** * Clause for fungible asset contracts, which enforces that no output state should have * a balance of zero. */ open class NoZeroSizedOutputs, C : CommandData, T : Any> : Clause>() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt index c30e73135d..e01f13618f 100644 --- a/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/contracts/DummyFungibleContract.kt @@ -8,13 +8,16 @@ import net.corda.core.contracts.clauses.AllOf import net.corda.core.contracts.clauses.FirstOf import net.corda.core.contracts.clauses.GroupClauseVerifier import net.corda.core.contracts.clauses.verifyClause -import net.corda.core.crypto.* +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.newSecureRandom +import net.corda.core.crypto.toBase58String import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.internal.Emoji import net.corda.schemas.SampleCashSchemaV1 @@ -36,7 +39,7 @@ class DummyFungibleContract : OnLedgerAsset>> + override fun groupStates(tx: LedgerTransaction): List>> = tx.groupStates> { it.amount.token } } @@ -126,7 +129,7 @@ class DummyFungibleContract : OnLedgerAsset().single() + override fun verify(tx: LedgerTransaction) { + val state = tx.outputs.map { it.data }.filterIsInstance().single() val attachment = tx.attachments.single() require(state.hash == attachment.id) } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt index bc445b4077..093870ce1f 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt @@ -1,9 +1,7 @@ package net.corda.irs.contract -import net.corda.contracts.* -import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.annotation.JsonProperty +import net.corda.contracts.* import net.corda.core.contracts.* import net.corda.core.contracts.clauses.* import net.corda.core.crypto.SecureHash @@ -13,6 +11,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.ServiceType import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.irs.api.NodeInterestRates import net.corda.irs.flows.FixingFlow @@ -461,7 +460,7 @@ class InterestRateSwap : Contract { fixingCalendar, index, indexSource, indexTenor) } - override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindow(), Clauses.Group()), tx.commands.select()) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindow(), Clauses.Group()), tx.commands.select()) interface Clauses { /** @@ -512,13 +511,13 @@ class InterestRateSwap : Contract { class Group : GroupClauseVerifier(AnyOf(Agree(), Fix(), Pay(), Mature())) { // Group by Trade ID for in / out states - override fun groupStates(tx: TransactionForContract): List> { + override fun groupStates(tx: LedgerTransaction): List> { return tx.groupStates { state -> state.linearId } } } class TimeWindow : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -532,7 +531,7 @@ class InterestRateSwap : Contract { class Agree : AbstractIRSClause() { override val requiredCommands: Set> = setOf(Commands.Agree::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -568,7 +567,7 @@ class InterestRateSwap : Contract { class Fix : AbstractIRSClause() { override val requiredCommands: Set> = setOf(Commands.Refix::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -613,7 +612,7 @@ class InterestRateSwap : Contract { class Pay : AbstractIRSClause() { override val requiredCommands: Set> = setOf(Commands.Pay::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -629,7 +628,7 @@ class InterestRateSwap : Contract { class Mature : AbstractIRSClause() { override val requiredCommands: Set> = setOf(Commands.Mature::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt index 488a9dd4a1..721bf0a3f4 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt @@ -3,13 +3,14 @@ package net.corda.vega.contracts import net.corda.core.contracts.* import net.corda.core.contracts.clauses.* import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction import java.math.BigDecimal /** * Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade. */ data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract { - override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select()) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select()) interface Commands : CommandData { class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade @@ -17,7 +18,7 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash. interface Clauses { class TimeWindowed : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -29,13 +30,13 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash. } class Group : GroupClauseVerifier(AnyOf(Agree())) { - override fun groupStates(tx: TransactionForContract): List> + override fun groupStates(tx: LedgerTransaction): List> // Group by Trade ID for in / out states = tx.groupStates { state -> state.linearId } } class Agree : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt index c946500748..72811505cb 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt @@ -3,6 +3,7 @@ package net.corda.vega.contracts import net.corda.core.contracts.* import net.corda.core.contracts.clauses.* import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction /** * Specifies the contract between two parties that are agreeing to a portfolio of trades and valuating that portfolio. @@ -10,7 +11,7 @@ import net.corda.core.crypto.SecureHash * of the portfolio arbitrarily. */ data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract { - override fun verify(tx: TransactionForContract) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select()) + override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select()) interface Commands : CommandData { class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio @@ -19,7 +20,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur interface Clauses { class TimeWindowed : Clause() { - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -31,7 +32,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur } class Group : GroupClauseVerifier(FirstOf(Agree(), Update())) { - override fun groupStates(tx: TransactionForContract): List> + override fun groupStates(tx: LedgerTransaction): List> // Group by Trade ID for in / out states = tx.groupStates { state -> state.linearId } } @@ -39,7 +40,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur class Update : Clause() { override val requiredCommands: Set> = setOf(Commands.Update::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, @@ -60,7 +61,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur class Agree : Clause() { override val requiredCommands: Set> = setOf(Commands.Agree::class.java) - override fun verify(tx: TransactionForContract, + override fun verify(tx: LedgerTransaction, inputs: List, outputs: List, commands: List>, diff --git a/test-utils/src/main/kotlin/net/corda/testing/AlwaysSucceedContract.kt b/test-utils/src/main/kotlin/net/corda/testing/AlwaysSucceedContract.kt index 77f5a358fc..ff26ed9ae8 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/AlwaysSucceedContract.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/AlwaysSucceedContract.kt @@ -1,10 +1,10 @@ package net.corda.testing import net.corda.core.contracts.Contract -import net.corda.core.contracts.TransactionForContract import net.corda.core.crypto.SecureHash +import net.corda.core.transactions.LedgerTransaction class AlwaysSucceedContract(override val legalContractReference: SecureHash = SecureHash.sha256("Always succeed contract")) : Contract { - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { } } diff --git a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt index e7a96d9a0b..6f01513f23 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt @@ -4,6 +4,7 @@ import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder // The dummy contract doesn't do anything useful. It exists for testing purposes. @@ -39,7 +40,7 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur class Move : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForContract) { + override fun verify(tx: LedgerTransaction) { // Always accepts. } diff --git a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt index f71c8dfedb..483643e186 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt @@ -2,9 +2,10 @@ package net.corda.testing.contracts import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash -import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.WireTransaction import net.corda.core.flows.ContractUpgradeFlow +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.WireTransaction // The dummy contract doesn't do anything useful. It exists for testing purposes. val DUMMY_V2_PROGRAM_ID = DummyContractV2() @@ -30,7 +31,7 @@ class DummyContractV2 : UpgradedContract = LinearState.ClauseVerifier() - override fun verify(tx: TransactionForContract) = verifyClause(tx, + override fun verify(tx: LedgerTransaction) = verifyClause(tx, FilterOn(clause, { states -> states.filterIsInstance() }), emptyList()) From bf98f64269b43e73540e22c7f63c2b3ec55f8d2e Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 20 Jul 2017 11:04:09 +0100 Subject: [PATCH 057/197] Patch DemoBench for JavaFX bugs when building installers. (#1069) * Replace buggy classes in rt.jar with patched versions. * Apply Java bugfixes to MacOSX. * Initial support for patching Windows JRE. * Resign DemoBench DMG after the bugfixe source has been deleted. * Apply Java bugfixes for DemoBench on Windows. --- tools/demobench/build.gradle | 4 + tools/demobench/package-demobench-exe.bat | 4 +- tools/demobench/package/bugfixes/apply.bat | 39 ++ tools/demobench/package/bugfixes/apply.sh | 32 ++ .../java/sun/swing/JLightweightFrame.java | 515 ++++++++++++++++++ .../package/linux/CordaDemoBench.spec | 8 + .../macosx/Corda DemoBench-post-image.sh | 19 +- .../windows/Corda DemoBench-post-image.wsf | 22 +- 8 files changed, 634 insertions(+), 9 deletions(-) create mode 100644 tools/demobench/package/bugfixes/apply.bat create mode 100755 tools/demobench/package/bugfixes/apply.sh create mode 100644 tools/demobench/package/bugfixes/java/sun/swing/JLightweightFrame.java diff --git a/tools/demobench/build.gradle b/tools/demobench/build.gradle index 8d71db2137..1eb6b3a8d8 100644 --- a/tools/demobench/build.gradle +++ b/tools/demobench/build.gradle @@ -189,6 +189,10 @@ task javapackage(dependsOn: distZip) { include(name: 'plugins/*.jar') include(name: 'explorer/*.jar') } + + fileset(dir: "$pkg_source/package", type: 'data') { + include(name: "bugfixes/**") + } } // This is specific to MacOSX packager. diff --git a/tools/demobench/package-demobench-exe.bat b/tools/demobench/package-demobench-exe.bat index e33d048e5d..c2f0b41e80 100644 --- a/tools/demobench/package-demobench-exe.bat +++ b/tools/demobench/package-demobench-exe.bat @@ -8,8 +8,8 @@ if not defined JAVA_HOME goto NoJavaHome set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. -call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage -if "%ERRORLEVEL%" neq "0" goto Fail +call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage %* +if ERRORLEVEL 1 goto Fail @echo @echo Wrote installer to %DIRNAME%\build\javapackage\bundles\ @echo diff --git a/tools/demobench/package/bugfixes/apply.bat b/tools/demobench/package/bugfixes/apply.bat new file mode 100644 index 0000000000..f770222122 --- /dev/null +++ b/tools/demobench/package/bugfixes/apply.bat @@ -0,0 +1,39 @@ +@echo off + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. + +set SOURCEDIR=%DIRNAME%java +set BUILDDIR=%DIRNAME%build + +if '%1' == '' ( + @echo Need location of rt.jar + exit /b 1 +) +if not "%~nx1" == "rt.jar" ( + @echo File '%1' is not rt.jar + exit /b 1 +) +if not exist %1 ( + @echo %1 not found. + exit /b 1 +) + +if exist "%BUILDDIR%" rmdir /s /q "%BUILDDIR%" +mkdir "%BUILDDIR%" + +for /r "%SOURCEDIR%" %%j in (*.java) do ( + javac -O -d "%BUILDDIR%" "%%j" + if ERRORLEVEL 1 ( + @echo "Failed to compile %%j" + exit /b 1 + ) +) + +jar uvf %1 -C "%BUILDDIR%" . +if ERRORLEVEL 1 ( + @echo "Failed to update %1" + exit /b 1 +) + +@echo "Completed" diff --git a/tools/demobench/package/bugfixes/apply.sh b/tools/demobench/package/bugfixes/apply.sh new file mode 100755 index 0000000000..560f83abc7 --- /dev/null +++ b/tools/demobench/package/bugfixes/apply.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +BASEDIR=$(dirname $0) +SOURCEDIR=$BASEDIR/java +BUILDDIR=$BASEDIR/build +RTJAR=$1 + +if [ -z "$RTJAR" ]; then + echo "Need location of rt.jar" + exit 1 +elif [ $(basename $RTJAR) != "rt.jar" ]; then + echo "File is not rt.jar" + exit 1 +elif [ ! -f $RTJAR ]; then + echo "$RTJAR not found" + exit 1 +fi + +# Bugfixes: +# ========= +# +# sun.swing.JLightweightFrame:473 +# https://github.com/JetBrains/jdk8u_jdk/issues/6 +# https://github.com/JetBrains/jdk8u/issues/8 + +rm -rf $BUILDDIR && mkdir $BUILDDIR +if (javac -O -d $BUILDDIR $(find $SOURCEDIR -name "*.java")); then + chmod u+w $RTJAR + jar uvf $RTJAR -C $BUILDDIR . + chmod ugo-w $RTJAR +fi + diff --git a/tools/demobench/package/bugfixes/java/sun/swing/JLightweightFrame.java b/tools/demobench/package/bugfixes/java/sun/swing/JLightweightFrame.java new file mode 100644 index 0000000000..6f39eb5d62 --- /dev/null +++ b/tools/demobench/package/bugfixes/java/sun/swing/JLightweightFrame.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.swing; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.PointerInfo; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragGestureRecognizer; +import java.awt.dnd.DragSource; +import java.awt.dnd.DropTarget; +import java.awt.dnd.InvalidDnDOperationException; +import java.awt.dnd.peer.DragSourceContextPeer; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.security.AccessController; +import java.util.logging.Logger; +import javax.swing.JComponent; + +import javax.swing.JLayeredPane; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.LayoutFocusTraversalPolicy; +import javax.swing.RepaintManager; +import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; + +import sun.awt.DisplayChangedListener; +import sun.awt.LightweightFrame; +import sun.security.action.GetPropertyAction; +import sun.swing.SwingUtilities2.RepaintListener; + +/** + * The frame serves as a lightweight container which paints its content + * to an offscreen image and provides access to the image's data via the + * {@link LightweightContent} interface. Note, that it may not be shown + * as a standalone toplevel frame. Its purpose is to provide functionality + * for lightweight embedding. + * + * @author Artem Ananiev + * @author Anton Tarasov + */ +public final class JLightweightFrame extends LightweightFrame implements RootPaneContainer { + + private final JRootPane rootPane = new JRootPane(); + + private LightweightContent content; + + private Component component; + private JPanel contentPane; + + private BufferedImage bbImage; + + private volatile int scaleFactor = 1; + + /** + * {@code copyBufferEnabled}, true by default, defines the following strategy. + * A duplicating (copy) buffer is created for the original pixel buffer. + * The copy buffer is synchronized with the original buffer every time the + * latter changes. {@code JLightweightFrame} passes the copy buffer array + * to the {@link LightweightContent#imageBufferReset} method. The code spot + * which synchronizes two buffers becomes the only critical section guarded + * by the lock (managed with the {@link LightweightContent#paintLock()}, + * {@link LightweightContent#paintUnlock()} methods). + */ + private static boolean copyBufferEnabled; + private int[] copyBuffer; + + private PropertyChangeListener layoutSizeListener; + private RepaintListener repaintListener; + + static { + SwingAccessor.setJLightweightFrameAccessor(new SwingAccessor.JLightweightFrameAccessor() { + @Override + public void updateCursor(JLightweightFrame frame) { + frame.updateClientCursor(); + } + }); + copyBufferEnabled = "true".equals(AccessController. + doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true"))); + } + + /** + * Constructs a new, initially invisible {@code JLightweightFrame} + * instance. + */ + public JLightweightFrame() { + super(); + copyBufferEnabled = "true".equals(AccessController. + doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true"))); + + add(rootPane, BorderLayout.CENTER); + setFocusTraversalPolicy(new LayoutFocusTraversalPolicy()); + if (getGraphicsConfiguration().isTranslucencyCapable()) { + setBackground(new Color(0, 0, 0, 0)); + } + + layoutSizeListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent e) { + Dimension d = (Dimension)e.getNewValue(); + + if ("preferredSize".equals(e.getPropertyName())) { + content.preferredSizeChanged(d.width, d.height); + + } else if ("maximumSize".equals(e.getPropertyName())) { + content.maximumSizeChanged(d.width, d.height); + + } else if ("minimumSize".equals(e.getPropertyName())) { + content.minimumSizeChanged(d.width, d.height); + } + } + }; + + repaintListener = (JComponent c, int x, int y, int w, int h) -> { + Window jlf = SwingUtilities.getWindowAncestor(c); + if (jlf != JLightweightFrame.this) { + return; + } + Point p = SwingUtilities.convertPoint(c, x, y, jlf); + Rectangle r = new Rectangle(p.x, p.y, w, h).intersection( + new Rectangle(0, 0, bbImage.getWidth() / scaleFactor, + bbImage.getHeight() / scaleFactor)); + + if (!r.isEmpty()) { + notifyImageUpdated(r.x, r.y, r.width, r.height); + } + }; + + SwingAccessor.getRepaintManagerAccessor().addRepaintListener( + RepaintManager.currentManager(this), repaintListener); + } + + @Override + public void dispose() { + SwingAccessor.getRepaintManagerAccessor().removeRepaintListener( + RepaintManager.currentManager(this), repaintListener); + super.dispose(); + } + + /** + * Sets the {@link LightweightContent} instance for this frame. + * The {@code JComponent} object returned by the + * {@link LightweightContent#getComponent()} method is immediately + * added to the frame's content pane. + * + * @param content the {@link LightweightContent} instance + */ + public void setContent(final LightweightContent content) { + if (content == null) { + System.err.println("JLightweightFrame.setContent: content may not be null!"); + return; + } + this.content = content; + this.component = content.getComponent(); + + Dimension d = this.component.getPreferredSize(); + content.preferredSizeChanged(d.width, d.height); + + d = this.component.getMaximumSize(); + content.maximumSizeChanged(d.width, d.height); + + d = this.component.getMinimumSize(); + content.minimumSizeChanged(d.width, d.height); + + initInterior(); + } + + @Override + public Graphics getGraphics() { + if (bbImage == null) return null; + + Graphics2D g = bbImage.createGraphics(); + g.setBackground(getBackground()); + g.setColor(getForeground()); + g.setFont(getFont()); + g.scale(scaleFactor, scaleFactor); + return g; + } + + /** + * {@inheritDoc} + * + * @see LightweightContent#focusGrabbed() + */ + @Override + public void grabFocus() { + if (content != null) content.focusGrabbed(); + } + + /** + * {@inheritDoc} + * + * @see LightweightContent#focusUngrabbed() + */ + @Override + public void ungrabFocus() { + if (content != null) content.focusUngrabbed(); + } + + @Override + public int getScaleFactor() { + return scaleFactor; + } + + @Override + public void notifyDisplayChanged(final int scaleFactor) { + if (scaleFactor != this.scaleFactor) { + if (!copyBufferEnabled) content.paintLock(); + try { + if (bbImage != null) { + resizeBuffer(getWidth(), getHeight(), scaleFactor); + } + } finally { + if (!copyBufferEnabled) content.paintUnlock(); + } + this.scaleFactor = scaleFactor; + } + if (getPeer() instanceof DisplayChangedListener) { + ((DisplayChangedListener)getPeer()).displayChanged(); + } + repaint(); + } + + @Override + public void addNotify() { + super.addNotify(); + if (getPeer() instanceof DisplayChangedListener) { + ((DisplayChangedListener)getPeer()).displayChanged(); + } + } + + private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, int scale) { + content.paintLock(); + try { + int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); + if (reset) { + copyBuffer = new int[srcBuffer.length]; + } + int linestride = bbImage.getWidth(); + + x *= scale; + y *= scale; + w *= scale; + h *= scale; + + for (int i=0; i= newW) && (oldH >= newH)) { + createBB = false; + } else { + if (oldW >= newW) { + newW = oldW; + } else { + newW = Math.max((int)(oldW * 1.2), width); + } + if (oldH >= newH) { + newH = oldH; + } else { + newH = Math.max((int)(oldH * 1.2), height); + } + } + } + } + } + if (createBB) { + resizeBuffer(newW, newH, scaleFactor); + return; + } + content.imageReshaped(0, 0, width, height); + + } finally { + if (!copyBufferEnabled) { + content.paintUnlock(); + } + } + } + + private void resizeBuffer(int width, int height, int newScaleFactor) { + bbImage = new BufferedImage(width*newScaleFactor,height*newScaleFactor, + BufferedImage.TYPE_INT_ARGB_PRE); + int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); + if (copyBufferEnabled) { + syncCopyBuffer(true, 0, 0, width, height, newScaleFactor); + pixels = copyBuffer; + } + content.imageBufferReset(pixels, 0, 0, width, height, + width * newScaleFactor, newScaleFactor); + } + + @Override + public JRootPane getRootPane() { + return rootPane; + } + + @Override + public void setContentPane(Container contentPane) { + getRootPane().setContentPane(contentPane); + } + + @Override + public Container getContentPane() { + return getRootPane().getContentPane(); + } + + @Override + public void setLayeredPane(JLayeredPane layeredPane) { + getRootPane().setLayeredPane(layeredPane); + } + + @Override + public JLayeredPane getLayeredPane() { + return getRootPane().getLayeredPane(); + } + + @Override + public void setGlassPane(Component glassPane) { + getRootPane().setGlassPane(glassPane); + } + + @Override + public Component getGlassPane() { + return getRootPane().getGlassPane(); + } + + + /* + * Notifies client toolkit that it should change a cursor. + * + * Called from the peer via SwingAccessor, because the + * Component.updateCursorImmediately method is final + * and could not be overridden. + */ + private void updateClientCursor() { + PointerInfo pointerInfo = MouseInfo.getPointerInfo(); + /* + * BUGFIX not yet applied upstream! + */ + if (pointerInfo == null) { + Logger log = Logger.getLogger(getClass().getName()); + log.warning("BUGFIX - NPE avoided"); + return; + } + Point p = pointerInfo.getLocation(); + SwingUtilities.convertPointFromScreen(p, this); + Component target = SwingUtilities.getDeepestComponentAt(this, p.x, p.y); + if (target != null) { + content.setCursor(target.getCursor()); + } + } + + public T createDragGestureRecognizer( + Class abstractRecognizerClass, + DragSource ds, Component c, int srcActions, + DragGestureListener dgl) + { + return content == null ? null : content.createDragGestureRecognizer( + abstractRecognizerClass, ds, c, srcActions, dgl); + } + + public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { + return content == null ? null : content.createDragSourceContextPeer(dge); + } + + public void addDropTarget(DropTarget dt) { + if (content == null) return; + content.addDropTarget(dt); + } + + public void removeDropTarget(DropTarget dt) { + if (content == null) return; + content.removeDropTarget(dt); + } +} + diff --git a/tools/demobench/package/linux/CordaDemoBench.spec b/tools/demobench/package/linux/CordaDemoBench.spec index d000f25216..b3d6c3f0e3 100644 --- a/tools/demobench/package/linux/CordaDemoBench.spec +++ b/tools/demobench/package/linux/CordaDemoBench.spec @@ -20,6 +20,8 @@ Autoreq: 0 %define __jar_repack %{nil} %define _javaHome %{getenv:JAVA_HOME} +%define _bugfixdir %{_sourcedir}/CordaDemoBench/app/bugfixes +%define _rtJar %{_sourcedir}/CordaDemoBench/runtime/lib/rt.jar %description Corda DemoBench @@ -29,6 +31,12 @@ Corda DemoBench %build %install +# Apply bugfixes to installed rt.jar +if [ -f %{_bugfixdir}/apply.sh ]; then + chmod ugo+x %{_bugfixdir}/apply.sh + %{_bugfixdir}/apply.sh %{_rtJar} + rm -rf %{_bugfixdir} +fi rm -rf %{buildroot} mkdir -p %{buildroot}/opt cp -r %{_sourcedir}/CordaDemoBench %{buildroot}/opt diff --git a/tools/demobench/package/macosx/Corda DemoBench-post-image.sh b/tools/demobench/package/macosx/Corda DemoBench-post-image.sh index 2d1c42fd2f..43a94041f4 100644 --- a/tools/demobench/package/macosx/Corda DemoBench-post-image.sh +++ b/tools/demobench/package/macosx/Corda DemoBench-post-image.sh @@ -13,14 +13,27 @@ function signApplication() { echo "**** Failed to re-sign the embedded JVM" return 1 fi + + # Resign the application because we've deleted the bugfixes directory. + if ! (codesign --force --sign "$IDENTITY" --preserve-metadata=identifier,entitlements,requirements --verbose "$APPDIR"); then + echo "*** Failed to resign DemoBench application" + return 1 + fi } # Switch to folder containing application. cd ../images/image-*/Corda\ DemoBench.app -INSTALL_HOME=Contents/PlugIns/Java.runtime/Contents/Home/jre/bin -if (mkdir -p $INSTALL_HOME); then - cp $JAVA_HOME/bin/java $INSTALL_HOME +JRE_HOME=Contents/PlugIns/Java.runtime/Contents/Home/jre +if (mkdir -p $JRE_HOME/bin); then + cp $JAVA_HOME/bin/java $JRE_HOME/bin +fi + +BUGFIX_HOME=Contents/Java/bugfixes +if [ -f $BUGFIX_HOME/apply.sh ]; then + chmod ugo+x $BUGFIX_HOME/apply.sh + $BUGFIX_HOME/apply.sh $JRE_HOME/lib/rt.jar + rm -rf $BUGFIX_HOME fi # Switch to image directory in order to sign it. diff --git a/tools/demobench/package/windows/Corda DemoBench-post-image.wsf b/tools/demobench/package/windows/Corda DemoBench-post-image.wsf index 6d54bde85f..54302824e1 100644 --- a/tools/demobench/package/windows/Corda DemoBench-post-image.wsf +++ b/tools/demobench/package/windows/Corda DemoBench-post-image.wsf @@ -4,13 +4,15 @@ From e702025f62846ea0bf25e711b36af42dd711aa9b Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 20 Jul 2017 13:32:53 +0100 Subject: [PATCH 058/197] Cleaned up TimeWindow and added a bit more docs. --- .gitignore | 2 +- core/src/main/kotlin/net/corda/core/Utils.kt | 4 - .../net/corda/core/contracts/Structures.kt | 58 ------------- .../net/corda/core/contracts/TimeWindow.kt | 87 +++++++++++++++++++ .../net/corda/core/internal/InternalUtils.kt | 7 ++ .../core/node/services/TimeWindowChecker.kt | 13 +-- .../corda/core/contracts/TimeWindowTest.kt | 67 ++++++++++++++ .../node/services/TimeWindowCheckerTests.kt | 4 +- .../docs/WorkflowTransactionBuildTutorial.kt | 2 +- .../services/messaging/P2PMessagingTest.kt | 6 +- .../net/corda/node/utilities/ClockUtils.kt | 4 +- .../net/corda/node/utilities/TestClock.kt | 9 +- .../main/kotlin/net/corda/irs/contract/IRS.kt | 5 +- .../net/corda/vega/contracts/OGTrade.kt | 4 +- .../net/corda/vega/contracts/PortfolioSwap.kt | 4 +- .../kotlin/net/corda/testing/driver/Driver.kt | 3 +- .../net/corda/testing/node/TestClock.kt | 4 +- .../net/corda/demobench/web/WebServer.kt | 2 +- 18 files changed, 189 insertions(+), 96 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt create mode 100644 core/src/test/kotlin/net/corda/core/contracts/TimeWindowTest.kt diff --git a/.gitignore b/.gitignore index 37fb7b0c14..ded13230cd 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,7 @@ lib/dokka.jar ## Plugin-specific files: # IntelliJ -/out/ +**/out/ /classes/ # mpeltonen/sbt-idea plugin diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 4654d4cd4d..94ef3ac8f5 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -21,7 +21,6 @@ import java.math.BigDecimal import java.nio.file.Files import java.nio.file.Path import java.time.Duration -import java.time.temporal.Temporal import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.Future @@ -95,9 +94,6 @@ fun ListenableFuture.toObservable(): Observable { } } -// Simple infix function to add back null safety that the JDK lacks: timeA until timeB -infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) - /** Returns the index of the given item or throws [IllegalArgumentException] if not found. */ fun List.indexOfOrThrow(item: T): Int { val i = indexOf(item) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index b4801a4efa..d18e33ec80 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -17,7 +17,6 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.security.PublicKey -import java.time.Duration import java.time.Instant import java.util.jar.JarInputStream @@ -328,63 +327,6 @@ data class AuthenticatedObject( ) // DOCEND 6 -/** - * A time-window is required for validation/notarization purposes. - * If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be - * between (fromTime, untilTime). - * Usually, a time-window is required to have both sides set (fromTime, untilTime). - * However, some apps may require that a time-window has a start [Instant] (fromTime), but no end [Instant] (untilTime) and vice versa. - * TODO: Consider refactoring using TimeWindow abstraction like TimeWindow.From, TimeWindow.Until, TimeWindow.Between. - */ -@CordaSerializable -class TimeWindow private constructor( - /** The time at which this transaction is said to have occurred is after this moment. */ - val fromTime: Instant?, - /** The time at which this transaction is said to have occurred is before this moment. */ - val untilTime: Instant? -) { - companion object { - /** Use when the left-side [fromTime] of a [TimeWindow] is only required and we don't need an end instant (untilTime). */ - @JvmStatic - fun fromOnly(fromTime: Instant) = TimeWindow(fromTime, null) - - /** Use when the right-side [untilTime] of a [TimeWindow] is only required and we don't need a start instant (fromTime). */ - @JvmStatic - fun untilOnly(untilTime: Instant) = TimeWindow(null, untilTime) - - /** Use when both sides of a [TimeWindow] must be set ([fromTime], [untilTime]). */ - @JvmStatic - fun between(fromTime: Instant, untilTime: Instant): TimeWindow { - require(fromTime < untilTime) { "fromTime should be earlier than untilTime" } - return TimeWindow(fromTime, untilTime) - } - - /** Use when we have a start time and a period of validity. */ - @JvmStatic - fun fromStartAndDuration(fromTime: Instant, duration: Duration): TimeWindow = between(fromTime, fromTime + duration) - - /** - * When we need to create a [TimeWindow] based on a specific time [Instant] and some tolerance in both sides of this instant. - * The result will be the following time-window: ([time] - [tolerance], [time] + [tolerance]). - */ - @JvmStatic - fun withTolerance(time: Instant, tolerance: Duration) = between(time - tolerance, time + tolerance) - } - - /** The midpoint is calculated as fromTime + (untilTime - fromTime)/2. Note that it can only be computed if both sides are set. */ - val midpoint: Instant get() = fromTime!! + Duration.between(fromTime, untilTime!!).dividedBy(2) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is TimeWindow) return false - return (fromTime == other.fromTime && untilTime == other.untilTime) - } - - override fun hashCode() = 31 * (fromTime?.hashCode() ?: 0) + (untilTime?.hashCode() ?: 0) - - override fun toString() = "TimeWindow(fromTime=$fromTime, untilTime=$untilTime)" -} - // DOCSTART 5 /** * Implemented by a program that implements business logic on the shared ledger. All participants run this code for diff --git a/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt b/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt new file mode 100644 index 0000000000..960bd9f310 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt @@ -0,0 +1,87 @@ +package net.corda.core.contracts + +import net.corda.core.internal.div +import net.corda.core.internal.until +import net.corda.core.serialization.CordaSerializable +import java.time.Duration +import java.time.Instant + +/** + * A time-window is required for validation/notarization purposes. If present in a transaction, contains a time that was + * verified by the uniqueness service. The true time must be in the time interval `[fromTime, untilTime)`. + * + * Usually a time-window is required to have both sides defined. However some apps may require a time-window which is + * open-ended on one of the two sides. + */ +@CordaSerializable +abstract class TimeWindow { + companion object { + /** Creates a [TimeWindow] with null [untilTime], i.e. the time interval `[fromTime, ∞]`. [midpoint] will return null. */ + @JvmStatic + fun fromOnly(fromTime: Instant): TimeWindow = From(fromTime) + + /** Creates a [TimeWindow] with null [fromTime], i.e. the time interval `[∞, untilTime)`. [midpoint] will return null. */ + @JvmStatic + fun untilOnly(untilTime: Instant): TimeWindow = Until(untilTime) + + /** + * Creates a [TimeWindow] with the time interval `[fromTime, untilTime)`. [midpoint] will return + * `fromTime + (untilTime - fromTime) / 2`. + * @throws IllegalArgumentException If [fromTime] ≥ [untilTime] + */ + @JvmStatic + fun between(fromTime: Instant, untilTime: Instant): TimeWindow = Between(fromTime, untilTime) + + /** + * Creates a [TimeWindow] with the time interval `[fromTime, fromTime + duration)`. [midpoint] will return + * `fromTime + duration / 2` + */ + @JvmStatic + fun fromStartAndDuration(fromTime: Instant, duration: Duration): TimeWindow = between(fromTime, fromTime + duration) + + /** + * Creates a [TimeWindow] which is centered around [instant] with the given [tolerance] on both sides, i.e the + * time interval `[instant - tolerance, instant + tolerance)`. [midpoint] will return [instant]. + */ + @JvmStatic + fun withTolerance(instant: Instant, tolerance: Duration) = between(instant - tolerance, instant + tolerance) + } + + /** Returns the inclusive lower-bound of this [TimeWindow]'s interval, with null implying infinity. */ + abstract val fromTime: Instant? + + /** Returns the exclusive upper-bound of this [TimeWindow]'s interval, with null implying infinity. */ + abstract val untilTime: Instant? + + /** + * Returns the midpoint of [fromTime] and [untilTime] if both are non-null, calculated as + * `fromTime + (untilTime - fromTime)/2`, otherwise returns null. + */ + abstract val midpoint: Instant? + + /** Returns true iff the given [instant] is within the time interval of this [TimeWindow]. */ + abstract operator fun contains(instant: Instant): Boolean + + private data class From(override val fromTime: Instant) : TimeWindow() { + override val untilTime: Instant? get() = null + override val midpoint: Instant? get() = null + override fun contains(instant: Instant): Boolean = instant >= fromTime + override fun toString(): String = "[$fromTime, ∞]" + } + + private data class Until(override val untilTime: Instant) : TimeWindow() { + override val fromTime: Instant? get() = null + override val midpoint: Instant? get() = null + override fun contains(instant: Instant): Boolean = instant < untilTime + override fun toString(): String = "[∞, $untilTime)" + } + + private data class Between(override val fromTime: Instant, override val untilTime: Instant) : TimeWindow() { + init { + require(fromTime < untilTime) { "fromTime must be earlier than untilTime" } + } + override val midpoint: Instant get() = fromTime + (fromTime until untilTime) / 2 + override fun contains(instant: Instant): Boolean = instant >= fromTime && instant < untilTime + override fun toString(): String = "[$fromTime, $untilTime)" + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 3e14f250a5..44df2fbb6e 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -7,9 +7,16 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.* import java.nio.file.attribute.FileAttribute +import java.time.Duration +import java.time.temporal.Temporal import java.util.stream.Stream import kotlin.reflect.KClass +infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) + +operator fun Duration.div(divider: Long): Duration = dividedBy(divider) +operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multiplicand) + /** * Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform * separator problems. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt index 35296f06a6..fe1e95d96b 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TimeWindowChecker.kt @@ -7,16 +7,5 @@ import java.time.Clock * Checks if the current instant provided by the input clock falls within the provided time-window. */ class TimeWindowChecker(val clock: Clock = Clock.systemUTC()) { - fun isValid(timeWindow: TimeWindow): Boolean { - val fromTime = timeWindow.fromTime - val untilTime = timeWindow.untilTime - - val now = clock.instant() - - // We don't need to test for (fromTime == null && untilTime == null) or backwards bounds because the TimeWindow - // constructor already checks that. - if (fromTime != null && now < fromTime) return false - if (untilTime != null && now > untilTime) return false - return true - } + fun isValid(timeWindow: TimeWindow): Boolean = clock.instant() in timeWindow } diff --git a/core/src/test/kotlin/net/corda/core/contracts/TimeWindowTest.kt b/core/src/test/kotlin/net/corda/core/contracts/TimeWindowTest.kt new file mode 100644 index 0000000000..453b01eb65 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/contracts/TimeWindowTest.kt @@ -0,0 +1,67 @@ +package net.corda.core.contracts + +import net.corda.core.utilities.millis +import net.corda.core.utilities.minutes +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneOffset.UTC + +class TimeWindowTest { + private val now = Instant.now() + + @Test + fun fromOnly() { + val timeWindow = TimeWindow.fromOnly(now) + assertThat(timeWindow.fromTime).isEqualTo(now) + assertThat(timeWindow.untilTime).isNull() + assertThat(timeWindow.midpoint).isNull() + assertThat(timeWindow.contains(now - 1.millis)).isFalse() + assertThat(timeWindow.contains(now)).isTrue() + assertThat(timeWindow.contains(now + 1.millis)).isTrue() + } + + @Test + fun untilOnly() { + val timeWindow = TimeWindow.untilOnly(now) + assertThat(timeWindow.fromTime).isNull() + assertThat(timeWindow.untilTime).isEqualTo(now) + assertThat(timeWindow.midpoint).isNull() + assertThat(timeWindow.contains(now - 1.millis)).isTrue() + assertThat(timeWindow.contains(now)).isFalse() + assertThat(timeWindow.contains(now + 1.millis)).isFalse() + } + + @Test + fun between() { + val today = LocalDate.now() + val fromTime = today.atTime(12, 0).toInstant(UTC) + val untilTime = today.atTime(12, 30).toInstant(UTC) + val timeWindow = TimeWindow.between(fromTime, untilTime) + assertThat(timeWindow.fromTime).isEqualTo(fromTime) + assertThat(timeWindow.untilTime).isEqualTo(untilTime) + assertThat(timeWindow.midpoint).isEqualTo(today.atTime(12, 15).toInstant(UTC)) + assertThat(timeWindow.contains(fromTime - 1.millis)).isFalse() + assertThat(timeWindow.contains(fromTime)).isTrue() + assertThat(timeWindow.contains(fromTime + 1.millis)).isTrue() + assertThat(timeWindow.contains(untilTime)).isFalse() + assertThat(timeWindow.contains(untilTime + 1.millis)).isFalse() + } + + @Test + fun fromStartAndDuration() { + val timeWindow = TimeWindow.fromStartAndDuration(now, 10.minutes) + assertThat(timeWindow.fromTime).isEqualTo(now) + assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes) + assertThat(timeWindow.midpoint).isEqualTo(now + 5.minutes) + } + + @Test + fun withTolerance() { + val timeWindow = TimeWindow.withTolerance(now, 10.minutes) + assertThat(timeWindow.fromTime).isEqualTo(now - 10.minutes) + assertThat(timeWindow.untilTime).isEqualTo(now + 10.minutes) + assertThat(timeWindow.midpoint).isEqualTo(now) + } +} diff --git a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt index 0546ae22a8..61d37b5211 100644 --- a/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/services/TimeWindowCheckerTests.kt @@ -5,12 +5,12 @@ import net.corda.core.utilities.seconds import org.junit.Test import java.time.Clock import java.time.Instant -import java.time.ZoneId +import java.time.ZoneOffset import kotlin.test.assertFalse import kotlin.test.assertTrue class TimeWindowCheckerTests { - val clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) + val clock: Clock = Clock.fixed(Instant.now(), ZoneOffset.UTC) val timeWindowChecker = TimeWindowChecker(clock) @Test diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index 38168a6351..c80e7a52cf 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -72,7 +72,7 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash */ override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand() - require(tx.timeWindow?.midpoint != null) { "must have a time-window" } + requireNotNull(tx.timeWindow) { "must have a time-window" } when (command.value) { is Commands.Issue -> { requireThat { diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index d357b21cb0..7a11417de3 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -2,8 +2,10 @@ package net.corda.services.messaging import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import net.corda.core.* import net.corda.core.crypto.random63BitValue +import net.corda.core.elapsedTime +import net.corda.core.getOrThrow +import net.corda.core.internal.times import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.ServiceInfo @@ -43,7 +45,7 @@ class P2PMessagingTest : NodeBasedTest() { // Start the network map a second time - this will restore message queues from the journal. // This will hang and fail prior the fix. https://github.com/corda/corda/issues/37 stopAllNodes() - startNodes().getOrThrow(timeout = startUpDuration.multipliedBy(3)) + startNodes().getOrThrow(timeout = startUpDuration * 3) } // https://github.com/corda/corda/issues/71 diff --git a/node/src/main/kotlin/net/corda/node/utilities/ClockUtils.kt b/node/src/main/kotlin/net/corda/node/utilities/ClockUtils.kt index 3a36904c31..1a7fa8ae8a 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ClockUtils.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ClockUtils.kt @@ -3,12 +3,12 @@ package net.corda.node.utilities import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.SettableFuture import com.google.common.util.concurrent.ListenableFuture +import net.corda.core.internal.until import net.corda.core.then import rx.Observable import rx.Subscriber import rx.subscriptions.Subscriptions import java.time.Clock -import java.time.Duration import java.time.Instant import java.util.concurrent.* import java.util.concurrent.atomic.AtomicLong @@ -80,7 +80,7 @@ fun Clock.awaitWithDeadline(deadline: Instant, future: Future<*> = GuavaSettable } else { null } - nanos = Duration.between(this.instant(), deadline).toNanos() + nanos = (instant() until deadline).toNanos() if (nanos > 0) { try { // This will return when it times out, or when the clock mutates or when when the original future completes. diff --git a/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt b/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt index 3b0487d3f0..3d1c57d312 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt @@ -1,10 +1,13 @@ package net.corda.node.utilities +import net.corda.core.internal.until import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializationToken import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken -import java.time.* +import java.time.Clock +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId import javax.annotation.concurrent.ThreadSafe /** @@ -21,7 +24,7 @@ class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableC val currentDate = LocalDate.now(this) if (currentDate.isBefore(date)) { // It's ok to increment - delegateClock = Clock.offset(delegateClock, Duration.between(currentDate.atStartOfDay(), date.atStartOfDay())) + delegateClock = Clock.offset(delegateClock, currentDate.atStartOfDay() until date.atStartOfDay()) notifyMutationObservers() return true } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt index 093870ce1f..104172dd45 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt @@ -522,7 +522,7 @@ class InterestRateSwap : Contract { outputs: List, commands: List>, groupingKey: Unit?): Set { - require(tx.timeWindow?.midpoint != null) { "must be have a time-window)" } + requireNotNull(tx.timeWindow) { "must be have a time-window)" } // We return an empty set because we don't process any commands return emptySet() } @@ -584,8 +584,7 @@ class InterestRateSwap : Contract { "There is only one change in the IRS floating leg payment schedule" using (paymentDifferences.size == 1) } - val changedRates = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier). - val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = changedRates + val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier). val fixValue = command.value.fix // Need to check that everything is the same apart from the new fixed rate entry. requireThat { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt index 721bf0a3f4..d91a886832 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/OGTrade.kt @@ -23,7 +23,7 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash. outputs: List, commands: List>, groupingKey: Unit?): Set { - require(tx.timeWindow?.midpoint != null) { "must have a time-window" } + requireNotNull(tx.timeWindow) { "must have a time-window" } // We return an empty set because we don't process any commands return emptySet() } @@ -43,7 +43,7 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash. groupingKey: UniqueIdentifier?): Set { val command = tx.commands.requireSingleCommand() - require(inputs.size == 0) { "Inputs must be empty" } + require(inputs.isEmpty()) { "Inputs must be empty" } require(outputs.size == 1) { "" } require(outputs[0].buyer != outputs[0].seller) require(outputs[0].participants.containsAll(outputs[0].participants)) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt index 72811505cb..0c8706c2ac 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioSwap.kt @@ -25,7 +25,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur outputs: List, commands: List>, groupingKey: Unit?): Set { - require(tx.timeWindow?.midpoint != null) { "must have a time-window)" } + requireNotNull(tx.timeWindow) { "must have a time-window)" } // We return an empty set because we don't process any commands return emptySet() } @@ -69,7 +69,7 @@ data class PortfolioSwap(override val legalContractReference: SecureHash = Secur val command = tx.commands.requireSingleCommand() requireThat { - "there are no inputs" using (inputs.size == 0) + "there are no inputs" using (inputs.isEmpty()) "there is one output" using (outputs.size == 1) "valuer must be a party" using (outputs[0].participants.contains(outputs[0].valuer)) } diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index bf7d4c5a94..e279266d41 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -16,6 +16,7 @@ import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName import net.corda.core.identity.Party import net.corda.core.internal.div +import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceInfo @@ -348,7 +349,7 @@ fun poll( override fun run() { if (resultFuture.isCancelled) return // Give up, caller can no longer get the result. if (++counter == warnCount) { - log.warn("Been polling $pollName for ${pollInterval.multipliedBy(warnCount.toLong()).seconds} seconds...") + log.warn("Been polling $pollName for ${(pollInterval * warnCount.toLong()).seconds} seconds...") } try { val checkResult = check() diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/TestClock.kt b/test-utils/src/main/kotlin/net/corda/testing/node/TestClock.kt index 46d26859e9..32728b2380 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/TestClock.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/TestClock.kt @@ -1,8 +1,8 @@ package net.corda.testing.node +import net.corda.core.internal.until import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializationToken import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken import net.corda.node.utilities.MutableClock import java.time.Clock @@ -35,7 +35,7 @@ class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableC * * This will only be approximate due to the time ticking away, but will be some time shortly after the requested [Instant]. */ - @Synchronized fun setTo(newInstant: Instant) = advanceBy(Duration.between(instant(), newInstant)) + @Synchronized fun setTo(newInstant: Instant) = advanceBy(instant() until newInstant) @Synchronized override fun instant(): Instant { return delegateClock.instant() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt index b948a7580e..b6bcfec5a2 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt @@ -5,7 +5,7 @@ import com.google.common.util.concurrent.RateLimiter import com.google.common.util.concurrent.SettableFuture import net.corda.core.catch import net.corda.core.utilities.minutes -import net.corda.core.until +import net.corda.core.internal.until import net.corda.core.utilities.loggerFor import net.corda.demobench.model.NodeConfig import net.corda.demobench.readErrorLines From 29ff4a7ef6087a9b87063d3a083cf60be689d658 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 19 Jul 2017 17:31:39 +0100 Subject: [PATCH 059/197] Stop logging entire result from a fiber, as it can be very large --- core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 4583d14a62..3490441af3 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -1,6 +1,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.abbreviate import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine @@ -163,7 +164,7 @@ abstract class FlowLogic { } logger.debug { "Calling subflow: $subLogic" } val result = subLogic.call() - logger.debug { "Subflow finished with result $result" } + logger.debug { "Subflow finished with result ${result.toString().abbreviate(300)}" } // It's easy to forget this when writing flows so we just step it to the DONE state when it completes. subLogic.progressTracker?.currentStep = ProgressTracker.DONE return result From b6902aada698404de7b83192fe2b5af35294238b Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 19 Jul 2017 18:23:33 +0100 Subject: [PATCH 060/197] Use notary identity in Bank of Corda demo Resolve notary identity via node identity in Bank of Corda demo. --- .../kotlin/net/corda/bank/api/BankOfCordaWebApi.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt index 31c8b3abf3..289a7d3f52 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt @@ -43,11 +43,13 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) { fun issueAssetRequest(params: IssueRequestParams): Response { // Resolve parties via RPC val issueToParty = rpc.partyFromX500Name(params.issueToPartyName) - ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.issueToPartyName} in Network Map Service").build() + ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.issueToPartyName} in identity service").build() val issuerBankParty = rpc.partyFromX500Name(params.issuerBankName) - ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.issuerBankName} in Network Map Service").build() + ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.issuerBankName} in identity service").build() val notaryParty = rpc.partyFromX500Name(params.notaryName) - ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.notaryName} in Network Map Service").build() + ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.notaryName} in identity service").build() + val notaryNode = rpc.nodeIdentityFromParty(notaryParty) + ?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${notaryParty} in network map service").build() val amount = Amount(params.amount, currency(params.currency)) val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte()) @@ -56,7 +58,7 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) { // invoke client side of Issuer Flow: IssuanceRequester // The line below blocks and waits for the future to resolve. return try { - rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, notaryParty, anonymous).returnValue.getOrThrow() + rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow() logger.info("Issue request completed successfully: $params") Response.status(Response.Status.CREATED).build() } catch (e: Exception) { From 6f7794ac93101cc61c7a10d045b4e743005bf02d Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 20 Jul 2017 15:35:35 +0100 Subject: [PATCH 061/197] Rewrote the doc for TimeWinow to make it clearer --- .../net/corda/core/contracts/TimeWindow.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt b/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt index 960bd9f310..e26ea50cd0 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TimeWindow.kt @@ -3,24 +3,29 @@ package net.corda.core.contracts import net.corda.core.internal.div import net.corda.core.internal.until import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.WireTransaction import java.time.Duration import java.time.Instant /** - * A time-window is required for validation/notarization purposes. If present in a transaction, contains a time that was - * verified by the uniqueness service. The true time must be in the time interval `[fromTime, untilTime)`. + * An interval on the time-line; not a single instantaneous point. * - * Usually a time-window is required to have both sides defined. However some apps may require a time-window which is - * open-ended on one of the two sides. + * There is no such thing as _exact_ time in networked systems due to the underlying physics involved and other issues + * such as network latency. The best that can be approximated is "fuzzy time" or an instant of time which has margin of + * tolerance around it. This is what [TimeWindow] represents. Time windows can be open-ended (i.e. specify only one of + * [fromTime] and [untilTime]) or they can be fully bounded. + * + * [WireTransaction] has an optional time-window property, which if specified, restricts the validity of the transaction + * to that time-interval as the Consensus Service will not sign it if it's received outside of this window. */ @CordaSerializable abstract class TimeWindow { companion object { - /** Creates a [TimeWindow] with null [untilTime], i.e. the time interval `[fromTime, ∞]`. [midpoint] will return null. */ + /** Creates a [TimeWindow] with null [untilTime], i.e. the time interval `[fromTime, ∞)`. [midpoint] will return null. */ @JvmStatic fun fromOnly(fromTime: Instant): TimeWindow = From(fromTime) - /** Creates a [TimeWindow] with null [fromTime], i.e. the time interval `[∞, untilTime)`. [midpoint] will return null. */ + /** Creates a [TimeWindow] with null [fromTime], i.e. the time interval `(∞, untilTime)`. [midpoint] will return null. */ @JvmStatic fun untilOnly(untilTime: Instant): TimeWindow = Until(untilTime) @@ -55,7 +60,7 @@ abstract class TimeWindow { /** * Returns the midpoint of [fromTime] and [untilTime] if both are non-null, calculated as - * `fromTime + (untilTime - fromTime)/2`, otherwise returns null. + * `fromTime + (untilTime - fromTime) / 2`, otherwise returns null. */ abstract val midpoint: Instant? @@ -66,14 +71,14 @@ abstract class TimeWindow { override val untilTime: Instant? get() = null override val midpoint: Instant? get() = null override fun contains(instant: Instant): Boolean = instant >= fromTime - override fun toString(): String = "[$fromTime, ∞]" + override fun toString(): String = "[$fromTime, ∞)" } private data class Until(override val untilTime: Instant) : TimeWindow() { override val fromTime: Instant? get() = null override val midpoint: Instant? get() = null override fun contains(instant: Instant): Boolean = instant < untilTime - override fun toString(): String = "[∞, $untilTime)" + override fun toString(): String = "(∞, $untilTime)" } private data class Between(override val fromTime: Instant, override val untilTime: Instant) : TimeWindow() { From bb4501c09cc6979c2076ac4a3e9f04d138af5dcd Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 21 Jul 2017 12:33:31 +0100 Subject: [PATCH 062/197] Updates the example CorDapp page. --- docs/source/building-against-master.rst | 34 + docs/source/corda-nodes-index.rst | 2 + docs/source/deploying-a-node.rst | 125 +++ docs/source/getting-set-up.rst | 9 +- docs/source/node-database.rst | 28 + docs/source/quickstart-index.rst | 1 + docs/source/running-a-node.rst | 168 +--- docs/source/tutorial-cordapp.rst | 1121 +++++++++-------------- 8 files changed, 624 insertions(+), 864 deletions(-) create mode 100644 docs/source/building-against-master.rst create mode 100644 docs/source/deploying-a-node.rst create mode 100644 docs/source/node-database.rst diff --git a/docs/source/building-against-master.rst b/docs/source/building-against-master.rst new file mode 100644 index 0000000000..af3d87e25d --- /dev/null +++ b/docs/source/building-against-master.rst @@ -0,0 +1,34 @@ +Building against Master +======================= + +When developing a CorDapp, it is advisable to use the most recent Corda Milestone release, which has been extensively +tested. However, if you need to use a very recent feature of the codebase, you may need to work against the unstable +Master branch. + +To work against the Master branch, proceed as follows: + +* Open a terminal window in the folder where you cloned the Corda repository + (available `here `_) + +* Use the following command to check out the latest master branch: + + ``git fetch; git checkout master`` + +* Publish Corda to your local Maven repository using the following commands: + + * Unix/Mac OSX: ``./gradlew install`` + * Windows: ``gradlew.bat install`` + + By default, the Maven local repository is found at: + + * ``~/.m2/repository`` on Unix/Mac OS X + * ``%HOMEPATH%\.m2`` on Windows + + This step is not necessary when using a Milestone releases, as the Milestone releases are published online + +.. warning:: If you do modify your local Corda repository after having published it to Maven local, then you must + re-publish it to Maven local for the local installation to reflect the changes you have made. + +.. warning:: As the Corda repository evolves on a daily basis, two clones of the Master branch at different points in + time may differ. If you are using a Master release and need help debugging an error, then please let us know the + **commit** you are working from. This will help us ascertain the issue. \ No newline at end of file diff --git a/docs/source/corda-nodes-index.rst b/docs/source/corda-nodes-index.rst index 0297f1c744..6389d7c23b 100644 --- a/docs/source/corda-nodes-index.rst +++ b/docs/source/corda-nodes-index.rst @@ -4,9 +4,11 @@ Corda nodes .. toctree:: :maxdepth: 1 + deploying-a-node running-a-node clientrpc shell + node-database node-administration corda-configuration-file out-of-process-verification \ No newline at end of file diff --git a/docs/source/deploying-a-node.rst b/docs/source/deploying-a-node.rst new file mode 100644 index 0000000000..d7ca6e2804 --- /dev/null +++ b/docs/source/deploying-a-node.rst @@ -0,0 +1,125 @@ +Deploying a node +================ + +Building nodes using Gradle +--------------------------- +Nodes are usually built using a Gradle task. The canonical Gradle file for building nodes is the one used by the +CorDapp template. Both a `Java version `_ and +a `Kotlin version `_ are available. + +The buildscript +~~~~~~~~~~~~~~~ +The buildscript is always located at the top of the build file. Among other things, this section determines which +version of Corda and the Corda gradle plugins are used to build the nodes and their CorDapps: + +.. sourcecode:: groovy + + ext.corda_release_version = '0.14-SNAPSHOT' + ext.corda_gradle_plugins_version = '0.14.2' + +Release versions suffixed by ``-SNAPSHOT`` are based on the unstable Master branch +(see :doc:`building-against-master`). You should generally use the latest Milestone release instead. + +``corda_gradle_plugins_versions`` are given in the form ``major.minor.patch``. You should use the same ``major`` and +``minor`` versions as the Corda version you are using, and the latest ``patch`` version. A list of all the available +versions can be found here: https://bintray.com/r3/corda/cordformation. + +Project dependencies +~~~~~~~~~~~~~~~~~~~~ +If your CorDapps have any additional external dependencies, they should be added to the ``dependencies`` section:` + +.. sourcecode:: groovy + + dependencies { + + ... + + // Cordapp dependencies + // Specify your cordapp's dependencies below, including dependent cordapps + } + +For further information about managing dependencies, see +`the Gradle docs `_. + +Building the nodes +~~~~~~~~~~~~~~~~~~ +Cordform is the local node deployment system for CorDapps. The nodes generated are intended for experimenting, +debugging, and testing node configurations, but not for production or testnet deployment. + +Here is an example Gradle task called ``deployNodes`` that uses the Cordform plugin to deploy three nodes, plus a +notary/network map node: + +.. sourcecode:: groovy + + task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + directory "./build/nodes" + networkMap "CN=Controller,O=R3,OU=corda,L=London,C=UK" + node { + name "CN=Controller,O=R3,OU=corda,L=London,C=UK" + advertisedServices = ["corda.notary.validating"] + p2pPort 10002 + rpcPort 10003 + webPort 10004 + cordapps = [] + } + node { + name "CN=NodeA,O=NodeA,L=London,C=UK" + advertisedServices = [] + p2pPort 10005 + rpcPort 10006 + webPort 10007 + cordapps = [] + rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + } + node { + name "CN=NodeB,O=NodeB,L=New York,C=US" + advertisedServices = [] + p2pPort 10008 + rpcPort 10009 + webPort 10010 + cordapps = [] + rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + } + node { + name "CN=NodeC,O=NodeC,L=Paris,C=FR" + advertisedServices = [] + p2pPort 10011 + rpcPort 10012 + webPort 10013 + cordapps = [] + rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + } + } + +You can extend ``deployNodes`` to generate any number of nodes you like. The only requirement is that you must specify +one node as running the network map service, by putting their name in the ``networkMap`` field. In our example, the +``Controller`` is set as the network map service. + +.. warning:: When adding nodes, make sure that there are no port clashes! + +Any CorDapps defined in the project's source folders are also automatically registered with all the nodes defined in +``deployNodes``, even if the CorDapps are not listed in each node's ``cordapps`` entry. + +Deploying your nodes +-------------------- +You deploy a set of nodes by running your ``build.gradle`` file's Cordform task. For example, if we were using the +standard ``deployNodes`` task defined above, we'd create our nodes by running the following commands in a terminal +window from the root of the project: + +* Unix/Mac OSX: ``./gradlew deployNodes`` +* Windows: ``gradlew.bat deployNodes`` + +After the build process has finished, you will find the newly-built nodes under ``kotlin-source/build/nodes``. There +will be one folder generated for each node you built, plus a ``runnodes`` shell script (or batch file on Windows) to +run all the nodes at once. Each node in the ``nodes`` folder has the following structure: + +.. sourcecode:: none + + . nodeName + ├── corda.jar // The Corda runtime + ├── node.conf // The node's configuration + └── plugins // Any installed CorDapps + +.. note:: Outside of development environments, do not store your node directories in the build folder. + +If you make any changes to your ``deployNodes`` task, you will need to re-run the task to see the changes take effect. \ No newline at end of file diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index ac221d2217..0dd81242e3 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -158,11 +158,14 @@ instead by running ``git checkout release-M12``. Next steps ---------- +The best way to check that everything is working fine is by running the :doc:`tutorial CorDapp ` and +the :doc:`samples `. -The best way to check that everything is working fine is by :doc:`running-the-demos`. +Next, you should read through :doc:`Corda Key Concepts ` to understand how Corda works. -Once you have these demos running, you may be interested in writing your own CorDapps, in which case you should refer to -:doc:`tutorial-cordapp`. +By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the +:doc:`Hello, World tutorial `. You may want to refer to the :doc:`API docs ` along the +way. If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the `forums `_ or via `slack `_. diff --git a/docs/source/node-database.rst b/docs/source/node-database.rst new file mode 100644 index 0000000000..e38cde81c9 --- /dev/null +++ b/docs/source/node-database.rst @@ -0,0 +1,28 @@ +Node database +============= + +Currently, nodes store their data in an H2 database. In the future, we plan to support a wide range of databases. + +You can connect directly to a running node's database to see its stored states, transactions and attachments as +follows: + +* Download the `h2 platform-independent zip `_, unzip the zip, and + navigate in a terminal window to the unzipped folder +* Change directories to the bin folder: + + ``cd h2/bin`` + +* Run the following command to open the h2 web console in a web browser tab: + + * Unix: ``sh h2.sh`` + * Windows: ``h2.bat`` + +* Find the node's JDBC connection string. Each node outputs its connection string in the terminal + window as it starts up. In a terminal window where a node is running, look for the following string: + + ``Database connection URL is : jdbc:h2:tcp://10.18.0.150:56736/node`` + +* Paste this string into the JDBC URL field and click ``Connect``, using the default username and password. + +You will be presented with a web interface that shows the contents of your node's storage and vault, and provides an +interface for you to query them using SQL. \ No newline at end of file diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst index 12404a0d8f..59f6042658 100644 --- a/docs/source/quickstart-index.rst +++ b/docs/source/quickstart-index.rst @@ -7,4 +7,5 @@ Quickstart getting-set-up tutorial-cordapp running-the-demos + building-against-master CLI-vs-IDE \ No newline at end of file diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index 9ec08fdc73..ec03aa80bd 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -1,38 +1,20 @@ Running a node ============== -Deploying your node -------------------- - -You deploy a node by running the ``gradle deployNodes`` task. This will output the node JAR to -``build/libs/corda.jar`` and several sample/standard node setups to ``build/nodes``. For now you can use the -``build/nodes/nodea`` configuration as a template. - -Each node server by default must have a ``node.conf`` file in the current working directory. After first -execution of the node server there will be many other configuration and persistence files created in this -workspace directory. The directory can be overridden by the ``--base-directory=`` command line argument. - -.. note:: Outside of development environments do not store your node directories in the build folder. - -.. warning:: Also note that the bootstrapping process of the ``corda.jar`` unpacks the Corda dependencies into a - temporary folder. It is therefore suggested that the CAPSULE_CACHE_DIR environment variable be set before - starting the process to control this location. - Starting your node ------------------ -Now you have a node server with your app installed, you can run it by navigating to ```` and running: +After following the steps in :doc:`deploying-a-node`, you should have deployed your node(s) with any chosen CorDapps +installed. You run each node by navigating to ```` in a terminal window and running: .. code-block:: shell Windows: java -jar corda.jar UNIX: ./corda.jar -The plugin should automatically be registered and the configuration file used. - .. warning:: If your working directory is not ```` your plugins and configuration will not be used. -The configuration file and workspace paths can be overidden on the command line e.g. +The configuration file and workspace paths can be overridden on the command line. For example: ``./corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``. @@ -41,14 +23,14 @@ Otherwise the workspace folder for the node is the current working path. Debugging your node ------------------- -To enable remote debugging of the corda process use a command line such as: +To enable remote debugging of the node, run the following from the terminal window: ``java -Dcapsule.jvm.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -jar corda.jar`` This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment. -Viewing persisted state of your node ------------------------------------- +Viewing the persisted state of your node +---------------------------------------- To make examining the persisted contract states of your node or the internal node database tables easier, and providing you are using the default database configuration used for demos, you should be able to connect to the internal node database over @@ -61,140 +43,4 @@ at the present time, and should certainly be treated as read-only. .. _CordaPluginRegistry: api/kotlin/corda/net.corda.core.node/-corda-plugin-registry/index.html .. _PluginServiceHub: api/kotlin/corda/net.corda.core.node/-plugin-service-hub/index.html -.. _ServiceHub: api/kotlin/corda/net.corda.core.node/-service-hub/index.html - -Building against Corda ----------------------- - -To publish to your local Maven repository (in ``~/.m2`` on Unix and ``%HOMEPATH%\.m2`` on Windows) run the following -in the root directory of the Corda code: - -.. code-block:: shell - - ./gradlew install - -This will publish corda-$version.jar, finance-$version.jar, core-$version.jar and node-$version.jar to the -group net.corda. You can now depend on these as you normally would a Maven dependency, using the group id -``net.corda``. - -There are several Gradle plugins that reduce your build.gradle boilerplate and make development of CorDapps easier. -The available plugins are in the gradle-plugins directory of the Corda repository. - -To install to your local Maven repository the plugins that CorDapp gradle files require, enter the ``gradle-plugins`` -directory and then run ``../gradle install``. The plugins will now be installed to your local Maven repository. - -Using Gradle plugins -~~~~~~~~~~~~~~~~~~~~ - -To use the plugins, if you are not already using the CorDapp template project, you must modify your build.gradle. Add -the following segments to the relevant part of your build.gradle. - -.. code-block:: groovy - - buildscript { - ext.corda_release_version = '' - ext.corda_gradle_plugins_version = '' // This is usually the same as corda_release_version. - ... your buildscript ... - - repositories { - ... other repositories ... - mavenLocal() - } - - dependencies { - ... your dependencies ... - classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version" - classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version" - classpath "net.corda.plugins:publish-utils:$corda_gradle_plugins_version" - } - } - - apply plugin: 'net.corda.plugins.cordformation' - apply plugin: 'net.corda.plugins.quasar-utils' - apply plugin: 'net.corda.plugins.publish-utils' - - repositories { - mavenLocal() - ... other repositories here ... - } - - dependencies { - compile "net.corda.core:$corda_release_version" - compile "net.corda.finance:$corda_release_version" - compile "net.corda.node:$corda_release_version" - compile "net.corda.corda:$corda_release_version" - ... other dependencies here ... - } - - ... your tasks ... - - // Standard way to publish CorDapps to maven local with the maven-publish and publish-utils plugin. - publishing { - publications { - jarAndSources(MavenPublication) { - from components.java - // The two lines below are the tasks added by this plugin. - artifact sourceJar - artifact javadocJar - } - } - } - - - -Cordformation -~~~~~~~~~~~~~ - -Cordformation is the local node deployment system for CorDapps, the nodes generated are intended to be used for -experimenting, debugging, and testing node configurations and setups but not intended for production or testnet -deployment. - -To use this gradle plugin you must add a new task that is of the type ``net.corda.plugins.Cordform`` to your -build.gradle and then configure the nodes you wish to deploy with the Node and nodes configuration DSL. -This DSL is specified in the `JavaDoc `_. An example of this is in the CorDapp template and -below -is a three node example; - -.. code-block:: text - - task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - directory "./build/nodes" // The output directory - networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB" // The distinguished name of the node named here will be used as the networkMapService.address on all other nodes. - node { - name "CN=Controller,O=R3,OU=corda,L=London,C=GB" - advertisedServices = [ "corda.notary.validating" ] - p2pPort 10002 - rpcPort 10003 - webPort 10004 - h2Port 11002 - cordapps [] - } - node { - name "CN=NodeA,O=R3,OU=corda,L=London,C=GB" - advertisedServices = [] - p2pPort 10005 - rpcPort 10006 - webPort 10007 - h2Port 11005 - cordapps [] - } - node { - name "CN=NodeB,O=R3,OU=corda,L=New York,C=US" - advertisedServices = [] - p2pPort 10008 - rpcPort 10009 - webPort 10010 - h2Port 11008 - cordapps [] - } - } - -You can create more configurations with new tasks that extend Cordform. - -New nodes can be added by simply adding another node block and giving it a different name, directory and ports. When you -run this task it will install the nodes to the directory specified and a script will be generated to run the nodes with -one command (``runnodes``). On MacOS X this script will run each node in a new terminal tab, and on Linux it will open -up a new XTerm for each node. On Windows the (``runnodes.bat``) script will run one node per window. - -Other CorDapps can also be specified if they are already specified as classpath or compile dependencies in your -``build.gradle``. +.. _ServiceHub: api/kotlin/corda/net.corda.core.node/-service-hub/index.html \ No newline at end of file diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index c17c81b874..350c0a63c4 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -7,832 +7,549 @@ The example CorDapp =================== -This guide covers how to get started with the example CorDapp. Please note there are several Corda repositories: +.. contents:: -* `corda `_ which contains the core platform code and sample CorDapps. -* `cordapp-tutorial `_ which contains an example CorDapp you can use to bootstrap your own CorDapps. It is the subject of this tutorial and should help you understand the basics. -* `cordapp-template `_ which contains a bare-bones template designed for starting new projects by copying. +The example CorDapp allows nodes to agree IOUs with each other. Nodes will always agree to the creation of a new IOU +unless: -We recommend you read the non-technical white paper and technical white paper before you get started with Corda: +* Its value is less than 1, or greater than 99 +* A node tries to issue an IOU to itself -1. `The Introductory white paper `_ describes the - motivating vision and background of the project. It is the kind of document your boss should read. It describes why the - project exists and briefly compares it to alternative systems on the market. -2. `The Technical white paper `_ describes the entire - intended design from beginning to end. It is the kind of document that you should read, or at least, read parts of. Note - that because the technical white paper describes the intended end state, it does not always align with the implementation. - -Background ----------- - -The example CorDapp implements a basic scenario where one party wishes to send an IOU to another party. The scenario -defines four nodes: +By default, the CorDapp is deployed on 4 test nodes: * **Controller**, which hosts the network map service and validating notary service * **NodeA** * **NodeB** * **NodeC** -The nodes can generate IOUs and send them to other nodes. The flows used to facilitate the agreement process always results in -an agreement with the recipient as long as the IOU meets the contract constraints which are defined in ``IOUContract.kt``. +Because data is only propagated on a need-to-know basis, any IOUs agreed between NodeA and NodeB become "shared facts" +between NodeA and NodeB only. NodeC won't be aware of these IOUs. -All agreed IOUs between NodeA and NodeB become "shared facts" between NodeA and NodeB. But note that NodeC -won't see any of these transactions or have copies of any of the resulting ``IOUState`` objects. This is -because data is only propagated on a need-to-know basis. - -Getting started ---------------- - -There are two ways to get started with the example CorDapp. You can either work from a milestone release of Corda or a -SNAPSHOT release of Corda. - -**Using a monthly Corda milestone release.** If you wish to develop your CorDapp using the most recent milestone release -then you can get started simply by cloning the ``cordapp-tutorial`` repository. Gradle will grab all the required dependencies -for you from our `public Maven repository `_. - -**Using a Corda SNAPSHOT build.** Alternatively, if you wish to work from the master branch of the Corda repo which contains -the most up-to-date Corda feature set then you will need to clone the ``corda`` repository and publish the latest master -build (or previously tagged releases) to your local Maven repository. You will then need to ensure that Gradle -grabs the correct dependencies for you from Maven local by changing the ``corda_release_version`` in ``build.gradle``. -This will be covered below in `Using a SNAPSHOT release`_. - -Firstly, follow the :doc:`getting set up ` page to download the JDK, IntelliJ and git if you didn't -already have it. - -Working from milestone releases -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you wish to build a CorDapp against a milestone release then please use these instructions. - -The process for developing your CorDapp from a milestone release is the most simple way to get started and is the preferred -approach. - -We publish all our milestone releases to a public Maven repository on a monthly basis. As such, Gradle will automatically -grab the appropriately versioned (specified in the ``cordapp-tutorial``'s ``build.gradle`` file) dependencies for you from Maven. -All you have to do is check out the release tag of the tutorial version you wish to use. - -By default, the ``master`` branch of the ``cordapp-tutorial`` points to a SNAPSHOT release of Corda, this is because it is -being constantly updated to reflect the changes in the master branch of the `corda` repository. - -.. note:: If you wish to use a SNAPSHOT release then follow the instructions below: `Using a SNAPSHOT release`_. - -To clone the ``cordapp-tutorial`` repository, use the following command: +Downloading the example CorDapp +------------------------------- +If you haven't already, set up your machine by following the :doc:`quickstart guide `. Then clone the +example CorDapp from the `cordapp-tutorial repository `_ using the following +command: ``git clone https://github.com/corda/cordapp-tutorial`` -Now change directories to the freshly cloned repo: +And change directories to the freshly cloned repo: ``cd cordapp-tutorial`` -To enumerate all the tagged releases. Use: +We want to work off the latest Milestone release. To enumerate all the Milestone releases, run: ``git tag`` -To checkout a specific tag, use: +And check out the latest (highest-numbered) Milestone release using: -``git checkout -b [local_branch_name] tags/[tag_name]`` +``git checkout [tag_name]`` -where ``local_branch_name`` is a name of your choice and ``tag_name`` is the name of the tag you wish to checkout. +Where ``tag_name`` is the name of the tag you wish to checkout. Gradle will grab all the required dependencies for you +from our `public Maven repository `_. -Gradle will handle all the dependencies for you. Now you are now ready to get started `building the example CorDapp`_. +.. note:: If you wish to build off the latest, unstable version of the codebase, follow the instructions in + `Using a SNAPSHOT release`_. -Using a SNAPSHOT release -~~~~~~~~~~~~~~~~~~~~~~~~ - -If you wish to build a CorDapp against the most current version of Corda, follow these instructions. - -Firstly navigate to the folder on your machine you wish to clone the Corda repository to. Then use the following command -to clone the Corda repository: - -``git clone https://github.com/corda/corda.git`` - -Now change directories: - -``cd corda`` - -Once you've cloned the ``corda`` repository and are in the repo directory you have the option to remain on the master -branch or checkout a specific branch. Use: - -``git branch --all`` - -to enumerate all the branches. To checkout a specific branch, use: - -``git checkout -b [local_branch_name] origin/[remote_branch_name]`` - -where ``local_branch_name`` is a name of your choice and ``remote_branch_name`` is the name of the remote branch you wish -to checkout. - -.. note:: When working with ``master`` you will have access to the most up-to-date feature set. However you will be - potentially sacrificing stability. We will endeavour to keep the ``master`` branch of the ``cordapp-tutorial`` repo in sync - with the ``master`` branch of ``corda`` repo. A milestone tagged release would be more stable for CorDapp development. - -The next step is to publish the Corda JARs to your local Maven repository. By default the Maven local repository can be -found: - -* ``~/.m2/repository`` on Unix/Mac OS X -* ``%HOMEPATH%\.m2`` on windows. - -Publishing can be done with running the following Gradle task from the root project directory: - -Unix/Mac OSX: ``./gradlew install`` - -Windows: ``gradlew.bat install`` - -This will install all required modules, along with sources and JavaDocs to your local Maven repository. The ``version`` -and ``groupid`` of Corda installed to Maven local is specified in the ``build.gradle`` file in the root of the ``corda`` -repository. You shouldn't have to change these values unless you want to publish multiple versions of a SNAPSHOT, e.g. -if you are trying out new features, in this case you can change ``version`` for each SNAPSHOT you publish. - -.. note:: **A quick point on corda version numbers used by Gradle.** - - In the ``build.gradle`` file for your CorDapp, you can specify the ``corda_release_version`` to use. It is important - that when developing your CorDapp that you use the correct version number. For example, when wanting to work from a SNAPSHOT - release, the release numbers are suffixed with 'SNAPSHOT', e.g. if the latest milestone release is M6 then the - SNAPSHOT release will be 0.7-SNAPSHOT, and so on. As such, you will set your ``corda_release_version`` to ``'0.7-SNAPSHOT'`` - in the ``build.gradle`` file in your CorDapp. Gradle will automatically grab the SNAPSHOT dependencies from your local - Maven repository. Alternatively, if working from a milestone release, you will use the version number only, for example - ``0.6`` or ``0.7``. - - Lastly, as the Corda repository evolves on a daily basis up until the next milestone release, it is worth nothing that - the substance of two SNAPSHOT releases of the same number may be different. If you are using a SNAPSHOT and need help - debugging an error then please tell us the **commit** you are working from. This will help us ascertain the issue. - -As additional feature branches are merged into Corda you can ``git pull`` the new changes from the ``corda`` repository. -If you are feeling inquisitive, you may also wish to review some of the current feature branches. All new features are -developed on separate branches. To enumerate all the current branches use: - -``git branch --all`` - -and to check out an open feature branch, use: - -``git checkout -b [local_branch_name] origin/[branch_name]`` - -.. note:: Publishing Corda JARs from unmerged feature branches might cause some unexpected behaviour / broken CorDapps. - It would also replace any previously published SNAPSHOTS of the same version. - -.. warning:: If you do modify Corda after you have previously published it to Maven local then you must republish your - SNAPSHOT build such that Maven reflects the changes you have made. - -Once you have published the Corda JARs to your local Maven repository, you are ready to get started building your -CorDapp using the latest Corda features. - -Opening the example CorDapp with IntelliJ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For those familiar with IntelliJ, you can skip this section. - -As noted in the getting started guide, we recommend using the IntelliJ IDE. Assuming you have already downloaded and -installed IntelliJ, lets now open the example CorDapp with IntelliJ. +Opening the example CorDapp in IntelliJ +--------------------------------------- +Let's open the example CorDapp in the IntelliJ IDE. **For those completely new to IntelliJ** -Firstly, load up IntelliJ. A dialogue will appear: +Upon opening IntelliJ, a dialogue will appear: .. image:: resources/intellij-welcome.png :width: 400 Click open, then navigate to the folder where you cloned the ``cordapp-tutorial`` and click OK. -Next, IntelliJ will show a bunch of pop-up windows. One of which requires our attention: +Next, IntelliJ will show several pop-up windows, one of which requires our attention: .. image:: resources/unlinked-gradle-project.png :width: 400 -Click the 'import gradle project' link. A dialogue will pop-up. Press OK. Gradle will now obtain all the -project dependencies and perform some indexing. It usually takes a minute or so. If you miss the 'import gradle project' -dialogue, simply close and re-open IntelliJ again to see it again. +Click the 'import gradle project' link. A dialogue will pop-up. Press OK. Gradle will now download all the +project dependencies and perform some indexing. This usually takes a minute or so. -**Alternative approach** - -Alternatively, one can instruct IntelliJ to create a new project through cloning a repository. From the IntelliJ welcome -dialogue (shown above), opt to 'check out from version control', then select git and enter the git URL for the example CorDapp -(https://github.com/corda/cordapp-tutorial). You'll then need to import the Gradle project when prompted, as explained above. +If the 'import gradle project' pop-up does not appear, click the small green speech bubble at the bottom-right of +the IDE, or simply close and re-open IntelliJ again to make it reappear. **If you already have IntelliJ open** -From the ``File`` menu, navigate to ``Open ...`` and then navigate to the directory where you cloned the ``cordapp-tutorial``. -Alternatively, if you wish to clone from github directly then navigate to ``File > New > Project from existing sources ...`` -and enter the URL to the example CorDapp (specified above). When instructed, be sure to import the Gradle project when prompted. +From the ``File`` menu, navigate to ``Open ...`` and then navigate to the directory where you cloned the +``cordapp-tutorial`` and click OK. -**The Gradle plugin** - -IntelliJ can be used to run Gradle tasks through the Gradle plugin which can be found via ``View > Tool windows > Gradle``. -All the Gradle projects are listed in the window on the right hand side of the IDE. Click on a project, then 'tasks' to -see all available Gradle tasks. - -* For the example CorDapp repo there will only be one Gradle project listed. -* For the Corda repo there will be many project listed, the root project ``corda`` and associated sub-projects: ``core``, - ``finance``, ``node``, etc. - -.. note:: It's worth noting that when you change branch in the example CorDapp, the ``corda_release_version`` will change to - reflect the version of the branch you are working from. - -To execute a task, double click it. The output will be shown in a console window. - -Building the example CorDapp ----------------------------- - -**From the command line** - -Firstly, return to your terminal window used above and make sure you are in the ``cordapp-tutorial`` directory. - -To build the example CorDapp use the following command: - -Unix/Mac OSX: ``./gradlew deployNodes`` - -Windows: ``gradlew.bat deployNodes`` - -This build process will build the example CorDapp defined in the example CorDapp source. CorDapps can be written in -any language targeting the JVM. In our case, we've provided the example source in both Kotlin (``/kotlin-source/src``) and -Java (``/java-source/src``) Since both sets of source files are functionally identical, we will refer to the Kotlin build -throughout the documentation. - -For more information on the example CorDapp see "The Example CorDapp" section below. Gradle will then grab all the -dependencies for you and build the example CorDapp. - -The ``deployNodes`` Gradle task allows you easily create a formation of Corda nodes. In the case of the example CorDapp -we are creating ``four`` nodes. - -After the building process has finished to see the newly built nodes, you can navigate to the ``kotlin-source/build/nodes`` folder -located in the ``cordapp-tutorial`` root directory. You can ignore the other folders in ``/build`` for now. The ``nodes`` -folder has the following structure: +Project structure +----------------- +The example CorDapp has the following directory structure: .. sourcecode:: none - . nodes - ├── controller - │   ├── corda.jar - │   ├── node.conf - │   └── plugins - ├── nodea - │   ├── corda.jar - │   ├── node.conf - │   └── plugins - ├── nodeb - │   ├── corda.jar - │   ├── node.conf - │   └── plugins - ├── nodec - │   ├── corda.jar - │   ├── node.conf - │   └── plugins - ├── runnodes - └── runnodes.bat + . + ├── LICENCE + ├── README.md + ├── TRADEMARK + ├── build.gradle + ├── config + │   ├── dev + │   │   └── log4j2.xml + │   └── test + │   └── log4j2.xml + ├── doc + │   └── example_flow.plantuml + ├── gradle + │   └── wrapper + │   ├── gradle-wrapper.jar + │   └── gradle-wrapper.properties + ├── gradle.properties + ├── gradlew + ├── gradlew.bat + ├── java-source + │   └── ... + ├── kotlin-source + │   ├── build.gradle + │   └── src + │   ├── main + │   │   ├── kotlin + │   │   │   └── com + │   │   │   └── example + │   │   │   ├── api + │   │   │   │   └── ExampleApi.kt + │   │   │   ├── client + │   │   │   │   └── ExampleClientRPC.kt + │   │   │   ├── contract + │   │   │   │   └── IOUContract.kt + │   │   │   ├── flow + │   │   │   │   └── ExampleFlow.kt + │   │   │   ├── model + │   │   │   │   └── IOU.kt + │   │   │   ├── plugin + │   │   │   │   └── ExamplePlugin.kt + │   │   │   ├── schema + │   │   │   │   └── IOUSchema.kt + │   │   │   └── state + │   │   │   └── IOUState.kt + │   │   └── resources + │   │   ├── META-INF + │   │   │   └── services + │   │   │   └── net.corda.webserver.services.WebServerPluginRegistry + │   │   ├── certificates + │   │   │   ├── readme.txt + │   │   │   ├── sslkeystore.jks + │   │   │   └── truststore.jks + │   │   └── exampleWeb + │   │   ├── index.html + │   │   └── js + │   │   └── angular-module.js + │   └── test + │   └── kotlin + │   └── com + │   └── example + │   ├── Main.kt + │   ├── contract + │   │   └── IOUContractTests.kt + │   └── flow + │   └── IOUFlowTests.kt + ├── lib + │   ├── README.txt + │   └── quasar.jar + └── settings.gradle -There will be one folder generated for each node you build (more on later when we get into the detail of the -``deployNodes`` Gradle task) and a ``runnodes`` shell script (batch file on Windows). +The most important files and directories to note are: -Each node folder contains the Corda JAR and a folder for plugins (or CorDapps). There is also -a node.conf file. See :doc:`Corda configuration files `. - -**Building from IntelliJ** - -Open the Gradle window by selecting ``View > Tool windows > Gradle`` from the main menu. You will see the Gradle window -open on the right hand side of the IDE. Expand `tasks` and then expand `other`. Double click on `deployNodes`. Gradle will -start the build process and output progress to a console window in the IDE. +* The **root directory** contains some gradle files, a README and a LICENSE +* **config** contains log4j configs +* **gradle** contains the gradle wrapper, which allows the use of Gradle without installing it yourself and worrying + about which version is required +* **lib** contains the Quasar jar which is required for runtime instrumentation of classes by Quasar +* **kotlin-source** contains the source code for the example CorDapp written in Kotlin + * **kotlin-source/src/main/kotlin** contains the source code for the example CorDapp + * **kotlin-source/src/main/python** contains a python script which accesses nodes via RPC + * **kotlin-source/src/main/resources** contains the certificate store, some static web content to be served by the + nodes and the WebServerPluginRegistry file + * **kotlin-source/src/test/kotlin** contains unit tests for the contracts and flows, and the driver to run the nodes + via IntelliJ +* **java-source** contains the same source code, but written in java. This is an aid for users who do not want to + develop in Kotlin, and serves as an example of how CorDapps can be developed in any language targeting the JVM Running the example CorDapp --------------------------- +There are two ways to run the example CorDapp: -To run the sample CorDapp navigate to the ``kotlin-source/build/nodes`` folder and execute the ``runnodes`` shell script with: +* Via the terminal +* Via IntelliJ -Unix: ``./runnodes`` or ``sh runnodes`` +We explain both below. -Windows: ``runnodes.bat`` +Terminal: Building the example CorDapp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Return to your terminal window and make sure you are in the ``cordapp-tutorial`` directory. To build the example +CorDapp use the following command: -The ``runnodes`` scripts should create a terminal tab for each node. In each terminal tab, you'll see the Corda welcome -message and some pertinent config information, see below: +* Unix/Mac OSX: ``./gradlew deployNodes`` +* Windows: ``gradlew.bat deployNodes`` + +This will package up our CorDapp source files into a plugin and automatically build four pre-configured nodes that have +our CorDapp plugin installed. These nodes are meant for local testing only. + +After the build process has finished, you will see the newly-build nodes in the ``kotlin-source/build/nodes``. There +will be one folder generated for each node you built, plus a ``runnodes`` shell script (or batch file on Windows). + +.. note:: CorDapps can be written in any language targeting the JVM. In our case, we've provided the example source in + both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``) Since both sets of source files are + functionally identical, we will refer to the Kotlin build throughout the documentation. + +Each node in the ``nodes`` folder has the following structure: .. sourcecode:: none - ______ __ - / ____/ _________/ /___ _ - / / __ / ___/ __ / __ `/ Computer science and finance together. - / /___ /_/ / / / /_/ / /_/ / You should see our crazy Christmas parties! - \____/ /_/ \__,_/\__,_/ + . nodeName + ├── corda.jar + ├── node.conf + └── plugins - --- DEVELOPER SNAPSHOT ------------------------------------------------------------ +``corda.jar` is the Corda runtime, ``plugins`` contains our node's CorDapps, and our node's configuration is provided +in ``node.conf``. - Logs can be found in : /Users/rogerwillis/Documents/Corda/cordapp-tutorial/kotlin-source/build/nodes/nodea/logs - Database connection URL is : jdbc:h2:tcp://10.18.0.196:50661/node - Node listening on address : localhost:10004 - Loaded plugins : com.example.plugin.ExamplePlugin - Embedded web server is listening on : http://10.18.0.196:10005/ - Node started up and registered in 39.0 sec +Terminal: Running the example CorDapp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +To run our nodes, run the following command from the root of the ``cordapp-tutorial`` folder: -You'll need to refer to the above later on for the JDBC connection string and port numbers. +* Unix/Mac OSX: ``kotlin-source/build/nodes/runnodes`` +* Windows: ``call kotlin-source\build\nodes\runnodes.bat`` -Depending on the speed of your machine, it usually takes around 30 seconds for the nodes to finish starting up. If you -want to double check all the nodes are running you can query the 'status' end-point located at -``http://host:post/api/status``. +On Unix/Mac OSX, do not click/change focus until all eight additional terminal windows have opened, or some nodes may +fail to start. -When booted up, the node will generate a bunch of files and directories in addition to the ones covered above: +The ``runnodes`` script creates a terminal tab/window for each node: .. sourcecode:: none - . - ├── artemis - ├── attachments - ├── cache - ├── certificates - ├── corda.jar - ├── identity-private-key - ├── identity-public - ├── logs - ├── node.conf - ├── persistence.mv.db - └── plugins + ______ __ + / ____/ _________/ /___ _ + / / __ / ___/ __ / __ `/ It's kind of like a block chain but + / /___ /_/ / / / /_/ / /_/ / cords sounded healthier than chains. + \____/ /_/ \__,_/\__,_/ -Notably: + --- Corda Open Source 0.12.1 (da47f1c) ----------------------------------------------- -* **artemis** contains the internal files for Artemis MQ, our message broker. -* **attachments** contains any persisted attachments. -* **certificates** contains the certificate store. -* **identity-private-key** is the node's private key. -* **identity-public** is the node's public key. -* **logs** contains the node's log files. -* **persistence.mv.db** is the h2 database where transactions and other data is persisted. + 📚 New! Training now available worldwide, see https://corda.net/corda-training/ -Additional files and folders are added as the node is running. + Logs can be found in : /Users/joeldudley/Desktop/cordapp-tutorial/kotlin-source/build/nodes/NodeA/logs + Database connection url is : jdbc:h2:tcp://10.163.199.132:54763/node + Listening on address : 127.0.0.1:10005 + RPC service listening on address : localhost:10006 + Loaded plugins : com.example.plugin.ExamplePlugin + Node for "NodeA" started up and registered in 35.0 sec -Running the example CorDapp via IntelliJ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To run the example CorDapp via IntelliJ you can use the ``Run Example CorDapp`` run configuration. Select it from the drop -down menu at the top right-hand side of the IDE and press the green arrow to start the nodes. See image below: + Welcome to the Corda interactive shell. + Useful commands include 'help' to see what is available, and 'bye' to shut down the node. + + Fri Jul 07 10:33:47 BST 2017>>> + +The script will also create a webserver terminal tab for each node: + +.. sourcecode:: none + + Logs can be found in /Users/joeldudley/Desktop/cordapp-tutorial/kotlin-source/build/nodes/NodeA/logs/web + Starting as webserver: localhost:10007 + Webserver started up in 42.02 sec + +Depending on your machine, it usually takes around 60 seconds for the nodes to finish starting up. If you want to +ensure that all the nodes are running OK, you can query the 'status' end-point located at +``http://localhost:[port]/api/status`` (e.g. ``http://localhost:10007/api/status`` for ``NodeA``). + +IntelliJ: Building and running the example CorDapp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +To run the example CorDapp via IntelliJ you can use the ``Run Example CorDapp - Kotlin`` run configuration. Select it +from the drop-down menu at the top right-hand side of the IDE and press the green arrow to start the nodes: .. image:: resources/run-config-drop-down.png :width: 400 -The node driver defined in ``/src/main/kotlin-source/com/example/Main.kt`` allows you to specify how many nodes you would like -to run and the various configuration settings for each node. With the example CorDapp, the Node driver starts four nodes -and sets up an RPC user for all but the "Controller" node (which hosts the notary Service and network map service): +The node driver defined in ``/src/test/kotlin/com/example/Main.kt`` allows you to specify how many nodes you would like +to run and the configuration settings for each node. With the example CorDapp, the driver starts up four nodes +and adds an RPC user for all but the "Controller" node (which serves as the notary and network map service): .. sourcecode:: kotlin - fun main(args: Array) { - // No permissions required as we are not invoking flows. - val user = User("user1", "test", permissions = setOf()) - driver(dsl = { - startNode("Controller", setOf(ServiceInfo(ValidatingNotaryService.type))) - startNode("NodeA", rpcUsers = listOf(user)) - startNode("NodeB", rpcUsers = listOf(user)) - startNode("NodeC", rpcUsers = listOf(user)) - waitForAllNodesToFinish() - }, isDebug = true) - } + fun main(args: Array) { + // No permissions required as we are not invoking flows. + val user = User("user1", "test", permissions = setOf()) + driver(isDebug = true) { + startNode(X500Name("CN=Controller,O=R3,OU=corda,L=London,C=UK"), setOf(ServiceInfo(ValidatingNotaryService.type))) + val (nodeA, nodeB, nodeC) = Futures.allAsList( + startNode(X500Name("CN=NodeA,O=NodeA,L=London,C=UK"), rpcUsers = listOf(user)), + startNode(X500Name("CN=NodeB,O=NodeB,L=New York,C=US"), rpcUsers = listOf(user)), + startNode(X500Name("CN=NodeC,O=NodeC,L=Paris,C=FR"), rpcUsers = listOf(user))).getOrThrow() -To stop the nodes, press the red "stop" button at the top right-hand side of the IDE. + startWebserver(nodeA) + startWebserver(nodeB) + startWebserver(nodeC) -The node driver can also be used to as a basis for `debugging your CorDapp`_ + waitForAllNodesToFinish() + } + } + +To stop the nodes, press the red square button at the top right-hand side of the IDE, next to the run configurations. + +We'll look later at how the node driver can be useful for `debugging your CorDapp`_. Interacting with the example CorDapp ------------------------------------ Via HTTP ~~~~~~~~ +The CorDapp defines several HTTP API end-points and a web front-end. The end-points allow you to list your existing +IOUs, agree new IOUs, and see who is on the network. -The CorDapp defines a few HTTP API end-points and also serves some static web content. The end-points allow you to -list IOUs and add IOUs. +The nodes are running locally on the following ports: -The nodes can be found using the following port numbers, defined in build.gradle and the respective node.conf file for -each node found in `kotlin-source/build/nodes/NodeX`` etc: +* Controller: ``localhost:10004`` +* NodeA: ``localhost:10007`` +* NodeB: ``localhost:10010`` +* NodeC: ``localhost:10013`` -* Controller: ``localhost:10003`` -* NodeA: ``localhost:10005`` -* NodeB: ``localhost:10007`` -* NodeC: ``localhost:10009`` +These ports are defined in build.gradle and in each node's node.conf file under ``kotlin-source/build/nodes/NodeX``. -Note that the ``deployNodes`` Gradle task is used to generate the ``node.conf`` files for each node. - -As the nodes start-up they should tell you which host and port the embedded web server is running on. The API endpoints -served are as follows: +As the nodes start up, they should tell you which port their embedded web server is running on. The available API +endpoints are: * ``/api/example/me`` * ``/api/example/peers`` * ``/api/example/ious`` * ``/api/example/{COUNTERPARTY}/create-iou`` -The static web content is served from ``/web/example``. +The web front-end is served from ``/web/example``. -An IOU can be created via accessing the ``api/example/create-iou`` end-point directly or through the +An IOU can be created by sending a PUT request to the ``api/example/create-iou`` end-point directly, or by using the the web form hosted at ``/web/example``. - .. warning:: **The content in ``web/example`` is only available for demonstration purposes and does not implement any - anti-XSS, anti-XSRF or any other security techniques. Do not copy such code directly into products meant for production use.** +.. warning:: The content in ``web/example`` is only available for demonstration purposes and does not implement + anti-XSS, anti-XSRF or any other security techniques. Do not use this code in production. -**Submitting an IOU via HTTP API:** +**Creating an IOU via the HTTP API:** -To create an IOU from NodeA to NodeB, use: +To create an IOU between NodeA and NodeB, we would run the following from the command line: .. sourcecode:: bash - echo '{"value": "1"}' | cURL -T - -H 'Content-Type: application/json' http://localhost:10005/api/example/NodeB/create-iou + echo '{"value": "1"}' | cURL -T - -H 'Content-Type: application/json' http://localhost:10007/api/example/NodeB/create-iou -Note the port number ``10005`` (NodeA) and NodeB referenced in the API end-point path. This command instructs NodeA to -create and send an IOU to NodeB. Upon verification and completion of the process, both nodes (but not NodeC) will -have a signed, notarised copy of the IOU. +Note that both NodeA's port number (``10007``) and NodeB are referenced in the PUT request path. This command instructs +NodeA to agree an IOU with NodeB. Once the process is complete, both nodes will have a signed, notarised copy of the +IOU. NodeC will not. -**Submitting an IOU via web/example:** +**Submitting an IOU via the web front-end:** -Navigate to the "create IOU" button at the top left of the page, and enter the IOU details - e.g. +Navigate to ``/web/example``, click the "create IOU" button at the top-left of the page, and enter the IOU details into +the web-form. The IOU must have a value of between 1 and 99. .. sourcecode:: none Counter-party: Select from list - Order Number: 1 - Delivery Date: 2018-09-15 - City: London - Country Code: GB - Item name: Wow such item - Item amount: 5 + Value (Int): 5 -and click submit (note you can add additional item types and amounts). Upon pressing submit, the modal dialogue should close. -To check what validation is performed over the IOU data, have a look at the ``IOUContract.Create`` class in -``IOUContract.kt`` which defines the following contract constraints (among others not included here): - -.. sourcecode:: kotlin - - // Generic constraints around the IOU transaction. - "No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty()) - "Only one output state should be created." using (tx.outputs.size == 1) - val out = tx.outputs.single() as IOUState - "The sender and the recipient cannot be the same entity." using (out.sender != out.recipient) - "All of the participants must be signers." using (command.signers.containsAll(out.participants)) - - // IOU-specific constraints. - "The IOU's value must be non-negative." using (out.iou.value > 0) +And click submit. Upon clicking submit, the modal dialogue will close, and the nodes will agree the IOU. **Once an IOU has been submitted:** -Inspect the terminal windows for the nodes. Assume all of the above contract constraints are met, you should see some -activity in the terminal windows for NodeA and NodeB (note: the green ticks are only visible on unix/mac): - -*NodeA:* +Assuming all went well, you should see some activity in NodeA's web-server terminal window: .. sourcecode:: none - ✅ Generating transaction based on new IOU. - ✅ Verifying contract constraints. - ✅ Signing transaction with our private key. - ✅ Sending proposed transaction to recipient for review. - ✅ Done + >> Generating transaction based on new IOU. + >> Verifying contract constraints. + >> Signing transaction with our private key. + >> Gathering the counterparty's signature. + >> Structural step change in child of Gathering the counterparty's signature. + >> Collecting signatures from counter-parties. + >> Verifying collected signatures. + >> Done + >> Obtaining notary signature and recording transaction. + >> Structural step change in child of Obtaining notary signature and recording transaction. + >> Requesting signature by notary service + >> Broadcasting transaction to participants + >> Done + >> Done -*NodeB:* - -.. sourcecode:: none - - ✅ Receiving proposed transaction from sender. - ✅ Verifying signatures and contract constraints. - ✅ Signing proposed transaction with our private key. - ✅ Obtaining notary signature and recording transaction. - ✅ Requesting signature by notary service - ✅ Requesting signature by Notary service - ✅ Validating response from Notary service - ✅ Broadcasting transaction to participants - ✅ Done - -*NodeC:* - -.. sourcecode:: none - - You shouldn't see any activity. - -Next you can view the newly created IOU by accessing the vault of NodeA or NodeB: +You can view the newly-created IOU by accessing the vault of NodeA or NodeB: *Via the HTTP API:* -For NodeA. navigate to http://localhost:10005/api/example/ious. For NodeB, -navigate to http://localhost:10007/api/example/ious. +* NodeA's vault: Navigate to http://localhost:10007/api/example/ious +* NodeB's vault: Navigate to http://localhost:10010/api/example/ious *Via web/example:* -Navigate to http://localhost:10005/web/example the refresh button in the top left-hand side of the page. You should -see the newly created agreement on the page. +* NodeA: Navigate to http://localhost:10007/web/example and hit the "refresh" button +* NodeA: Navigate to http://localhost:10010/web/example and hit the "refresh" button -Using the h2 web console -~~~~~~~~~~~~~~~~~~~~~~~~ +If you access the vault or web front-end of NodeC (on ``localhost:10013``), there will be no IOUs. This is because +NodeC was not involved in this transaction. -You can connect to the h2 database to see the current state of the ledger, among other data such as the current state of -the network map cache. Firstly, navigate to the folder where you downloaded the h2 web console as part of the pre-requisites -section, above. Change directories to the bin folder: +Via the interactive shell (terminal only) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Once a node has been started via the terminal, it will display an interactive shell: -``cd h2/bin`` +.. sourcecode:: none -Where there are a bunch of shell scripts and batch files. Run the web console: + Welcome to the Corda interactive shell. + Useful commands include 'help' to see what is available, and 'bye' to shut down the node. -Unix: + Fri Jul 07 16:36:29 BST 2017>>> -``sh h2.sh`` +You can see a list of the flows that your node can run using `flow list`. In our case, this will return the following +list: -Windows: +.. sourcecode:: none -``h2.bat`` + com.example.flow.ExampleFlow$Initiator + net.corda.flows.CashExitFlow + net.corda.flows.CashIssueFlow + net.corda.flows.CashPaymentFlow + net.corda.flows.ContractUpgradeFlow -The h2 web console should start up in a web browser tab. To connect we first need to obtain a JDBC connection string. Each -node outputs its connection string in the terminal window as it starts up. In a terminal window where a node is running, -look for the following string: +We can create a new IOU using the ``ExampleFlow$Initiator`` flow. For example, from the interactive shell of NodeA, you +can agree an IOU of 50 with NodeB by running ``flow start Initiator iouValue: 50, otherParty: NodeB``. -``Database connection URL is : jdbc:h2:tcp://10.18.0.150:56736/node`` +This will print out the following progress steps: -you can use the string on the right to connect to the h2 database: just paste it in to the JDBC URL field and click Connect. -You will be presented with a web application that enumerates all the available tables and provides an interface for you to -query them using SQL. +.. sourcecode:: none -Using the Example RPC client + ✅ Generating transaction based on new IOU. + ✅ Verifying contract constraints. + ✅ Signing transaction with our private key. + ✅ Gathering the counterparty's signature. + ✅ Collecting signatures from counter-parties. + ✅ Verifying collected signatures. + ✅ Obtaining notary signature and recording transaction. + ✅ Requesting signature by notary service + Requesting signature by Notary service + Validating response from Notary service + ✅ Broadcasting transaction to participants + ✅ Done + +We can also issue RPC operations to the node via the interactive shell. Type ``run`` to see the full list of available +operations. + +We can see a list of the states in our node's vault using ``run vaultAndUpdates``: + +.. sourcecode:: none + + --- + first: + - state: + data: + iou: + value: 50 + sender: "CN=NodeB,O=NodeB,L=New York,C=US" + recipient: "CN=NodeA,O=NodeA,L=London,C=UK" + linearId: + externalId: null + id: "84628565-2688-45ef-bb06-aae70fcf3be7" + contract: + legalContractReference: "4DDE2A47C361106CBAEC06CC40FE418A994822A3C8054851FEECD51207BFAF82" + participants: + - "CN=NodeB,O=NodeB,L=New York,C=US" + - "CN=NodeA,O=NodeA,L=London,C=UK" + notary: "CN=Controller,O=R3,OU=corda,L=London,C=UK,OU=corda.notary.validating" + encumbrance: null + ref: + txhash: "52A1B18E6ABD535EF36B2075469B01D2EF888034F721C4BECD26F40355C8C9DC" + index: 0 + second: "(observable)" + +We can also see the transactions stored in our node's local storage using ``run verifiedTransactions`` (we've +abbreviated the output below): + +.. sourcecode:: none + + first: + - txBits: "Y29yZGEAAAEOAQEAamF2YS51dGlsLkFycmF5TGlz9AABAAABAAEBAW5ldC5jb3JkYS5jb3JlLmNvbnRyYWN0cy5UcmFuc2FjdGlvblN0YXTlA1RyYW5zYWN0aW9uU3RhdGUuZGF04VRyYW5zYWN0aW9uU3RhdGUuZW5jdW1icmFuY+VUcmFuc2FjdGlvblN0YXRlLm5vdGFy+WkBAmNvbS5leGFtcGxlLnN0YXRlLklPVVN0YXTlBElPVVN0YXRlLmlv9UlPVVN0YXRlLmxpbmVhcknkSU9VU3RhdGUucmVjaXBpZW70SU9VU3RhdGUuc2VuZGXyDQEBSU9VLnZhbHXlAWQCAQA0ADIBAlVuaXF1ZUlkZW50aWZpZXIuZXh0ZXJuYWxJ5FVuaXF1ZUlkZW50aWZpZXIuaeQBgDAvAC0BAlVVSUQubGVhc3RTaWdCaXTzVVVJRC5tb3N0U2lnQml08wmxkIaDnsaq+YkNDAsACaHovZfbpr2d9wMCAQACAQBIAEYBAkFic3RyYWN0UGFydHkub3duaW5nS2X5UGFydHkubmFt5SIuIOnhdbFQY3EL/LQD90w6y+kCfj4x8UWXaqKtW68GBPlnREMAQTkwPjEOMAwGA1UEAwwFTm9kZUExDjAMBgNVBAoMBU5vZGVBMQ8wDQYDVQQHDAZMb25kb24xCzAJBgNVBAYTAlVLAgEAJgAkASIuIHI7goTSxPMdaRgJgGJVLQbFEzE++qJeYbEbQjrYxzuVRkUAQzkwQDEOMAwGA1UEAwwFTm9kZUIxDjAMBgNVBAoMBU5vZGVCMREwDwYDVQQHDAhOZXcgWW9yazELMAkGA1UEBhMCVVMCAQABAAABAAAkASIuIMqulslvpZ0PaM6fdyFZm+JsDGkuJ7xWnL3zB6PqpzANdwB1OTByMRMwEQYDVQQDDApDb250cm9sbGVyMQswCQYDVQQKDAJSMzEOMAwGA1UECwwFY29yZGExDzANBgNVBAcMBkxvbmRvbjELMAkGA1UEBhMCVUsxIDAeBgNVBAsMF2NvcmRhLm5vdGFyeS52YWxpZGF0aW5nAQAAAQABAQNuZXQuY29yZGEuY29yZS5jb250cmFjdHMuQ29tbWFu5AJDb21tYW5kLnNpZ25lcvNDb21tYW5kLnZhbHXlRwEAAi4gcjuChNLE8x1pGAmAYlUtBsUTMT76ol5hsRtCOtjHO5UuIOnhdbFQY3EL/LQD90w6y+kCfj4x8UWXaqKtW68GBPlnADMBBGNvbS5leGFtcGxlLmNvbnRyYWN0LklPVUNvbnRyYWN0JENvbW1hbmRzJENyZWF05QAAAQVuZXQuY29yZGEuY29yZS5pZGVudGl0eS5QYXJ0+SIuIMqulslvpZ0PaM6fdyFZm+JsDGkuJ7xWnL3zB6PqpzANAHU5MHIxEzARBgNVBAMMCkNvbnRyb2xsZXIxCzAJBgNVBAoMAlIzMQ4wDAYDVQQLDAVjb3JkYTEPMA0GA1UEBwwGTG9uZG9uMQswCQYDVQQGEwJVSzEgMB4GA1UECwwXY29yZGEubm90YXJ5LnZhbGlkYXRpbmcAAQACLiByO4KE0sTzHWkYCYBiVS0GxRMxPvqiXmGxG0I62Mc7lS4g6eF1sVBjcQv8tAP3TDrL6QJ+PjHxRZdqoq1brwYE+WcBBm5ldC5jb3JkYS5jb3JlLmNvbnRyYWN0cy5UcmFuc2FjdGlvblR5cGUkR2VuZXJh7AA=" + sigs: + - "cRgJlF8cUMMooyaV2OIKmR4/+3XmMsEPsbdlhU5YqngRhqgy9+tLzylh7kvWOhYZ4hjjOfrazLoZ6uOx6BAMCQ==" + - "iGLRDIbhlwguMz6yayX5p6vfQcAsp8haZc1cLGm7DPDIgq6hFyx2fzoI03DjXAV/mBT1upcUjM9UZ4gbRMedAw==" + id: "52A1B18E6ABD535EF36B2075469B01D2EF888034F721C4BECD26F40355C8C9DC" + tx: + inputs: [] + attachments: [] + outputs: + - data: + iou: + value: 50 + sender: "CN=NodeB,O=NodeB,L=New York,C=US" + recipient: "CN=NodeA,O=NodeA,L=London,C=UK" + linearId: + externalId: null + id: "84628565-2688-45ef-bb06-aae70fcf3be7" + contract: + legalContractReference: "4DDE2A47C361106CBAEC06CC40FE418A994822A3C8054851FEECD51207BFAF82" + participants: + - "CN=NodeB,O=NodeB,L=New York,C=US" + - "CN=NodeA,O=NodeA,L=London,C=UK" + notary: "CN=Controller,O=R3,OU=corda,L=London,C=UK,OU=corda.notary.validating" + encumbrance: null + commands: + - value: {} + signers: + - "8Kqd4oWdx4KQAVc3u5qvHZTGJxMtrShFudAzLUTdZUzbF9aPQcCZD5KXViC" + - "8Kqd4oWdx4KQAVcBx98LBHwXwC3a7hNptQomrg9mq2ScY7t1Qqsyk5dCNAr" + notary: "CN=Controller,O=R3,OU=corda,L=London,C=UK,OU=corda.notary.validating" + type: {} + timeWindow: null + mustSign: + - "8Kqd4oWdx4KQAVc3u5qvHZTGJxMtrShFudAzLUTdZUzbF9aPQcCZD5KXViC" + - "8Kqd4oWdx4KQAVcBx98LBHwXwC3a7hNptQomrg9mq2ScY7t1Qqsyk5dCNAr" + id: "52A1B18E6ABD535EF36B2075469B01D2EF888034F721C4BECD26F40355C8C9DC" + merkleTree: ... + availableComponents: ... + availableComponentHashes: ... + serialized: "Y29yZGEAAAEOAQEAamF2YS51dGlsLkFycmF5TGlz9AABAAABAAEBAW5ldC5jb3JkYS5jb3JlLmNvbnRyYWN0cy5UcmFuc2FjdGlvblN0YXTlA1RyYW5zYWN0aW9uU3RhdGUuZGF04VRyYW5zYWN0aW9uU3RhdGUuZW5jdW1icmFuY+VUcmFuc2FjdGlvblN0YXRlLm5vdGFy+WkBAmNvbS5leGFtcGxlLnN0YXRlLklPVVN0YXTlBElPVVN0YXRlLmlv9UlPVVN0YXRlLmxpbmVhcknkSU9VU3RhdGUucmVjaXBpZW70SU9VU3RhdGUuc2VuZGXyDQEBSU9VLnZhbHXlAWQCAQA0ADIBAlVuaXF1ZUlkZW50aWZpZXIuZXh0ZXJuYWxJ5FVuaXF1ZUlkZW50aWZpZXIuaeQBgDAvAC0BAlVVSUQubGVhc3RTaWdCaXTzVVVJRC5tb3N0U2lnQml08wmxkIaDnsaq+YkNDAsACaHovZfbpr2d9wMCAQACAQBIAEYBAkFic3RyYWN0UGFydHkub3duaW5nS2X5UGFydHkubmFt5SIuIOnhdbFQY3EL/LQD90w6y+kCfj4x8UWXaqKtW68GBPlnREMAQTkwPjEOMAwGA1UEAwwFTm9kZUExDjAMBgNVBAoMBU5vZGVBMQ8wDQYDVQQHDAZMb25kb24xCzAJBgNVBAYTAlVLAgEAJgAkASIuIHI7goTSxPMdaRgJgGJVLQbFEzE++qJeYbEbQjrYxzuVRkUAQzkwQDEOMAwGA1UEAwwFTm9kZUIxDjAMBgNVBAoMBU5vZGVCMREwDwYDVQQHDAhOZXcgWW9yazELMAkGA1UEBhMCVVMCAQABAAABAAAkASIuIMqulslvpZ0PaM6fdyFZm+JsDGkuJ7xWnL3zB6PqpzANdwB1OTByMRMwEQYDVQQDDApDb250cm9sbGVyMQswCQYDVQQKDAJSMzEOMAwGA1UECwwFY29yZGExDzANBgNVBAcMBkxvbmRvbjELMAkGA1UEBhMCVUsxIDAeBgNVBAsMF2NvcmRhLm5vdGFyeS52YWxpZGF0aW5nAQAAAQABAQNuZXQuY29yZGEuY29yZS5jb250cmFjdHMuQ29tbWFu5AJDb21tYW5kLnNpZ25lcvNDb21tYW5kLnZhbHXlRwEAAi4gcjuChNLE8x1pGAmAYlUtBsUTMT76ol5hsRtCOtjHO5UuIOnhdbFQY3EL/LQD90w6y+kCfj4x8UWXaqKtW68GBPlnADMBBGNvbS5leGFtcGxlLmNvbnRyYWN0LklPVUNvbnRyYWN0JENvbW1hbmRzJENyZWF05QAAAQVuZXQuY29yZGEuY29yZS5pZGVudGl0eS5QYXJ0+SIuIMqulslvpZ0PaM6fdyFZm+JsDGkuJ7xWnL3zB6PqpzANAHU5MHIxEzARBgNVBAMMCkNvbnRyb2xsZXIxCzAJBgNVBAoMAlIzMQ4wDAYDVQQLDAVjb3JkYTEPMA0GA1UEBwwGTG9uZG9uMQswCQYDVQQGEwJVSzEgMB4GA1UECwwXY29yZGEubm90YXJ5LnZhbGlkYXRpbmcAAQACLiByO4KE0sTzHWkYCYBiVS0GxRMxPvqiXmGxG0I62Mc7lS4g6eF1sVBjcQv8tAP3TDrL6QJ+PjHxRZdqoq1brwYE+WcBBm5ldC5jb3JkYS5jb3JlLmNvbnRyYWN0cy5UcmFuc2FjdGlvblR5cGUkR2VuZXJh7AA=" + second: "(observable)" + +The same states and transactions will be present on NodeB, who was NodeA's counterparty in the creation of the IOU. +However, the vault and local storage of NodeC will remain empty, since NodeC was not involved in the transaction. + +Via the h2 web console +~~~~~~~~~~~~~~~~~~~~~~ +You can connect directly to your node's database to see its stored states, transactions and attachments. To do so, +please follow the instructions in :doc:`node-database`. + +Using the example RPC client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` file is a simple utility that uses the client +RPC library to connect to a node. It will log any existing IOUs and listen for any future IOUs. If you haven't created +any IOUs when you first connect to one of the nodes, the client will simply log any future IOUs that are agreed. -The ``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` file is a simple utility which uses the client RPC library -to connect to a node and log the created IOUs. It will log any existing IOUs and listen for any future -IOUs. If you haven't created any IOUs when you connect to one of the Nodes via RPC then the client will log -and future IOUs which are agreed. +*Running the client via IntelliJ:* -To build the client use the following gradle task: - -``./gradlew runExampleClientRPC`` - -*To run the client, via IntelliJ:* - -Select the 'Run Example RPC Client' run configuration which, by default, connects to NodeA (Artemis port 10004). Click the +Select the 'Run Example RPC Client' run configuration which, by default, connects to NodeA (Artemis port 10007). Click the Green Arrow to run the client. You can edit the run configuration to connect on a different port. -*Via command line:* +*Running the client via the command line:* Run the following gradle task: -``./gradlew runExampleClientRPC localhost:10004`` +``./gradlew runExampleClientRPC localhost:10007`` -To close the application use ``ctrl+C``. For more information on the client RPC interface and how to build an RPC client -application see: +You can close the application using ``ctrl+C``. + +For more information on the client RPC interface and how to build an RPC client application, see: * :doc:`Client RPC documentation ` * :doc:`Client RPC tutorial ` -Extending the example CorDapp ------------------------------ - -cordapp-tutorial project structure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The example CorDapp has the following directory structure: - -.. sourcecode:: none - - . cordapp-tutorial - ├── README.md - ├── LICENSE - ├── build.gradle - ├── config - │   ├── ... - ├── gradle - │   └── ... - ├── gradle.properties - ├── gradlew - ├── gradlew.bat - ├── lib - │   ├── ... - ├── settings.gradle - ├── kotlin-source - │ └── src - │ ├── main - │    │   ├── kotlin - │    │   │   └── com - │    │   │   └── example - │    │   │   ├── Main.kt - │    │   │   ├── api - │    │   │   │   └── ExampleApi.kt - │    │   │   ├── client - │    │   │   │   └── ExampleClientRPC.kt - │    │   │   ├── contract - │    │   │   │   ├── IOUContract.kt - │    │   │   │   └── IOUState.kt - │    │   │   ├── model - │    │   │   │   └── IOU.kt - │    │   │   ├── plugin - │    │   │   │   └── ExamplePlugin.kt - │    │   │   └── flow - │    │   │   └── ExampleFlow.kt - │    │   │   └── service - │    │   │   └── ExampleService.kt - │ │   ├── python - │ │   └── resources - │ │   ├── META-INF - │ │   │   └── services - │   │   │   ├── net.corda.core.node.CordaPluginRegistry - │   │ │ └── net.corda.webserver.services.WebServerPluginRegistry - │ │   ├── certificates - │ │   │   ├── readme.txt - │ │   │   ├── sslkeystore.jks - │ │   │   └── truststore.jks - │ │   └── exampleWeb - │ │   ├── index.html - │ │   └── js - │ │   └── example.js - │ └── test - │ ├── java - │ ├── kotlin - │ │   └── com - │ │   └── example - │ │   └── ExampleTest.kt - │    └── resources - └── java-source - └── src - ├── main -    │   ├── java -    │   │   └── com -    │   │   └── example -    │   │   ├── Main.java -    │   │   ├── api -    │   │   │   └── ExampleApi.java -    │   │   ├── client -    │   │   │   └── ExampleClientRPC.java -    │   │   ├── contract -    │   │   │   ├── IOUContract.java -    │   │   │   └── IOUState.java -    │   │   ├── model -    │   │   │   └── IOU.java -    │   │   ├── plugin -    │   │   │   └── ExamplePlugin.java -    │   │   └── flow -    │   │   └── ExampleFlow.java -    │   │   └── service -    │   │   └── ExampleService.java - │   ├── python - │   └── resources - │   ├── META-INF - │   │   └── services -    │   │   ├── net.corda.core.node.CordaPluginRegistry -    │ │ └── net.corda.webserver.services.WebServerPluginRegistry - │   ├── certificates - │   │   ├── readme.txt - │   │   ├── sslkeystore.jks - │   │   └── truststore.jks - │   └── exampleWeb - │   ├── index.html - │   └── js - │   └── example.js - └── test - ├── java - ├── kotlin - │   └── com - │   └── example - │   └── ExampleTest.kt -    └── resources - -In the file structure above, the most important files and directories to note are: - -* The **root directory** contains some gradle files, a README and a LICENSE. -* **config** contains log4j configs. -* **gradle** contains the gradle wrapper, which allows the use of Gradle without installing it yourself and worrying - about which version is required. -* **lib** contains the Quasar.jar which is required for runtime instrumentation of classes by Quasar. -* **kotlin-source** contains the source code for the example CorDapp written in Kotlin. - * **kotlin-source/src/main/kotlin** contains the source code for the example CorDapp. - * **kotlin-source/src/main/python** contains a python script which accesses nodes via RPC. - * **kotlin-source/src/main/resources** contains the certificate store, some static web content to be served by the nodes and the - PluginServiceRegistry file. - * **kotlin-source/src/test/kotlin** contains unit tests for protocols, contracts, etc. -* **java-source** contains the same source code, but written in java. This is an aid for users who do not want to develop in - Kotlin, and serves as an example of how CorDapps can be developed in any language targeting the JVM. - -Some elements are covered in more detail below. - -The build.gradle file -~~~~~~~~~~~~~~~~~~~~~ - -It is usually necessary to make a couple of changes to the ``build.gradle`` file. Here will cover the most pertinent bits. - -**The buildscript** - -The buildscript is always located at the top of the file. It determines which plugins, task classes, and other classes -are available for use in the rest of the build script. It also specifies version numbers for dependencies, among other -things. - -If you are working from a Corda SNAPSHOT release which you have publish to Maven local then ensure that -``corda_release_version`` is the same as the version of the Corda core modules you published to Maven local. If not then -change the ``kotlin_version`` property. Also, if you are working from a previous cordapp-tutorial milestone release, then -be sure to ``git checkout`` the correct version of the example CorDapp from the ``cordapp-tutorial`` repo. - -.. sourcecode:: groovy - - buildscript { - ext.kotlin_version = '1.0.4' - ext.corda_release_version = '0.5-SNAPSHOT' // Ensure this version is the same as the corda core modules you are using. - ext.quasar_version = '0.7.6' - ext.jersey_version = '2.23.1' - - repositories { - ... - } - - dependencies { - ... - } - } - -**Project dependencies** - -If you have any additional external dependencies for your CorDapp then add them below the comment at the end of this -code snippet.package. Use the standard format: - -``compile "{groupId}:{artifactId}:{versionNumber}"`` - -.. sourcecode:: groovy - - dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - testCompile group: 'junit', name: 'junit', version: '4.11' - - // Corda integration dependencies - compile "net.corda:client:$corda_release_version" - compile "net.corda:core:$corda_release_version" - compile "net.corda:contracts:$corda_release_version" - compile "net.corda:node:$corda_release_version" - compile "net.corda:corda:$corda_release_version" - compile "net.corda:test-utils:$corda_release_version" - - ... - - // Cordapp dependencies - // Specify your cordapp's dependencies below, including dependent cordapps - } - -For further information about managing dependencies with `look at the Gradle docs `_. - -**CordFormation** - -This is the local node deployment system for CorDapps, the nodes generated are intended to be used for experimenting, -debugging, and testing node configurations but not intended for production or testnet deployment. - -In the CorDapp build.gradle file you'll find a ``deployNodes`` task, this is where you configure the nodes you would -like to deploy for testing. See further details below: - -.. sourcecode:: groovy - - task deployNodes(type: com.r3corda.plugins.Cordform, dependsOn: ['jar']) { - directory "./kotlin-source/build/nodes" // The output directory. - networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB" // The distinguished name of the node to be used as the network map. - node { - name "CN=Controller,O=R3,OU=corda,L=London,C=GB" // Distinguished name of node to be deployed. - advertisedServices = ["corda.notary.validating"] // A list of services you wish the node to offer. - p2pPort 10002 - rpcPort 10003 // Usually 1 higher than the messaging port. - webPort 10004 // Usually 1 higher than the RPC port. - cordapps = [] // Add package names of CordaApps. - } - node { // Create an additional node. - name "CN=NodeA,O=R3,OU=corda,L=London,C=GB" - advertisedServices = [] - p2pPort 10005 - rpcPort 10006 - webPort 10007 - cordapps = [] - } - ... - } - -You can add any number of nodes, with any number of services / CorDapps by editing the templates in ``deployNodes``. The -only requirement is that you must specify a node to run as the network map service and one as the notary service. - -.. note:: CorDapps in the current cordapp-tutorial project are automatically registered with all nodes defined in - ``deployNodes``, although we expect this to change in the near future. - -.. warning:: Make sure that there are no port clashes! - -When you are finished editing your *CordFormation* the changes will be reflected the next time you run ``./gradlew deployNodes``. - -Service Provider Configuration File -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you are building a CorDapp from scratch or adding a new CorDapp to the cordapp-tutorial project then you must provide -a reference to your sub-class of ``CordaPluginRegistry`` or ``WebServerPluginRegistry`` (for Wep API) in the provider-configuration file -located in the ``resources/META-INF/services`` directory. - -Re-Deploying Your Nodes Locally -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you need to create any additional nodes you can do it via the ``build.gradle`` file as discussed above in -``the build.gradle file`` and in more detail in the "cordFormation" section. - -You may also wish to edit the ``/kotlin-source/build/nodes//node.conf`` files for your nodes. For more information on -doing this, see the :doc:`Corda configuration file ` page. - -Once you have made some changes to your CorDapp you can redeploy it with the following command: - -Unix/Mac OSX: ``./gradlew deployNodes`` - -Windows: ``gradlew.bat deployNodes`` - Running Nodes Across Machines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The nodes can also be set up to communicate between separate machines on the -same subnet. +----------------------------- +The nodes can also be configured to communicate across the network when residing on different machines. After deploying the nodes, navigate to the build folder (``kotlin-source/build/nodes`` or ``java-source/build/nodes``) -and move some of the individual node folders to separate machines on the same subnet (e.g. using a USB key). -It is important that no nodes - including the controller node - end up on more than one machine. Each computer -should also have a copy of ``runnodes`` and ``runnodes.bat``. +and move some of the individual node folders to separate machines (e.g. using a USB key). It is important that none of +the nodes - including the controller node - end up on more than one machine. Each computer should also have a copy of +``runnodes`` and ``runnodes.bat``. For example, you may end up with the following layout: @@ -843,45 +560,49 @@ You must now edit the configuration file for each node, including the controller and make the following changes: * Change the Artemis messaging address to the machine's IP address (e.g. ``p2pAddress="10.18.0.166:10006"``) -* Change the network map service details to the IP address of the machine where the - controller node is running and to its legal name (e.g. ``networkMapService.address="10.18.0.166:10002"`` and - ``networkMapService.legalName=controller``) (please note that the controller will not have the ``networkMapService`` config) +* Change the network map service's address to the IP address of the machine where the controller node is running + (e.g. ``networkMapService { address="10.18.0.166:10002" ...``). The controller will not have the + ``networkMapService`` config -Now run each node. +After starting each node, they should be able to see one another and agree IOUs among themselves. Debugging your CorDapp -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- +Debugging is done via IntelliJ as follows: -Debugging is done via IntelliJ and can be done using the following steps. - -1. Set your breakpoints. -2. Edit the node driver code in ``Main.kt`` to reflect how many nodes you wish to start along with any other - configuration options. For example, the below starts 4 nodes, with one being the network map service / notary and - sets up RPC credentials for 3 of the nodes. +1. Edit the node driver code in ``Main.kt`` to reflect the number of nodes you wish to start, along with any other + configuration options. For example, the code below starts 4 nodes, with one being the network map service and notary. + It also sets up RPC credentials for the three non-notary nodes .. sourcecode:: kotlin fun main(args: Array) { // No permissions required as we are not invoking flows. val user = User("user1", "test", permissions = setOf()) - driver(dsl = { - startNode("Controller", setOf(ServiceInfo(ValidatingNotaryService.type))) - startNode("NodeA", rpcUsers = listOf(user)) - startNode("NodeB", rpcUsers = listOf(user)) - startNode("NodeC", rpcUsers = listOf(user)) + driver(isDebug = true) { + startNode(X500Name("CN=Controller,O=R3,OU=corda,L=London,C=UK"), setOf(ServiceInfo(ValidatingNotaryService.type))) + val (nodeA, nodeB, nodeC) = Futures.allAsList( + startNode(X500Name("CN=NodeA,O=NodeA,L=London,C=UK"), rpcUsers = listOf(user)), + startNode(X500Name("CN=NodeB,O=NodeB,L=New York,C=US"), rpcUsers = listOf(user)), + startNode(X500Name("CN=NodeC,O=NodeC,L=Paris,C=FR"), rpcUsers = listOf(user))).getOrThrow() + + startWebserver(nodeA) + startWebserver(nodeB) + startWebserver(nodeC) + waitForAllNodesToFinish() - }, isDebug = true) + } } -3. Select and run the “Run Example CorDapp” run configuration in IntelliJ. -4. IntelliJ will build and run the CorDapp. Observe the console output for the remote debug ports. The “Controller” - node will generally be on port 5005, with NodeA on port 5006 an so-on. +2. Select and run the “Run Example CorDapp” run configuration in IntelliJ +3. IntelliJ will build and run the CorDapp. The remote debug ports for each node will be automatically generated and + printed to the terminal. For example: .. sourcecode:: none - Listening for transport dt_socket at address: 5008 - Listening for transport dt_socket at address: 5007 - Listening for transport dt_socket at address: 5006 + [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Working Directory: /Users/joeldudley/cordapp-tutorial/build/20170707142746/NodeA + [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Debug port: dt_socket:5007 -5. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to. -6. Run the “Debug CorDapp” run configuration. +4. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to +5. Run the “Debug CorDapp” run configuration +6. Set your breakpoints and start using your node. When your node hits a breakpoint, execution will pause \ No newline at end of file From fe9db6f1f7ef1e8c973d8021ea29c24204427723 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Fri, 21 Jul 2017 14:23:05 +0100 Subject: [PATCH 063/197] Remove links to Kryo from serialization "clients" (#1079) --- client/jackson/build.gradle | 2 + .../net/corda/jackson/JacksonSupportTest.kt | 3 +- .../corda/client/jfx/NodeMonitorModelTest.kt | 6 +- .../client/jfx/model/NodeMonitorModel.kt | 5 +- .../client/rpc/CordaRPCJavaClientTest.java | 54 ++-- .../corda/client/rpc/CordaRPCClientTest.kt | 2 +- .../net/corda/client/rpc/RPCStabilityTests.kt | 17 +- .../net/corda/client/rpc/CordaRPCClient.kt | 36 ++- .../corda/client/rpc/internal/RPCClient.kt | 14 +- .../rpc/internal/RPCClientProxyHandler.kt | 25 +- .../rpc/serialization/SerializationScheme.kt | 27 ++ .../rpc/ClientRPCInfrastructureTests.kt | 4 +- .../corda/client/rpc/RPCPermissionsTests.kt | 2 +- .../kotlin/net/corda/core/crypto/MetaData.kt | 4 +- .../net/corda/core/crypto/SignedData.kt | 3 +- .../core/crypto/composite/CompositeKey.kt | 5 +- .../corda/core/internal/WriteOnceProperty.kt | 18 ++ .../net/corda/core/serialization/Kryo.kt | 124 +-------- .../core/serialization/SerializationAPI.kt | 141 ++++++++++ .../core/serialization/SerializationToken.kt | 21 +- .../core/transactions/MerkleTransaction.kt | 5 +- .../core/transactions/WireTransaction.kt | 12 +- .../net/corda/core/utilities/ByteArrays.kt | 138 +++++++-- .../contracts/TransactionEncumbranceTests.kt | 27 +- .../contracts/TransactionGraphSearchTests.kt | 11 +- .../corda/core/contracts/TransactionTests.kt | 7 +- .../corda/core/crypto/CompositeKeyTests.kt | 3 +- .../core/crypto/PartialMerkleTreeTest.kt | 19 +- .../net/corda/core/crypto/SignedDataTest.kt | 12 +- .../core/crypto/TransactionSignatureTest.kt | 3 +- .../core/flows/ContractUpgradeFlowTest.kt | 2 +- .../core/flows/ResolveTransactionsFlowTest.kt | 7 +- .../core/flows/TransactionKeyFlowTests.kt | 18 +- .../core/node/AttachmentClassLoaderTests.kt | 82 ++---- .../net/corda/core/serialization/KryoTests.kt | 70 ++--- .../serialization/SerializationTokenTest.kt | 77 ++--- .../TransactionSerializationTests.kt | 2 +- .../corda/core/utilities/KotlinUtilsTest.kt | 3 +- .../corda/core/utilities/NonEmptySetTest.kt | 14 +- .../core/utilities/ProgressTrackerTest.kt | 20 +- .../corda/docs/IntegrationTestingTutorial.kt | 2 +- .../corda/contracts/CommercialPaperTests.kt | 3 +- .../net/corda/contracts/asset/CashTests.kt | 32 ++- .../corda/contracts/asset/ObligationTests.kt | 16 ++ .../flows/BroadcastTransactionFlowTest.kt | 9 + .../main/kotlin/net/corda/nodeapi/RPCApi.kt | 31 +-- .../kotlin/net/corda/nodeapi/RPCStructures.kt | 2 +- .../serialization/SerializationScheme.kt | 263 ++++++++++++++++++ .../services/vault/schemas/VaultSchemaTest.kt | 7 +- .../kotlin/net/corda/node/BootTests.kt | 2 +- .../node/utilities/JDBCHashMapTestSuite.kt | 8 +- .../services/messaging/MQSecurityTest.kt | 2 +- .../kotlin/net/corda/node/internal/Node.kt | 22 +- .../node/serialization/SerializationScheme.kt | 26 ++ .../node/services/messaging/Messaging.kt | 3 +- .../services/messaging/NodeMessagingClient.kt | 9 +- .../node/services/messaging/RPCServer.kt | 24 +- .../persistence/DBCheckpointStorage.kt | 6 +- .../statemachine/StateMachineManager.kt | 55 +--- .../services/vault/HibernateVaultQueryImpl.kt | 4 +- .../node/services/vault/NodeVaultService.kt | 14 +- .../net/corda/node/utilities/JDBCHashMap.kt | 9 +- .../utilities/ServiceIdentityGenerator.kt | 1 - .../services/vault/VaultQueryJavaTests.java | 75 ++--- .../node/messaging/InMemoryMessagingTests.kt | 17 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 22 +- .../corda/node/services/NotaryChangeTests.kt | 2 +- .../database/HibernateConfigurationTest.kt | 31 +-- .../database/RequeryConfigurationTest.kt | 12 +- .../events/NodeSchedulerServiceTest.kt | 4 + .../messaging/ArtemisMessagingTests.kt | 2 +- .../network/InMemoryIdentityServiceTests.kt | 76 ++--- .../network/InMemoryNetworkMapCacheTest.kt | 8 +- .../persistence/DBCheckpointStorageTests.kt | 5 +- .../persistence/DBTransactionStorageTests.kt | 7 +- .../DistributedImmutableMapTests.kt | 5 +- .../PersistentUniquenessProviderTests.kt | 3 +- .../services/vault/NodeVaultServiceTest.kt | 2 +- .../node/services/vault/VaultQueryTests.kt | 4 +- .../node/services/vault/VaultWithCashTest.kt | 15 +- .../kotlin/net/corda/irs/IRSDemoTest.kt | 2 +- .../corda/irs/api/NodeInterestRatesTest.kt | 5 +- .../kotlin/net/corda/irs/contract/IRSTests.kt | 37 ++- .../net/corda/traderdemo/TraderDemoTest.kt | 4 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 15 +- .../kotlin/net/corda/testing/RPCDriver.kt | 9 +- .../corda/testing/SerializationTestHelpers.kt | 140 ++++++++++ .../testing/TestDependencyInjectionBase.kt | 19 ++ .../kotlin/net/corda/testing/driver/Driver.kt | 12 +- .../kotlin/net/corda/testing/node/MockNode.kt | 9 +- .../net/corda/testing/node/NodeBasedTest.kt | 9 +- .../kotlin/net/corda/verifier/Verifier.kt | 30 ++ 92 files changed, 1454 insertions(+), 722 deletions(-) create mode 100644 client/rpc/src/main/kotlin/net/corda/client/rpc/serialization/SerializationScheme.kt create mode 100644 core/src/main/kotlin/net/corda/core/internal/WriteOnceProperty.kt create mode 100644 core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/serialization/SerializationScheme.kt create mode 100644 node/src/main/kotlin/net/corda/node/serialization/SerializationScheme.kt create mode 100644 test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt create mode 100644 test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle index 1457f01bb4..ed76eb3e89 100644 --- a/client/jackson/build.gradle +++ b/client/jackson/build.gradle @@ -6,6 +6,8 @@ apply plugin: 'com.jfrog.artifactory' dependencies { compile project(':core') compile project(':finance') + testCompile project(':test-utils') + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" diff --git a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt index b1bbfa8f67..12a226f7b0 100644 --- a/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/jackson/JacksonSupportTest.kt @@ -7,6 +7,7 @@ import com.pholser.junit.quickcheck.runner.JUnitQuickcheck import net.corda.core.contracts.Amount import net.corda.core.contracts.USD import net.corda.core.testing.PublicKeyGenerator +import net.corda.testing.TestDependencyInjectionBase import net.i2p.crypto.eddsa.EdDSAPublicKey import org.junit.Test import org.junit.runner.RunWith @@ -15,7 +16,7 @@ import java.util.* import kotlin.test.assertEquals @RunWith(JUnitQuickcheck::class) -class JacksonSupportTest { +class JacksonSupportTest : TestDependencyInjectionBase() { companion object { val mapper = JacksonSupport.createNonRpcMapper() } diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 87a3c4da54..29617faad5 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -51,7 +51,7 @@ class NodeMonitorModelTest : DriverBasedTest() { lateinit var networkMapUpdates: Observable lateinit var newNode: (X500Name) -> NodeInfo - override fun setup() = driver { + override fun setup() = driver(initialiseSerialization = false) { val cashUser = User("user1", "test", permissions = setOf( startFlowPermission(), startFlowPermission(), @@ -72,14 +72,14 @@ class NodeMonitorModelTest : DriverBasedTest() { vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed() networkMapUpdates = monitor.networkMap.bufferUntilSubscribed() - monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) + monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false) rpc = monitor.proxyObservable.value!! val bobNodeHandle = startNode(BOB.name, rpcUsers = listOf(cashUser)).getOrThrow() bobNode = bobNodeHandle.nodeInfo val monitorBob = NodeMonitorModel() stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed() - monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) + monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false) rpcBob = monitorBob.proxyObservable.value!! runTest() } diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 944c49643c..e952a524a6 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -51,12 +51,13 @@ class NodeMonitorModel { * Register for updates to/from a given vault. * TODO provide an unsubscribe mechanism */ - fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String) { + fun register(nodeHostAndPort: NetworkHostAndPort, username: String, password: String, initialiseSerialization: Boolean = true) { val client = CordaRPCClient( hostAndPort = nodeHostAndPort, configuration = CordaRPCClientConfiguration.default.copy( connectionMaxRetryInterval = 10.seconds - ) + ), + initialiseSerialization = initialiseSerialization ) val connection = client.start(username, password) val proxy = connection.proxy diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index a09af59e85..626890b4ae 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -1,30 +1,38 @@ package net.corda.client.rpc; -import com.google.common.util.concurrent.*; -import net.corda.client.rpc.internal.*; -import net.corda.contracts.asset.*; -import net.corda.core.contracts.*; -import net.corda.core.messaging.*; -import net.corda.core.node.services.*; -import net.corda.core.node.services.vault.*; -import net.corda.core.utilities.*; -import net.corda.flows.*; -import net.corda.node.internal.*; -import net.corda.node.services.transactions.*; -import net.corda.nodeapi.*; -import net.corda.schemas.*; -import net.corda.testing.node.*; -import org.junit.*; +import com.google.common.util.concurrent.ListenableFuture; +import net.corda.client.rpc.internal.RPCClient; +import net.corda.contracts.asset.Cash; +import net.corda.core.contracts.Amount; +import net.corda.core.messaging.CordaRPCOps; +import net.corda.core.messaging.FlowHandle; +import net.corda.core.node.services.ServiceInfo; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.vault.Builder; +import net.corda.core.node.services.vault.QueryCriteria; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.flows.AbstractCashFlow; +import net.corda.flows.CashIssueFlow; +import net.corda.flows.CashPaymentFlow; +import net.corda.node.internal.Node; +import net.corda.node.services.transactions.ValidatingNotaryService; +import net.corda.nodeapi.User; +import net.corda.schemas.CashSchemaV1; +import net.corda.testing.node.NodeBasedTest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; -import java.io.*; -import java.lang.reflect.*; +import java.io.IOException; +import java.lang.reflect.Field; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; -import static kotlin.test.AssertionsKt.*; -import static net.corda.client.rpc.CordaRPCClientConfiguration.*; -import static net.corda.node.services.RPCUserServiceKt.*; -import static net.corda.testing.TestConstants.*; +import static kotlin.test.AssertionsKt.assertEquals; +import static net.corda.client.rpc.CordaRPCClientConfiguration.getDefault; +import static net.corda.node.services.RPCUserServiceKt.startFlowPermission; +import static net.corda.testing.TestConstants.getALICE; public class CordaRPCJavaClientTest extends NodeBasedTest { private List perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); @@ -46,7 +54,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { Set services = new HashSet<>(Collections.singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null))); ListenableFuture nodeFuture = startNode(getALICE().getName(), 1, services, Arrays.asList(rpcUser), Collections.emptyMap()); node = nodeFuture.get(); - client = new CordaRPCClient(node.getConfiguration().getRpcAddress(), null, getDefault()); + client = new CordaRPCClient(node.getConfiguration().getRpcAddress(), null, getDefault(), false); } @After diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 2aa147d3a9..aba7ddd5f7 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -49,7 +49,7 @@ class CordaRPCClientTest : NodeBasedTest() { @Before fun setUp() { node = startNode(ALICE.name, rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow() - client = CordaRPCClient(node.configuration.rpcAddress!!) + client = CordaRPCClient(node.configuration.rpcAddress!!, initialiseSerialization = false) } @After diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index bf20920824..51455b3062 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -1,10 +1,5 @@ package net.corda.client.rpc -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool import com.google.common.util.concurrent.Futures import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration @@ -14,11 +9,11 @@ import net.corda.core.getOrThrow import net.corda.core.messaging.RPCOps import net.corda.core.utilities.millis import net.corda.core.utilities.seconds +import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.Try import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.RPCKryo import net.corda.testing.* import net.corda.testing.driver.poll import org.apache.activemq.artemis.api.core.SimpleString @@ -305,16 +300,8 @@ class RPCStabilityTests { return Observable.interval(interval.toMillis(), TimeUnit.MILLISECONDS).map { chunk } } } - val dummyObservableSerialiser = object : Serializer>() { - override fun write(kryo: Kryo?, output: Output?, `object`: Observable?) { - } - override fun read(kryo: Kryo?, input: Input?, type: Class>?): Observable { - return Observable.empty() - } - } @Test fun `slow consumers are kicked`() { - val kryoPool = KryoPool.Builder { RPCKryo(dummyObservableSerialiser) }.build() rpcDriver { val server = startRpcServer(maxBufferedBytesPerClient = 10 * 1024 * 1024, ops = SlowConsumerRPCOpsImpl()).get() @@ -339,7 +326,7 @@ class RPCStabilityTests { methodName = SlowConsumerRPCOps::streamAtInterval.name, arguments = listOf(10.millis, 123456) ) - request.writeToClientMessage(kryoPool, message) + request.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message) producer.send(message) session.commit() diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 78baa8d906..584b8c7cd1 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -2,11 +2,16 @@ package net.corda.client.rpc import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration +import net.corda.client.rpc.serialization.KryoClientSerializationScheme import net.corda.core.messaging.CordaRPCOps +import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.config.SSLConfiguration +import net.corda.nodeapi.serialization.KRYO_P2P_CONTEXT +import net.corda.nodeapi.serialization.KRYO_RPC_CLIENT_CONTEXT +import net.corda.nodeapi.serialization.SerializationFactoryImpl import java.time.Duration /** @see RPCClient.RPCConnection */ @@ -35,11 +40,22 @@ data class CordaRPCClientConfiguration( class CordaRPCClient( hostAndPort: NetworkHostAndPort, sslConfiguration: SSLConfiguration? = null, - configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default + configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default, + initialiseSerialization: Boolean = true ) { + init { + // Init serialization. It's plausible there are multiple clients in a single JVM, so be tolerant of + // others having registered first. + // TODO: allow clients to have serialization factory etc injected and align with RPC protocol version? + if (initialiseSerialization) { + initialiseSerialization() + } + } + private val rpcClient = RPCClient( tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), - configuration.toRpcClientConfiguration() + configuration.toRpcClientConfiguration(), + KRYO_RPC_CLIENT_CONTEXT ) fun start(username: String, password: String): CordaRPCConnection { @@ -49,4 +65,20 @@ class CordaRPCClient( inline fun use(username: String, password: String, block: (CordaRPCConnection) -> A): A { return start(username, password).use(block) } + + companion object { + fun initialiseSerialization() { + try { + SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { + registerScheme(KryoClientSerializationScheme()) + } + SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT + SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT + } catch(e: IllegalStateException) { + // Check that it's registered as we expect + check(SerializationDefaults.SERIALIZATION_FACTORY is SerializationFactoryImpl) { "RPC client encountered conflicting configuration of serialization subsystem." } + check((SerializationDefaults.SERIALIZATION_FACTORY as SerializationFactoryImpl).alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) { "RPC client encountered conflicting configuration of serialization subsystem." } + } + } + } } \ No newline at end of file diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 059f5d2be7..54a9964d92 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -1,9 +1,11 @@ package net.corda.client.rpc.internal +import net.corda.core.crypto.random63BitValue import net.corda.core.logElapsedTime import net.corda.core.messaging.RPCOps +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.minutes -import net.corda.core.crypto.random63BitValue import net.corda.core.utilities.seconds import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor @@ -85,13 +87,15 @@ data class RPCClientConfiguration( */ class RPCClient( val transport: TransportConfiguration, - val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default + val rpcConfiguration: RPCClientConfiguration = RPCClientConfiguration.default, + val serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT ) { constructor( hostAndPort: NetworkHostAndPort, sslConfiguration: SSLConfiguration? = null, - configuration: RPCClientConfiguration = RPCClientConfiguration.default - ) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration) + configuration: RPCClientConfiguration = RPCClientConfiguration.default, + serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT + ) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext) companion object { private val log = loggerFor>() @@ -146,7 +150,7 @@ class RPCClient( minLargeMessageSize = rpcConfiguration.maxFileSize } - val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass) + val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext) try { proxyHandler.start() 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 e0cb142d82..03cc1bec69 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 @@ -4,7 +4,6 @@ import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import com.google.common.cache.RemovalCause @@ -18,7 +17,7 @@ import net.corda.core.internal.LazyPool import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle import net.corda.core.messaging.RPCOps -import net.corda.core.serialization.KryoPoolWithContext +import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.* import net.corda.nodeapi.* import org.apache.activemq.artemis.api.core.SimpleString @@ -64,7 +63,8 @@ class RPCClientProxyHandler( private val rpcPassword: String, private val serverLocator: ServerLocator, private val clientAddress: SimpleString, - private val rpcOpsClass: Class + private val rpcOpsClass: Class, + serializationContext: SerializationContext ) : InvocationHandler { private enum class State { @@ -77,9 +77,6 @@ class RPCClientProxyHandler( private companion object { val log = loggerFor() - // Note that this KryoPool is not yet capable of deserialising Observables, it requires Proxy-specific context - // to do that. However it may still be used for serialisation of RPC requests and related messages. - val kryoPool: KryoPool = KryoPool.Builder { RPCKryo(RpcClientObservableSerializer) }.build() // To check whether toString() is being invoked val toStringMethod: Method = Object::toString.javaMethod!! } @@ -112,8 +109,7 @@ class RPCClientProxyHandler( private val observablesToReap = ThreadBox(object { var observables = ArrayList() }) - // A Kryo pool that automatically adds the observable context when an instance is requested. - private val kryoPoolWithObservableContext = RpcClientObservableSerializer.createPoolWithContext(kryoPool, observableContext) + private val serializationContextWithObservableContext = RpcClientObservableSerializer.createContext(serializationContext, observableContext) private fun createRpcObservableMap(): RpcObservableMap { val onObservableRemove = RemovalListener>> { @@ -197,7 +193,7 @@ class RPCClientProxyHandler( val replyFuture = SettableFuture.create() sessionAndProducerPool.run { val message = it.session.createMessage(false) - request.writeToClientMessage(kryoPool, message) + request.writeToClientMessage(serializationContextWithObservableContext, message) log.debug { val argumentsString = arguments?.joinToString() ?: "" @@ -224,7 +220,7 @@ class RPCClientProxyHandler( // The handler for Artemis messages. private fun artemisMessageHandler(message: ClientMessage) { - val serverToClient = RPCApi.ServerToClient.fromClientMessage(kryoPoolWithObservableContext, message) + val serverToClient = RPCApi.ServerToClient.fromClientMessage(serializationContextWithObservableContext, message) log.debug { "Got message from RPC server $serverToClient" } when (serverToClient) { is RPCApi.ServerToClient.RpcReply -> { @@ -351,7 +347,7 @@ private typealias CallSiteMap = ConcurrentHashMap * @param observableMap holds the Observables that are ultimately exposed to the user. * @param hardReferenceStore holds references to Observables we want to keep alive while they are subscribed to. */ -private data class ObservableContext( +data class ObservableContext( val callSiteMap: CallSiteMap?, val observableMap: RpcObservableMap, val hardReferenceStore: MutableSet> @@ -360,10 +356,11 @@ private data class ObservableContext( /** * A [Serializer] to deserialise Observables once the corresponding Kryo instance has been provided with an [ObservableContext]. */ -private object RpcClientObservableSerializer : Serializer>() { +object RpcClientObservableSerializer : Serializer>() { private object RpcObservableContextKey - fun createPoolWithContext(kryoPool: KryoPool, observableContext: ObservableContext): KryoPool { - return KryoPoolWithContext(kryoPool, RpcObservableContextKey, observableContext) + + fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext { + return serializationContext.withProperty(RpcObservableContextKey, observableContext) } override fun read(kryo: Kryo, input: Input, type: Class>): Observable { diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/serialization/SerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/serialization/SerializationScheme.kt new file mode 100644 index 0000000000..53b70c3b7a --- /dev/null +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/serialization/SerializationScheme.kt @@ -0,0 +1,27 @@ +package net.corda.client.rpc.serialization + +import com.esotericsoftware.kryo.pool.KryoPool +import net.corda.client.rpc.internal.RpcClientObservableSerializer +import net.corda.core.serialization.DefaultKryoCustomizer +import net.corda.core.serialization.SerializationContext +import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.RPCKryo +import net.corda.nodeapi.serialization.AbstractKryoSerializationScheme +import net.corda.nodeapi.serialization.KryoHeaderV0_1 + +class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return byteSequence == KryoHeaderV0_1 && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) + } + + override fun rpcClientKryoPool(context: SerializationContext): KryoPool { + return KryoPool.Builder { + DefaultKryoCustomizer.customize(RPCKryo(RpcClientObservableSerializer, context.whitelist)).apply { classLoader = context.deserializationClassLoader } + }.build() + } + + // We're on the client and don't have access to server classes. + override fun rpcServerKryoPool(context: SerializationContext): KryoPool { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index 0117504c2e..6edb347ed1 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -27,7 +27,9 @@ import kotlin.test.assertTrue class ClientRPCInfrastructureTests : AbstractRPCTest() { // TODO: Test that timeouts work - private fun RPCDriverExposedDSLInterface.testProxy() = testProxy(TestOpsImpl()).ops + private fun RPCDriverExposedDSLInterface.testProxy(): TestOps { + return testProxy(TestOpsImpl()).ops + } interface TestOps : RPCOps { @Throws(IllegalArgumentException::class) diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index ebc9cef461..f31469bcb4 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -1,8 +1,8 @@ package net.corda.client.rpc import net.corda.core.messaging.RPCOps -import net.corda.node.services.messaging.requirePermission import net.corda.node.services.messaging.getRpcContext +import net.corda.node.services.messaging.requirePermission import net.corda.nodeapi.PermissionException import net.corda.nodeapi.User import net.corda.testing.RPCDriverExposedDSLInterface diff --git a/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt b/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt index edcf018e82..24735f8c24 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/MetaData.kt @@ -1,8 +1,8 @@ package net.corda.core.crypto import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.opaque import net.corda.core.serialization.serialize +import net.corda.core.utilities.sequence import java.security.PublicKey import java.time.Instant import java.util.* @@ -51,7 +51,7 @@ open class MetaData( if (timestamp != other.timestamp) return false if (visibleInputs != other.visibleInputs) return false if (signedInputs != other.signedInputs) return false - if (merkleRoot.opaque() != other.merkleRoot.opaque()) return false + if (merkleRoot.sequence() != other.merkleRoot.sequence()) return false if (publicKey != other.publicKey) return false return true } diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt index f1262f84af..472a8a1024 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignedData.kt @@ -23,7 +23,8 @@ open class SignedData(val raw: SerializedBytes, val sig: DigitalSign @Throws(SignatureException::class) fun verified(): T { sig.by.verify(raw.bytes, sig) - val data = raw.deserialize() + @Suppress("UNCHECKED_CAST") + val data = raw.deserialize() as T verifyData(data) return data } diff --git a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt index 9e865ff098..56035333bb 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/composite/CompositeKey.kt @@ -4,13 +4,12 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.composite.CompositeKey.NodeAndWeight import net.corda.core.crypto.keys import net.corda.core.crypto.provider.CordaObjectIdentifier -import net.corda.core.crypto.toSHA256Bytes import net.corda.core.crypto.toStringShort import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.sequence import org.bouncycastle.asn1.* import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import java.nio.ByteBuffer import java.security.PublicKey import java.util.* @@ -145,7 +144,7 @@ class CompositeKey private constructor(val threshold: Int, children: List() { + private var v: T? = null + + operator fun getValue(thisRef: Any?, property: KProperty<*>) = v ?: throw IllegalStateException("Write-once property $property not set.") + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + check(v == null) { "Cannot set write-once property $property more than once." } + v = value + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index f91119f9e8..fa0da924d7 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -3,17 +3,16 @@ package net.corda.core.serialization import com.esotericsoftware.kryo.* import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoCallback -import com.esotericsoftware.kryo.pool.KryoPool import com.esotericsoftware.kryo.util.MapReferenceResolver import com.google.common.annotations.VisibleForTesting import net.corda.core.contracts.* -import net.corda.core.crypto.* +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.MetaData +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignatureType import net.corda.core.crypto.composite.CompositeKey import net.corda.core.identity.Party import net.corda.core.transactions.WireTransaction -import net.corda.core.internal.LazyPool -import net.corda.core.utilities.OpaqueBytes import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec @@ -25,11 +24,8 @@ import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.io.InputStream import java.lang.reflect.InvocationTargetException -import java.nio.file.Files -import java.nio.file.Path import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath @@ -77,58 +73,6 @@ import kotlin.reflect.jvm.javaType * TODO: eliminate internal, storage related whitelist issues, such as private keys in blob storage. */ -// A convenient instance of Kryo pre-configured with some useful things. Used as a default by various functions. -fun p2PKryo(): KryoPool = kryoPool - -// Same again, but this has whitelisting turned off for internal storage use only. -fun storageKryo(): KryoPool = internalKryoPool - - -/** - * A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize] - * to get the original object back. - */ -@Suppress("unused") // Type parameter is just for documentation purposes. -class SerializedBytes(bytes: ByteArray, val internalOnly: Boolean = false) : OpaqueBytes(bytes) { - // It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer. - val hash: SecureHash by lazy { bytes.sha256() } - - fun writeToFile(path: Path): Path = Files.write(path, bytes) -} - -// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB -private val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray()) - -// Some extension functions that make deserialisation convenient and provide auto-casting of the result. -fun ByteArray.deserialize(kryo: KryoPool = p2PKryo()): T { - Input(this).use { - val header = OpaqueBytes(it.readBytes(8)) - if (header != KryoHeaderV0_1) { - throw KryoException("Serialized bytes header does not match any known format.") - } - @Suppress("UNCHECKED_CAST") - return kryo.run { k -> k.readClassAndObject(it) as T } - } -} - -// TODO: The preferred usage is with a pool. Try and eliminate use of this from RPC. -fun ByteArray.deserialize(kryo: Kryo): T = deserialize(kryo.asPool()) - -fun OpaqueBytes.deserialize(kryo: KryoPool = p2PKryo()): T { - return this.bytes.deserialize(kryo) -} - -// The more specific deserialize version results in the bytes being cached, which is faster. -@JvmName("SerializedBytesWireTransaction") -fun SerializedBytes.deserialize(kryo: KryoPool = p2PKryo()): WireTransaction = WireTransaction.deserialize(this, kryo) - -fun SerializedBytes.deserialize(kryo: KryoPool = if (internalOnly) storageKryo() else p2PKryo()): T = bytes.deserialize(kryo) - -fun SerializedBytes.deserialize(kryo: Kryo): T = bytes.deserialize(kryo.asPool()) - -// Internal adapter for use when we haven't yet converted to a pool, or for tests. -private fun Kryo.asPool(): KryoPool = (KryoPool.Builder { this }.build()) - /** * A serialiser that avoids writing the wrapper class to the byte stream, thus ensuring [SerializedBytes] is a pure * type safety hack. @@ -144,36 +88,6 @@ object SerializedBytesSerializer : Serializer>() { } } -/** - * Can be called on any object to convert it to a byte array (wrapped by [SerializedBytes]), regardless of whether - * the type is marked as serializable or was designed for it (so be careful!). - */ -fun T.serialize(kryo: KryoPool = p2PKryo(), internalOnly: Boolean = false): SerializedBytes { - return kryo.run { k -> serialize(k, internalOnly) } -} - - -private val serializeBufferPool = LazyPool( - newInstance = { ByteArray(64 * 1024) } -) -private val serializeOutputStreamPool = LazyPool( - clear = ByteArrayOutputStream::reset, - shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large - newInstance = { ByteArrayOutputStream(64 * 1024) } -) -fun T.serialize(kryo: Kryo, internalOnly: Boolean = false): SerializedBytes { - return serializeOutputStreamPool.run { stream -> - serializeBufferPool.run { buffer -> - Output(buffer).use { - it.outputStream = stream - it.writeBytes(KryoHeaderV0_1.bytes) - kryo.writeClassAndObject(it, this) - } - SerializedBytes(stream.toByteArray(), internalOnly) - } - } -} - /** * Serializes properties and deserializes by using the constructor. This assumes that all backed properties are * set via the constructor and the class is immutable. @@ -463,14 +377,6 @@ inline fun readListOfLength(kryo: Kryo, input: Input, minLen: Int = return list } -// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors. -private val internalKryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeAllButBlacklistedClassResolver())) }.build() -private val kryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeStandardClassResolver())) }.build() - -// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors. -@VisibleForTesting -fun createTestKryo(): Kryo = DefaultKryoCustomizer.customize(CordaKryo(makeNoWhitelistClassResolver())) - /** * We need to disable whitelist checking during calls from our Kryo code to register a serializer, since it checks * for existing registrations and then will enter our [CordaClassResolver.getRegistration] method. @@ -649,25 +555,3 @@ object X509CertificateSerializer : Serializer() { output.writeBytes(obj.encoded) } } - -class KryoPoolWithContext(val baseKryoPool: KryoPool, val contextKey: Any, val context: Any) : KryoPool { - override fun run(callback: KryoCallback): T { - val kryo = borrow() - try { - return callback.execute(kryo) - } finally { - release(kryo) - } - } - - override fun borrow(): Kryo { - val kryo = baseKryoPool.borrow() - require(kryo.context.put(contextKey, context) == null) { "KryoPool already has context" } - return kryo - } - - override fun release(kryo: Kryo) { - requireNotNull(kryo.context.remove(contextKey)) { "Kryo instance lost context while borrowed" } - baseKryoPool.release(kryo) - } -} diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt new file mode 100644 index 0000000000..7ecb71f160 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -0,0 +1,141 @@ +package net.corda.core.serialization + +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.sha256 +import net.corda.core.internal.WriteOnceProperty +import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT +import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY +import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.sequence + +/** + * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via + * a header / prefix in the bytes. + */ +interface SerializationFactory { + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + */ + fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + + /** + * Serialize an object to bytes using the preferred serialization format version from the context. + * + * @param obj The object to be serialized. + * @param context A context that configures various parameters to serialization, including the serialization format version. + */ + fun serialize(obj: T, context: SerializationContext): SerializedBytes +} + +/** + * Parameters to serialization and deserialization. + */ +interface SerializationContext { + /** + * When serializing, use the format this header sequence represents. + */ + val preferedSerializationVersion: ByteSequence + /** + * The class loader to use for deserialization. + */ + val deserializationClassLoader: ClassLoader + /** + * A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized. + */ + val whitelist: ClassWhitelist + /** + * A map of any addition properties specific to the particular use case. + */ + val properties: Map + /** + * Duplicate references to the same object preserved in the wire format and when deserialized when this is true, + * otherwise they appear as new copies of the object. + */ + val objectReferencesEnabled: Boolean + /** + * The use case we are serializing or deserializing for. See [UseCase]. + */ + val useCase: UseCase + /** + * Helper method to return a new context based on this context with the property added. + */ + fun withProperty(property: Any, value: Any): SerializationContext + + /** + * Helper method to return a new context based on this context with object references disabled. + */ + fun withoutReferences(): SerializationContext + + /** + * Helper method to return a new context based on this context with the deserialization class loader changed. + */ + fun withClassLoader(classLoader: ClassLoader): SerializationContext + + /** + * Helper method to return a new context based on this context with the given class specifically whitelisted. + */ + fun withWhitelisted(clazz: Class<*>): SerializationContext + + /** + * The use case that we are serializing for, since it influences the implementations chosen. + */ + enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint } +} + +/** + * Global singletons to be used as defaults that are injected elsewhere (generally, in the node or in RPC client). + */ +object SerializationDefaults { + var SERIALIZATION_FACTORY: SerializationFactory by WriteOnceProperty() + var P2P_CONTEXT: SerializationContext by WriteOnceProperty() + var RPC_SERVER_CONTEXT: SerializationContext by WriteOnceProperty() + var RPC_CLIENT_CONTEXT: SerializationContext by WriteOnceProperty() + var STORAGE_CONTEXT: SerializationContext by WriteOnceProperty() + var CHECKPOINT_CONTEXT: SerializationContext by WriteOnceProperty() +} + +/** + * Convenience extension method for deserializing a ByteSequence, utilising the defaults. + */ +inline fun ByteSequence.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T { + return serializationFactory.deserialize(this, T::class.java, context) +} + +/** + * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. + */ +inline fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T { + return serializationFactory.deserialize(this, T::class.java, context) +} + +/** + * Convenience extension method for deserializing a ByteArray, utilising the defaults. + */ +inline fun ByteArray.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): T = this.sequence().deserialize(serializationFactory, context) + +/** + * Convenience extension method for serializing an object of type T, utilising the defaults. + */ +fun T.serialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): SerializedBytes { + return serializationFactory.serialize(this, context) +} + +/** + * A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize] + * to get the original object back. + */ +@Suppress("unused") // Type parameter is just for documentation purposes. +class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { + // It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer. + val hash: SecureHash by lazy { bytes.sha256() } +} + +// The more specific deserialize version results in the bytes being cached, which is faster. +@JvmName("SerializedBytesWireTransaction") +fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): WireTransaction = WireTransaction.deserialize(this, serializationFactory, context) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt index c141435a4e..86d4fdfa1b 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt @@ -5,7 +5,6 @@ import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken @@ -57,17 +56,9 @@ class SerializeAsTokenSerializer : Serializer() { private val serializationContextKey = SerializeAsTokenContext::class.java -fun Kryo.serializationContext() = context.get(serializationContextKey) as? SerializeAsTokenContext +fun SerializationContext.withTokenContext(serializationContext: SerializeAsTokenContext): SerializationContext = this.withProperty(serializationContextKey, serializationContext) -fun Kryo.withSerializationContext(serializationContext: SerializeAsTokenContext, block: () -> T) = run { - context.containsKey(serializationContextKey) && throw IllegalStateException("There is already a serialization context.") - context.put(serializationContextKey, serializationContext) - try { - block() - } finally { - context.remove(serializationContextKey) - } -} +fun Kryo.serializationContext(): SerializeAsTokenContext? = context.get(serializationContextKey) as? SerializeAsTokenContext /** * A context for mapping SerializationTokens to/from SerializeAsTokens. @@ -79,12 +70,8 @@ fun Kryo.withSerializationContext(serializationContext: SerializeAsTokenCont * on the Kryo instance when serializing to enable/disable tokenization. */ class SerializeAsTokenContext internal constructor(val serviceHub: ServiceHub, init: SerializeAsTokenContext.() -> Unit) { - constructor(toBeTokenized: Any, kryoPool: KryoPool, serviceHub: ServiceHub) : this(serviceHub, { - kryoPool.run { kryo -> - kryo.withSerializationContext(this) { - toBeTokenized.serialize(kryo) - } - } + constructor(toBeTokenized: Any, serializationFactory: SerializationFactory, context: SerializationContext, serviceHub: ServiceHub) : this(serviceHub, { + serializationFactory.serialize(toBeTokenized, context.withTokenContext(this)) }) private val classNameToSingleton = mutableMapOf() diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 0e611fb242..29f1c3d38b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -7,14 +7,13 @@ import net.corda.core.crypto.PartialMerkleTree import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.p2PKryo +import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT import net.corda.core.serialization.serialize -import net.corda.core.serialization.withoutReferences import java.security.PublicKey import java.util.function.Predicate fun serializedHash(x: T): SecureHash { - return p2PKryo().run { kryo -> kryo.withoutReferences { x.serialize(kryo).hash } } + return x.serialize(context = P2P_CONTEXT.withoutReferences()).hash } /** 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 af16bd728a..6715365840 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -1,6 +1,5 @@ package net.corda.core.transactions -import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.contracts.* import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTree @@ -9,10 +8,9 @@ import net.corda.core.crypto.keys import net.corda.core.identity.Party import net.corda.core.indexOfOrThrow import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.p2PKryo -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT +import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY import net.corda.core.internal.Emoji import java.security.PublicKey import java.security.SignatureException @@ -48,8 +46,8 @@ class WireTransaction( override val id: SecureHash by lazy { merkleTree.hash } companion object { - fun deserialize(data: SerializedBytes, kryo: KryoPool = p2PKryo()): WireTransaction { - val wtx = data.bytes.deserialize(kryo) + fun deserialize(data: SerializedBytes, serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): WireTransaction { + val wtx = data.deserialize(serializationFactory, context) wtx.cachedBytes = data return wtx } diff --git a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt index 3102086b43..866170ed4f 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -5,39 +5,143 @@ package net.corda.core.utilities import com.google.common.io.BaseEncoding import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream -import java.util.* + +/** + * An abstraction of a byte array, with offset and size that does no copying of bytes unless asked to. + * + * The data of interest typically starts at position [offset] within the [bytes] and is [size] bytes long. + */ +@CordaSerializable +sealed class ByteSequence : Comparable { + /** + * The underlying bytes. + */ + abstract val bytes: ByteArray + /** + * The number of bytes this sequence represents. + */ + abstract val size: Int + /** + * The start position of the sequence within the byte array. + */ + abstract val offset: Int + /** Returns a [ByteArrayInputStream] of the bytes */ + fun open() = ByteArrayInputStream(bytes, offset, size) + + /** + * Create a sub-sequence backed by the same array. + * + * @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array. + * @param size The size of the intended sub sequence. + */ + fun subSequence(offset: Int, size: Int): ByteSequence { + require(offset >= 0) + require(offset + size <= this.size) + return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size) + } + + companion object { + /** + * Construct a [ByteSequence] given a [ByteArray] and optional offset and size, that represents that potentially + * sub-sequence of bytes. The returned implementation is optimised when the whole [ByteArray] is the sequence. + */ + @JvmStatic + @JvmOverloads + fun of(bytes: ByteArray, offset: Int = 0, size: Int = bytes.size): ByteSequence { + return if (offset == 0 && size == bytes.size && size != 0) OpaqueBytes(bytes) else OpaqueBytesSubSequence(bytes, offset, size) + } + } + + /** + * Take the first n bytes of this sequence as a sub-sequence. See [subSequence] for further semantics. + */ + fun take(n: Int): ByteSequence { + require(size >= n) + return subSequence(0, n) + } + + /** + * Copy this sequence, complete with new backing array. This can be helpful to break references to potentially + * large backing arrays from small sub-sequences. + */ + fun copy(): ByteSequence = of(bytes.copyOfRange(offset, offset + size)) + + /** + * Compare byte arrays byte by byte. Arrays that are shorter are deemed less than longer arrays if all the bytes + * of the shorter array equal those in the same position of the longer array. + */ + override fun compareTo(other: ByteSequence): Int { + val min = minOf(this.size, other.size) + // Compare min bytes + for (index in 0 until min) { + val unsignedThis = java.lang.Byte.toUnsignedInt(this.bytes[this.offset + index]) + val unsignedOther = java.lang.Byte.toUnsignedInt(other.bytes[other.offset + index]) + if (unsignedThis != unsignedOther) { + return Integer.signum(unsignedThis - unsignedOther) + } + } + // First min bytes is the same, so now resort to size + return Integer.signum(this.size - other.size) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ByteSequence) return false + if (this.size != other.size) return false + return subArraysEqual(this.bytes, this.offset, this.size, other.bytes, other.offset) + } + + private fun subArraysEqual(a: ByteArray, aOffset: Int, length: Int, b: ByteArray, bOffset: Int): Boolean { + var bytesRemaining = length + var aPos = aOffset + var bPos = bOffset + while (bytesRemaining-- > 0) { + if (a[aPos++] != b[bPos++]) return false + } + return true + } + + override fun hashCode(): Int { + var result = 1 + for (index in offset until (offset + size)) { + result = 31 * result + bytes[index] + } + return result + } + + override fun toString(): String = "[${BaseEncoding.base16().encode(bytes, offset, size)}]" +} /** * A simple class that wraps a byte array and makes the equals/hashCode/toString methods work as you actually expect. * In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such * functionality to Java, but it won't arrive for a few years yet! */ -@CordaSerializable -open class OpaqueBytes(val bytes: ByteArray) { +open class OpaqueBytes(override val bytes: ByteArray) : ByteSequence() { companion object { @JvmStatic fun of(vararg b: Byte) = OpaqueBytes(byteArrayOf(*b)) } init { - check(bytes.isNotEmpty()) + require(bytes.isNotEmpty()) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is OpaqueBytes) return false - return Arrays.equals(bytes, other.bytes) - } - - override fun hashCode() = Arrays.hashCode(bytes) - override fun toString() = "[" + bytes.toHexString() + "]" - - val size: Int get() = bytes.size - - /** Returns a [ByteArrayInputStream] of the bytes */ - fun open() = ByteArrayInputStream(bytes) + override val size: Int get() = bytes.size + override val offset: Int get() = 0 } +@Deprecated("Use sequence instead") fun ByteArray.opaque(): OpaqueBytes = OpaqueBytes(this) + +fun ByteArray.sequence(offset: Int = 0, size: Int = this.size) = ByteSequence.of(this, offset, size) + fun ByteArray.toHexString(): String = BaseEncoding.base16().encode(this) fun String.parseAsHex(): ByteArray = BaseEncoding.base16().decode(this) + +private class OpaqueBytesSubSequence(override val bytes: ByteArray, override val offset: Int, override val size: Int) : ByteSequence() { + init { + require(offset >= 0 && offset < bytes.size) + require(size >= 0 && size <= bytes.size) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt index 9e6be70bec..7352579be3 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt @@ -7,7 +7,6 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.testing.MEGA_CORP import net.corda.testing.MINI_CORP import net.corda.testing.ledger -import net.corda.testing.transaction import org.junit.Test import java.time.Instant import java.time.temporal.ChronoUnit @@ -115,22 +114,26 @@ class TransactionEncumbranceTests { @Test fun `state cannot be encumbered by itself`() { - transaction { - input { state } - output(encumbrance = 0) { stateWithNewOwner } - command(MEGA_CORP.owningKey) { Cash.Commands.Move() } - this `fails with` "Missing required encumbrance 0 in OUTPUT" + ledger { + transaction { + input { state } + output(encumbrance = 0) { stateWithNewOwner } + command(MEGA_CORP.owningKey) { Cash.Commands.Move() } + this `fails with` "Missing required encumbrance 0 in OUTPUT" + } } } @Test fun `encumbrance state index must be valid`() { - transaction { - input { state } - output(encumbrance = 2) { stateWithNewOwner } - output { timeLock } - command(MEGA_CORP.owningKey) { Cash.Commands.Move() } - this `fails with` "Missing required encumbrance 2 in OUTPUT" + ledger { + transaction { + input { state } + output(encumbrance = 2) { stateWithNewOwner } + output { timeLock } + command(MEGA_CORP.owningKey) { Cash.Commands.Move() } + this `fails with` "Missing required encumbrance 2 in OUTPUT" + } } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionGraphSearchTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionGraphSearchTests.kt index 8f55aa7317..0fa477f166 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionGraphSearchTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionGraphSearchTests.kt @@ -1,20 +1,17 @@ package net.corda.core.contracts -import net.corda.testing.contracts.DummyContract -import net.corda.testing.contracts.DummyState import net.corda.core.crypto.newSecureRandom import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.testing.MEGA_CORP_KEY -import net.corda.testing.MEGA_CORP_PUBKEY +import net.corda.testing.* +import net.corda.testing.contracts.DummyContract +import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices import net.corda.testing.node.MockTransactionStorage import org.junit.Test import kotlin.test.assertEquals -class TransactionGraphSearchTests { +class TransactionGraphSearchTests : TestDependencyInjectionBase() { class GraphTransactionStorage(val originTx: SignedTransaction, val inputTx: SignedTransaction) : MockTransactionStorage() { init { addTransaction(originTx) diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt index 400fc7fff7..36f37a6acf 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt @@ -1,9 +1,8 @@ package net.corda.core.contracts import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY -import net.corda.testing.contracts.DummyContract -import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.sign import net.corda.core.identity.Party @@ -12,13 +11,13 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.testing.* +import net.corda.testing.contracts.DummyContract import org.junit.Test import java.security.KeyPair import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class TransactionTests { - +class TransactionTests : TestDependencyInjectionBase() { private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair): SignedTransaction { val bytes: SerializedBytes = wtx.serialized return SignedTransaction(bytes, keys.map { it.sign(wtx.id.bytes) }) diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 9498f15b03..2da44f4aa5 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -11,6 +11,7 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.loadOrCreateKeyStore import net.corda.node.utilities.save +import net.corda.testing.TestDependencyInjectionBase import org.bouncycastle.asn1.x500.X500Name import org.junit.Rule import org.junit.Test @@ -21,7 +22,7 @@ import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -class CompositeKeyTests { +class CompositeKeyTests : TestDependencyInjectionBase() { @Rule @JvmField val tempFolder: TemporaryFolder = TemporaryFolder() diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index 6b69c32e13..b333fe0ba2 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -6,21 +6,24 @@ import net.corda.contracts.asset.Cash import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party -import net.corda.core.serialization.p2PKryo import net.corda.core.serialization.serialize import net.corda.core.transactions.WireTransaction -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_PUBKEY_1 -import net.corda.testing.TEST_TX_TIME import net.corda.testing.* import org.junit.Test import java.security.PublicKey import java.util.function.Predicate import kotlin.test.* -class PartialMerkleTreeTest { +class PartialMerkleTreeTest : TestDependencyInjectionBase() { val nodes = "abcdef" - val hashed = nodes.map { it.serialize().sha256() } + val hashed = nodes.map { + initialiseTestSerialization() + try { + it.serialize().sha256() + } finally { + resetTestSerialization() + } + } val expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash val merkleTree = MerkleTree.getMerkleTree(hashed) @@ -215,9 +218,7 @@ class PartialMerkleTreeTest { @Test(expected = KryoException::class) fun `hash map serialization not allowed`() { val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4) - p2PKryo().run { kryo -> - hm1.serialize(kryo) - } + hm1.serialize() } private fun makeSimpleCashWtx(notary: Party, timeWindow: TimeWindow? = null, attachments: List = emptyList()): WireTransaction { diff --git a/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt index cb83d847da..c8f35a77a5 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/SignedDataTest.kt @@ -1,13 +1,21 @@ package net.corda.core.crypto +import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.serialize +import net.corda.testing.TestDependencyInjectionBase +import org.junit.Before import org.junit.Test import java.security.SignatureException import kotlin.test.assertEquals -class SignedDataTest { +class SignedDataTest : TestDependencyInjectionBase() { + @Before + fun initialise() { + serialized = data.serialize() + } + val data = "Just a simple test string" - val serialized = data.serialize() + lateinit var serialized: SerializedBytes @Test fun `make sure correctly signed data is released`() { diff --git a/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt b/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt index 4aad5b6580..6ef8aa9921 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/TransactionSignatureTest.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.testing.TestDependencyInjectionBase import org.junit.Test import java.security.SignatureException import java.time.Instant @@ -8,7 +9,7 @@ import kotlin.test.assertTrue /** * Digital signature MetaData tests */ -class TransactionSignatureTest { +class TransactionSignatureTest : TestDependencyInjectionBase() { val testBytes = "12345678901234567890123456789012".toByteArray() diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index acac7c2574..f7f7e126f3 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -116,7 +116,7 @@ class ContractUpgradeFlowTest { @Test fun `2 parties contract upgrade using RPC`() { - rpcDriver { + rpcDriver(initialiseSerialization = false) { // Create dummy contract. val twoPartyDummyContract = DummyContract.generateInitial(0, notary, a.info.legalIdentity.ref(1), b.info.legalIdentity.ref(1)) val signedByA = a.services.signInitialTransaction(twoPartyDummyContract) diff --git a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt index b11d93564f..27c857714c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt @@ -1,15 +1,15 @@ package net.corda.core.flows -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.SecureHash import net.corda.core.getOrThrow import net.corda.core.identity.Party -import net.corda.core.utilities.opaque import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.sequence import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_KEY import net.corda.testing.MINI_CORP +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockServices import org.junit.After @@ -17,7 +17,6 @@ import org.junit.Before import org.junit.Test import java.io.ByteArrayOutputStream import java.io.InputStream -import java.security.SignatureException import java.util.jar.JarEntry import java.util.jar.JarOutputStream import kotlin.test.assertEquals @@ -141,7 +140,7 @@ class ResolveTransactionsFlowTest { jar.write("Some test file".toByteArray()) jar.closeEntry() jar.close() - return bs.toByteArray().opaque().open() + return bs.toByteArray().sequence().open() } // TODO: this operation should not require an explicit transaction val id = a.database.transaction { 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 b91e1a2744..2bb1c0f5f7 100644 --- a/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TransactionKeyFlowTests.kt @@ -8,8 +8,6 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.testing.node.MockNetwork -import org.junit.After -import org.junit.Before import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -17,22 +15,10 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue class TransactionKeyFlowTests { - lateinit var mockNet: MockNetwork - - @Before - fun before() { - mockNet = MockNetwork(false) - } - - @After - fun cleanUp() { - mockNet.stopNodes() - } - @Test fun `issue key`() { // We run this in parallel threads to help catch any race conditions that may exist. - mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(false, true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) @@ -66,5 +52,7 @@ class TransactionKeyFlowTests { assertTrue { bobAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } assertFalse { aliceAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } assertFalse { bobAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys } + + mockNet.stopNodes() } } diff --git a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt index efe0a254c0..33d64c768d 100644 --- a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt @@ -1,6 +1,5 @@ package net.corda.core.node -import com.esotericsoftware.kryo.Kryo import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* @@ -9,14 +8,15 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.testing.DUMMY_NOTARY import net.corda.testing.MEGA_CORP +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.node.MockAttachmentStorage import org.apache.commons.io.IOUtils import org.junit.Assert -import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -36,15 +36,14 @@ interface DummyContractBackdoor { val ATTACHMENT_TEST_PROGRAM_ID = AttachmentClassLoaderTests.AttachmentDummyContract() -class AttachmentClassLoaderTests { +class AttachmentClassLoaderTests : TestDependencyInjectionBase() { companion object { val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentClassLoaderTests::class.java.getResource("isolated.jar") - private fun Kryo.withAttachmentStorage(attachmentStorage: AttachmentStorage, block: () -> T) = run { - context.put(WireTransactionSerializer.attachmentsClassLoaderEnabled, true) + private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext { val serviceHub = mock() whenever(serviceHub.attachments).thenReturn(attachmentStorage) - withSerializationContext(SerializeAsTokenContext(serviceHub) {}, block) + return this.withTokenContext(SerializeAsTokenContext(serviceHub) {}).withProperty(WireTransactionSerializer.attachmentsClassLoaderEnabled, true) } } @@ -89,16 +88,6 @@ class AttachmentClassLoaderTests { class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader) - lateinit var kryo: Kryo - lateinit var kryo2: Kryo - - @Before - fun setup() { - // Do not release these back to the pool, since we do some unorthodox modifications to them below. - kryo = p2PKryo().borrow() - kryo2 = p2PKryo().borrow() - } - @Test fun `dynamically load AnotherDummyContract from isolated contracts jar`() { val child = ClassLoaderForTests() @@ -229,10 +218,8 @@ class AttachmentClassLoaderTests { val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - kryo.classLoader = cl - kryo.addToWhitelist(contract.javaClass) - - val state2 = bytes.deserialize(kryo) + val context = P2P_CONTEXT.withClassLoader(cl).withWhitelisted(contract.javaClass) + val state2 = bytes.deserialize(context = context) assertTrue(state2.javaClass.classLoader is AttachmentsClassLoader) assertNotNull(state2) } @@ -247,8 +234,9 @@ class AttachmentClassLoaderTests { assertNotNull(data.contract) - kryo2.addToWhitelist(data.contract.javaClass) - val bytes = data.serialize(kryo2) + val context2 = P2P_CONTEXT.withWhitelisted(data.contract.javaClass) + + val bytes = data.serialize(context = context2) val storage = MockAttachmentStorage() @@ -258,20 +246,18 @@ class AttachmentClassLoaderTests { val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - kryo.classLoader = cl - kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl)) + val context = P2P_CONTEXT.withClassLoader(cl).withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl)) - val state2 = bytes.deserialize(kryo) + val state2 = bytes.deserialize(context = context) assertEquals(cl, state2.contract.javaClass.classLoader) assertNotNull(state2) // We should be able to load same class from a different class loader and have them be distinct. val cl2 = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) - kryo.classLoader = cl2 - kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl2)) + val context3 = P2P_CONTEXT.withClassLoader(cl2).withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract", true, cl2)) - val state3 = bytes.deserialize(kryo) + val state3 = bytes.deserialize(context = context3) assertEquals(cl2, state3.contract.javaClass.classLoader) assertNotNull(state3) } @@ -295,30 +281,22 @@ class AttachmentClassLoaderTests { val contract = contractClass.newInstance() as DummyContractBackdoor val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY) val storage = MockAttachmentStorage() - kryo.addToWhitelist(contract.javaClass) - kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$State", true, child)) - kryo.addToWhitelist(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child)) + val context = P2P_CONTEXT.withWhitelisted(contract.javaClass) + .withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$State", true, child)) + .withWhitelisted(Class.forName("net.corda.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child)) + .withAttachmentStorage(storage) // todo - think about better way to push attachmentStorage down to serializer - val bytes = kryo.withAttachmentStorage(storage) { - + val bytes = run { val attachmentRef = importJar(storage) - tx.addAttachment(storage.openAttachment(attachmentRef)!!.id) - val wireTransaction = tx.toWireTransaction() - - wireTransaction.serialize(kryo) - } - // use empty attachmentStorage - kryo2.withAttachmentStorage(storage) { - - val copiedWireTransaction = bytes.deserialize(kryo2) - - assertEquals(1, copiedWireTransaction.outputs.size) - val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor - assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data)) + wireTransaction.serialize(context = context) } + val copiedWireTransaction = bytes.deserialize(context = context) + assertEquals(1, copiedWireTransaction.outputs.size) + val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor + assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data)) } @Test @@ -331,21 +309,19 @@ class AttachmentClassLoaderTests { // todo - think about better way to push attachmentStorage down to serializer val attachmentRef = importJar(storage) - val bytes = kryo.withAttachmentStorage(storage) { + val bytes = run { tx.addAttachment(storage.openAttachment(attachmentRef)!!.id) val wireTransaction = tx.toWireTransaction() - wireTransaction.serialize(kryo) + wireTransaction.serialize(context = P2P_CONTEXT.withAttachmentStorage(storage)) } // use empty attachmentStorage - kryo2.withAttachmentStorage(MockAttachmentStorage()) { - val e = assertFailsWith(MissingAttachmentsException::class) { - bytes.deserialize(kryo2) - } - assertEquals(attachmentRef, e.ids.single()) + val e = assertFailsWith(MissingAttachmentsException::class) { + bytes.deserialize(context = P2P_CONTEXT.withAttachmentStorage(MockAttachmentStorage())) } + assertEquals(attachmentRef, e.ids.single()) } } diff --git a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt index e27932c757..e227564aed 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt @@ -1,10 +1,13 @@ package net.corda.core.serialization -import com.esotericsoftware.kryo.Kryo import com.google.common.primitives.Ints import net.corda.core.crypto.* -import net.corda.core.utilities.opaque +import net.corda.core.utilities.sequence +import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.nodeapi.serialization.KryoHeaderV0_1 +import net.corda.nodeapi.serialization.SerializationContextImpl +import net.corda.nodeapi.serialization.SerializationFactoryImpl import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.BOB_PUBKEY @@ -19,62 +22,66 @@ import java.io.InputStream import java.security.cert.CertPath import java.security.cert.CertificateFactory import java.time.Instant -import java.util.* import kotlin.test.assertEquals import kotlin.test.assertTrue class KryoTests { - - private lateinit var kryo: Kryo + private lateinit var factory: SerializationFactory + private lateinit var context: SerializationContext @Before fun setup() { - // We deliberately do not return this, since we do some unorthodox registering below and do not want to pollute the pool. - kryo = p2PKryo().borrow() + factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } + context = SerializationContextImpl(KryoHeaderV0_1, + javaClass.classLoader, + AllWhitelist, + emptyMap(), + true, + SerializationContext.UseCase.P2P) } @Test fun ok() { val birthday = Instant.parse("1984-04-17T00:30:00.00Z") val mike = Person("mike", birthday) - val bits = mike.serialize(kryo) - assertThat(bits.deserialize(kryo)).isEqualTo(Person("mike", birthday)) + val bits = mike.serialize(factory, context) + assertThat(bits.deserialize(factory, context)).isEqualTo(Person("mike", birthday)) } @Test fun nullables() { val bob = Person("bob", null) - val bits = bob.serialize(kryo) - assertThat(bits.deserialize(kryo)).isEqualTo(Person("bob", null)) + val bits = bob.serialize(factory, context) + assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null)) } @Test fun `serialised form is stable when the same object instance is added to the deserialised object graph`() { - kryo.noReferencesWithin>() - val obj = Ints.toByteArray(0x01234567).opaque() + val noReferencesContext = context.withoutReferences() + val obj = Ints.toByteArray(0x01234567).sequence() val originalList = arrayListOf(obj) - val deserialisedList = originalList.serialize(kryo).deserialize(kryo) + val deserialisedList = originalList.serialize(factory, noReferencesContext).deserialize(factory, noReferencesContext) originalList += obj deserialisedList += obj - assertThat(deserialisedList.serialize(kryo)).isEqualTo(originalList.serialize(kryo)) + assertThat(deserialisedList.serialize(factory, noReferencesContext)).isEqualTo(originalList.serialize(factory, noReferencesContext)) } @Test fun `serialised form is stable when the same object instance occurs more than once, and using java serialisation`() { - kryo.noReferencesWithin>() + val noReferencesContext = context.withoutReferences() val instant = Instant.ofEpochMilli(123) val instantCopy = Instant.ofEpochMilli(123) assertThat(instant).isNotSameAs(instantCopy) val listWithCopies = arrayListOf(instant, instantCopy) val listWithSameInstances = arrayListOf(instant, instant) - assertThat(listWithSameInstances.serialize(kryo)).isEqualTo(listWithCopies.serialize(kryo)) + assertThat(listWithSameInstances.serialize(factory, noReferencesContext)).isEqualTo(listWithCopies.serialize(factory, noReferencesContext)) } @Test fun `cyclic object graph`() { val cyclic = Cyclic(3) - val bits = cyclic.serialize(kryo) - assertThat(bits.deserialize(kryo)).isEqualTo(cyclic) + val bits = cyclic.serialize(factory, context) + assertThat(bits.deserialize(factory, context)).isEqualTo(cyclic) } @Test @@ -86,7 +93,7 @@ class KryoTests { signature.verify(bitsToSign) assertThatThrownBy { signature.verify(wrongBits) } - val deserialisedKeyPair = keyPair.serialize(kryo).deserialize(kryo) + val deserialisedKeyPair = keyPair.serialize(factory, context).deserialize(factory, context) val deserialisedSignature = deserialisedKeyPair.sign(bitsToSign) deserialisedSignature.verify(bitsToSign) assertThatThrownBy { deserialisedSignature.verify(wrongBits) } @@ -94,15 +101,15 @@ class KryoTests { @Test fun `write and read Kotlin object singleton`() { - val serialised = TestSingleton.serialize(kryo) - val deserialised = serialised.deserialize(kryo) + val serialised = TestSingleton.serialize(factory, context) + val deserialised = serialised.deserialize(factory, context) assertThat(deserialised).isSameAs(TestSingleton) } @Test fun `InputStream serialisation`() { val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() }) - val readRubbishStream: InputStream = rubbish.inputStream().serialize(kryo).deserialize(kryo) + val readRubbishStream: InputStream = rubbish.inputStream().serialize(factory, context).deserialize(factory, context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) } @@ -118,15 +125,16 @@ class KryoTests { bitSet.set(3) val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), bitSet, bitSet, testBytes, keyPair1.public) - val serializedMetaData = meta.bytes() - val meta2 = serializedMetaData.deserialize() + val serializedMetaData = meta.serialize(factory, context).bytes + val meta2 = serializedMetaData.deserialize(factory, context) assertEquals(meta2, meta) } @Test fun `serialize - deserialize Logger`() { + val storageContext: SerializationContext = context // TODO: make it storage context val logger = LoggerFactory.getLogger("aName") - val logger2 = logger.serialize(storageKryo()).deserialize(storageKryo()) + val logger2 = logger.serialize(factory, storageContext).deserialize(factory, storageContext) assertEquals(logger.name, logger2.name) assertTrue(logger === logger2) } @@ -134,7 +142,7 @@ class KryoTests { @Test fun `HashCheckingStream (de)serialize`() { val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() }) - val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(SecureHash.sha256(rubbish), rubbish.size, ByteArrayInputStream(rubbish)).serialize(kryo).deserialize(kryo) + val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(SecureHash.sha256(rubbish), rubbish.size, ByteArrayInputStream(rubbish)).serialize(factory, context).deserialize(factory, context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) } @@ -144,8 +152,8 @@ class KryoTests { @Test fun `serialize - deserialize X509CertififcateHolder`() { val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) - val serialized = expected.serialize(kryo).bytes - val actual: X509CertificateHolder = serialized.deserialize(kryo) + val serialized = expected.serialize(factory, context).bytes + val actual: X509CertificateHolder = serialized.deserialize(factory, context) assertEquals(expected, actual) } @@ -156,8 +164,8 @@ class KryoTests { val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey) val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY) val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert)) - val serialized = expected.serialize(kryo).bytes - val actual: CertPath = serialized.deserialize(kryo) + val serialized = expected.serialize(factory, context).bytes + val actual: CertPath = serialized.deserialize(factory, context) assertEquals(expected, actual) } diff --git a/core/src/test/kotlin/net/corda/core/serialization/SerializationTokenTest.kt b/core/src/test/kotlin/net/corda/core/serialization/SerializationTokenTest.kt index 9b2517c8d5..0f48a5fb8c 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/SerializationTokenTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/SerializationTokenTest.kt @@ -6,24 +6,29 @@ import com.esotericsoftware.kryo.io.Output import com.nhaarman.mockito_kotlin.mock import net.corda.core.node.ServiceHub import net.corda.core.utilities.OpaqueBytes +import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.nodeapi.serialization.KryoHeaderV0_1 +import net.corda.nodeapi.serialization.SerializationContextImpl +import net.corda.nodeapi.serialization.SerializationFactoryImpl import org.assertj.core.api.Assertions.assertThat -import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayOutputStream class SerializationTokenTest { - lateinit var kryo: Kryo + lateinit var factory: SerializationFactory + lateinit var context: SerializationContext @Before fun setup() { - kryo = storageKryo().borrow() - } - - @After - fun cleanup() { - storageKryo().release(kryo) + factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } + context = SerializationContextImpl(KryoHeaderV0_1, + javaClass.classLoader, + AllWhitelist, + emptyMap(), + true, + SerializationContext.UseCase.P2P) } // Large tokenizable object so we can tell from the smaller number of serialized bytes it was actually tokenized @@ -38,20 +43,18 @@ class SerializationTokenTest { override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size } - companion object { - private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContext(toBeTokenized, storageKryo(), mock()) - } + private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContext(toBeTokenized, factory, context, mock()) @Test fun `write token and read tokenizable`() { val tokenizableBefore = LargeTokenizable() val context = serializeAsTokenContext(tokenizableBefore) - kryo.withSerializationContext(context) { - val serializedBytes = tokenizableBefore.serialize(kryo) - assertThat(serializedBytes.size).isLessThan(tokenizableBefore.numBytes) - val tokenizableAfter = serializedBytes.deserialize(kryo) - assertThat(tokenizableAfter).isSameAs(tokenizableBefore) - } + val testContext = this.context.withTokenContext(context) + + val serializedBytes = tokenizableBefore.serialize(factory, testContext) + assertThat(serializedBytes.size).isLessThan(tokenizableBefore.numBytes) + val tokenizableAfter = serializedBytes.deserialize(factory, testContext) + assertThat(tokenizableAfter).isSameAs(tokenizableBefore) } private class UnitSerializeAsToken : SingletonSerializeAsToken() @@ -60,51 +63,50 @@ class SerializationTokenTest { fun `write and read singleton`() { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(tokenizableBefore) - kryo.withSerializationContext(context) { - val serializedBytes = tokenizableBefore.serialize(kryo) - val tokenizableAfter = serializedBytes.deserialize(kryo) + val testContext = this.context.withTokenContext(context) + val serializedBytes = tokenizableBefore.serialize(factory, testContext) + val tokenizableAfter = serializedBytes.deserialize(factory, testContext) assertThat(tokenizableAfter).isSameAs(tokenizableBefore) - } } @Test(expected = UnsupportedOperationException::class) fun `new token encountered after context init`() { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(emptyList()) - kryo.withSerializationContext(context) { - tokenizableBefore.serialize(kryo) - } + val testContext = this.context.withTokenContext(context) + tokenizableBefore.serialize(factory, testContext) } @Test(expected = UnsupportedOperationException::class) fun `deserialize unregistered token`() { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(emptyList()) - kryo.withSerializationContext(context) { - val serializedBytes = tokenizableBefore.toToken(serializeAsTokenContext(emptyList())).serialize(kryo) - serializedBytes.deserialize(kryo) - } + val testContext = this.context.withTokenContext(context) + val serializedBytes = tokenizableBefore.toToken(serializeAsTokenContext(emptyList())).serialize(factory, testContext) + serializedBytes.deserialize(factory, testContext) } @Test(expected = KryoException::class) fun `no context set`() { val tokenizableBefore = UnitSerializeAsToken() - tokenizableBefore.serialize(kryo) + tokenizableBefore.serialize(factory, context) } @Test(expected = KryoException::class) fun `deserialize non-token`() { val tokenizableBefore = UnitSerializeAsToken() val context = serializeAsTokenContext(tokenizableBefore) - kryo.withSerializationContext(context) { - val stream = ByteArrayOutputStream() + val testContext = this.context.withTokenContext(context) + + val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(makeNoWhitelistClassResolver())) + val stream = ByteArrayOutputStream() Output(stream).use { + it.write(KryoHeaderV0_1.bytes) kryo.writeClass(it, SingletonSerializeAsToken::class.java) kryo.writeObject(it, emptyList()) } - val serializedBytes = SerializedBytes(stream.toByteArray()) - serializedBytes.deserialize(kryo) - } + val serializedBytes = SerializedBytes(stream.toByteArray()) + serializedBytes.deserialize(factory, testContext) } private class WrongTypeSerializeAsToken : SerializeAsToken { @@ -119,9 +121,8 @@ class SerializationTokenTest { fun `token returns unexpected type`() { val tokenizableBefore = WrongTypeSerializeAsToken() val context = serializeAsTokenContext(tokenizableBefore) - kryo.withSerializationContext(context) { - val serializedBytes = tokenizableBefore.serialize(kryo) - serializedBytes.deserialize(kryo) - } + val testContext = this.context.withTokenContext(context) + val serializedBytes = tokenizableBefore.serialize(factory, testContext) + serializedBytes.deserialize(factory, testContext) } } diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 729ff16822..cbadb624a3 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -17,7 +17,7 @@ import kotlin.test.assertFailsWith val TEST_PROGRAM_ID = TransactionSerializationTests.TestCash() -class TransactionSerializationTests { +class TransactionSerializationTests : TestDependencyInjectionBase() { class TestCash : Contract { override val legalContractReference = SecureHash.sha256("TestCash") diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt index b58c74a51f..ae4a67c093 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt @@ -4,10 +4,11 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.testing.TestDependencyInjectionBase import org.assertj.core.api.Assertions.assertThat import org.junit.Test -class KotlinUtilsTest { +class KotlinUtilsTest : TestDependencyInjectionBase() { @Test fun `transient property which is null`() { val test = NullTransientProperty() diff --git a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt index c6991c407a..299dec166e 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/NonEmptySetTest.kt @@ -7,6 +7,8 @@ import com.google.common.collect.testing.features.CollectionSize import junit.framework.TestSuite import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.testing.initialiseTestSerialization +import net.corda.testing.resetTestSerialization import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -47,9 +49,15 @@ class NonEmptySetTest { @Test fun `serialize deserialize`() { - val original = NonEmptySet.of(-17, 22, 17) - val copy = original.serialize().deserialize() - assertThat(copy).isEqualTo(original).isNotSameAs(original) + initialiseTestSerialization() + try { + val original = NonEmptySet.of(-17, 22, 17) + val copy = original.serialize().deserialize() + + assertThat(copy).isEqualTo(original).isNotSameAs(original) + } finally { + resetTestSerialization() + } } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt index 8b56b4ad13..55cc536086 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -4,8 +4,13 @@ import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoSerializable import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import net.corda.core.serialization.createTestKryo +import net.corda.core.serialization.AllWhitelist +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.serialize +import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.nodeapi.serialization.KryoHeaderV0_1 +import net.corda.nodeapi.serialization.SerializationContextImpl +import net.corda.nodeapi.serialization.SerializationFactoryImpl import org.junit.Before import org.junit.Test import java.util.* @@ -106,10 +111,6 @@ class ProgressTrackerTest { } } - val kryo = createTestKryo().apply { - // This is required to make sure Kryo walks through the auto-generated members for the lambda below. - fieldSerializerConfig.isIgnoreSyntheticFields = false - } pt.setChildProgressTracker(SimpleSteps.TWO, pt2) class Tmp { val unserializable = Unserializable() @@ -119,6 +120,13 @@ class ProgressTrackerTest { } } Tmp() - pt.serialize(kryo) + val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } + val context = SerializationContextImpl(KryoHeaderV0_1, + javaClass.classLoader, + AllWhitelist, + emptyMap(), + true, + SerializationContext.UseCase.P2P) + pt.serialize(factory, context) } } diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index e0e6f6be4a..bb8f9a84da 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -15,10 +15,10 @@ import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow -import net.corda.testing.driver.driver import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.nodeapi.User +import net.corda.testing.driver.driver import net.corda.testing.expect import net.corda.testing.expectEvents import net.corda.testing.parallel diff --git a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt index 893d8ce3e4..f34fda4c0b 100644 --- a/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/CommercialPaperTests.kt @@ -209,7 +209,7 @@ class CommercialPaperTestsGeneric { @Test fun `issue move and then redeem`() { - + initialiseTestSerialization() val dataSourcePropsAlice = makeTestDataSourceProperties() val databaseAlice = configureDatabase(dataSourcePropsAlice) databaseAlice.transaction { @@ -307,5 +307,6 @@ class CommercialPaperTestsGeneric { validRedemption.toLedgerTransaction(aliceServices).verify() // soft lock not released after success either!!! (as transaction not recorded) } + resetTestSerialization() } } diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt index 0fa711a408..04518c251f 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt @@ -1,8 +1,6 @@ package net.corda.contracts.asset -import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyState import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty @@ -10,13 +8,15 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.services.VaultService import net.corda.core.node.services.unconsumedStates -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* +import net.corda.testing.contracts.DummyState +import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.node.MockKeyManagementService import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties @@ -26,7 +26,7 @@ import java.security.KeyPair import java.util.* import kotlin.test.* -class CashTests { +class CashTests : TestDependencyInjectionBase() { val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) val defaultIssuer = MEGA_CORP.ref(defaultRef) val inState = Cash.State( @@ -76,6 +76,7 @@ class CashTests { vaultStatesUnconsumed = miniCorpServices.vaultService.unconsumedStates().toList() } + resetTestSerialization() } @Test @@ -152,6 +153,7 @@ class CashTests { @Test fun generateIssueRaw() { + initialiseTestSerialization() // Test generation works. val tx: WireTransaction = TransactionType.General.Builder(notary = null).apply { Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(DUMMY_PUBKEY_1), notary = DUMMY_NOTARY) @@ -167,6 +169,7 @@ class CashTests { @Test fun generateIssueFromAmount() { + initialiseTestSerialization() // Test issuance from an issued amount val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) val tx: WireTransaction = TransactionType.General.Builder(notary = null).apply { @@ -239,6 +242,7 @@ class CashTests { */ @Test(expected = IllegalStateException::class) fun `reject issuance with inputs`() { + initialiseTestSerialization() // Issue some cash var ptx = TransactionType.General.Builder(DUMMY_NOTARY) @@ -490,6 +494,7 @@ class CashTests { */ @Test fun generateSimpleExit() { + initialiseTestSerialization() val wtx = makeExit(100.DOLLARS, MEGA_CORP, 1) assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(0, wtx.outputs.size) @@ -505,6 +510,7 @@ class CashTests { */ @Test fun generatePartialExit() { + initialiseTestSerialization() val wtx = makeExit(50.DOLLARS, MEGA_CORP, 1) assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(1, wtx.outputs.size) @@ -516,6 +522,7 @@ class CashTests { */ @Test fun generateAbsentExit() { + initialiseTestSerialization() assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 1) } } @@ -524,6 +531,7 @@ class CashTests { */ @Test fun generateInvalidReferenceExit() { + initialiseTestSerialization() assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 2) } } @@ -532,6 +540,7 @@ class CashTests { */ @Test fun generateInsufficientExit() { + initialiseTestSerialization() assertFailsWith { makeExit(1000.DOLLARS, MEGA_CORP, 1) } } @@ -540,6 +549,7 @@ class CashTests { */ @Test fun generateOwnerWithNoStatesExit() { + initialiseTestSerialization() assertFailsWith { makeExit(100.POUNDS, CHARLIE, 1) } } @@ -548,6 +558,7 @@ class CashTests { */ @Test fun generateExitWithEmptyVault() { + initialiseTestSerialization() assertFailsWith { val tx = TransactionType.General.Builder(DUMMY_NOTARY) Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList()) @@ -556,9 +567,8 @@ class CashTests { @Test fun generateSimpleDirectSpend() { - + initialiseTestSerialization() database.transaction { - val wtx = makeSpend(100.DOLLARS, THEIR_IDENTITY_1) @Suppress("UNCHECKED_CAST") @@ -571,7 +581,7 @@ class CashTests { @Test fun generateSimpleSpendWithParties() { - + initialiseTestSerialization() database.transaction { val tx = TransactionType.General.Builder(DUMMY_NOTARY) @@ -583,7 +593,7 @@ class CashTests { @Test fun generateSimpleSpendWithChange() { - + initialiseTestSerialization() database.transaction { val wtx = makeSpend(10.DOLLARS, THEIR_IDENTITY_1) @@ -599,7 +609,7 @@ class CashTests { @Test fun generateSpendWithTwoInputs() { - + initialiseTestSerialization() database.transaction { val wtx = makeSpend(500.DOLLARS, THEIR_IDENTITY_1) @@ -615,7 +625,7 @@ class CashTests { @Test fun generateSpendMixedDeposits() { - + initialiseTestSerialization() database.transaction { val wtx = makeSpend(580.DOLLARS, THEIR_IDENTITY_1) assertEquals(3, wtx.inputs.size) @@ -636,7 +646,7 @@ class CashTests { @Test fun generateSpendInsufficientBalance() { - + initialiseTestSerialization() database.transaction { val e: InsufficientBalanceException = assertFailsWith("balance") { diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index fe3113b2af..e9f19c4db4 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -13,6 +13,7 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.days import net.corda.core.utilities.hours import net.corda.testing.* +import org.junit.After import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices import org.junit.Test @@ -57,6 +58,11 @@ class ObligationTests { } } + @After + fun reset() { + resetTestSerialization() + } + @Test fun trivial() { transaction { @@ -127,6 +133,7 @@ class ObligationTests { this.verifies() } + initialiseTestSerialization() // Test generation works. val tx = TransactionType.General.Builder(notary = null).apply { Obligation().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity, @@ -142,6 +149,7 @@ class ObligationTests { assertEquals(tx.outputs[0].data, expected) assertTrue(tx.commands[0].value is Obligation.Commands.Issue) assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) + resetTestSerialization() // We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer. transaction { @@ -204,6 +212,7 @@ class ObligationTests { */ @Test(expected = IllegalStateException::class) fun `reject issuance with inputs`() { + initialiseTestSerialization() // Issue some obligation val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { Obligation().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity, @@ -221,6 +230,7 @@ class ObligationTests { /** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */ @Test fun `generate close-out net transaction`() { + initialiseTestSerialization() val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE) val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { @@ -232,6 +242,7 @@ class ObligationTests { /** Test generating a transaction to net two obligations of the different sizes, and confirm the balance is correct. */ @Test fun `generate close-out net transaction with remainder`() { + initialiseTestSerialization() val obligationAliceToBob = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB) val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE) val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { @@ -246,6 +257,7 @@ class ObligationTests { /** Test generating a transaction to net two obligations of the same size, and therefore there are no outputs. */ @Test fun `generate payment net transaction`() { + initialiseTestSerialization() val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE) val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { @@ -257,6 +269,7 @@ class ObligationTests { /** Test generating a transaction to two obligations, where one is bigger than the other and therefore there is a remainder. */ @Test fun `generate payment net transaction with remainder`() { + initialiseTestSerialization() val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE) val tx = TransactionType.General.Builder(null).apply { @@ -271,6 +284,7 @@ class ObligationTests { /** Test generating a transaction to mark outputs as having defaulted. */ @Test fun `generate set lifecycle`() { + initialiseTestSerialization() // We don't actually verify the states, this is just here to make things look sensible val dueBefore = TEST_TX_TIME - 7.days @@ -309,6 +323,7 @@ class ObligationTests { /** Test generating a transaction to settle an obligation. */ @Test fun `generate settlement transaction`() { + initialiseTestSerialization() val cashTx = TransactionType.General.Builder(null).apply { Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP, DUMMY_NOTARY) }.toWireTransaction() @@ -857,6 +872,7 @@ class ObligationTests { @Test fun `summing balances due between parties`() { + initialiseTestSerialization() val simple: Map, Amount> = mapOf(Pair(Pair(ALICE, BOB), Amount(100000000, GBP))) val expected: Map = mapOf(Pair(ALICE, -100000000L), Pair(BOB, 100000000L)) val actual = sumAmountsDue(simple) diff --git a/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt index cc0ba55107..95f66909a8 100644 --- a/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/flows/BroadcastTransactionFlowTest.kt @@ -10,6 +10,9 @@ import net.corda.contracts.testing.SignedTransactionGenerator import net.corda.core.flows.BroadcastTransactionFlow.NotifyTxRequest import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.testing.initialiseTestSerialization +import net.corda.testing.resetTestSerialization +import org.junit.After import org.junit.runner.RunWith import kotlin.test.assertEquals @@ -18,10 +21,16 @@ class BroadcastTransactionFlowTest { class NotifyTxRequestMessageGenerator : Generator(NotifyTxRequest::class.java) { override fun generate(random: SourceOfRandomness, status: GenerationStatus): NotifyTxRequest { + initialiseTestSerialization() return NotifyTxRequest(tx = SignedTransactionGenerator().generate(random, status)) } } + @After + fun teardown() { + resetTestSerialization() + } + @Property fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: NotifyTxRequest) { val serialized = message.serialize().bytes diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt index 8e5aaf8cfb..58dbce9e94 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt @@ -1,7 +1,6 @@ package net.corda.nodeapi -import com.esotericsoftware.kryo.pool.KryoPool -import net.corda.core.serialization.KryoPoolWithContext +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.Try @@ -96,12 +95,12 @@ object RPCApi { val methodName: String, val arguments: List ) : ClientToServer() { - fun writeToClientMessage(kryoPool: KryoPool, message: ClientMessage) { + fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { MessageUtil.setJMSReplyTo(message, clientAddress) message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REQUEST.ordinal) message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong) message.putStringProperty(METHOD_NAME_FIELD_NAME, methodName) - message.bodyBuffer.writeBytes(arguments.serialize(kryoPool).bytes) + message.bodyBuffer.writeBytes(arguments.serialize(context = context).bytes) } } @@ -119,14 +118,14 @@ object RPCApi { } companion object { - fun fromClientMessage(kryoPool: KryoPool, message: ClientMessage): ClientToServer { + fun fromClientMessage(context: SerializationContext, message: ClientMessage): ClientToServer { val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)] return when (tag) { RPCApi.ClientToServer.Tag.RPC_REQUEST -> RpcRequest( clientAddress = MessageUtil.getJMSReplyTo(message), id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)), methodName = message.getStringProperty(METHOD_NAME_FIELD_NAME), - arguments = message.getBodyAsByteArray().deserialize(kryoPool) + arguments = message.getBodyAsByteArray().deserialize(context = context) ) RPCApi.ClientToServer.Tag.OBSERVABLES_CLOSED -> { val ids = ArrayList() @@ -148,16 +147,16 @@ object RPCApi { OBSERVATION } - abstract fun writeToClientMessage(kryoPool: KryoPool, message: ClientMessage) + abstract fun writeToClientMessage(context: SerializationContext, message: ClientMessage) data class RpcReply( val id: RpcRequestId, val result: Try ) : ServerToClient() { - override fun writeToClientMessage(kryoPool: KryoPool, message: ClientMessage) { + override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REPLY.ordinal) message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong) - message.bodyBuffer.writeBytes(result.serialize(kryoPool).bytes) + message.bodyBuffer.writeBytes(result.serialize(context = context).bytes) } } @@ -165,31 +164,31 @@ object RPCApi { val id: ObservableId, val content: Notification ) : ServerToClient() { - override fun writeToClientMessage(kryoPool: KryoPool, message: ClientMessage) { + override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVATION.ordinal) message.putLongProperty(OBSERVABLE_ID_FIELD_NAME, id.toLong) - message.bodyBuffer.writeBytes(content.serialize(kryoPool).bytes) + message.bodyBuffer.writeBytes(content.serialize(context = context).bytes) } } companion object { - fun fromClientMessage(kryoPool: KryoPool, message: ClientMessage): ServerToClient { + fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient { val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)] return when (tag) { RPCApi.ServerToClient.Tag.RPC_REPLY -> { val id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)) - val poolWithIdContext = KryoPoolWithContext(kryoPool, RpcRequestOrObservableIdKey, id.toLong) + val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong) RpcReply( id = id, - result = message.getBodyAsByteArray().deserialize(poolWithIdContext) + result = message.getBodyAsByteArray().deserialize(context = poolWithIdContext) ) } RPCApi.ServerToClient.Tag.OBSERVATION -> { val id = ObservableId(message.getLongProperty(OBSERVABLE_ID_FIELD_NAME)) - val poolWithIdContext = KryoPoolWithContext(kryoPool, RpcRequestOrObservableIdKey, id.toLong) + val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong) Observation( id = id, - content = message.getBodyAsByteArray().deserialize(poolWithIdContext) + content = message.getBodyAsByteArray().deserialize(context = poolWithIdContext) ) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/RPCStructures.kt b/node-api/src/main/kotlin/net/corda/nodeapi/RPCStructures.kt index 0b5236f68f..796d0e38f3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/RPCStructures.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/RPCStructures.kt @@ -46,7 +46,7 @@ class PermissionException(msg: String) : RuntimeException(msg) // The Kryo used for the RPC wire protocol. Every type in the wire protocol is listed here explicitly. // This is annoying to write out, but will make it easier to formalise the wire protocol when the time comes, // because we can see everything we're using in one place. -class RPCKryo(observableSerializer: Serializer>) : CordaKryo(makeStandardClassResolver()) { +class RPCKryo(observableSerializer: Serializer>, whitelist: ClassWhitelist) : CordaKryo(CordaClassResolver(whitelist)) { init { DefaultKryoCustomizer.customize(this) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/serialization/SerializationScheme.kt new file mode 100644 index 0000000000..1dbd5ce947 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/serialization/SerializationScheme.kt @@ -0,0 +1,263 @@ +package net.corda.nodeapi.serialization + +import co.paralleluniverse.fibers.Fiber +import co.paralleluniverse.io.serialization.kryo.KryoSerializer +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.KryoException +import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output +import com.esotericsoftware.kryo.pool.KryoPool +import io.requery.util.CloseableIterator +import net.corda.core.internal.LazyPool +import net.corda.core.serialization.* +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.OpaqueBytes +import java.io.ByteArrayOutputStream +import java.io.NotSerializableException +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +object NotSupportedSeralizationScheme : SerializationScheme { + private fun doThrow(): Nothing = throw UnsupportedOperationException("Serialization scheme not supported.") + + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean = doThrow() + + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T = doThrow() + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes = doThrow() +} + +data class SerializationContextImpl(override val preferedSerializationVersion: ByteSequence, + override val deserializationClassLoader: ClassLoader, + override val whitelist: ClassWhitelist, + override val properties: Map, + override val objectReferencesEnabled: Boolean, + override val useCase: SerializationContext.UseCase) : SerializationContext { + + override fun withProperty(property: Any, value: Any): SerializationContext { + return copy(properties = properties + (property to value)) + } + + override fun withoutReferences(): SerializationContext { + return copy(objectReferencesEnabled = false) + } + + override fun withClassLoader(classLoader: ClassLoader): SerializationContext { + return copy(deserializationClassLoader = classLoader) + } + + override fun withWhitelisted(clazz: Class<*>): SerializationContext { + return copy(whitelist = object : ClassWhitelist { + override fun hasListed(type: Class<*>): Boolean = whitelist.hasListed(type) || type.name == clazz.name + }) + } +} + +open class SerializationFactoryImpl : SerializationFactory { + private val creator: List = Exception().stackTrace.asList() + + private val registeredSchemes: MutableCollection = Collections.synchronizedCollection(mutableListOf()) + + // TODO: This is read-mostly. Probably a faster implementation to be found. + private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() + + private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme { + // truncate sequence to 8 bytes + return schemes.computeIfAbsent(byteSequence.take(8).copy() to target) { + for (scheme in registeredSchemes) { + if (scheme.canDeserializeVersion(it.first, it.second)) { + return@computeIfAbsent scheme + } + } + NotSupportedSeralizationScheme + } + } + + @Throws(NotSerializableException::class) + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T = schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { + return schemeFor(context.preferedSerializationVersion, context.useCase).serialize(obj, context) + } + + fun registerScheme(scheme: SerializationScheme) { + check(schemes.isEmpty()) { "All serialization schemes must be registered before any scheme is used." } + registeredSchemes += scheme + } + + val alreadyRegisteredSchemes: Collection get() = Collections.unmodifiableCollection(registeredSchemes) + + override fun toString(): String { + return "${this.javaClass.name} registeredSchemes=$registeredSchemes ${creator.joinToString("\n")}" + } + + override fun equals(other: Any?): Boolean { + return other is SerializationFactoryImpl && + other.registeredSchemes == this.registeredSchemes + } + + override fun hashCode(): Int = registeredSchemes.hashCode() +} + +private object AutoCloseableSerialisationDetector : Serializer() { + override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { + val message = if (closeable is CloseableIterator<*>) { + "A live Iterator pointing to the database has been detected during flow checkpointing. This may be due " + + "to a Vault query - move it into a private method." + } else { + "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " + + "Restoring such resources across node restarts is not supported. Make sure code accessing it is " + + "confined to a private method or the reference is nulled out." + } + throw UnsupportedOperationException(message) + } + + override fun read(kryo: Kryo, input: Input, type: Class) = throw IllegalStateException("Should not reach here!") +} + +abstract class AbstractKryoSerializationScheme : SerializationScheme { + private val kryoPoolsForContexts = ConcurrentHashMap, KryoPool>() + + protected abstract fun rpcClientKryoPool(context: SerializationContext): KryoPool + protected abstract fun rpcServerKryoPool(context: SerializationContext): KryoPool + + private fun getPool(context: SerializationContext): KryoPool { + return kryoPoolsForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { + when (context.useCase) { + SerializationContext.UseCase.Checkpoint -> + KryoPool.Builder { + val serializer = Fiber.getFiberSerializer(false) as KryoSerializer + val classResolver = makeNoWhitelistClassResolver().apply { setKryo(serializer.kryo) } + // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that + val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true } + serializer.kryo.apply { + field.set(this, classResolver) + DefaultKryoCustomizer.customize(this) + addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector) + classLoader = it.second + } + }.build() + SerializationContext.UseCase.RPCClient -> + rpcClientKryoPool(context) + SerializationContext.UseCase.RPCServer -> + rpcServerKryoPool(context) + else -> + KryoPool.Builder { + DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(context.whitelist))).apply { classLoader = it.second } + }.build() + } + } + } + + private fun withContext(kryo: Kryo, context: SerializationContext, block: (Kryo) -> T): T { + kryo.context.ensureCapacity(context.properties.size) + context.properties.forEach { kryo.context.put(it.key, it.value) } + try { + return block(kryo) + } finally { + kryo.context.clear() + } + } + + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { + val pool = getPool(context) + Input(byteSequence.bytes, byteSequence.offset, byteSequence.size).use { input -> + val header = OpaqueBytes(input.readBytes(8)) + if (header != KryoHeaderV0_1) { + throw KryoException("Serialized bytes header does not match expected format.") + } + return pool.run { kryo -> + withContext(kryo, context) { + @Suppress("UNCHECKED_CAST") + if (context.objectReferencesEnabled) { + kryo.readClassAndObject(input) as T + } else { + kryo.withoutReferences { kryo.readClassAndObject(input) as T } + } + } + } + } + } + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { + val pool = getPool(context) + return pool.run { kryo -> + withContext(kryo, context) { + serializeOutputStreamPool.run { stream -> + serializeBufferPool.run { buffer -> + Output(buffer).use { + it.outputStream = stream + it.writeBytes(KryoHeaderV0_1.bytes) + if (context.objectReferencesEnabled) { + kryo.writeClassAndObject(it, obj) + } else { + kryo.withoutReferences { kryo.writeClassAndObject(it, obj) } + } + } + SerializedBytes(stream.toByteArray()) + } + } + } + } + } +} + +private val serializeBufferPool = LazyPool( + newInstance = { ByteArray(64 * 1024) } +) +private val serializeOutputStreamPool = LazyPool( + clear = ByteArrayOutputStream::reset, + shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large + newInstance = { ByteArrayOutputStream(64 * 1024) } +) + +// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB +val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray(Charsets.UTF_8)) + + +val KRYO_P2P_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.P2P) +val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.RPCServer) +val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.RPCClient) +val KRYO_STORAGE_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + AllButBlacklisted, + emptyMap(), + true, + SerializationContext.UseCase.Storage) +val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + QuasarWhitelist, + emptyMap(), + true, + SerializationContext.UseCase.Checkpoint) + +object QuasarWhitelist : ClassWhitelist { + override fun hasListed(type: Class<*>): Boolean = true +} + +interface SerializationScheme { + // byteSequence expected to just be the 8 bytes necessary for versioning + fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean + + @Throws(NotSerializableException::class) + fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + + @Throws(NotSerializableException::class) + fun serialize(obj: T, context: SerializationContext): SerializedBytes +} \ No newline at end of file diff --git a/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt b/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt index f53ad70a38..8b4a4d8adb 100644 --- a/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt +++ b/node-schemas/src/test/kotlin/net/corda/node/services/vault/schemas/VaultSchemaTest.kt @@ -21,10 +21,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.LedgerTransaction import net.corda.node.services.vault.schemas.requery.* -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_KEY +import net.corda.testing.* import net.corda.testing.contracts.DummyContract import org.h2.jdbcx.JdbcDataSource import org.junit.After @@ -40,7 +37,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue -class VaultSchemaTest { +class VaultSchemaTest : TestDependencyInjectionBase() { var instance: KotlinEntityDataStore? = null val data: KotlinEntityDataStore get() = instance!! diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 3048c19417..545fa7e77b 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -9,13 +9,13 @@ import net.corda.core.messaging.startFlow import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType import net.corda.testing.ALICE -import net.corda.testing.driver.driver import net.corda.node.internal.NodeStartup import net.corda.node.services.startFlowPermission import net.corda.nodeapi.User import net.corda.testing.driver.ListenProcessDeathException import net.corda.testing.driver.NetworkMapStartStrategy import net.corda.testing.ProjectStructure.projectRootDir +import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt index 00d5cc66ac..029eb68cff 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/JDBCHashMapTestSuite.kt @@ -10,7 +10,10 @@ import com.google.common.collect.testing.features.MapFeature import com.google.common.collect.testing.features.SetFeature import com.google.common.collect.testing.testers.* import junit.framework.TestSuite +import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.initialiseTestSerialization import net.corda.testing.node.makeTestDataSourceProperties +import net.corda.testing.resetTestSerialization import org.assertj.core.api.Assertions.assertThat import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.transactions.TransactionManager @@ -42,6 +45,7 @@ class JDBCHashMapTestSuite { @JvmStatic @BeforeClass fun before() { + initialiseTestSerialization() database = configureDatabase(makeTestDataSourceProperties()) setUpDatabaseTx() loadOnInitFalseMap = JDBCHashMap("test_map_false", loadOnInit = false) @@ -57,6 +61,7 @@ class JDBCHashMapTestSuite { fun after() { closeDatabaseTx() database.close() + resetTestSerialization() } @JvmStatic @@ -198,7 +203,7 @@ class JDBCHashMapTestSuite { * * If the Map reloads, then so will the Set as it just delegates. */ - class MapCanBeReloaded { + class MapCanBeReloaded : TestDependencyInjectionBase() { private val ops = listOf(Triple(AddOrRemove.ADD, "A", "1"), Triple(AddOrRemove.ADD, "B", "2"), Triple(AddOrRemove.ADD, "C", "3"), @@ -235,7 +240,6 @@ class JDBCHashMapTestSuite { database.close() } - @Test fun `fill map and check content after reconstruction`() { database.transaction { diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 9cae274260..87cc2d2363 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -151,7 +151,7 @@ abstract class MQSecurityTest : NodeBasedTest() { } fun loginToRPC(target: NetworkHostAndPort, rpcUser: User, sslConfiguration: SSLConfiguration? = null): CordaRPCOps { - return CordaRPCClient(target, sslConfiguration).start(rpcUser.username, rpcUser.password).proxy + return CordaRPCClient(target, sslConfiguration, initialiseSerialization = false).start(rpcUser.username, rpcUser.password).proxy } fun loginToRPCAndGetClientQueue(): String { diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index bdd44c655e..35183e4fdc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -4,12 +4,15 @@ import com.codahale.metrics.JmxReporter import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture -import net.corda.core.* +import net.corda.core.flatMap import net.corda.core.messaging.RPCOps import net.corda.core.node.ServiceHub import net.corda.core.node.services.ServiceInfo +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.thenMatch import net.corda.core.utilities.* import net.corda.node.VersionInfo +import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.NodeClock import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl @@ -29,6 +32,7 @@ import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.internal.ShutdownHook import net.corda.nodeapi.internal.addShutdownHook +import net.corda.nodeapi.serialization.* import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.client.ActiveMQClient @@ -54,7 +58,8 @@ import kotlin.system.exitProcess open class Node(override val configuration: FullNodeConfiguration, advertisedServices: Set, val versionInfo: VersionInfo, - clock: Clock = NodeClock()) : AbstractNode(configuration, advertisedServices, clock) { + clock: Clock = NodeClock(), + val initialiseSerialization: Boolean = true) : AbstractNode(configuration, advertisedServices, clock) { companion object { private val logger = loggerFor() var renderBasicInfoToConsole = true @@ -290,6 +295,9 @@ open class Node(override val configuration: FullNodeConfiguration, val startupComplete: ListenableFuture = SettableFuture.create() override fun start(): Node { + if (initialiseSerialization) { + initialiseSerialization() + } super.start() networkMapRegistrationFuture.thenMatch({ @@ -321,6 +329,16 @@ open class Node(override val configuration: FullNodeConfiguration, return this } + private fun initialiseSerialization() { + SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { + registerScheme(KryoServerSerializationScheme()) + } + SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT + SerializationDefaults.RPC_SERVER_CONTEXT = KRYO_RPC_SERVER_CONTEXT + SerializationDefaults.STORAGE_CONTEXT = KRYO_STORAGE_CONTEXT + SerializationDefaults.CHECKPOINT_CONTEXT = KRYO_CHECKPOINT_CONTEXT + } + /** Starts a blocking event loop for message dispatch. */ fun run() { (network as NodeMessagingClient).run(messageBroker!!.serverControl) diff --git a/node/src/main/kotlin/net/corda/node/serialization/SerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/SerializationScheme.kt new file mode 100644 index 0000000000..9fdb211720 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/serialization/SerializationScheme.kt @@ -0,0 +1,26 @@ +package net.corda.node.serialization + +import com.esotericsoftware.kryo.pool.KryoPool +import net.corda.core.serialization.DefaultKryoCustomizer +import net.corda.core.serialization.SerializationContext +import net.corda.core.utilities.ByteSequence +import net.corda.node.services.messaging.RpcServerObservableSerializer +import net.corda.nodeapi.RPCKryo +import net.corda.nodeapi.serialization.AbstractKryoSerializationScheme +import net.corda.nodeapi.serialization.KryoHeaderV0_1 + +class KryoServerSerializationScheme : AbstractKryoSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return byteSequence.equals(KryoHeaderV0_1) && target != SerializationContext.UseCase.RPCClient + } + + override fun rpcClientKryoPool(context: SerializationContext): KryoPool { + throw UnsupportedOperationException() + } + + override fun rpcServerKryoPool(context: SerializationContext): KryoPool { + return KryoPool.Builder { + DefaultKryoCustomizer.customize(RPCKryo(RpcServerObservableSerializer, context.whitelist)).apply { classLoader = context.deserializationClassLoader } + }.build() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index baf8c623d3..58bf226723 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -154,7 +154,8 @@ fun MessagingService.onNext(topic: String, sessionId: Long): Listenabl val messageFuture = SettableFuture.create() runOnNextMessage(topic, sessionId) { message -> messageFuture.catch { - message.data.deserialize() + @Suppress("UNCHECKED_CAST") + message.data.deserialize() as M } } return messageFuture diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index afd2df6204..81f9a47701 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -1,18 +1,21 @@ package net.corda.node.services.messaging import com.google.common.util.concurrent.ListenableFuture -import net.corda.core.* +import net.corda.core.ThreadBox +import net.corda.core.andForget import net.corda.core.crypto.random63BitValue +import net.corda.core.getOrThrow import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo import net.corda.core.node.services.TransactionVerifierService -import net.corda.core.utilities.opaque +import net.corda.core.thenMatch import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.sequence import net.corda.core.utilities.trace import net.corda.node.VersionInfo import net.corda.node.services.RPCUserService @@ -346,7 +349,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, private val message: ClientMessage) : ReceivedMessage { override val data: ByteArray by lazy { ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) } } override val debugTimestamp: Instant get() = Instant.ofEpochMilli(message.timestamp) - override fun toString() = "${topicSession.topic}#${data.opaque()}" + override fun toString() = "${topicSession.topic}#${data.sequence()}" } private fun deliver(msg: ReceivedMessage): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index cb42b35110..2fb64c02f4 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -4,7 +4,6 @@ import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import com.google.common.cache.RemovalListener @@ -17,7 +16,8 @@ import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle import net.corda.core.messaging.RPCOps import net.corda.core.utilities.seconds -import net.corda.core.serialization.KryoPoolWithContext +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT import net.corda.core.utilities.* import net.corda.node.services.RPCUserService import net.corda.nodeapi.* @@ -81,7 +81,6 @@ class RPCServer( ) { private companion object { val log = loggerFor() - val kryoPool = KryoPool.Builder { RPCKryo(RpcServerObservableSerializer) }.build() } private enum class State { UNSTARTED, @@ -258,7 +257,7 @@ class RPCServer( private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) { lifeCycle.requireState(State.STARTED) - val clientToServer = RPCApi.ClientToServer.fromClientMessage(kryoPool, artemisMessage) + val clientToServer = RPCApi.ClientToServer.fromClientMessage(RPC_SERVER_CONTEXT, artemisMessage) log.debug { "-> RPC -> $clientToServer" } when (clientToServer) { is RPCApi.ClientToServer.RpcRequest -> { @@ -302,8 +301,7 @@ class RPCServer( clientAddress, serverControl!!, sessionAndProducerPool, - observationSendExecutor!!, - kryoPool + observationSendExecutor!! ) val buffered = bufferIfQueueNotBound(clientAddress, reply, observableContext) @@ -385,19 +383,19 @@ class ObservableContext( val clientAddress: SimpleString, val serverControl: ActiveMQServerControl, val sessionAndProducerPool: LazyStickyPool, - val observationSendExecutor: ExecutorService, - kryoPool: KryoPool + val observationSendExecutor: ExecutorService ) { private companion object { val log = loggerFor() } - private val kryoPoolWithObservableContext = RpcServerObservableSerializer.createPoolWithContext(kryoPool, this) + private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this) + fun sendMessage(serverToClient: RPCApi.ServerToClient) { try { sessionAndProducerPool.run(rpcRequestId) { val artemisMessage = it.session.createMessage(false) - serverToClient.writeToClientMessage(kryoPoolWithObservableContext, artemisMessage) + serverToClient.writeToClientMessage(serializationContextWithObservableContext, artemisMessage) it.producer.send(clientAddress, artemisMessage) log.debug("<- RPC <- $serverToClient") } @@ -408,12 +406,12 @@ class ObservableContext( } } -private object RpcServerObservableSerializer : Serializer>() { +object RpcServerObservableSerializer : Serializer>() { private object RpcObservableContextKey private val log = loggerFor() - fun createPoolWithContext(kryoPool: KryoPool, observableContext: ObservableContext): KryoPool { - return KryoPoolWithContext(kryoPool, RpcObservableContextKey, observableContext) + fun createContext(observableContext: ObservableContext): SerializationContext { + return RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) } override fun read(kryo: Kryo?, input: Input?, type: Class>?): Observable { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt index 89db282424..1d4ccde166 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt @@ -1,10 +1,10 @@ package net.corda.node.services.persistence import net.corda.core.crypto.SecureHash +import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.serialization.storageKryo import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.utilities.* @@ -39,7 +39,7 @@ class DBCheckpointStorage : CheckpointStorage { private val checkpointStorage = synchronizedMap(CheckpointMap()) override fun addCheckpoint(checkpoint: Checkpoint) { - checkpointStorage.put(checkpoint.id, checkpoint.serialize(storageKryo(), true)) + checkpointStorage.put(checkpoint.id, checkpoint.serialize(context = CHECKPOINT_CONTEXT)) } override fun removeCheckpoint(checkpoint: Checkpoint) { @@ -49,7 +49,7 @@ class DBCheckpointStorage : CheckpointStorage { override fun forEach(block: (Checkpoint) -> Boolean) { synchronized(checkpointStorage) { for (checkpoint in checkpointStorage.values) { - if (!block(checkpoint.deserialize())) { + if (!block(checkpoint.deserialize(context = CHECKPOINT_CONTEXT))) { break } } 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 1fa7885055..3104a3b9b2 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 @@ -2,20 +2,12 @@ package net.corda.node.services.statemachine import co.paralleluniverse.fibers.Fiber import co.paralleluniverse.fibers.FiberExecutorScheduler -import co.paralleluniverse.io.serialization.kryo.KryoSerializer import co.paralleluniverse.strands.Strand import com.codahale.metrics.Gauge -import com.esotericsoftware.kryo.ClassResolver -import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool import com.google.common.collect.HashMultimap import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors -import io.requery.util.CloseableIterator import net.corda.core.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.crypto.SecureHash @@ -25,9 +17,10 @@ import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party -import net.corda.core.internal.declaredField import net.corda.core.messaging.DataFeed import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT +import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY import net.corda.core.then import net.corda.core.utilities.Try import net.corda.core.utilities.debug @@ -85,34 +78,6 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) - private val quasarKryoPool = KryoPool.Builder { - val serializer = Fiber.getFiberSerializer(false) as KryoSerializer - val classResolver = makeNoWhitelistClassResolver().apply { setKryo(serializer.kryo) } - serializer.kryo.apply { - // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that - declaredField(Kryo::class, "classResolver").value = classResolver - DefaultKryoCustomizer.customize(this) - addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector) - } - }.build() - - // TODO Move this into the blacklist and upgrade the blacklist to allow custom messages - private object AutoCloseableSerialisationDetector : Serializer() { - override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { - val message = if (closeable is CloseableIterator<*>) { - "A live Iterator pointing to the database has been detected during flow checkpointing. This may be due " + - "to a Vault query - move it into a private method." - } else { - "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " + - "Restoring such resources across node restarts is not supported. Make sure code accessing it is " + - "confined to a private method or the reference is nulled out." - } - throw UnsupportedOperationException(message) - } - - override fun read(kryo: Kryo, input: Input, type: Class) = throw IllegalStateException("Should not reach here!") - } - companion object { private val logger = loggerFor() internal val sessionTopic = TopicSession("platform.session") @@ -173,7 +138,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, internal val tokenizableServices = ArrayList() // Context for tokenized services in checkpoints private val serializationContext by lazy { - SerializeAsTokenContext(tokenizableServices, quasarKryoPool, serviceHub) + SerializeAsTokenContext(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) } /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ @@ -410,22 +375,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, } private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes> { - return quasarKryoPool.run { kryo -> - // add the map of tokens -> tokenizedServices to the kyro context - kryo.withSerializationContext(serializationContext) { - fiber.serialize(kryo) - } - } + return fiber.serialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)) } private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { return try { - quasarKryoPool.run { kryo -> - // put the map of token -> tokenized into the kryo context - kryo.withSerializationContext(serializationContext) { - checkpoint.serializedFiber.deserialize(kryo) - }.apply { fromCheckpoint = true } - } + checkpoint.serializedFiber.deserialize>(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { fromCheckpoint = true } } catch (t: Throwable) { logger.error("Encountered unrestorable checkpoint!", t) null diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt index fe91e3f579..929312f753 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt @@ -13,9 +13,9 @@ import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.VaultQueryService import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria +import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize -import net.corda.core.serialization.storageKryo import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.node.services.database.HibernateConfiguration @@ -96,7 +96,7 @@ class HibernateVaultQueryImpl(hibernateConfig: HibernateConfiguration, return@forEachIndexed val vaultState = result[0] as VaultSchemaV1.VaultStates val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) - val state = vaultState.contractState.deserialize>(storageKryo()) + val state = vaultState.contractState.deserialize>(context = STORAGE_CONTEXT) statesMeta.add(Vault.StateMetadata(stateRef, vaultState.contractStateClassName, vaultState.recordedTime, vaultState.consumedTime, vaultState.stateStatus, vaultState.notaryName, vaultState.notaryKey, vaultState.lockId, vaultState.lockUpdateTime)) statesAndRefs.add(StateAndRef(state, stateRef)) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index cd049ace71..2e436f1a95 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -26,10 +26,10 @@ import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService import net.corda.core.node.services.unconsumedStates +import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.serialization.storageKryo import net.corda.core.tee import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction @@ -95,7 +95,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P index = it.key.index stateStatus = Vault.StateStatus.UNCONSUMED contractStateClassName = it.value.state.data.javaClass.name - contractState = it.value.state.serialize(storageKryo()).bytes + contractState = it.value.state.serialize(context = STORAGE_CONTEXT).bytes notaryName = it.value.state.notary.name.toString() notaryKey = it.value.state.notary.owningKey.toBase58String() recordedTime = services.clock.instant() @@ -198,7 +198,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P Sequence { iterator } .map { it -> val stateRef = StateRef(SecureHash.parse(it.txId), it.index) - val state = it.contractState.deserialize>(storageKryo()) + val state = it.contractState.deserialize>(context = STORAGE_CONTEXT) Vault.StateMetadata(stateRef, it.contractStateClassName, it.recordedTime, it.consumedTime, it.stateStatus, it.notaryName, it.notaryKey, it.lockId, it.lockUpdateTime) StateAndRef(state, stateRef) } @@ -217,7 +217,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P .and(VaultSchema.VaultStates::index eq it.index) result.get()?.each { val stateRef = StateRef(SecureHash.parse(it.txId), it.index) - val state = it.contractState.deserialize>(storageKryo()) + val state = it.contractState.deserialize>(context = STORAGE_CONTEXT) results += StateAndRef(state, stateRef) } } @@ -380,7 +380,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P val txHash = SecureHash.parse(rs.getString(1)) val index = rs.getInt(2) val stateRef = StateRef(txHash, index) - val state = rs.getBytes(3).deserialize>(storageKryo()) + val state = rs.getBytes(3).deserialize>(context = STORAGE_CONTEXT) val pennies = rs.getLong(4) totalPennies = rs.getLong(5) val rowLockId = rs.getString(6) @@ -435,7 +435,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P query.get() .map { it -> val stateRef = StateRef(SecureHash.parse(it.txId), it.index) - val state = it.contractState.deserialize>(storageKryo()) + val state = it.contractState.deserialize>(context = STORAGE_CONTEXT) StateAndRef(state, stateRef) }.toList() } @@ -480,7 +480,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P result.get().forEach { val txHash = SecureHash.parse(it.txId) val index = it.index - val state = it.contractState.deserialize>(storageKryo()) + val state = it.contractState.deserialize>(context = STORAGE_CONTEXT) consumedStates.add(StateAndRef(state, StateRef(txHash, index))) } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt b/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt index 96147f2739..5b193ec2e8 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/JDBCHashMap.kt @@ -1,9 +1,9 @@ package net.corda.node.utilities +import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.serialization.storageKryo import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import org.jetbrains.exposed.sql.* @@ -65,17 +65,18 @@ fun bytesToBlob(value: SerializedBytes<*>, finalizables: MutableList<() -> Unit> return blob } -fun serializeToBlob(value: Any, finalizables: MutableList<() -> Unit>): Blob = bytesToBlob(value.serialize(storageKryo(), true), finalizables) +fun serializeToBlob(value: Any, finalizables: MutableList<() -> Unit>): Blob = bytesToBlob(value.serialize(context = STORAGE_CONTEXT), finalizables) fun bytesFromBlob(blob: Blob): SerializedBytes { try { - return SerializedBytes(blob.getBytes(0, blob.length().toInt()), true) + return SerializedBytes(blob.getBytes(0, blob.length().toInt())) } finally { blob.free() } } -fun deserializeFromBlob(blob: Blob): T = bytesFromBlob(blob).deserialize() +@Suppress("UNCHECKED_CAST") +fun deserializeFromBlob(blob: Blob): T = bytesFromBlob(blob).deserialize(context = STORAGE_CONTEXT) as T /** * A convenient JDBC table backed hash set with iteration order based on insertion order. diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 43daa96cbc..fc87226b3d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -37,7 +37,6 @@ object ServiceIdentityGenerator { keyPairs.zip(dirs) { keyPair, dir -> Files.createDirectories(dir) Files.write(dir.resolve(compositeKeyFile), notaryKey.encoded) - // Use storageKryo as our whitelist is not available in the gradle build environment: Files.write(dir.resolve(privateKeyFile), keyPair.private.encoded) Files.write(dir.resolve(publicKeyFile), keyPair.public.encoded) } diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 5583482b51..0d5fff054a 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -1,47 +1,60 @@ package net.corda.node.services.vault; -import com.google.common.collect.*; -import kotlin.*; -import net.corda.contracts.*; -import net.corda.contracts.asset.*; +import com.google.common.collect.ImmutableSet; +import net.corda.contracts.DealState; +import net.corda.contracts.asset.Cash; import net.corda.core.contracts.*; -import net.corda.core.crypto.*; -import net.corda.core.identity.*; -import net.corda.core.messaging.*; -import net.corda.core.node.services.*; +import net.corda.core.crypto.EncodingUtils; +import net.corda.core.crypto.SecureHash; +import net.corda.core.identity.AbstractParty; +import net.corda.core.messaging.DataFeed; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.VaultQueryException; +import net.corda.core.node.services.VaultQueryService; +import net.corda.core.node.services.VaultService; import net.corda.core.node.services.vault.*; -import net.corda.core.node.services.vault.QueryCriteria.*; -import net.corda.testing.contracts.DummyLinearContract; -import net.corda.core.schemas.*; -import net.corda.core.transactions.*; -import net.corda.core.utilities.*; +import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; +import net.corda.core.schemas.MappedSchema; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.WireTransaction; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.node.services.database.HibernateConfiguration; +import net.corda.node.services.schema.NodeSchemaService; import net.corda.node.utilities.CordaPersistence; -import net.corda.node.services.database.*; -import net.corda.node.services.schema.*; -import net.corda.schemas.*; -import net.corda.testing.*; -import net.corda.testing.contracts.*; -import net.corda.testing.node.*; +import net.corda.schemas.CashSchemaV1; +import net.corda.testing.TestConstants; +import net.corda.testing.TestDependencyInjectionBase; +import net.corda.testing.contracts.DummyLinearContract; +import net.corda.testing.contracts.VaultFiller; +import net.corda.testing.node.MockServices; import net.corda.testing.schemas.DummyLinearStateSchemaV1; -import org.jetbrains.annotations.*; -import org.junit.*; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import rx.Observable; -import java.io.*; -import java.lang.reflect.*; +import java.io.IOException; +import java.lang.reflect.Field; import java.util.*; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import static net.corda.contracts.asset.CashKt.*; -import static net.corda.core.contracts.ContractsDSL.*; -import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; +import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER; +import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY; +import static net.corda.core.contracts.ContractsDSL.USD; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; +import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.node.utilities.CordaPersistenceKt.configureDatabase; import static net.corda.testing.CoreTestUtils.*; -import static net.corda.testing.node.MockServicesKt.*; -import static net.corda.core.utilities.ByteArrays.toHexString; -import static org.assertj.core.api.Assertions.*; +import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties; +import static org.assertj.core.api.Assertions.assertThat; -public class VaultQueryJavaTests { +public class VaultQueryJavaTests extends TestDependencyInjectionBase { private MockServices services; private VaultService vaultSvc; diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index ad09bb67a8..7dbce0a062 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -8,6 +8,8 @@ import net.corda.node.services.messaging.createMessage import net.corda.node.services.network.NetworkMapService import net.corda.testing.node.MockNetwork import org.junit.After +import net.corda.testing.resetTestSerialization +import org.junit.Before import org.junit.Test import java.util.* import kotlin.test.assertEquals @@ -15,11 +17,20 @@ import kotlin.test.assertFails import kotlin.test.assertTrue class InMemoryMessagingTests { - val mockNet = MockNetwork() + lateinit var mockNet: MockNetwork + + @Before + fun setUp() { + mockNet = MockNetwork() + } @After - fun cleanUp() { - mockNet.stopNodes() + fun tearDown() { + if (mockNet.nodes.isNotEmpty()) { + mockNet.stopNodes() + } else { + resetTestSerialization() + } } @Test diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index e261576ccd..97664e7f0a 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -76,7 +76,6 @@ class TwoPartyTradeFlowTests { @Before fun before() { - mockNet = MockNetwork(false) LogHelper.setLevel("platform.trade", "core.contract.TransactionGroup", "recordingmap") } @@ -93,7 +92,7 @@ class TwoPartyTradeFlowTests { // allow interruption half way through. mockNet = MockNetwork(false, true) - ledger { + ledger(initialiseSerialization = false) { val basketOfNodes = mockNet.createSomeNodes(3) val notaryNode = basketOfNodes.notaryNode val aliceNode = basketOfNodes.partyNodes[0] @@ -140,7 +139,7 @@ class TwoPartyTradeFlowTests { fun `trade cash for commercial paper fails using soft locking`() { mockNet = MockNetwork(false, true) - ledger { + ledger(initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) @@ -191,7 +190,8 @@ class TwoPartyTradeFlowTests { @Test fun `shutdown and restore`() { - ledger { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) @@ -313,13 +313,15 @@ class TwoPartyTradeFlowTests { @Test fun `check dependencies of sale asset are resolved`() { + mockNet = MockNetwork(false) + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) - ledger(aliceNode.services) { + ledger(aliceNode.services, initialiseSerialization = false) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() @@ -412,13 +414,15 @@ class TwoPartyTradeFlowTests { @Test fun `track works`() { + mockNet = MockNetwork(false) + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) val issuer = bankNode.info.legalIdentity.ref(1, 2, 3) - ledger(aliceNode.services) { + ledger(aliceNode.services, initialiseSerialization = false) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() @@ -487,14 +491,16 @@ class TwoPartyTradeFlowTests { @Test fun `dependency with error on buyer side`() { - ledger { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { runWithError(true, false, "at least one asset input") } } @Test fun `dependency with error on seller side`() { - ledger { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { runWithError(false, true, "Issuances must have a time-window") } } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index cfd8a53088..7aeadcf487 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -34,7 +34,7 @@ class NotaryChangeTests { lateinit var clientNodeB: MockNetwork.MockNode @Before - fun setup() { + fun setUp() { mockNet = MockNetwork() oldNotaryNode = mockNet.createNode( legalName = DUMMY_NOTARY.name, diff --git a/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt index e26830be2f..fe17999d5c 100644 --- a/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/database/HibernateConfigurationTest.kt @@ -3,39 +3,32 @@ package net.corda.node.services.database import net.corda.contracts.asset.Cash import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DummyFungibleContract -import net.corda.testing.contracts.consumeCash -import net.corda.testing.contracts.fillWithSomeTestCash -import net.corda.testing.contracts.fillWithSomeTestDeals -import net.corda.testing.contracts.fillWithSomeTestLinearStates import net.corda.core.contracts.* import net.corda.core.crypto.toBase58String import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService +import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.PersistentStateRef -import net.corda.testing.schemas.DummyLinearStateSchemaV1 -import net.corda.testing.schemas.DummyLinearStateSchemaV2 -import net.corda.core.serialization.storageKryo +import net.corda.core.serialization.deserialize import net.corda.core.transactions.SignedTransaction -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.BOB_KEY -import net.corda.testing.DUMMY_NOTARY import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.NodeVaultService -import net.corda.core.schemas.CommonSchemaV1 -import net.corda.core.serialization.deserialize import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.schemas.CashSchemaV1 import net.corda.schemas.SampleCashSchemaV2 import net.corda.schemas.SampleCashSchemaV3 -import net.corda.testing.BOB_PUBKEY -import net.corda.testing.BOC -import net.corda.testing.BOC_KEY +import net.corda.testing.* +import net.corda.testing.contracts.consumeCash +import net.corda.testing.contracts.fillWithSomeTestCash +import net.corda.testing.contracts.fillWithSomeTestDeals +import net.corda.testing.contracts.fillWithSomeTestLinearStates import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties +import net.corda.testing.schemas.DummyLinearStateSchemaV1 +import net.corda.testing.schemas.DummyLinearStateSchemaV2 import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.hibernate.SessionFactory @@ -48,7 +41,7 @@ import javax.persistence.EntityManager import javax.persistence.Tuple import javax.persistence.criteria.CriteriaBuilder -class HibernateConfigurationTest { +class HibernateConfigurationTest : TestDependencyInjectionBase() { lateinit var services: MockServices lateinit var database: CordaPersistence @@ -655,7 +648,7 @@ class HibernateConfigurationTest { val queryResults = entityManager.createQuery(criteriaQuery).resultList queryResults.forEach { - val contractState = it.contractState.deserialize>(storageKryo()) + val contractState = it.contractState.deserialize>() val cashState = contractState.data as Cash.State println("${it.stateRef} with owner: ${cashState.owner.owningKey.toBase58String()}") } @@ -739,7 +732,7 @@ class HibernateConfigurationTest { // execute query val queryResults = entityManager.createQuery(criteriaQuery).resultList queryResults.forEach { - val contractState = it.contractState.deserialize>(storageKryo()) + val contractState = it.contractState.deserialize>() val cashState = contractState.data as Cash.State println("${it.stateRef} with owner ${cashState.owner.owningKey.toBase58String()} and participants ${cashState.participants.map { it.owningKey.toBase58String() }}") } diff --git a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt index a670cf2a4d..5153d9189e 100644 --- a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt @@ -5,7 +5,6 @@ import io.requery.kotlin.eq import io.requery.sql.KotlinEntityDataStore import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.crypto.testing.NullPublicKey @@ -13,11 +12,8 @@ import net.corda.core.crypto.toBase58String import net.corda.core.identity.AnonymousParty import net.corda.core.node.services.Vault import net.corda.core.serialization.serialize -import net.corda.core.serialization.storageKryo import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_PUBKEY_1 import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.vault.schemas.requery.Models import net.corda.node.services.vault.schemas.requery.VaultCashBalancesEntity @@ -25,6 +21,10 @@ import net.corda.node.services.vault.schemas.requery.VaultSchema import net.corda.node.services.vault.schemas.requery.VaultStatesEntity import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.DUMMY_PUBKEY_1 +import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.contracts.DummyContract import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions import org.junit.After @@ -35,7 +35,7 @@ import org.junit.Test import java.time.Instant import java.util.* -class RequeryConfigurationTest { +class RequeryConfigurationTest : TestDependencyInjectionBase() { lateinit var database: CordaPersistence lateinit var transactionStorage: DBTransactionStorage @@ -175,7 +175,7 @@ class RequeryConfigurationTest { index = txnState.index stateStatus = Vault.StateStatus.UNCONSUMED contractStateClassName = DummyContract.SingleOwnerState::class.java.name - contractState = DummyContract.SingleOwnerState(owner = AnonymousParty(DUMMY_PUBKEY_1)).serialize(storageKryo()).bytes + contractState = DummyContract.SingleOwnerState(owner = AnonymousParty(DUMMY_PUBKEY_1)).serialize().bytes notaryName = txn.tx.notary!!.name.toString() notaryKey = txn.tx.notary!!.owningKey.toBase58String() recordedTime = Instant.now() 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 abce8ae027..f3c60fae4f 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 @@ -25,6 +25,8 @@ import net.corda.testing.node.MockKeyManagementService import net.corda.testing.node.TestClock import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.testNodeConfiguration +import net.corda.testing.initialiseTestSerialization +import net.corda.testing.resetTestSerialization import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x500.X500Name import org.junit.After @@ -67,6 +69,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @Before fun setup() { + initialiseTestSerialization() countDown = CountDownLatch(1) smmHasRemovedAllFlows = CountDownLatch(1) calls = 0 @@ -114,6 +117,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { smmExecutor.shutdown() smmExecutor.awaitTermination(60, TimeUnit.SECONDS) database.close() + resetTestSerialization() } class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant) : LinearState, SchedulableState { diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 59f71acde4..f9cd07f578 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -37,7 +37,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNull //TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment -class ArtemisMessagingTests { +class ArtemisMessagingTests : TestDependencyInjectionBase() { @Rule @JvmField val temporaryFolder = TemporaryFolder() val serverPort = freePort() diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt index 602481e6c6..9fa1d0e0e5 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt @@ -80,16 +80,18 @@ class InMemoryIdentityServiceTests { */ @Test fun `assert unknown anonymous key is unrecognised`() { - val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey) - val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) - // TODO: Generate certificate with an EdDSA key rather than ECDSA - val identity = Party(CertificateAndKeyPair(rootCert, rootKey)) - val txIdentity = AnonymousParty(txKey.public) + withTestSerialization { + val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey) + val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) + // TODO: Generate certificate with an EdDSA key rather than ECDSA + val identity = Party(CertificateAndKeyPair(rootCert, rootKey)) + val txIdentity = AnonymousParty(txKey.public) - assertFailsWith { - service.assertOwnership(identity, txIdentity) + assertFailsWith { + service.assertOwnership(identity, txIdentity) + } } } @@ -122,38 +124,40 @@ class InMemoryIdentityServiceTests { */ @Test fun `assert ownership`() { - val trustRoot = DUMMY_CA - val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot) + withTestSerialization { + val trustRoot = DUMMY_CA + val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot) - val certFactory = CertificateFactory.getInstance("X509") - val bobRootKey = Crypto.generateKeyPair() - val bobRoot = getTestPartyAndCertificate(BOB.name, bobRootKey.public) - val bobRootCert = bobRoot.certificate - val bobTxKey = Crypto.generateKeyPair() - val bobTxCert = X509Utilities.createCertificate(CertificateType.IDENTITY, bobRootCert, bobRootKey, BOB.name, bobTxKey.public) - val bobCertPath = certFactory.generateCertPath(listOf(bobTxCert.cert, bobRootCert.cert)) - val bob = PartyAndCertificate(BOB.name, bobRootKey.public, bobRootCert, bobCertPath) + val certFactory = CertificateFactory.getInstance("X509") + val bobRootKey = Crypto.generateKeyPair() + val bobRoot = getTestPartyAndCertificate(BOB.name, bobRootKey.public) + val bobRootCert = bobRoot.certificate + val bobTxKey = Crypto.generateKeyPair() + val bobTxCert = X509Utilities.createCertificate(CertificateType.IDENTITY, bobRootCert, bobRootKey, BOB.name, bobTxKey.public) + val bobCertPath = certFactory.generateCertPath(listOf(bobTxCert.cert, bobRootCert.cert)) + val bob = PartyAndCertificate(BOB.name, bobRootKey.public, bobRootCert, bobCertPath) - // Now we have identities, construct the service and let it know about both - val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert) - service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party) + // Now we have identities, construct the service and let it know about both + val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert) + service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party) - val anonymousBob = AnonymousPartyAndPath(AnonymousParty(bobTxKey.public),bobCertPath) - service.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party) + val anonymousBob = AnonymousPartyAndPath(AnonymousParty(bobTxKey.public),bobCertPath) + service.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party) - // Verify that paths are verified - service.assertOwnership(alice.party, aliceTxIdentity.party) - service.assertOwnership(bob.party, anonymousBob.party) - assertFailsWith { - service.assertOwnership(alice.party, anonymousBob.party) - } - assertFailsWith { - service.assertOwnership(bob.party, aliceTxIdentity.party) - } + // Verify that paths are verified + service.assertOwnership(alice.party, aliceTxIdentity.party) + service.assertOwnership(bob.party, anonymousBob.party) + assertFailsWith { + service.assertOwnership(alice.party, anonymousBob.party) + } + assertFailsWith { + service.assertOwnership(bob.party, aliceTxIdentity.party) + } - assertFailsWith { - val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded) - service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.party) + assertFailsWith { + val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded) + service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.party) + } } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt index 4ac1ff00cc..2d25f28741 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapCacheTest.kt @@ -7,12 +7,18 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.node.MockNetwork import org.junit.After +import org.junit.Before import org.junit.Test import java.math.BigInteger import kotlin.test.assertEquals class InMemoryNetworkMapCacheTest { - private val mockNet = MockNetwork() + lateinit var mockNet: MockNetwork + + @Before + fun setUp() { + mockNet = MockNetwork() + } @After fun teardown() { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt index 9ddc8f8b07..b303a8b425 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -2,12 +2,13 @@ package net.corda.node.services.persistence import com.google.common.primitives.Ints import net.corda.core.serialization.SerializedBytes -import net.corda.testing.LogHelper import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase +import net.corda.testing.LogHelper +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -24,7 +25,7 @@ internal fun CheckpointStorage.checkpoints(): List { return checkpoints } -class DBCheckpointStorageTests { +class DBCheckpointStorageTests : TestDependencyInjectionBase() { lateinit var checkpointStorage: DBCheckpointStorage lateinit var database: CordaPersistence diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 352e0f52ba..f6c14b3deb 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -8,11 +8,12 @@ import net.corda.core.crypto.testing.NullPublicKey import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.LogHelper import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.LogHelper +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -21,7 +22,7 @@ import org.junit.Test import java.util.concurrent.TimeUnit import kotlin.test.assertEquals -class DBTransactionStorageTests { +class DBTransactionStorageTests : TestDependencyInjectionBase() { lateinit var database: CordaPersistence lateinit var transactionStorage: DBTransactionStorage diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt index 41c79f7373..bb0742468a 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt @@ -8,10 +8,11 @@ import io.atomix.copycat.server.storage.Storage import io.atomix.copycat.server.storage.StorageLevel import net.corda.core.getOrThrow import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.LogHelper import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase +import net.corda.testing.LogHelper +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.freeLocalHostAndPort import net.corda.testing.node.makeTestDataSourceProperties import org.jetbrains.exposed.sql.Transaction @@ -22,7 +23,7 @@ import java.util.concurrent.CompletableFuture import kotlin.test.assertEquals import kotlin.test.assertTrue -class DistributedImmutableMapTests { +class DistributedImmutableMapTests : TestDependencyInjectionBase() { data class Member(val client: CopycatClient, val server: CopycatServer) lateinit var cluster: List diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 8bfb983ee9..858bae0539 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -6,6 +6,7 @@ import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.LogHelper import net.corda.testing.MEGA_CORP +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.generateStateRef import net.corda.testing.node.makeTestDataSourceProperties import org.junit.After @@ -14,7 +15,7 @@ import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class PersistentUniquenessProviderTests { +class PersistentUniquenessProviderTests : TestDependencyInjectionBase() { val identity = MEGA_CORP val txID = SecureHash.randomSHA256() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 2647bbbd89..08e4b913e3 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -34,7 +34,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue -class NodeVaultServiceTest { +class NodeVaultServiceTest : TestDependencyInjectionBase() { lateinit var services: MockServices val vaultSvc: VaultService get() = services.vaultService lateinit var database: CordaPersistence diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index af9e8c009b..2b2f6a6038 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -15,8 +15,8 @@ import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toHexString import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.schema.NodeSchemaService @@ -46,7 +46,7 @@ import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.* -class VaultQueryTests { +class VaultQueryTests : TestDependencyInjectionBase() { lateinit var services: MockServices val vaultSvc: VaultService get() = services.vaultService diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 7f18cc6c81..9ab1cc3f8b 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -1,13 +1,8 @@ package net.corda.node.services.vault -import net.corda.testing.contracts.DummyDealContract import net.corda.contracts.asset.Cash import net.corda.contracts.asset.DUMMY_CASH_ISSUER -import net.corda.testing.contracts.fillWithSomeTestCash -import net.corda.testing.contracts.fillWithSomeTestDeals -import net.corda.testing.contracts.fillWithSomeTestLinearStates import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyLinearContract import net.corda.core.identity.AnonymousParty import net.corda.core.node.services.VaultService import net.corda.core.node.services.consumedStates @@ -15,12 +10,8 @@ import net.corda.core.node.services.unconsumedStates import net.corda.core.transactions.SignedTransaction import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase -import net.corda.testing.BOB -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.testing.LogHelper -import net.corda.testing.MEGA_CORP -import net.corda.testing.MEGA_CORP_KEY +import net.corda.testing.* +import net.corda.testing.contracts.* import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat @@ -36,7 +27,7 @@ import kotlin.test.assertNull // TODO: Move this to the cash contract tests once mock services are further split up. -class VaultWithCashTest { +class VaultWithCashTest : TestDependencyInjectionBase() { lateinit var services: MockServices val vault: VaultService get() = services.vaultService lateinit var database: CordaPersistence 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 66dce242fe..8eb0df9e3b 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 @@ -79,7 +79,7 @@ class IRSDemoTest : IntegrationTestCategory { fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } fun getFixingDateObservable(config: FullNodeConfiguration): Observable { - val client = CordaRPCClient(config.rpcAddress!!) + val client = CordaRPCClient(config.rpcAddress!!, initialiseSerialization = false) val proxy = client.start("user", "password").proxy val vaultUpdates = proxy.vaultAndUpdates().second diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 0c65e25f2f..ac2ee52fc1 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -14,7 +14,6 @@ import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo import net.corda.core.transactions.TransactionBuilder -import net.corda.testing.LogHelper import net.corda.core.utilities.ProgressTracker import net.corda.irs.flows.RatesFixFlow import net.corda.node.utilities.CordaPersistence @@ -34,7 +33,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse -class NodeInterestRatesTest { +class NodeInterestRatesTest : TestDependencyInjectionBase() { val TEST_DATA = NodeInterestRates.parseFile(""" LIBOR 2016-03-16 1M = 0.678 LIBOR 2016-03-16 2M = 0.685 @@ -202,7 +201,7 @@ class NodeInterestRatesTest { @Test fun `network tearoff`() { - val mockNet = MockNetwork() + val mockNet = MockNetwork(initialiseSerialization = false) val n1 = mockNet.createNotaryNode() val n2 = mockNet.createNode(n1.network.myAddress, advertisedServices = ServiceInfo(NodeInterestRates.Oracle.type)) n2.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 576dee021e..ce7e0b17ef 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -4,9 +4,6 @@ import net.corda.contracts.* import net.corda.core.contracts.* import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.testing.TEST_TX_TIME import net.corda.testing.* import net.corda.testing.node.MockServices import org.junit.Test @@ -200,7 +197,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State { } } -class IRSTests { +class IRSTests : TestDependencyInjectionBase() { val megaCorpServices = MockServices(MEGA_CORP_KEY) val miniCorpServices = MockServices(MINI_CORP_KEY) val notaryServices = MockServices(DUMMY_NOTARY_KEY) @@ -370,7 +367,7 @@ class IRSTests { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") - return ledger { + return ledger(initialiseSerialization = false) { transaction("Agreement") { output("irs post agreement") { singleIRS() } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } @@ -401,7 +398,7 @@ class IRSTests { @Test fun `ensure failure occurs when there are inbound states for an agreement command`() { val irs = singleIRS() - transaction { + transaction(initialiseSerialization = false) { input { irs } output("irs post agreement") { irs } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } @@ -414,7 +411,7 @@ class IRSTests { fun `ensure failure occurs when no events in fix schedule`() { val irs = singleIRS() val emptySchedule = mutableMapOf() - transaction { + transaction(initialiseSerialization = false) { output { irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)) } @@ -428,7 +425,7 @@ class IRSTests { fun `ensure failure occurs when no events in floating schedule`() { val irs = singleIRS() val emptySchedule = mutableMapOf() - transaction { + transaction(initialiseSerialization = false) { output { irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)) } @@ -441,7 +438,7 @@ class IRSTests { @Test fun `ensure notionals are non zero`() { val irs = singleIRS() - transaction { + transaction(initialiseSerialization = false) { output { irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))) } @@ -450,7 +447,7 @@ class IRSTests { this `fails with` "All notionals must be non zero" } - transaction { + transaction(initialiseSerialization = false) { output { irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))) } @@ -464,7 +461,7 @@ class IRSTests { fun `ensure positive rate on fixed leg`() { val irs = singleIRS() val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1")))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS } @@ -481,7 +478,7 @@ class IRSTests { fun `ensure same currency notionals`() { val irs = singleIRS() val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY")))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS } @@ -495,7 +492,7 @@ class IRSTests { fun `ensure notional amounts are equal`() { val irs = singleIRS() val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS } @@ -509,7 +506,7 @@ class IRSTests { fun `ensure trade date and termination date checks are done pt1`() { val irs = singleIRS() val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS1 } @@ -519,7 +516,7 @@ class IRSTests { } val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS2 } @@ -534,7 +531,7 @@ class IRSTests { val irs = singleIRS() val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS3 } @@ -545,7 +542,7 @@ class IRSTests { val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { + transaction(initialiseSerialization = false) { output { modifiedIRS4 } @@ -561,7 +558,7 @@ class IRSTests { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") - transaction { + transaction(initialiseSerialization = false) { output("irs post agreement") { singleIRS() } command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timeWindow(TEST_TX_TIME) @@ -574,7 +571,7 @@ class IRSTests { oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), oldIRS.common) - transaction { + transaction(initialiseSerialization = false) { input { oldIRS @@ -654,7 +651,7 @@ class IRSTests { val irs = singleIRS() - return ledger { + return ledger(initialiseSerialization = false) { transaction("Agreement") { output("irs post agreement1") { irs.copy( diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index ddd7127651..a4a9854376 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -10,11 +10,11 @@ import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_NOTARY import net.corda.flows.IssuerFlow -import net.corda.testing.driver.poll import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.SimpleNotaryService import net.corda.nodeapi.User import net.corda.testing.BOC +import net.corda.testing.driver.poll import net.corda.testing.node.NodeBasedTest import net.corda.traderdemo.flow.BuyerFlow import net.corda.traderdemo.flow.SellerFlow @@ -40,7 +40,7 @@ class TraderDemoTest : NodeBasedTest() { nodeA.registerInitiatedFlow(BuyerFlow::class.java) val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map { - val client = CordaRPCClient(it.configuration.rpcAddress!!) + val client = CordaRPCClient(it.configuration.rpcAddress!!, initialiseSerialization = false) client.start(demoUser[0].username, demoUser[0].password).proxy } diff --git a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 6c36d638c6..b39a2a2e22 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -126,11 +126,17 @@ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List.() -> Unit ): LedgerDSL { - val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services)) - dsl(ledgerDsl) - return ledgerDsl + if (initialiseSerialization) initialiseTestSerialization() + try { + val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services)) + dsl(ledgerDsl) + return ledgerDsl + } finally { + if (initialiseSerialization) resetTestSerialization() + } } /** @@ -141,8 +147,9 @@ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List.() -> EnforceVerifyOrFail -) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) } +) = ledger(initialiseSerialization = initialiseSerialization) { this.transaction(transactionLabel, transactionBuilder, dsl) } fun testNodeConfiguration( baseDirectory: Path, diff --git a/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt b/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt index ff78e3deeb..a85426fefe 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -5,6 +5,7 @@ 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 import net.corda.core.internal.div @@ -22,6 +23,7 @@ import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi import net.corda.nodeapi.User +import net.corda.nodeapi.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.testing.driver.* import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.TransportConfiguration @@ -224,6 +226,7 @@ fun rpcDriver( debugPortAllocation: PortAllocation = globalDebugPortAllocation, systemProperties: Map = emptyMap(), useTestClock: Boolean = false, + initialiseSerialization: Boolean = true, networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, dsl: RPCDriverExposedDSLInterface.() -> A @@ -241,7 +244,8 @@ fun rpcDriver( ) ), coerce = { it }, - dsl = dsl + dsl = dsl, + initialiseSerialization = initialiseSerialization ) private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityManager3 { @@ -510,7 +514,8 @@ class RandomRpcUser { val hostAndPort = args[1].parseNetworkHostAndPort() val username = args[2] val password = args[3] - val handle = RPCClient(hostAndPort, null).start(rpcClass, username, password) + CordaRPCClient.initialiseSerialization() + val handle = RPCClient(hostAndPort, null, serializationContext = KRYO_RPC_CLIENT_CONTEXT).start(rpcClass, username, password) val callGenerators = rpcClass.declaredMethods.map { method -> Generator.sequence(method.parameters.map { generatorStore[it.type] ?: throw Exception("No generator for ${it.type}") diff --git a/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt new file mode 100644 index 0000000000..5a6f381cc1 --- /dev/null +++ b/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -0,0 +1,140 @@ +package net.corda.testing + +import net.corda.client.rpc.serialization.KryoClientSerializationScheme +import net.corda.core.serialization.* +import net.corda.core.utilities.ByteSequence +import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.nodeapi.serialization.* + +fun withTestSerialization(block: () -> T): T { + initialiseTestSerialization() + try { + return block() + } finally { + resetTestSerialization() + } +} + +fun initialiseTestSerialization() { + // Check that everything is configured for testing with mutable delegating instances. + try { + check(SerializationDefaults.SERIALIZATION_FACTORY is TestSerializationFactory) { + "Found non-test serialization configuration: ${SerializationDefaults.SERIALIZATION_FACTORY}" + } + } catch(e: IllegalStateException) { + SerializationDefaults.SERIALIZATION_FACTORY = TestSerializationFactory() + } + try { + check(SerializationDefaults.P2P_CONTEXT is TestSerializationContext) + } catch(e: IllegalStateException) { + SerializationDefaults.P2P_CONTEXT = TestSerializationContext() + } + try { + check(SerializationDefaults.RPC_SERVER_CONTEXT is TestSerializationContext) + } catch(e: IllegalStateException) { + SerializationDefaults.RPC_SERVER_CONTEXT = TestSerializationContext() + } + try { + check(SerializationDefaults.RPC_CLIENT_CONTEXT is TestSerializationContext) + } catch(e: IllegalStateException) { + SerializationDefaults.RPC_CLIENT_CONTEXT = TestSerializationContext() + } + try { + check(SerializationDefaults.STORAGE_CONTEXT is TestSerializationContext) + } catch(e: IllegalStateException) { + SerializationDefaults.STORAGE_CONTEXT = TestSerializationContext() + } + try { + check(SerializationDefaults.CHECKPOINT_CONTEXT is TestSerializationContext) + } catch(e: IllegalStateException) { + SerializationDefaults.CHECKPOINT_CONTEXT = TestSerializationContext() + } + + // Check that the previous test, if there was one, cleaned up after itself. + // IF YOU SEE THESE MESSAGES, THEN IT MEANS A TEST HAS NOT CALLED resetTestSerialization() + check((SerializationDefaults.SERIALIZATION_FACTORY as TestSerializationFactory).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.SERIALIZATION_FACTORY}" }) + check((SerializationDefaults.P2P_CONTEXT as TestSerializationContext).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.P2P_CONTEXT}" }) + check((SerializationDefaults.RPC_SERVER_CONTEXT as TestSerializationContext).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.RPC_SERVER_CONTEXT}" }) + check((SerializationDefaults.RPC_CLIENT_CONTEXT as TestSerializationContext).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.RPC_CLIENT_CONTEXT}" }) + check((SerializationDefaults.STORAGE_CONTEXT as TestSerializationContext).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.STORAGE_CONTEXT}" }) + check((SerializationDefaults.CHECKPOINT_CONTEXT as TestSerializationContext).delegate == null, { "Expected uninitialised serialization framework but found it set from: ${SerializationDefaults.CHECKPOINT_CONTEXT}" }) + + // Now configure all the testing related delegates. + (SerializationDefaults.SERIALIZATION_FACTORY as TestSerializationFactory).delegate = SerializationFactoryImpl().apply { + registerScheme(KryoClientSerializationScheme()) + registerScheme(KryoServerSerializationScheme()) + } + (SerializationDefaults.P2P_CONTEXT as TestSerializationContext).delegate = KRYO_P2P_CONTEXT + (SerializationDefaults.RPC_SERVER_CONTEXT as TestSerializationContext).delegate = KRYO_RPC_SERVER_CONTEXT + (SerializationDefaults.RPC_CLIENT_CONTEXT as TestSerializationContext).delegate = KRYO_RPC_CLIENT_CONTEXT + (SerializationDefaults.STORAGE_CONTEXT as TestSerializationContext).delegate = KRYO_STORAGE_CONTEXT + (SerializationDefaults.CHECKPOINT_CONTEXT as TestSerializationContext).delegate = KRYO_CHECKPOINT_CONTEXT +} + +fun resetTestSerialization() { + (SerializationDefaults.SERIALIZATION_FACTORY as TestSerializationFactory).delegate = null + (SerializationDefaults.P2P_CONTEXT as TestSerializationContext).delegate = null + (SerializationDefaults.RPC_SERVER_CONTEXT as TestSerializationContext).delegate = null + (SerializationDefaults.RPC_CLIENT_CONTEXT as TestSerializationContext).delegate = null + (SerializationDefaults.STORAGE_CONTEXT as TestSerializationContext).delegate = null + (SerializationDefaults.CHECKPOINT_CONTEXT as TestSerializationContext).delegate = null +} + +class TestSerializationFactory : SerializationFactory { + var delegate: SerializationFactory? = null + set(value) { + field = value + stackTrace = Exception().stackTrace.asList() + } + private var stackTrace: List? = null + + override fun toString(): String = stackTrace?.joinToString("\n") ?: "null" + + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { + return delegate!!.deserialize(byteSequence, clazz, context) + } + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { + return delegate!!.serialize(obj, context) + } +} + +class TestSerializationContext : SerializationContext { + var delegate: SerializationContext? = null + set(value) { + field = value + stackTrace = Exception().stackTrace.asList() + } + private var stackTrace: List? = null + + override fun toString(): String = stackTrace?.joinToString("\n") ?: "null" + + override val preferedSerializationVersion: ByteSequence + get() = delegate!!.preferedSerializationVersion + override val deserializationClassLoader: ClassLoader + get() = delegate!!.deserializationClassLoader + override val whitelist: ClassWhitelist + get() = delegate!!.whitelist + override val properties: Map + get() = delegate!!.properties + override val objectReferencesEnabled: Boolean + get() = delegate!!.objectReferencesEnabled + override val useCase: SerializationContext.UseCase + get() = delegate!!.useCase + + override fun withProperty(property: Any, value: Any): SerializationContext { + return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withProperty(property, value) } + } + + override fun withoutReferences(): SerializationContext { + return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withoutReferences() } + } + + override fun withClassLoader(classLoader: ClassLoader): SerializationContext { + return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withClassLoader(classLoader) } + } + + override fun withWhitelisted(clazz: Class<*>): SerializationContext { + return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) } + } +} diff --git a/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt b/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt new file mode 100644 index 0000000000..549cd2ac6d --- /dev/null +++ b/test-utils/src/main/kotlin/net/corda/testing/TestDependencyInjectionBase.kt @@ -0,0 +1,19 @@ +package net.corda.testing + +import org.junit.After +import org.junit.Before + +/** + * The beginnings of somewhere to inject implementations for unit tests. + */ +abstract class TestDependencyInjectionBase { + @Before + fun initialiseSerialization() { + initialiseTestSerialization() + } + + @After + fun resetInitialisation() { + resetTestSerialization() + } +} \ No newline at end of file diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index e279266d41..d75150b844 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -40,6 +40,8 @@ import net.corda.testing.BOB import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY import net.corda.testing.node.MOCK_VERSION_INFO +import net.corda.testing.initialiseTestSerialization +import net.corda.testing.resetTestSerialization import okhttp3.OkHttpClient import okhttp3.Request import org.bouncycastle.asn1.x500.X500Name @@ -187,7 +189,7 @@ sealed class NodeHandle { val nodeThread: Thread ) : NodeHandle() - fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!) + fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!, initialiseSerialization = false) } data class WebserverHandle( @@ -250,6 +252,7 @@ fun driver( debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005), systemProperties: Map = emptyMap(), useTestClock: Boolean = false, + initialiseSerialization: Boolean = true, networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), startNodesInProcess: Boolean = false, dsl: DriverDSLExposedInterface.() -> A @@ -278,9 +281,11 @@ fun driver( */ fun genericDriver( driverDsl: D, + initialiseSerialization: Boolean = true, coerce: (D) -> DI, dsl: DI.() -> A ): A { + if (initialiseSerialization) initialiseTestSerialization() val shutdownHook = addShutdownHook(driverDsl::shutdown) try { driverDsl.start() @@ -291,6 +296,7 @@ fun genericD } finally { driverDsl.shutdown() shutdownHook.cancel() + if (initialiseSerialization) resetTestSerialization() } } @@ -511,7 +517,7 @@ class DriverDSL( } private fun establishRpc(nodeAddress: NetworkHostAndPort, sslConfig: SSLConfiguration, processDeathFuture: ListenableFuture): ListenableFuture { - val client = CordaRPCClient(nodeAddress, sslConfig) + val client = CordaRPCClient(nodeAddress, sslConfig, initialiseSerialization = false) val connectionFuture = poll(executorService, "RPC connection") { try { client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER) @@ -769,7 +775,7 @@ class DriverDSL( writeConfig(nodeConf.baseDirectory, "node.conf", config) val clock: Clock = if (nodeConf.useTestClock) TestClock() else NodeClock() // TODO pass the version in? - val node = Node(nodeConf, nodeConf.calculateServices(), MOCK_VERSION_INFO, clock) + val node = Node(nodeConf, nodeConf.calculateServices(), MOCK_VERSION_INFO, clock, initialiseSerialization = false) node.start() val nodeThread = thread(name = nodeConf.myLegalName.commonName) { node.run() diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt index 28e856a203..34b3b53401 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -5,11 +5,11 @@ import com.google.common.jimfs.Jimfs import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.* import net.corda.core.crypto.CertificateAndKeyPair import net.corda.core.crypto.cert import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue +import net.corda.core.getOrThrow import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory @@ -19,11 +19,11 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.ServiceEntry -import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.node.WorldMapLocation import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.ServiceInfo +import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.node.internal.AbstractNode import net.corda.node.services.config.NodeConfiguration @@ -67,7 +67,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, private val threadPerNode: Boolean = false, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - private val defaultFactory: Factory = MockNetwork.DefaultFactory) { + private val defaultFactory: Factory = MockNetwork.DefaultFactory, + private val initialiseSerialization: Boolean = true) { val nextNodeId get() = _nextNodeId private var _nextNodeId = 0 @@ -85,6 +86,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, val nodes: List = _nodes init { + if (initialiseSerialization) initialiseTestSerialization() filesystem.getPath("/nodes").createDirectory() } @@ -396,6 +398,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, fun stopNodes() { nodes.forEach { if (it.started) it.stop() } + if (initialiseSerialization) resetTestSerialization() } // Test method to block until all scheduled activity, active flows diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt b/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt index 7aa63a557f..a59e94a238 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt @@ -3,12 +3,14 @@ package net.corda.testing.node import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors.listeningDecorator -import net.corda.core.* import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName +import net.corda.core.flatMap +import net.corda.core.getOrThrow import net.corda.core.internal.createDirectories import net.corda.core.internal.div +import net.corda.core.map import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType import net.corda.core.utilities.WHITESPACE @@ -23,6 +25,7 @@ import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.User import net.corda.nodeapi.config.parseAs import net.corda.testing.DUMMY_MAP +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts import org.apache.logging.log4j.Level @@ -39,7 +42,7 @@ import kotlin.concurrent.thread * purposes. Use the driver if you need to run the nodes in separate processes otherwise this class will suffice. */ // TODO Some of the logic here duplicates what's in the driver -abstract class NodeBasedTest { +abstract class NodeBasedTest : TestDependencyInjectionBase() { @Rule @JvmField val tempFolder = TemporaryFolder() @@ -161,7 +164,7 @@ abstract class NodeBasedTest { val parsedConfig = config.parseAs() val node = Node(parsedConfig, parsedConfig.calculateServices(), MOCK_VERSION_INFO.copy(platformVersion = platformVersion), - if (parsedConfig.useTestClock) TestClock() else NodeClock()) + if (parsedConfig.useTestClock) TestClock() else NodeClock(), initialiseSerialization = false) node.start() nodes += node thread(name = legalName.commonName) { diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index f5c54a013a..e8eb1cb910 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -1,9 +1,13 @@ package net.corda.verifier +import com.esotericsoftware.kryo.pool.KryoPool import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import net.corda.core.internal.div +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor @@ -14,6 +18,10 @@ import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.getValue import net.corda.nodeapi.internal.addShutdownHook +import net.corda.nodeapi.serialization.AbstractKryoSerializationScheme +import net.corda.nodeapi.serialization.KRYO_P2P_CONTEXT +import net.corda.nodeapi.serialization.KryoHeaderV0_1 +import net.corda.nodeapi.serialization.SerializationFactoryImpl import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths @@ -55,6 +63,7 @@ class Verifier { session.close() sessionFactory.close() } + initialiseSerialization() val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME) val replyProducer = session.createProducer() consumer.setMessageHandler { @@ -77,5 +86,26 @@ class Verifier { log.info("Verifier started") Thread.sleep(Long.MAX_VALUE) } + + private fun initialiseSerialization() { + SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { + registerScheme(KryoVerifierSerializationScheme) + } + SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT + } + } + + object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return byteSequence.equals(KryoHeaderV0_1) && target == SerializationContext.UseCase.P2P + } + + override fun rpcClientKryoPool(context: SerializationContext): KryoPool { + throw UnsupportedOperationException() + } + + override fun rpcServerKryoPool(context: SerializationContext): KryoPool { + throw UnsupportedOperationException() + } } } \ No newline at end of file From 9a02a27619dcc39abd49627f62d087326ae253ec Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Tue, 18 Jul 2017 12:02:56 +0100 Subject: [PATCH 064/197] Add state query methods to LedgerTransaction. Update to use LedgerTransaction api Push query output logic onto BaseTransaction and update usages where possible Migrate a few more uses Address some PR comments Address some PR comments Fixup after rebase --- .../corda/core/contracts/TransactionTypes.kt | 4 +- .../core/contracts/clauses/ClauseVerifier.kt | 2 +- .../corda/core/flows/ContractUpgradeFlow.kt | 2 +- .../net/corda/core/flows/FinalityFlow.kt | 2 +- .../core/transactions/BaseTransaction.kt | 123 +++++++- .../core/transactions/LedgerTransaction.kt | 176 ++++++++++- .../core/transactions/WireTransaction.kt | 13 +- .../contracts/LedgerTransactionQueryTests.kt | 289 ++++++++++++++++++ .../contracts/TransactionEncumbranceTests.kt | 2 +- .../core/flows/CollectSignaturesFlowTests.kt | 6 +- .../core/node/AttachmentClassLoaderTests.kt | 2 +- .../kotlin/net/corda/docs/FlowCookbook.kt | 9 +- .../docs/WorkflowTransactionBuildTutorial.kt | 2 +- .../contracts/universal/UniversalContract.kt | 16 +- .../net/corda/contracts/CommercialPaper.kt | 4 +- .../corda/contracts/CommercialPaperLegacy.kt | 4 +- .../net/corda/contracts/asset/Obligation.kt | 4 +- .../main/kotlin/net/corda/flows/IssuerFlow.kt | 7 +- .../net/corda/flows/TwoPartyTradeFlow.kt | 4 +- .../net/corda/contracts/asset/CashTests.kt | 10 +- .../corda/contracts/asset/ObligationTests.kt | 10 +- .../net/corda/flows/CashExitFlowTests.kt | 2 +- .../net/corda/flows/CashIssueFlowTests.kt | 2 +- .../node/services/vault/NodeVaultService.kt | 6 +- .../corda/node/services/NotaryChangeTests.kt | 12 +- .../corda/attachmentdemo/AttachmentDemo.kt | 4 +- .../kotlin/net/corda/irs/contract/IRSTests.kt | 6 +- .../net/corda/explorer/views/Network.kt | 4 +- .../corda/explorer/views/TransactionViewer.kt | 7 +- 29 files changed, 650 insertions(+), 84 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt index d22eadb11f..da33d63e5d 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionTypes.kt @@ -123,7 +123,7 @@ sealed class TransactionType { */ private fun verifyContracts(tx: LedgerTransaction) { // TODO: This will all be replaced in future once the sandbox and contract constraints work is done. - val contracts = (tx.inputs.map { it.state.data.contract } + tx.outputs.map { it.data.contract }).toSet() + val contracts = (tx.inputStates.map { it.contract } + tx.outputStates.map { it.contract }).toSet() for (contract in contracts) { try { contract.verify(tx) @@ -171,6 +171,6 @@ sealed class TransactionType { } } - override fun getRequiredSigners(tx: LedgerTransaction) = tx.inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() + override fun getRequiredSigners(tx: LedgerTransaction) = tx.inputStates.flatMap { it.participants }.map { it.owningKey }.toSet() } } diff --git a/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt b/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt index db970d5f07..3583fe1779 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/clauses/ClauseVerifier.kt @@ -23,7 +23,7 @@ fun verifyClause(tx: LedgerTransaction, Clause.log.trace("Tx ${tx.id} clause: $clause") } } - val matchedCommands = clause.verify(tx, tx.inputs.map { it.state.data }, tx.outputs.map { it.data }, commands, null) + val matchedCommands = clause.verify(tx, tx.inputStates, tx.outputStates, commands, null) check(matchedCommands.containsAll(commands.map { it.value })) { "The following commands were not matched at the end of execution: " + (commands - matchedCommands) } } diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index 9ec39b7093..81b9657ff3 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -24,7 +24,7 @@ class ContractUpgradeFlow, * overriding functions. */ protected fun extractParticipants(ltx: LedgerTransaction): List { - return ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants } + return ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants } } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 876efdda82..896fead8be 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -2,8 +2,10 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.identity.Party +import net.corda.core.indexOfOrThrow import java.security.PublicKey import java.util.* +import java.util.function.Predicate /** * An abstract class defining fields shared by all transaction types in the system. @@ -56,4 +58,123 @@ abstract class BaseTransaction( override fun hashCode() = Objects.hash(notary, mustSign, type, timeWindow) override fun toString(): String = "${javaClass.simpleName}(id=$id)" -} + + /** + * Returns a [StateAndRef] for the given output index. + */ + @Suppress("UNCHECKED_CAST") + fun outRef(index: Int): StateAndRef = StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) + + + /** + * Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. + */ + fun outRef(state: ContractState): StateAndRef = outRef(outputStates.indexOfOrThrow(state)) + + /** + * Helper property to return a list of [ContractState] objects, rather than the often less convenient [TransactionState] + */ + val outputStates: List get() = outputs.map { it.data } + + /** + * Helper to simplify getting an indexed output. + * @param index the position of the item in the output. + * @return The ContractState at the requested index + */ + fun getOutput(index: Int): ContractState = outputs[index].data + + /** + * Helper to simplify getting all output states of a particular class, interface, or base class. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the possibly empty list of output states matching the clazz restriction. + */ + fun outputsOfType(clazz: Class): List { + @Suppress("UNCHECKED_CAST") + return outputs.filter { clazz.isInstance(it.data) }.map { it.data as T } + } + + /** + * Helper to simplify filtering outputs according to a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the possibly empty list of output states matching the predicate and clazz restrictions. + */ + fun filterOutputs(predicate: Predicate, clazz: Class): List { + return outputsOfType(clazz).filter { predicate.test(it) } + } + + /** + * Helper to simplify finding a single output matching a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the single item matching the predicate. + * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. + */ + fun findOutput(predicate: Predicate, clazz: Class): T { + return filterOutputs(predicate, clazz).single() + } + + /** + * Helper to simplify getting all output [StateAndRef] items of a particular state class, interface, or base class. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the possibly empty list of output [StateAndRef] states matching the clazz restriction. + */ + fun outRefsOfType(clazz: Class): List> { + @Suppress("UNCHECKED_CAST") + return outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(id, index)) } + .filter { clazz.isInstance(it.state.data) } + .map { it as StateAndRef } + } + + /** + * Helper to simplify filtering output [StateAndRef] items according to a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the possibly empty list of output [StateAndRef] states matching the predicate and clazz restrictions. + */ + fun filterOutRefs(predicate: Predicate, clazz: Class): List> { + return outRefsOfType(clazz).filter { predicate.test(it.state.data) } + } + + /** + * Helper to simplify finding a single output [StateAndRef] matching a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * Clazz must be an extension of [ContractState]. + * @return the single [StateAndRef] item matching the predicate. + * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. + */ + fun findOutRef(predicate: Predicate, clazz: Class): StateAndRef { + return filterOutRefs(predicate, clazz).single() + } + + //Kotlin extension methods to take advantage of Kotlin's smart type inference when querying the LedgerTransaction + inline fun outputsOfType(): List = this.outputsOfType(T::class.java) + + inline fun filterOutputs(crossinline predicate: (T) -> Boolean): List { + return filterOutputs(Predicate { predicate(it) }, T::class.java) + } + + inline fun findOutput(crossinline predicate: (T) -> Boolean): T { + return findOutput(Predicate { predicate(it) }, T::class.java) + } + + inline fun outRefsOfType(): List> = this.outRefsOfType(T::class.java) + + inline fun filterOutRefs(crossinline predicate: (T) -> Boolean): List> { + return filterOutRefs(Predicate { predicate(it) }, T::class.java) + } + + inline fun findOutRef(crossinline predicate: (T) -> Boolean): StateAndRef { + return findOutRef(Predicate { predicate(it) }, T::class.java) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index c00c93b6ae..dd370b6e6c 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -6,6 +6,7 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import java.security.PublicKey import java.util.* +import java.util.function.Predicate /** * A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations: @@ -42,8 +43,15 @@ class LedgerTransaction( checkInvariants() } + val inputStates: List get() = inputs.map { it.state.data } + + /** + * Returns the typed input StateAndRef at the specified index + * @param index The index into the inputs. + * @return The [StateAndRef] + */ @Suppress("UNCHECKED_CAST") - fun outRef(index: Int) = StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) + fun inRef(index: Int) = inputs[index] as StateAndRef /** * Verifies this transaction and throws an exception if not valid, depending on the type. For general transactions: @@ -100,8 +108,8 @@ class LedgerTransaction( */ // DOCSTART 2 fun groupStates(ofType: Class, selector: (T) -> K): List> { - val inputs = inputs.map { it.state.data }.filterIsInstance(ofType) - val outputs = outputs.map { it.data }.filterIsInstance(ofType) + val inputs = inputsOfType(ofType) + val outputs = outputsOfType(ofType) val inGroups: Map> = inputs.groupBy(selector) val outGroups: Map> = outputs.groupBy(selector) @@ -136,4 +144,166 @@ class LedgerTransaction( // DOCSTART 3 data class InOutGroup(val inputs: List, val outputs: List, val groupingKey: K) // DOCEND 3 + + /** + * Helper to simplify getting an indexed input [ContractState]. + * @param index the position of the item in the inputs. + * @return The [StateAndRef] at the requested index + */ + fun getInput(index: Int): ContractState = inputs[index].state.data + + /** + * Helper to simplify getting all inputs states of a particular class, interface, or base class. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [ContractState]. + * @return the possibly empty list of inputs matching the clazz restriction. + */ + fun inputsOfType(clazz: Class): List { + @Suppress("UNCHECKED_CAST") + return inputs.map { it.state.data }.filterIsInstance(clazz) + } + + /** + * Helper to simplify getting all inputs states of a particular class, interface, or base class. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [ContractState]. + * @return the possibly empty list of inputs [StateAndRef] matching the clazz restriction. + */ + fun inRefsOfType(clazz: Class): List> { + @Suppress("UNCHECKED_CAST") + return inputs.filter { clazz.isInstance(it.state.data) }.map { it as StateAndRef } + } + + /** + * Helper to simplify filtering inputs according to a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [ContractState]. + * @return the possibly empty list of input states matching the predicate and clazz restrictions. + */ + fun filterInputs(predicate: Predicate, clazz: Class): List = inputsOfType(clazz).filter { predicate.test(it) } + + /** + * Helper to simplify filtering inputs according to a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [ContractState]. + * @return the possibly empty list of inputs [StateAndRef] matching the predicate and clazz restrictions. + */ + fun filterInRefs(predicate: Predicate, clazz: Class): List> = inRefsOfType(clazz).filter { predicate.test(it.state.data) } + + /** + * Helper to simplify finding a single input [ContractState] matching a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of ContractState. + * @return the single item matching the predicate. + * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. + */ + fun findInput(predicate: Predicate, clazz: Class): T = filterInputs(predicate, clazz).single() + + /** + * Helper to simplify finding a single input matching a [Predicate]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of ContractState. + * @return the single item matching the predicate. + * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. + */ + fun findInRef(predicate: Predicate, clazz: Class): StateAndRef = filterInRefs(predicate, clazz).single() + + /** + * Helper to simplify getting an indexed command. + * @param index the position of the item in the commands. + * @return The Command at the requested index + */ + fun getCommand(index: Int): Command = Command(commands[index].value, commands[index].signers) + + /** + * Helper to simplify getting all [Command] items with a [CommandData] of a particular class, interface, or base class. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [CommandData]. + * @return the possibly empty list of commands with [CommandData] values matching the clazz restriction. + */ + fun commandsOfType(clazz: Class): List { + return commands.filter { clazz.isInstance(it.value) }.map { Command(it.value, it.signers) } + } + + /** + * Helper to simplify filtering [Command] items according to a [Predicate]. + * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [CommandData]. + * @return the possibly empty list of [Command] items with [CommandData] values matching the predicate and clazz restrictions. + */ + fun filterCommands(predicate: Predicate, clazz: Class): List { + @Suppress("UNCHECKED_CAST") + return commandsOfType(clazz).filter { predicate.test(it.value as T) } + } + + + /** + * Helper to simplify finding a single [Command] items according to a [Predicate]. + * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. + * @param clazz The class type used for filtering via an [Class.isInstance] check. + * [clazz] must be an extension of [CommandData]. + * @return the [Command] item with [CommandData] values matching the predicate and clazz restrictions. + * @throws IllegalArgumentException if no items, or multiple items matched the requirements. + */ + fun findCommand(predicate: Predicate, clazz: Class): Command { + return filterCommands(predicate, clazz).single() + } + + /** + * Helper to simplify getting an indexed attachment. + * @param index the position of the item in the attachments. + * @return The Attachment at the requested index. + */ + fun getAttachment(index: Int): Attachment = attachments[index] + + /** + * Helper to simplify getting an indexed attachment. + * @param id the SecureHash of the desired attachment. + * @return The Attachment with the matching id. + * @throws IllegalArgumentException if no item matches the id. + */ + fun getAttachment(id: SecureHash): Attachment = attachments.single { it.id == id } + + //Kotlin extension methods to take advantage of Kotlin's smart type inference when querying the LedgerTransaction + inline fun inputsOfType(): List = this.inputsOfType(T::class.java) + + inline fun inRefsOfType(): List> = this.inRefsOfType(T::class.java) + + inline fun filterInputs(crossinline predicate: (T) -> Boolean): List { + return filterInputs(Predicate { predicate(it) }, T::class.java) + } + + inline fun filterInRefs(crossinline predicate: (T) -> Boolean): List> { + return filterInRefs(Predicate { predicate(it) }, T::class.java) + } + + inline fun findInRef(crossinline predicate: (T) -> Boolean): StateAndRef { + return findInRef(Predicate { predicate(it) }, T::class.java) + } + + inline fun findInput(crossinline predicate: (T) -> Boolean): T { + return findInput(Predicate { predicate(it) }, T::class.java) + } + + inline fun commandsOfType(): List = this.commandsOfType(T::class.java) + + inline fun filterCommands(crossinline predicate: (T) -> Boolean): List { + return filterCommands(Predicate { predicate(it) }, T::class.java) + } + + inline fun findCommand(crossinline predicate: (T) -> Boolean): Command { + return findCommand(Predicate { predicate(it) }, T::class.java) + } } + 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 6715365840..dc487f52e1 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -6,12 +6,11 @@ import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.SecureHash import net.corda.core.crypto.keys import net.corda.core.identity.Party -import net.corda.core.indexOfOrThrow +import net.corda.core.internal.Emoji import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.* import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY -import net.corda.core.internal.Emoji import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate @@ -53,16 +52,6 @@ class WireTransaction( } } - /** Returns a [StateAndRef] for the given output index. */ - @Suppress("UNCHECKED_CAST") - fun outRef(index: Int): StateAndRef { - require(index >= 0 && index < outputs.size) - return StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) - } - - /** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */ - fun outRef(state: ContractState): StateAndRef = outRef(outputs.map { it.data }.indexOfOrThrow(state)) - /** * Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to * have been fully resolved using the resolution flow by this point. diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt new file mode 100644 index 0000000000..55b36038c6 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt @@ -0,0 +1,289 @@ +package net.corda.core.contracts + +import net.corda.core.identity.AbstractParty +import net.corda.core.node.ServiceHub +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.contracts.DummyContract +import net.corda.testing.node.MockServices +import org.junit.Before +import org.junit.Test +import java.util.function.Predicate +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class LedgerTransactionQueryTests : TestDependencyInjectionBase() { + + private lateinit var services: ServiceHub + + @Before + fun setup() { + services = MockServices() + } + + interface Commands { + data class Cmd1(val id: Int) : CommandData, Commands + data class Cmd2(val id: Int) : CommandData, Commands + } + + + private class StringTypeDummyState(val data: String) : ContractState { + override val contract: Contract = DummyContract() + override val participants: List = emptyList() + } + + private class IntTypeDummyState(val data: Int) : ContractState { + override val contract: Contract = DummyContract() + override val participants: List = emptyList() + } + + private fun makeDummyState(data: Any): ContractState { + return when (data) { + is String -> StringTypeDummyState(data) + is Int -> IntTypeDummyState(data) + else -> throw IllegalArgumentException() + } + } + + private fun makeDummyStateAndRef(data: Any): StateAndRef<*> { + val dummyState = makeDummyState(data) + val fakeIssueTx = services.signInitialTransaction(TransactionBuilder(notary = DUMMY_NOTARY).addOutputState(dummyState)) + services.recordTransactions(fakeIssueTx) + val dummyStateRef = StateRef(fakeIssueTx.id, 0) + return StateAndRef(TransactionState(dummyState, DUMMY_NOTARY, null), dummyStateRef) + } + + private fun makeDummyTransaction(): LedgerTransaction { + val tx = TransactionBuilder(notary = DUMMY_NOTARY) + for (i in 0..4) { + tx.addInputState(makeDummyStateAndRef(i)) + tx.addInputState(makeDummyStateAndRef(i.toString())) + tx.addOutputState(makeDummyState(i)) + tx.addOutputState(makeDummyState(i.toString())) + tx.addCommand(Commands.Cmd1(i), listOf(services.myInfo.legalIdentity.owningKey)) + tx.addCommand(Commands.Cmd2(i), listOf(services.myInfo.legalIdentity.owningKey)) + } + return tx.toLedgerTransaction(services) + } + + @Test + fun `Simple InRef Indexer tests`() { + val ltx = makeDummyTransaction() + assertEquals(0, ltx.inRef(0).state.data.data) + assertEquals("0", ltx.inRef(1).state.data.data) + assertEquals(3, ltx.inRef(6).state.data.data) + assertEquals("3", ltx.inRef(7).state.data.data) + assertFailsWith { ltx.inRef(10) } + } + + @Test + fun `Simple OutRef Indexer tests`() { + val ltx = makeDummyTransaction() + assertEquals(0, ltx.outRef(0).state.data.data) + assertEquals("0", ltx.outRef(1).state.data.data) + assertEquals(3, ltx.outRef(6).state.data.data) + assertEquals("3", ltx.outRef(7).state.data.data) + assertFailsWith { ltx.outRef(10) } + } + + @Test + fun `Simple Input Indexer tests`() { + val ltx = makeDummyTransaction() + assertEquals(0, (ltx.getInput(0) as IntTypeDummyState).data) + assertEquals("0", (ltx.getInput(1) as StringTypeDummyState).data) + assertEquals(3, (ltx.getInput(6) as IntTypeDummyState).data) + assertEquals("3", (ltx.getInput(7) as StringTypeDummyState).data) + assertFailsWith { ltx.getInput(10) } + } + + @Test + fun `Simple Output Indexer tests`() { + val ltx = makeDummyTransaction() + assertEquals(0, (ltx.getOutput(0) as IntTypeDummyState).data) + assertEquals("0", (ltx.getOutput(1) as StringTypeDummyState).data) + assertEquals(3, (ltx.getOutput(6) as IntTypeDummyState).data) + assertEquals("3", (ltx.getOutput(7) as StringTypeDummyState).data) + assertFailsWith { ltx.getOutput(10) } + } + + @Test + fun `Simple Command Indexer tests`() { + val ltx = makeDummyTransaction() + assertEquals(0, (ltx.getCommand(0).value as Commands.Cmd1).id) + assertEquals(0, (ltx.getCommand(1).value as Commands.Cmd2).id) + assertEquals(3, (ltx.getCommand(6).value as Commands.Cmd1).id) + assertEquals(3, (ltx.getCommand(7).value as Commands.Cmd2).id) + assertFailsWith { ltx.getOutput(10) } + } + + @Test + fun `Simple Inputs of type tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.inputsOfType(IntTypeDummyState::class.java) + assertEquals(5, intStates.size) + assertEquals(listOf(0, 1, 2, 3, 4), intStates.map { it.data }) + val stringStates = ltx.inputsOfType() + assertEquals(5, stringStates.size) + assertEquals(listOf("0", "1", "2", "3", "4"), stringStates.map { it.data }) + val notPresentQuery = ltx.inputsOfType(FungibleAsset::class.java) + assertEquals(emptyList(), notPresentQuery) + } + + @Test + fun `Simple InputsRefs of type tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.inRefsOfType(IntTypeDummyState::class.java) + assertEquals(5, intStates.size) + assertEquals(listOf(0, 1, 2, 3, 4), intStates.map { it.state.data.data }) + assertEquals(listOf(ltx.inputs[0], ltx.inputs[2], ltx.inputs[4], ltx.inputs[6], ltx.inputs[8]), intStates) + val stringStates = ltx.inRefsOfType() + assertEquals(5, stringStates.size) + assertEquals(listOf("0", "1", "2", "3", "4"), stringStates.map { it.state.data.data }) + assertEquals(listOf(ltx.inputs[1], ltx.inputs[3], ltx.inputs[5], ltx.inputs[7], ltx.inputs[9]), stringStates) + } + + @Test + fun `Simple Outputs of type tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.outputsOfType(IntTypeDummyState::class.java) + assertEquals(5, intStates.size) + assertEquals(listOf(0, 1, 2, 3, 4), intStates.map { it.data }) + val stringStates = ltx.outputsOfType() + assertEquals(5, stringStates.size) + assertEquals(listOf("0", "1", "2", "3", "4"), stringStates.map { it.data }) + val notPresentQuery = ltx.outputsOfType(FungibleAsset::class.java) + assertEquals(emptyList(), notPresentQuery) + } + + @Test + fun `Simple OutputsRefs of type tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.outRefsOfType(IntTypeDummyState::class.java) + assertEquals(5, intStates.size) + assertEquals(listOf(0, 1, 2, 3, 4), intStates.map { it.state.data.data }) + assertEquals(listOf(0, 2, 4, 6, 8), intStates.map { it.ref.index }) + assertTrue(intStates.all { it.ref.txhash == ltx.id }) + val stringStates = ltx.outRefsOfType() + assertEquals(5, stringStates.size) + assertEquals(listOf("0", "1", "2", "3", "4"), stringStates.map { it.state.data.data }) + assertEquals(listOf(1, 3, 5, 7, 9), stringStates.map { it.ref.index }) + assertTrue(stringStates.all { it.ref.txhash == ltx.id }) + } + + @Test + fun `Simple Commands of type tests`() { + val ltx = makeDummyTransaction() + val intCmd1 = ltx.commandsOfType(Commands.Cmd1::class.java) + assertEquals(5, intCmd1.size) + assertEquals(listOf(0, 1, 2, 3, 4), intCmd1.map { (it.value as Commands.Cmd1).id }) + val intCmd2 = ltx.commandsOfType() + assertEquals(5, intCmd2.size) + assertEquals(listOf(0, 1, 2, 3, 4), intCmd2.map { (it.value as Commands.Cmd2).id }) + val notPresentQuery = ltx.commandsOfType(FungibleAsset.Commands.Exit::class.java) + assertEquals(emptyList(), notPresentQuery) + } + + @Test + fun `Filtered Input Tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.filterInputs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + assertEquals(3, intStates.size) + assertEquals(listOf(0, 2, 4), intStates.map { it.data }) + val stringStates: List = ltx.filterInputs { it.data == "3" } + assertEquals("3", stringStates.single().data) + } + + @Test + fun `Filtered InRef Tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.filterInRefs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + assertEquals(3, intStates.size) + assertEquals(listOf(0, 2, 4), intStates.map { it.state.data.data }) + assertEquals(listOf(ltx.inputs[0], ltx.inputs[4], ltx.inputs[8]), intStates) + val stringStates: List> = ltx.filterInRefs { it.data == "3" } + assertEquals("3", stringStates.single().state.data.data) + assertEquals(ltx.inputs[7], stringStates.single()) + } + + @Test + fun `Filtered Output Tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.filterOutputs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + assertEquals(3, intStates.size) + assertEquals(listOf(0, 2, 4), intStates.map { it.data }) + val stringStates: List = ltx.filterOutputs { it.data == "3" } + assertEquals("3", stringStates.single().data) + } + + @Test + fun `Filtered OutRef Tests`() { + val ltx = makeDummyTransaction() + val intStates = ltx.filterOutRefs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + assertEquals(3, intStates.size) + assertEquals(listOf(0, 2, 4), intStates.map { it.state.data.data }) + assertEquals(listOf(0, 4, 8), intStates.map { it.ref.index }) + assertTrue(intStates.all { it.ref.txhash == ltx.id }) + val stringStates: List> = ltx.filterOutRefs { it.data == "3" } + assertEquals("3", stringStates.single().state.data.data) + assertEquals(7, stringStates.single().ref.index) + assertEquals(ltx.id, stringStates.single().ref.txhash) + } + + @Test + fun `Filtered Commands Tests`() { + val ltx = makeDummyTransaction() + val intCmds1 = ltx.filterCommands(Predicate { it.id.rem(2) == 0 }, Commands.Cmd1::class.java) + assertEquals(3, intCmds1.size) + assertEquals(listOf(0, 2, 4), intCmds1.map { (it.value as Commands.Cmd1).id }) + val intCmds2 = ltx.filterCommands { it.id == 3 } + assertEquals(3, (intCmds2.single().value as Commands.Cmd2).id) + } + + @Test + fun `Find Input Tests`() { + val ltx = makeDummyTransaction() + val intState = ltx.findInput(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + assertEquals(ltx.getInput(8), intState) + val stringState: StringTypeDummyState = ltx.findInput { it.data == "3" } + assertEquals(ltx.getInput(7), stringState) + } + + @Test + fun `Find InRef Tests`() { + val ltx = makeDummyTransaction() + val intState = ltx.findInRef(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + assertEquals(ltx.inRef(8), intState) + val stringState: StateAndRef = ltx.findInRef { it.data == "3" } + assertEquals(ltx.inRef(7), stringState) + } + + @Test + fun `Find Output Tests`() { + val ltx = makeDummyTransaction() + val intState = ltx.findOutput(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + assertEquals(ltx.getOutput(8), intState) + val stringState: StringTypeDummyState = ltx.findOutput { it.data == "3" } + assertEquals(ltx.getOutput(7), stringState) + } + + @Test + fun `Find OutRef Tests`() { + val ltx = makeDummyTransaction() + val intState = ltx.findOutRef(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + assertEquals(ltx.outRef(8), intState) + val stringState: StateAndRef = ltx.findOutRef { it.data == "3" } + assertEquals(ltx.outRef(7), stringState) + } + + @Test + fun `Find Commands Tests`() { + val ltx = makeDummyTransaction() + val intCmd1 = ltx.findCommand(Predicate { it.id == 2 }, Commands.Cmd1::class.java) + assertEquals(ltx.getCommand(4), intCmd1) + val intCmd2 = ltx.findCommand { it.id == 3 } + assertEquals(ltx.getCommand(7), intCmd2) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt index 7352579be3..2d05c6ea3d 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt @@ -29,7 +29,7 @@ class TransactionEncumbranceTests { class DummyTimeLock : Contract { override val legalContractReference = SecureHash.sha256("DummyTimeLock") override fun verify(tx: LedgerTransaction) { - val timeLockInput = tx.inputs.map { it.state.data }.filterIsInstance().singleOrNull() ?: return + val timeLockInput = tx.inputsOfType().singleOrNull() ?: return val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window") requireThat { "the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index a322952e49..22bce20926 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -61,9 +61,10 @@ class CollectSignaturesFlowTests { val flow = object : SignTransactionFlow(otherParty) { @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat { val tx = stx.tx + val ltx = tx.toLedgerTransaction(serviceHub) "There should only be one output state" using (tx.outputs.size == 1) "There should only be one output state" using (tx.inputs.isEmpty()) - val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState + val magicNumberState = ltx.outputsOfType().single() "Must be 1337 or greater" using (magicNumberState.magicNumber >= 1337) } } @@ -118,9 +119,10 @@ class CollectSignaturesFlowTests { val flow = object : SignTransactionFlow(otherParty) { @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat { val tx = stx.tx + val ltx = tx.toLedgerTransaction(serviceHub) "There should only be one output state" using (tx.outputs.size == 1) "There should only be one output state" using (tx.inputs.isEmpty()) - val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState + val magicNumberState = ltx.outputsOfType().single() "Must be 1337 or greater" using (magicNumberState.magicNumber >= 1337) } } diff --git a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt index 33d64c768d..3e2e54096a 100644 --- a/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt +++ b/core/src/test/kotlin/net/corda/core/node/AttachmentClassLoaderTests.kt @@ -295,7 +295,7 @@ class AttachmentClassLoaderTests : TestDependencyInjectionBase() { } val copiedWireTransaction = bytes.deserialize(context = context) assertEquals(1, copiedWireTransaction.outputs.size) - val contract2 = copiedWireTransaction.outputs[0].data.contract as DummyContractBackdoor + val contract2 = copiedWireTransaction.getOutput(0).contract as DummyContractBackdoor assertEquals(42, contract2.inspectState(copiedWireTransaction.outputs[0].data)) } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 88d9e05d6b..46550b3c15 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -12,10 +12,7 @@ import net.corda.core.node.services.ServiceType import net.corda.core.node.services.Vault.Page import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder -import net.corda.core.transactions.WireTransaction +import net.corda.core.transactions.* import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step import net.corda.core.utilities.UntrustworthyData @@ -416,7 +413,7 @@ object FlowCookbook { // sign it! We need to make sure the transaction represents an // agreement we actually want to enter into. // DOCSTART 34 - val outputState: DummyState = wireTx.outputs.single().data as DummyState + val outputState: DummyState = wireTx.outputsOfType().single() if (outputState.magicNumber == 777) { // ``FlowException`` is a special exception type. It will be // propagated back to any counterparty flows waiting for a @@ -548,7 +545,7 @@ object FlowCookbook { val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterparty) { override fun checkTransaction(stx: SignedTransaction) = requireThat { // Any additional checking we see fit... - val outputState = stx.tx.outputs.single().data as DummyState + val outputState = stx.tx.outputsOfType().single() assert(outputState.magicNumber == 777) } } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index c80e7a52cf..8be1f3a8be 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -79,7 +79,7 @@ data class TradeApprovalContract(override val legalContractReference: SecureHash "Issue of new WorkflowContract must not include any inputs" using (tx.inputs.isEmpty()) "Issue of new WorkflowContract must be in a unique transaction" using (tx.outputs.size == 1) } - val issued = tx.outputs[0].data as TradeApprovalContract.State + val issued = tx.outputsOfType().single() requireThat { "Issue requires the source Party as signer" using (command.signers.contains(issued.source.owningKey)) "Initial Issue state must be NEW" using (issued.state == WorkflowState.NEW) diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt index 33f94b3bc4..0e695837ef 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt @@ -192,7 +192,7 @@ class UniversalContract : Contract { when (value) { is Commands.Action -> { - val inState = tx.inputs.single().state.data as State + val inState = tx.inputsOfType().single() val arr = when (inState.details) { is Actions -> inState.details is RollOut -> reduceRollOut(inState.details) @@ -222,7 +222,7 @@ class UniversalContract : Contract { when (tx.outputs.size) { 1 -> { - val outState = tx.outputs.single().data as State + val outState = tx.outputsOfType().single() requireThat { "output state must match action result state" using (arrangement.equals(outState.details)) "output state must match action result state" using (rest == zero) @@ -230,7 +230,7 @@ class UniversalContract : Contract { } 0 -> throw IllegalArgumentException("must have at least one out state") else -> { - val allContracts = And(tx.outputs.map { (it.data as State).details }.toSet()) + val allContracts = And(tx.outputsOfType().map { it.details }.toSet()) requireThat { "output states must match action result state" using (arrangement.equals(allContracts)) @@ -240,15 +240,15 @@ class UniversalContract : Contract { } } is Commands.Issue -> { - val outState = tx.outputs.single().data as State + val outState = tx.outputsOfType().single() requireThat { "the transaction is signed by all liable parties" using (liableParties(outState.details).all { it in cmd.signers }) "the transaction has no input states" using tx.inputs.isEmpty() } } is Commands.Move -> { - val inState = tx.inputs.single().state.data as State - val outState = tx.outputs.single().data as State + val inState = tx.inputsOfType().single() + val outState = tx.outputsOfType().single() requireThat { "the transaction is signed by all liable parties" using (liableParties(outState.details).all { it in cmd.signers }) @@ -257,13 +257,13 @@ class UniversalContract : Contract { } } is Commands.Fix -> { - val inState = tx.inputs.single().state.data as State + val inState = tx.inputsOfType().single() val arr = when (inState.details) { is Actions -> inState.details is RollOut -> reduceRollOut(inState.details) else -> throw IllegalArgumentException("Unexpected arrangement, " + tx.inputs.single()) } - val outState = tx.outputs.single().data as State + val outState = tx.outputsOfType().single() val unusedFixes = value.fixes.map { it.of }.toMutableSet() val expectedArr = replaceFixing(tx, arr, diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt index c0b31b72c2..0320ed785a 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaper.kt @@ -13,13 +13,13 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.toBase58String import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.internal.Emoji import net.corda.core.node.services.VaultService import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.internal.Emoji import net.corda.schemas.CommercialPaperSchemaV1 import java.time.Instant import java.util.* @@ -172,7 +172,7 @@ class CommercialPaper : Contract { val timeWindow = tx.timeWindow val input = inputs.single() - val received = tx.outputs.map { it.data }.sumCashBy(input.owner) + val received = tx.outputStates.sumCashBy(input.owner) val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window") requireThat { "the paper must have matured" using (time >= input.maturityDate) diff --git a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt index 5648959ac6..b53246d25b 100644 --- a/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt +++ b/finance/src/main/kotlin/net/corda/contracts/CommercialPaperLegacy.kt @@ -7,10 +7,10 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.testing.NULL_PARTY import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.internal.Emoji import net.corda.core.node.services.VaultService import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.internal.Emoji import java.time.Instant import java.util.* @@ -81,7 +81,7 @@ class CommercialPaperLegacy : Contract { is Commands.Redeem -> { // Redemption of the paper requires movement of on-ledger cash. val input = inputs.single() - val received = tx.outputs.map { it.data }.sumCashBy(input.owner) + val received = tx.outputStates.sumCashBy(input.owner) val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window") requireThat { "the paper must have matured" using (time >= input.maturityDate) diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt index b0b06973bb..7fef1625ea 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Obligation.kt @@ -14,10 +14,10 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party +import net.corda.core.internal.Emoji import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.internal.Emoji import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.seconds import org.bouncycastle.asn1.x500.X500Name @@ -159,7 +159,7 @@ class Obligation

: Contract { // Move (signed by B) // // That would pass this check. Ensuring they do not is best addressed in the transaction generation stage. - val assetStates = tx.outputs.map { it.data }.filterIsInstance>() + val assetStates = tx.outputsOfType>() val acceptableAssetStates = assetStates // TODO: This filter is nonsense, because it just checks there is an asset contract loaded, we need to // verify the asset contract is the asset contract we expect. diff --git a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt b/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt index 72d11fe0c7..ccd6001fdf 100644 --- a/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/IssuerFlow.kt @@ -6,8 +6,8 @@ import net.corda.core.contracts.* import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.OpaqueBytes import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import java.util.* @@ -49,10 +49,7 @@ object IssuerFlow { return sendAndReceive(issuerBankParty, issueRequest).unwrap { res -> val tx = res.stx.tx val expectedAmount = Amount(amount.quantity, Issued(issuerBankParty.ref(issueToPartyRef), amount.token)) - val cashOutputs = tx.outputs - .map { it.data} - .filterIsInstance() - .filter { state -> state.owner == res.recipient } + val cashOutputs = tx.filterOutputs { state -> state.owner == res.recipient } require(cashOutputs.size == 1) { "Require a single cash output paying ${res.recipient}, found ${tx.outputs}" } require(cashOutputs.single().amount == expectedAmount) { "Require payment of $expectedAmount"} res diff --git a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt index 1290536405..b03c655c3f 100644 --- a/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt @@ -8,11 +8,11 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo -import net.corda.core.utilities.seconds import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap import java.security.PublicKey import java.util.* @@ -85,7 +85,7 @@ object TwoPartyTradeFlow { // DOCSTART 5 val signTransactionFlow = object : SignTransactionFlow(otherParty, VERIFYING_AND_SIGNING.childProgressTracker()) { override fun checkTransaction(stx: SignedTransaction) { - if (stx.tx.outputs.map { it.data }.sumCashBy(me).withoutIssuer() != price) + if (stx.tx.outputStates.sumCashBy(me).withoutIssuer() != price) throw FlowException("Transaction is not sending us the right amount of cash") } } diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt index 04518c251f..a0d5fbc242 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/CashTests.kt @@ -159,7 +159,7 @@ class CashTests : TestDependencyInjectionBase() { Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(DUMMY_PUBKEY_1), notary = DUMMY_NOTARY) }.toWireTransaction() assertTrue(tx.inputs.isEmpty()) - val s = tx.outputs[0].data as Cash.State + val s = tx.outputsOfType().single() assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party) assertEquals(AnonymousParty(DUMMY_PUBKEY_1), s.owner) @@ -514,7 +514,7 @@ class CashTests : TestDependencyInjectionBase() { val wtx = makeExit(50.DOLLARS, MEGA_CORP, 1) assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(1, wtx.outputs.size) - assertEquals(WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.splitEvenly(2).first()), wtx.outputs[0].data) + assertEquals(WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.splitEvenly(2).first()), wtx.getOutput(0)) } /** @@ -574,7 +574,7 @@ class CashTests : TestDependencyInjectionBase() { @Suppress("UNCHECKED_CAST") val vaultState = vaultStatesUnconsumed.elementAt(0) assertEquals(vaultState.ref, wtx.inputs[0]) - assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data) + assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.getOutput(0)) assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -618,7 +618,7 @@ class CashTests : TestDependencyInjectionBase() { val vaultState1 = vaultStatesUnconsumed.elementAt(1) assertEquals(vaultState0.ref, wtx.inputs[0]) assertEquals(vaultState1.ref, wtx.inputs[1]) - assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) + assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0)) assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -639,7 +639,7 @@ class CashTests : TestDependencyInjectionBase() { assertEquals(vaultState1.ref, wtx.inputs[1]) assertEquals(vaultState2.ref, wtx.inputs[2]) assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) - assertEquals(vaultState2.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data) + assertEquals(vaultState2.state.data.copy(owner = THEIR_IDENTITY_1), wtx.getOutput(0)) assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } diff --git a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt index e9f19c4db4..cc2ed93af0 100644 --- a/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/contracts/asset/ObligationTests.kt @@ -146,7 +146,7 @@ class ObligationTests { beneficiary = CHARLIE, template = megaCorpDollarSettlement ) - assertEquals(tx.outputs[0].data, expected) + assertEquals(tx.getOutput(0), expected) assertTrue(tx.commands[0].value is Obligation.Commands.Issue) assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) resetTestSerialization() @@ -250,7 +250,7 @@ class ObligationTests { }.toWireTransaction() assertEquals(1, tx.outputs.size) - val actual = tx.outputs[0].data + val actual = tx.getOutput(0) assertEquals((1000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB), actual) } @@ -277,7 +277,7 @@ class ObligationTests { }.toWireTransaction() assertEquals(1, tx.outputs.size) val expected = obligationBobToAlice.copy(quantity = obligationBobToAlice.quantity - obligationAliceToBob.quantity) - val actual = tx.outputs[0].data + val actual = tx.getOutput(0) assertEquals(expected, actual) } @@ -305,7 +305,7 @@ class ObligationTests { stx = notaryServices.addSignature(ptx) assertEquals(1, stx.tx.outputs.size) - assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.DEFAULTED), stx.tx.outputs[0].data) + assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.DEFAULTED), stx.tx.getOutput(0)) stx.verifyRequiredSignatures() // And set it back @@ -316,7 +316,7 @@ class ObligationTests { ptx = miniCorpServices.signInitialTransaction(tx) stx = notaryServices.addSignature(ptx) assertEquals(1, stx.tx.outputs.size) - assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.NORMAL), stx.tx.outputs[0].data) + assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.NORMAL), stx.tx.getOutput(0)) stx.verifyRequiredSignatures() } diff --git a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt index 9ed4a335ed..7809764116 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashExitFlowTests.kt @@ -55,7 +55,7 @@ class CashExitFlowTests { val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) assertEquals(1, exitTx.inputs.size) assertEquals(1, exitTx.outputs.size) - val output = exitTx.outputs.map { it.data }.filterIsInstance().single() + val output = exitTx.outputsOfType().single() assertEquals(expected, output.amount) } diff --git a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt index 36f0edddd4..c94ca63492 100644 --- a/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/flows/CashIssueFlowTests.kt @@ -47,7 +47,7 @@ class CashIssueFlowTests { notary)).resultFuture mockNet.runNetwork() val issueTx = future.getOrThrow().stx - val output = issueTx.tx.outputs.single().data as Cash.State + val output = issueTx.tx.outputsOfType().single() assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 2e436f1a95..773772ad5f 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -457,7 +457,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P // Retrieve unspent and unlocked cash states that meet our spending criteria. val acceptableCoins = unconsumedStatesForSpending(amount, onlyFromParties, tx.notary, tx.lockId) return OnLedgerAsset.generateSpend(tx, amount, to, acceptableCoins, - { state, amount, owner -> deriveState(state, amount, owner) }, + { state, quantity, owner -> deriveState(state, quantity, owner) }, { Cash().generateMoveCommand() }) } @@ -466,9 +466,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P @VisibleForTesting internal fun makeUpdate(tx: WireTransaction, ourKeys: Set): Vault.Update { - val ourNewStates = tx.outputs. - filter { isRelevant(it.data, ourKeys) }. - map { tx.outRef(it.data) } + val ourNewStates = tx.filterOutRefs { isRelevant(it, ourKeys) } // Retrieve all unconsumed states for this transaction's inputs val consumedStates = HashSet>() diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 7aeadcf487..1f1cdc48bf 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -1,19 +1,19 @@ package net.corda.node.services import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.generateKeyPair +import net.corda.core.flows.NotaryChangeFlow +import net.corda.core.flows.StateReplacementException import net.corda.core.getOrThrow import net.corda.core.identity.Party import net.corda.core.node.services.ServiceInfo -import net.corda.core.utilities.seconds import net.corda.core.transactions.WireTransaction -import net.corda.core.flows.NotaryChangeFlow -import net.corda.core.flows.StateReplacementException +import net.corda.core.utilities.seconds import net.corda.node.internal.AbstractNode import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.SimpleNotaryService import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.contracts.DummyContract import net.corda.testing.getTestPartyAndCertificate import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -109,8 +109,8 @@ class NotaryChangeTests { val notaryChangeTx = clientNodeA.services.validatedTransactions.getTransaction(newState.ref.txhash)!!.tx // Check that all encumbrances have been propagated to the outputs - val originalOutputs = issueTx.outputs.map { it.data } - val newOutputs = notaryChangeTx.outputs.map { it.data } + val originalOutputs = issueTx.outputStates + val newOutputs = notaryChangeTx.outputStates assertTrue(originalOutputs.minus(newOutputs).isEmpty()) // Check that encumbrance links aren't broken after notary change diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 120dc3680c..7fab70f1e1 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -125,7 +125,7 @@ fun recipient(rpc: CordaRPCOps) { val wtx = stx.tx if (wtx.attachments.isNotEmpty()) { if (wtx.outputs.isNotEmpty()) { - val state = wtx.outputs.map { it.data }.filterIsInstance().single() + val state = wtx.outputsOfType().single() require(rpc.attachmentExists(state.hash)) // Download the attachment via the Web endpoint. @@ -173,7 +173,7 @@ class AttachmentContract : Contract { get() = SecureHash.zeroHash // TODO not implemented override fun verify(tx: LedgerTransaction) { - val state = tx.outputs.map { it.data }.filterIsInstance().single() + val state = tx.outputsOfType().single() val attachment = tx.attachments.single() require(state.hash == attachment.id) } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index ce7e0b17ef..b068329b61 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -2,8 +2,8 @@ package net.corda.irs.contract import net.corda.contracts.* import net.corda.core.contracts.* -import net.corda.core.utilities.seconds import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.seconds import net.corda.testing.* import net.corda.testing.node.MockServices import org.junit.Test @@ -246,7 +246,7 @@ class IRSTests : TestDependencyInjectionBase() { * Utility so I don't have to keep typing this. */ fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State { - return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance().single() + return generateIRSTxn(irsSelector).tx.outputsOfType().single() } /** @@ -300,7 +300,7 @@ class IRSTests : TestDependencyInjectionBase() { var previousTXN = generateIRSTxn(1) previousTXN.toLedgerTransaction(services).verify() services.recordTransactions(previousTXN) - fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance().single() + fun currentIRS() = previousTXN.tx.outputsOfType().single() while (true) { val nextFix: FixOf = currentIRS().nextFixingOf() ?: break diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt index 44e08e45b2..b3d07e6363 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt @@ -27,8 +27,8 @@ import javafx.util.Duration import net.corda.client.jfx.model.* import net.corda.client.jfx.utils.* import net.corda.core.contracts.ContractState -import net.corda.core.identity.Party import net.corda.core.crypto.toBase58String +import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.explorer.formatters.PartyNameFormatter import net.corda.explorer.model.CordaView @@ -77,7 +77,7 @@ class Network : CordaView() { .map { it as? PartiallyResolvedTransaction.InputResolution.Resolved } .filterNotNull() .map { it.stateAndRef.state.data }.getParties() - val outputParties = it.transaction.tx.outputs.map { it.data }.observable().getParties() + val outputParties = it.transaction.tx.outputStates.observable().getParties() val signingParties = it.transaction.sigs.map { getModel().lookup(it.by) } // Input parties fire a bullets to all output parties, and to the signing parties. !! This is a rough guess of how the message moves in the network. // TODO : Expose artemis queue to get real message information. diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt index 858117de3f..ae8a6252e7 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt @@ -22,7 +22,10 @@ import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.sequence import net.corda.contracts.asset.Cash import net.corda.core.contracts.* -import net.corda.core.crypto.* +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.commonName +import net.corda.core.crypto.toBase58String +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.node.NodeInfo import net.corda.explorer.AmountDiff @@ -124,7 +127,7 @@ class TransactionViewer : CordaView("Transactions") { totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity, reportingExchange, resolved.map { it.state.data }.lift(), - it.transaction.tx.outputs.map { it.data }.lift()) + it.transaction.tx.outputStates.lift()) ) } From a485bbada8fff801d6aef90ebf1da2eb10db570a Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 21 Jul 2017 12:44:58 +0100 Subject: [PATCH 065/197] Moved ThreadBox into core.internal --- .../rpc/internal/RPCClientProxyHandler.kt | 2 +- core/src/main/kotlin/net/corda/core/Utils.kt | 29 ----------------- .../net/corda/core/internal/ThreadBox.kt | 32 +++++++++++++++++++ .../services/events/NodeSchedulerService.kt | 2 +- .../keys/E2ETestKeyManagementService.kt | 2 +- .../keys/PersistentKeyManagementService.kt | 2 +- .../messaging/ArtemisMessagingServer.kt | 1 + .../services/messaging/NodeMessagingClient.kt | 8 ++--- .../services/network/NetworkMapService.kt | 2 +- .../network/PersistentNetworkMapService.kt | 2 +- .../DBTransactionMappingStorage.kt | 2 +- ...achineRecordedTransactionMappingStorage.kt | 2 +- .../statemachine/StateMachineManager.kt | 2 +- .../PersistentUniquenessProvider.kt | 2 +- .../services/vault/HibernateVaultQueryImpl.kt | 2 +- .../node/services/vault/NodeVaultService.kt | 2 +- .../net/corda/irs/api/NodeInterestRates.kt | 2 +- .../kotlin/net/corda/testing/driver/Driver.kt | 1 + .../testing/node/InMemoryMessagingNetwork.kt | 2 +- 19 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt 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 03cc1bec69..dce8fb99dd 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 @@ -10,7 +10,7 @@ import com.google.common.cache.RemovalCause import com.google.common.cache.RemovalListener import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.ThreadFactoryBuilder -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow import net.corda.core.internal.LazyPool diff --git a/core/src/main/kotlin/net/corda/core/Utils.kt b/core/src/main/kotlin/net/corda/core/Utils.kt index 94ef3ac8f5..11121de66c 100644 --- a/core/src/main/kotlin/net/corda/core/Utils.kt +++ b/core/src/main/kotlin/net/corda/core/Utils.kt @@ -25,12 +25,10 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.Future import java.util.concurrent.TimeUnit -import java.util.concurrent.locks.ReentrantLock import java.util.zip.Deflater import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream -import kotlin.concurrent.withLock // TODO: Review by EOY2016 if we ever found these utilities helpful. val Int.bd: BigDecimal get() = BigDecimal(this) @@ -163,33 +161,6 @@ fun logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T fun Logger.logElapsedTime(label: String, body: () -> T): T = logElapsedTime(label, this, body) -/** - * A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state. - * Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only - * instance to the ThreadBox constructor. You can now use the [locked] method with a lambda to take the lock in a - * way that ensures it'll be released if there's an exception. - * - * Note that this technique is not infallible: if you capture a reference to the fields in another lambda which then - * gets stored and invoked later, there may still be unsafe multi-threaded access going on, so watch out for that. - * This is just a simple guard rail that makes it harder to slip up. - * - * Example: - * - * private class MutableState { var i = 5 } - * private val state = ThreadBox(MutableState()) - * - * val ii = state.locked { i } - */ -class ThreadBox(val content: T, val lock: ReentrantLock = ReentrantLock()) { - inline fun locked(body: T.() -> R): R = lock.withLock { body(content) } - inline fun alreadyLocked(body: T.() -> R): R { - check(lock.isHeldByCurrentThread, { "Expected $lock to already be locked." }) - return body(content) - } - - fun checkNotLocked() = check(!lock.isHeldByCurrentThread) -} - /** * Given a path to a zip file, extracts it to the given directory. */ diff --git a/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt b/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt new file mode 100644 index 0000000000..eedd576694 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/ThreadBox.kt @@ -0,0 +1,32 @@ +package net.corda.core.internal + +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state. + * Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only + * instance to the ThreadBox constructor. You can now use the [locked] method with a lambda to take the lock in a + * way that ensures it'll be released if there's an exception. + * + * Note that this technique is not infallible: if you capture a reference to the fields in another lambda which then + * gets stored and invoked later, there may still be unsafe multi-threaded access going on, so watch out for that. + * This is just a simple guard rail that makes it harder to slip up. + * + * Example: + *``` + * private class MutableState { var i = 5 } + * private val state = ThreadBox(MutableState()) + * + * val ii = state.locked { i } + * ``` + */ +class ThreadBox(val content: T, val lock: ReentrantLock = ReentrantLock()) { + inline fun locked(body: T.() -> R): R = lock.withLock { body(content) } + inline fun alreadyLocked(body: T.() -> R): R { + check(lock.isHeldByCurrentThread, { "Expected $lock to already be locked." }) + return body(content) + } + + fun checkNotLocked(): Unit = check(!lock.isHeldByCurrentThread) +} diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index b69906afbe..2ec91e357e 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.events import com.google.common.util.concurrent.SettableFuture -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef diff --git a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt index cd09d34e3c..c41398a236 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt @@ -1,6 +1,6 @@ package net.corda.node.services.keys -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.keys diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 19108a422c..d145003723 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -1,6 +1,6 @@ package net.corda.node.services.keys -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.keys diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 221460f952..a598dded88 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -7,6 +7,7 @@ import net.corda.core.* import net.corda.core.crypto.* import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.core.internal.ThreadBox import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 81f9a47701..463a89002b 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -1,10 +1,10 @@ package net.corda.node.services.messaging import com.google.common.util.concurrent.ListenableFuture -import net.corda.core.ThreadBox import net.corda.core.andForget import net.corda.core.crypto.random63BitValue import net.corda.core.getOrThrow +import net.corda.core.internal.ThreadBox import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps @@ -468,8 +468,8 @@ class NodeMessagingClient(override val config: NodeConfiguration, } private fun sendWithRetry(retryCount: Int, address: String, message: ClientMessage, retryId: Long) { - fun randomiseDuplicateId(message: ClientMessage) { - message.putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString())) + fun ClientMessage.randomiseDuplicateId() { + putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString())) } log.trace { "Attempting to retry #$retryCount message delivery for $retryId" } @@ -479,7 +479,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, return } - randomiseDuplicateId(message) + message.randomiseDuplicateId() state.locked { log.trace { "Retry #$retryCount sending message $message to $address for $retryId" } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt index 7ee02601b5..b5d5aa729b 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.network import com.google.common.annotations.VisibleForTesting -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SignedData import net.corda.core.crypto.isFulfilledBy diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt index 2220d94a19..1cb7de5292 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt @@ -1,6 +1,6 @@ package net.corda.node.services.network -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.SingleMessageRecipient import net.corda.node.services.api.ServiceHubInternal diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index b9c376b768..9b455f9544 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -1,6 +1,6 @@ package net.corda.node.services.persistence -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.crypto.SecureHash import net.corda.core.flows.StateMachineRunId diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/InMemoryStateMachineRecordedTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/InMemoryStateMachineRecordedTransactionMappingStorage.kt index 168fee3bc8..8c4a684a11 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/InMemoryStateMachineRecordedTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/InMemoryStateMachineRecordedTransactionMappingStorage.kt @@ -1,6 +1,6 @@ package net.corda.node.services.persistence -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.crypto.SecureHash import net.corda.core.flows.StateMachineRunId 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 3104a3b9b2..6ee71cb3c1 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 @@ -8,7 +8,7 @@ import com.esotericsoftware.kryo.KryoException import com.google.common.collect.HashMultimap import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.crypto.SecureHash import net.corda.core.crypto.random63BitValue diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index f6419d2c24..4f94975f5e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -1,6 +1,6 @@ package net.corda.node.services.transactions -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt index 929312f753..c7cb8b6647 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateVaultQueryImpl.kt @@ -1,6 +1,6 @@ package net.corda.node.services.vault -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 773772ad5f..3a7dc5ad0e 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -12,7 +12,7 @@ import io.requery.kotlin.notNull import io.requery.query.RowExpression import net.corda.contracts.asset.Cash import net.corda.contracts.asset.OnLedgerAsset -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.bufferUntilSubscribed import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 01bcebfb17..d75ab5f5c8 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -8,7 +8,7 @@ import net.corda.contracts.Tenor import net.corda.contracts.math.CubicSplineInterpolator import net.corda.contracts.math.Interpolator import net.corda.contracts.math.InterpolatorFactory -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.contracts.Command import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTreeException diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index d75150b844..a056e03e7e 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -15,6 +15,7 @@ import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName import net.corda.core.identity.Party +import net.corda.core.internal.ThreadBox import net.corda.core.internal.div import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index 2c9601b48f..92e420aa7d 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -3,7 +3,7 @@ package net.corda.testing.node import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture -import net.corda.core.ThreadBox +import net.corda.core.internal.ThreadBox import net.corda.core.crypto.X509Utilities import net.corda.core.getOrThrow import net.corda.core.messaging.AllPossibleRecipients From 3e199e51fc6e63efd47e29b3e712a251b7cb0703 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 21 Jul 2017 15:38:03 +0100 Subject: [PATCH 066/197] Simplified the implementation of the tx query APIs with the introduction of Class.castIfPossible --- .../net/corda/core/internal/InternalUtils.kt | 2 + .../core/serialization/SerializationToken.kt | 7 +- .../core/transactions/BaseTransaction.kt | 78 ++++++------ .../core/transactions/LedgerTransaction.kt | 117 +++++++++--------- .../contracts/LedgerTransactionQueryTests.kt | 20 +-- .../database/HibernateConfiguration.kt | 18 ++- .../services/statemachine/SessionMessage.kt | 10 +- .../statemachine/StateMachineManager.kt | 10 +- 8 files changed, 129 insertions(+), 133 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 44df2fbb6e..9db156bcee 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -54,6 +54,8 @@ fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, var fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) +fun Class.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null + /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ fun Class<*>.staticField(name: String): DeclaredField = DeclaredField(this, name, null) /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [KClass]. */ diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt index 86d4fdfa1b..f6670d7a20 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt @@ -5,6 +5,7 @@ import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.Serializer import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import net.corda.core.internal.castIfPossible import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken @@ -46,11 +47,7 @@ class SerializeAsTokenSerializer : Serializer() { override fun read(kryo: Kryo, input: Input, type: Class): T { val token = (kryo.readClassAndObject(input) as? SerializationToken) ?: throw KryoException("Non-token read for tokenized type: ${type.name}") val fromToken = token.fromToken(kryo.serializationContext() ?: throw KryoException("Attempt to read a token for a ${SerializeAsToken::class.simpleName} instance of ${type.name} without initialising a context")) - if (type.isAssignableFrom(fromToken.javaClass)) { - return type.cast(fromToken) - } else { - throw KryoException("Token read ($token) did not return expected tokenized type: ${type.name}") - } + return type.castIfPossible(fromToken) ?: throw KryoException("Token read ($token) did not return expected tokenized type: ${type.name}") } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 896fead8be..9f6bb582a5 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -3,6 +3,7 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.identity.Party import net.corda.core.indexOfOrThrow +import net.corda.core.internal.castIfPossible import java.security.PublicKey import java.util.* import java.util.function.Predicate @@ -65,7 +66,6 @@ abstract class BaseTransaction( @Suppress("UNCHECKED_CAST") fun outRef(index: Int): StateAndRef = StateAndRef(outputs[index] as TransactionState, StateRef(id, index)) - /** * Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */ @@ -89,34 +89,41 @@ abstract class BaseTransaction( * Clazz must be an extension of [ContractState]. * @return the possibly empty list of output states matching the clazz restriction. */ - fun outputsOfType(clazz: Class): List { - @Suppress("UNCHECKED_CAST") - return outputs.filter { clazz.isInstance(it.data) }.map { it.data as T } - } + fun outputsOfType(clazz: Class): List = outputs.mapNotNull { clazz.castIfPossible(it.data) } + + inline fun outputsOfType(): List = outputsOfType(T::class.java) /** * Helper to simplify filtering outputs according to a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * Clazz must be an extension of [ContractState]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. * @return the possibly empty list of output states matching the predicate and clazz restrictions. */ - fun filterOutputs(predicate: Predicate, clazz: Class): List { + fun filterOutputs(clazz: Class, predicate: Predicate): List { return outputsOfType(clazz).filter { predicate.test(it) } } + inline fun filterOutputs(crossinline predicate: (T) -> Boolean): List { + return filterOutputs(T::class.java, Predicate { predicate(it) }) + } + /** * Helper to simplify finding a single output matching a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * Clazz must be an extension of [ContractState]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. * @return the single item matching the predicate. * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. */ - fun findOutput(predicate: Predicate, clazz: Class): T { - return filterOutputs(predicate, clazz).single() + fun findOutput(clazz: Class, predicate: Predicate): T { + return outputsOfType(clazz).single { predicate.test(it) } + } + + inline fun findOutput(crossinline predicate: (T) -> Boolean): T { + return findOutput(T::class.java, Predicate { predicate(it) }) } /** @@ -126,55 +133,44 @@ abstract class BaseTransaction( * @return the possibly empty list of output [StateAndRef] states matching the clazz restriction. */ fun outRefsOfType(clazz: Class): List> { - @Suppress("UNCHECKED_CAST") - return outputs.mapIndexed { index, state -> StateAndRef(state, StateRef(id, index)) } - .filter { clazz.isInstance(it.state.data) } - .map { it as StateAndRef } + return outputs.mapIndexedNotNull { index, state -> + @Suppress("UNCHECKED_CAST") + clazz.castIfPossible(state.data)?.let { StateAndRef(state as TransactionState, StateRef(id, index)) } + } } + inline fun outRefsOfType(): List> = outRefsOfType(T::class.java) + /** * Helper to simplify filtering output [StateAndRef] items according to a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * Clazz must be an extension of [ContractState]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. * @return the possibly empty list of output [StateAndRef] states matching the predicate and clazz restrictions. */ - fun filterOutRefs(predicate: Predicate, clazz: Class): List> { + fun filterOutRefs(clazz: Class, predicate: Predicate): List> { return outRefsOfType(clazz).filter { predicate.test(it.state.data) } } + inline fun filterOutRefs(crossinline predicate: (T) -> Boolean): List> { + return filterOutRefs(T::class.java, Predicate { predicate(it) }) + } + /** * Helper to simplify finding a single output [StateAndRef] matching a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * Clazz must be an extension of [ContractState]. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. * @return the single [StateAndRef] item matching the predicate. * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. */ - fun findOutRef(predicate: Predicate, clazz: Class): StateAndRef { - return filterOutRefs(predicate, clazz).single() - } - - //Kotlin extension methods to take advantage of Kotlin's smart type inference when querying the LedgerTransaction - inline fun outputsOfType(): List = this.outputsOfType(T::class.java) - - inline fun filterOutputs(crossinline predicate: (T) -> Boolean): List { - return filterOutputs(Predicate { predicate(it) }, T::class.java) - } - - inline fun findOutput(crossinline predicate: (T) -> Boolean): T { - return findOutput(Predicate { predicate(it) }, T::class.java) - } - - inline fun outRefsOfType(): List> = this.outRefsOfType(T::class.java) - - inline fun filterOutRefs(crossinline predicate: (T) -> Boolean): List> { - return filterOutRefs(Predicate { predicate(it) }, T::class.java) + fun findOutRef(clazz: Class, predicate: Predicate): StateAndRef { + return outRefsOfType(clazz).single { predicate.test(it.state.data) } } inline fun findOutRef(crossinline predicate: (T) -> Boolean): StateAndRef { - return findOutRef(Predicate { predicate(it) }, T::class.java) + return findOutRef(T::class.java, Predicate { predicate(it) }) } } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index dd370b6e6c..7745e7162c 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -3,6 +3,7 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party +import net.corda.core.internal.castIfPossible import net.corda.core.serialization.CordaSerializable import java.security.PublicKey import java.util.* @@ -51,7 +52,7 @@ class LedgerTransaction( * @return The [StateAndRef] */ @Suppress("UNCHECKED_CAST") - fun inRef(index: Int) = inputs[index] as StateAndRef + fun inRef(index: Int): StateAndRef = inputs[index] as StateAndRef /** * Verifies this transaction and throws an exception if not valid, depending on the type. For general transactions: @@ -158,10 +159,9 @@ class LedgerTransaction( * [clazz] must be an extension of [ContractState]. * @return the possibly empty list of inputs matching the clazz restriction. */ - fun inputsOfType(clazz: Class): List { - @Suppress("UNCHECKED_CAST") - return inputs.map { it.state.data }.filterIsInstance(clazz) - } + fun inputsOfType(clazz: Class): List = inputs.mapNotNull { clazz.castIfPossible(it.state.data) } + + inline fun inputsOfType(): List = inputsOfType(T::class.java) /** * Helper to simplify getting all inputs states of a particular class, interface, or base class. @@ -171,18 +171,26 @@ class LedgerTransaction( */ fun inRefsOfType(clazz: Class): List> { @Suppress("UNCHECKED_CAST") - return inputs.filter { clazz.isInstance(it.state.data) }.map { it as StateAndRef } + return inputs.mapNotNull { if (clazz.isInstance(it.state.data)) it as StateAndRef else null } } + inline fun inRefsOfType(): List> = inRefsOfType(T::class.java) + /** * Helper to simplify filtering inputs according to a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * [clazz] must be an extension of [ContractState]. + * @param predicate A filtering function taking a state of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. * @return the possibly empty list of input states matching the predicate and clazz restrictions. */ - fun filterInputs(predicate: Predicate, clazz: Class): List = inputsOfType(clazz).filter { predicate.test(it) } + fun filterInputs(clazz: Class, predicate: Predicate): List { + return inputsOfType(clazz).filter { predicate.test(it) } + } + + inline fun filterInputs(crossinline predicate: (T) -> Boolean): List { + return filterInputs(T::class.java, Predicate { predicate(it) }) + } /** * Helper to simplify filtering inputs according to a [Predicate]. @@ -192,29 +200,47 @@ class LedgerTransaction( * [clazz] must be an extension of [ContractState]. * @return the possibly empty list of inputs [StateAndRef] matching the predicate and clazz restrictions. */ - fun filterInRefs(predicate: Predicate, clazz: Class): List> = inRefsOfType(clazz).filter { predicate.test(it.state.data) } + fun filterInRefs(clazz: Class, predicate: Predicate): List> { + return inRefsOfType(clazz).filter { predicate.test(it.state.data) } + } + + inline fun filterInRefs(crossinline predicate: (T) -> Boolean): List> { + return filterInRefs(T::class.java, Predicate { predicate(it) }) + } /** * Helper to simplify finding a single input [ContractState] matching a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * [clazz] must be an extension of ContractState. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. * @return the single item matching the predicate. * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. */ - fun findInput(predicate: Predicate, clazz: Class): T = filterInputs(predicate, clazz).single() + fun findInput(clazz: Class, predicate: Predicate): T { + return inputsOfType(clazz).single { predicate.test(it) } + } + + inline fun findInput(crossinline predicate: (T) -> Boolean): T { + return findInput(T::class.java, Predicate { predicate(it) }) + } /** * Helper to simplify finding a single input matching a [Predicate]. - * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * [clazz] must be an extension of ContractState. + * @param predicate A filtering function taking a state of type T and returning true if this is the desired item. + * The class filtering is applied before the predicate. * @return the single item matching the predicate. * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements. */ - fun findInRef(predicate: Predicate, clazz: Class): StateAndRef = filterInRefs(predicate, clazz).single() + fun findInRef(clazz: Class, predicate: Predicate): StateAndRef { + return inRefsOfType(clazz).single { predicate.test(it.state.data) } + } + + inline fun findInRef(crossinline predicate: (T) -> Boolean): StateAndRef { + return findInRef(T::class.java, Predicate { predicate(it) }) + } /** * Helper to simplify getting an indexed command. @@ -230,34 +256,44 @@ class LedgerTransaction( * @return the possibly empty list of commands with [CommandData] values matching the clazz restriction. */ fun commandsOfType(clazz: Class): List { - return commands.filter { clazz.isInstance(it.value) }.map { Command(it.value, it.signers) } + return commands.mapNotNull { (signers, _, value) -> clazz.castIfPossible(value)?.let { Command(it, signers) } } } + inline fun commandsOfType(): List = commandsOfType(T::class.java) + /** * Helper to simplify filtering [Command] items according to a [Predicate]. - * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * [clazz] must be an extension of [CommandData]. + * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. * @return the possibly empty list of [Command] items with [CommandData] values matching the predicate and clazz restrictions. */ - fun filterCommands(predicate: Predicate, clazz: Class): List { + fun filterCommands(clazz: Class, predicate: Predicate): List { @Suppress("UNCHECKED_CAST") return commandsOfType(clazz).filter { predicate.test(it.value as T) } } + inline fun filterCommands(crossinline predicate: (T) -> Boolean): List { + return filterCommands(T::class.java, Predicate { predicate(it) }) + } /** * Helper to simplify finding a single [Command] items according to a [Predicate]. - * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. - * The class filtering is applied before the predicate. * @param clazz The class type used for filtering via an [Class.isInstance] check. * [clazz] must be an extension of [CommandData]. + * @param predicate A filtering function taking a [CommandData] item of type T and returning true if it should be included in the list. + * The class filtering is applied before the predicate. * @return the [Command] item with [CommandData] values matching the predicate and clazz restrictions. * @throws IllegalArgumentException if no items, or multiple items matched the requirements. */ - fun findCommand(predicate: Predicate, clazz: Class): Command { - return filterCommands(predicate, clazz).single() + fun findCommand(clazz: Class, predicate: Predicate): Command { + @Suppress("UNCHECKED_CAST") + return commandsOfType(clazz).single { predicate.test(it.value as T) } + } + + inline fun findCommand(crossinline predicate: (T) -> Boolean): Command { + return findCommand(T::class.java, Predicate { predicate(it) }) } /** @@ -273,37 +309,6 @@ class LedgerTransaction( * @return The Attachment with the matching id. * @throws IllegalArgumentException if no item matches the id. */ - fun getAttachment(id: SecureHash): Attachment = attachments.single { it.id == id } - - //Kotlin extension methods to take advantage of Kotlin's smart type inference when querying the LedgerTransaction - inline fun inputsOfType(): List = this.inputsOfType(T::class.java) - - inline fun inRefsOfType(): List> = this.inRefsOfType(T::class.java) - - inline fun filterInputs(crossinline predicate: (T) -> Boolean): List { - return filterInputs(Predicate { predicate(it) }, T::class.java) - } - - inline fun filterInRefs(crossinline predicate: (T) -> Boolean): List> { - return filterInRefs(Predicate { predicate(it) }, T::class.java) - } - - inline fun findInRef(crossinline predicate: (T) -> Boolean): StateAndRef { - return findInRef(Predicate { predicate(it) }, T::class.java) - } - - inline fun findInput(crossinline predicate: (T) -> Boolean): T { - return findInput(Predicate { predicate(it) }, T::class.java) - } - - inline fun commandsOfType(): List = this.commandsOfType(T::class.java) - - inline fun filterCommands(crossinline predicate: (T) -> Boolean): List { - return filterCommands(Predicate { predicate(it) }, T::class.java) - } - - inline fun findCommand(crossinline predicate: (T) -> Boolean): Command { - return findCommand(Predicate { predicate(it) }, T::class.java) - } + fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt index 55b36038c6..752b0c9c02 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt @@ -189,7 +189,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Filtered Input Tests`() { val ltx = makeDummyTransaction() - val intStates = ltx.filterInputs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + val intStates = ltx.filterInputs(IntTypeDummyState::class.java, Predicate { it.data.rem(2) == 0 }) assertEquals(3, intStates.size) assertEquals(listOf(0, 2, 4), intStates.map { it.data }) val stringStates: List = ltx.filterInputs { it.data == "3" } @@ -199,7 +199,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Filtered InRef Tests`() { val ltx = makeDummyTransaction() - val intStates = ltx.filterInRefs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + val intStates = ltx.filterInRefs(IntTypeDummyState::class.java, Predicate { it.data.rem(2) == 0 }) assertEquals(3, intStates.size) assertEquals(listOf(0, 2, 4), intStates.map { it.state.data.data }) assertEquals(listOf(ltx.inputs[0], ltx.inputs[4], ltx.inputs[8]), intStates) @@ -211,7 +211,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Filtered Output Tests`() { val ltx = makeDummyTransaction() - val intStates = ltx.filterOutputs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + val intStates = ltx.filterOutputs(IntTypeDummyState::class.java, Predicate { it.data.rem(2) == 0 }) assertEquals(3, intStates.size) assertEquals(listOf(0, 2, 4), intStates.map { it.data }) val stringStates: List = ltx.filterOutputs { it.data == "3" } @@ -221,7 +221,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Filtered OutRef Tests`() { val ltx = makeDummyTransaction() - val intStates = ltx.filterOutRefs(Predicate { it.data.rem(2) == 0 }, IntTypeDummyState::class.java) + val intStates = ltx.filterOutRefs(IntTypeDummyState::class.java, Predicate { it.data.rem(2) == 0 }) assertEquals(3, intStates.size) assertEquals(listOf(0, 2, 4), intStates.map { it.state.data.data }) assertEquals(listOf(0, 4, 8), intStates.map { it.ref.index }) @@ -235,7 +235,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Filtered Commands Tests`() { val ltx = makeDummyTransaction() - val intCmds1 = ltx.filterCommands(Predicate { it.id.rem(2) == 0 }, Commands.Cmd1::class.java) + val intCmds1 = ltx.filterCommands(Commands.Cmd1::class.java, Predicate { it.id.rem(2) == 0 }) assertEquals(3, intCmds1.size) assertEquals(listOf(0, 2, 4), intCmds1.map { (it.value as Commands.Cmd1).id }) val intCmds2 = ltx.filterCommands { it.id == 3 } @@ -245,7 +245,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Find Input Tests`() { val ltx = makeDummyTransaction() - val intState = ltx.findInput(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + val intState = ltx.findInput(IntTypeDummyState::class.java, Predicate { it.data == 4 }) assertEquals(ltx.getInput(8), intState) val stringState: StringTypeDummyState = ltx.findInput { it.data == "3" } assertEquals(ltx.getInput(7), stringState) @@ -254,7 +254,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Find InRef Tests`() { val ltx = makeDummyTransaction() - val intState = ltx.findInRef(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + val intState = ltx.findInRef(IntTypeDummyState::class.java, Predicate { it.data == 4 }) assertEquals(ltx.inRef(8), intState) val stringState: StateAndRef = ltx.findInRef { it.data == "3" } assertEquals(ltx.inRef(7), stringState) @@ -263,7 +263,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Find Output Tests`() { val ltx = makeDummyTransaction() - val intState = ltx.findOutput(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + val intState = ltx.findOutput(IntTypeDummyState::class.java, Predicate { it.data == 4 }) assertEquals(ltx.getOutput(8), intState) val stringState: StringTypeDummyState = ltx.findOutput { it.data == "3" } assertEquals(ltx.getOutput(7), stringState) @@ -272,7 +272,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Find OutRef Tests`() { val ltx = makeDummyTransaction() - val intState = ltx.findOutRef(Predicate { it.data == 4 }, IntTypeDummyState::class.java) + val intState = ltx.findOutRef(IntTypeDummyState::class.java, Predicate { it.data == 4 }) assertEquals(ltx.outRef(8), intState) val stringState: StateAndRef = ltx.findOutRef { it.data == "3" } assertEquals(ltx.outRef(7), stringState) @@ -281,7 +281,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Find Commands Tests`() { val ltx = makeDummyTransaction() - val intCmd1 = ltx.findCommand(Predicate { it.id == 2 }, Commands.Cmd1::class.java) + val intCmd1 = ltx.findCommand(Commands.Cmd1::class.java, Predicate { it.id == 2 }) assertEquals(ltx.getCommand(4), intCmd1) val intCmd2 = ltx.findCommand { it.id == 3 } assertEquals(ltx.getCommand(7), intCmd2) diff --git a/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt index 85d777d8d0..00d72cd0b4 100644 --- a/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/database/HibernateConfiguration.kt @@ -1,5 +1,6 @@ package net.corda.node.services.database +import net.corda.core.internal.castIfPossible import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.loggerFor import net.corda.node.services.api.SchemaService @@ -100,17 +101,14 @@ class HibernateConfiguration(val schemaService: SchemaService, val useDefaultLog override fun supportsAggressiveRelease(): Boolean = true - override fun getConnection(): Connection = - DatabaseTransactionManager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ).connection - - override fun unwrap(unwrapType: Class): T { - try { - return unwrapType.cast(this) - } catch(e: ClassCastException) { - throw UnknownUnwrapTypeException(unwrapType) - } + override fun getConnection(): Connection { + return DatabaseTransactionManager.newTransaction(Connection.TRANSACTION_REPEATABLE_READ).connection } - override fun isUnwrappableAs(unwrapType: Class<*>?): Boolean = (unwrapType == NodeDatabaseConnectionProvider::class.java) + override fun unwrap(unwrapType: Class): T { + return unwrapType.castIfPossible(this) ?: throw UnknownUnwrapTypeException(unwrapType) + } + + override fun isUnwrappableAs(unwrapType: Class<*>?): Boolean = unwrapType == NodeDatabaseConnectionProvider::class.java } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt index 8b25d0b0b7..baee9d549f 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt @@ -4,6 +4,7 @@ import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.identity.Party +import net.corda.core.internal.castIfPossible import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.UntrustworthyData @@ -42,10 +43,7 @@ data class ErrorSessionEnd(override val recipientSessionId: Long, val errorRespo data class ReceivedSessionMessage(val sender: Party, val message: M) fun ReceivedSessionMessage.checkPayloadIs(type: Class): UntrustworthyData { - if (type.isInstance(message.payload)) { - return UntrustworthyData(type.cast(message.payload)) - } else { - throw UnexpectedFlowEndException("We were expecting a ${type.name} from $sender but we instead got a " + - "${message.payload.javaClass.name} (${message.payload})") - } + return type.castIfPossible(message.payload)?.let { UntrustworthyData(it) } ?: + throw UnexpectedFlowEndException("We were expecting a ${type.name} from $sender but we instead got a " + + "${message.payload.javaClass.name} (${message.payload})") } 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 6ee71cb3c1..009c5a10ec 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 @@ -17,6 +17,7 @@ import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party +import net.corda.core.internal.castIfPossible import net.corda.core.messaging.DataFeed import net.corda.core.serialization.* import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT @@ -145,10 +146,9 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, fun

, T> findStateMachines(flowClass: Class

): List>> { @Suppress("UNCHECKED_CAST") return mutex.locked { - stateMachines.keys - .map { it.logic } - .filterIsInstance(flowClass) - .map { it to (it.stateMachine as FlowStateMachineImpl).resultFuture } + stateMachines.keys.mapNotNull { + flowClass.castIfPossible(it.logic)?.let { it to (it.stateMachine as FlowStateMachineImpl).resultFuture } + } } } @@ -380,7 +380,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { return try { - checkpoint.serializedFiber.deserialize>(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { fromCheckpoint = true } + checkpoint.serializedFiber.deserialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { fromCheckpoint = true } } catch (t: Throwable) { logger.error("Encountered unrestorable checkpoint!", t) null From d2eb5507f90c3fe190501ff6eab8de32eeb0d3d9 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Mon, 24 Jul 2017 14:48:39 +0100 Subject: [PATCH 067/197] Refactor transaction serialization caching (#1078) * Cache deserialized rather than serialized WireTransaction. Prevent repeated deserialization when adding signatures to the SignedTransaction. * Added a test to check that stx copying and signature collection still works properly after (de)serialization --- .../serialization/DefaultKryoCustomizer.kt | 2 +- .../net/corda/core/serialization/Kryo.kt | 24 +++++++++++--- .../core/serialization/SerializationAPI.kt | 4 --- .../core/transactions/SignedTransaction.kt | 33 +++++++++++++------ .../core/transactions/TransactionBuilder.kt | 2 +- .../core/transactions/WireTransaction.kt | 21 ++---------- .../corda/core/contracts/TransactionTests.kt | 3 +- .../core/crypto/PartialMerkleTreeTest.kt | 3 +- .../TransactionSerializationTests.kt | 14 ++++++++ .../net/corda/contracts/testing/Generators.kt | 2 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 3 +- .../database/RequeryConfigurationTest.kt | 2 +- .../persistence/DBTransactionStorageTests.kt | 2 +- .../main/kotlin/net/corda/testing/TestDSL.kt | 8 ++--- 14 files changed, 71 insertions(+), 52 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt index 18fc43b3b4..3e77f7bae3 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt @@ -60,7 +60,7 @@ object DefaultKryoCustomizer { instantiatorStrategy = CustomInstantiatorStrategy() register(Arrays.asList("").javaClass, ArraysAsListSerializer()) - register(SignedTransaction::class.java, ImmutableClassSerializer(SignedTransaction::class)) + register(SignedTransaction::class.java, SignedTransactionSerializer) register(WireTransaction::class.java, WireTransactionSerializer) register(SerializedBytes::class.java, SerializedBytesSerializer) diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index fa0da924d7..e98ef6161a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -6,12 +6,10 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.MapReferenceResolver import com.google.common.annotations.VisibleForTesting import net.corda.core.contracts.* -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.MetaData -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SignatureType +import net.corda.core.crypto.* import net.corda.core.crypto.composite.CompositeKey import net.corda.core.identity.Party +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey @@ -277,11 +275,27 @@ object WireTransactionSerializer : Serializer() { } } +@ThreadSafe +object SignedTransactionSerializer : Serializer() { + override fun write(kryo: Kryo, output: Output, obj: SignedTransaction) { + kryo.writeClassAndObject(output, obj.txBits) + kryo.writeClassAndObject(output, obj.sigs) + } + + @Suppress("UNCHECKED_CAST") + override fun read(kryo: Kryo, input: Input, type: Class): SignedTransaction { + return SignedTransaction( + kryo.readClassAndObject(input) as SerializedBytes, + kryo.readClassAndObject(input) as List + ) + } +} + /** For serialising an ed25519 private key */ @ThreadSafe object Ed25519PrivateKeySerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { - check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec ) + check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec) output.writeBytesWithLength(obj.seed) } 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 7ecb71f160..504556e46a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -135,7 +135,3 @@ class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { // It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer. val hash: SecureHash by lazy { bytes.sha256() } } - -// The more specific deserialize version results in the bytes being cached, which is faster. -@JvmName("SerializedBytesWireTransaction") -fun SerializedBytes.deserialize(serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): WireTransaction = WireTransaction.deserialize(this, serializationFactory, context) diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index a0b2b0a654..ee7b564bee 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -10,6 +10,8 @@ import net.corda.core.crypto.isFulfilledBy import net.corda.core.node.ServiceHub import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.toNonEmptySet import java.security.PublicKey @@ -34,20 +36,21 @@ data class SignedTransaction(val txBits: SerializedBytes, val sigs: List ) : NamedByHash { // DOCEND 1 + constructor(wtx: WireTransaction, sigs: List) : this(wtx.serialize(), sigs) { + cachedTransaction = wtx + } + init { require(sigs.isNotEmpty()) } - // TODO: This needs to be reworked to ensure that the inner WireTransaction is only ever deserialised sandboxed. + /** Cache the deserialized form of the transaction. This is useful when building a transaction or collecting signatures. */ + @Volatile @Transient private var cachedTransaction: WireTransaction? = null /** Lazily calculated access to the deserialised/hashed transaction data. */ - val tx: WireTransaction by lazy { WireTransaction.deserialize(txBits) } + val tx: WireTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this } - /** - * The Merkle root of the inner [WireTransaction]. Note that this is _not_ the same as the simple hash of - * [txBits], which would not use the Merkle tree structure. If the difference isn't clear, please consult - * the user guide section "Transaction tear-offs" to learn more about Merkle trees. - */ + /** The id of the contained [WireTransaction]. */ override val id: SecureHash get() = tx.id @CordaSerializable @@ -87,7 +90,6 @@ data class SignedTransaction(val txBits: SerializedBytes, val needed = getMissingSignatures() - allowedToBeMissing if (needed.isNotEmpty()) throw SignaturesMissingException(needed.toNonEmptySet(), getMissingKeyDescriptions(needed), id) - check(tx.id == id) return tx } @@ -131,10 +133,21 @@ data class SignedTransaction(val txBits: SerializedBytes, } /** Returns the same transaction but with an additional (unchecked) signature. */ - fun withAdditionalSignature(sig: DigitalSignature.WithKey) = copy(sigs = sigs + sig) + fun withAdditionalSignature(sig: DigitalSignature.WithKey) = copyWithCache(listOf(sig)) /** Returns the same transaction but with an additional (unchecked) signatures. */ - fun withAdditionalSignatures(sigList: Iterable) = copy(sigs = sigs + sigList) + fun withAdditionalSignatures(sigList: Iterable) = copyWithCache(sigList) + + /** + * Creates a copy of the SignedTransaction that includes the provided [sigList]. Also propagates the [cachedTransaction] + * so the contained transaction does not need to be deserialized again. + */ + private fun copyWithCache(sigList: Iterable): SignedTransaction { + val cached = cachedTransaction + return copy(sigs = sigs + sigList).apply { + cachedTransaction = cached + } + } /** Alias for [withAdditionalSignature] to let you use Kotlin operator overloading. */ operator fun plus(sig: DigitalSignature.WithKey) = withAdditionalSignature(sig) 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 61a42d2dbb..bfeaa9a81d 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -178,7 +178,7 @@ open class TransactionBuilder( throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.joinToString()}") } val wtx = toWireTransaction() - return SignedTransaction(wtx.serialize(), ArrayList(currentSigs)) + return SignedTransaction(wtx, ArrayList(currentSigs)) } /** 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 dc487f52e1..7d7177f011 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -8,18 +8,14 @@ import net.corda.core.crypto.keys import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.* -import net.corda.core.serialization.SerializationDefaults.P2P_CONTEXT -import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate /** * A transaction ready for serialisation, without any signatures attached. A WireTransaction is usually wrapped - * by a [SignedTransaction] that carries the signatures over this payload. The hash of the wire transaction is - * the identity of the transaction, that is, it's possible for two [SignedTransaction]s with different sets of - * signatures to have the same identity hash. + * by a [SignedTransaction] that carries the signatures over this payload. + * The identity of the transaction is the Merkle tree root of its components (see [MerkleTree]). */ class WireTransaction( /** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */ @@ -38,20 +34,9 @@ class WireTransaction( checkInvariants() } - // Cache the serialised form of the transaction and its hash to give us fast access to it. - @Volatile @Transient private var cachedBytes: SerializedBytes? = null - val serialized: SerializedBytes get() = cachedBytes ?: serialize().apply { cachedBytes = this } - + /** The transaction id is represented by the root hash of Merkle tree over the transaction components. */ override val id: SecureHash by lazy { merkleTree.hash } - companion object { - fun deserialize(data: SerializedBytes, serializationFactory: SerializationFactory = SERIALIZATION_FACTORY, context: SerializationContext = P2P_CONTEXT): WireTransaction { - val wtx = data.deserialize(serializationFactory, context) - wtx.cachedBytes = data - return wtx - } - } - /** * Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to * have been fully resolved using the resolution flow by this point. diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt index 36f37a6acf..8a5be8e652 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt @@ -19,8 +19,7 @@ import kotlin.test.assertFailsWith class TransactionTests : TestDependencyInjectionBase() { private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair): SignedTransaction { - val bytes: SerializedBytes = wtx.serialized - return SignedTransaction(bytes, keys.map { it.sign(wtx.id.bytes) }) + return SignedTransaction(wtx, keys.map { it.sign(wtx.id.bytes) }) } @Test diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index b333fe0ba2..482ba7ca3a 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -6,6 +6,7 @@ import net.corda.contracts.asset.Cash import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.WireTransaction import net.corda.testing.* @@ -110,7 +111,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) val leaves = mt.filteredLeaves - val d = WireTransaction.deserialize(testTx.serialized) + val d = testTx.serialize().deserialize() assertEquals(testTx.id, d.id) assertEquals(1, leaves.commands.size) assertEquals(1, leaves.outputs.size) diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index cbadb624a3..0e929b1799 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -104,4 +104,18 @@ class TransactionSerializationTests : TestDependencyInjectionBase() { val stx = notaryServices.addSignature(ptx) assertEquals(TEST_TX_TIME, stx.tx.timeWindow?.midpoint) } + + @Test + fun storeAndLoadWhenSigning() { + val ptx = megaCorpServices.signInitialTransaction(tx) + ptx.verifySignaturesExcept(notaryServices.key.public) + + val stored = ptx.serialize() + val loaded = stored.deserialize() + + assertEquals(loaded, ptx) + + val final = notaryServices.addSignature(loaded) + final.verifyRequiredSignatures() + } } diff --git a/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt b/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt index 1ca4c5d998..de19f715ef 100644 --- a/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt +++ b/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt @@ -84,7 +84,7 @@ class SignedTransactionGenerator : Generator(SignedTransactio override fun generate(random: SourceOfRandomness, status: GenerationStatus): SignedTransaction { val wireTransaction = WiredTransactionGenerator().generate(random, status) return SignedTransaction( - txBits = wireTransaction.serialized, + wtx = wireTransaction, sigs = listOf(NullSignature) ) } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 97664e7f0a..d574684c01 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -605,7 +605,6 @@ class TwoPartyTradeFlowTests { vararg extraSigningNodes: AbstractNode): Map { val signed = wtxToSign.map { - val bits = it.serialize() val id = it.id val sigs = mutableListOf() sigs.add(node.services.keyManagementService.sign(id.bytes, node.services.legalIdentityKey)) @@ -613,7 +612,7 @@ class TwoPartyTradeFlowTests { extraSigningNodes.forEach { currentNode -> sigs.add(currentNode.services.keyManagementService.sign(id.bytes, currentNode.info.legalIdentity.owningKey)) } - SignedTransaction(bits, sigs) + SignedTransaction(it, sigs) } return node.database.transaction { node.services.recordTransactions(signed) diff --git a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt index 5153d9189e..9086884215 100644 --- a/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/database/RequeryConfigurationTest.kt @@ -214,6 +214,6 @@ class RequeryConfigurationTest : TestDependencyInjectionBase() { type = TransactionType.General, timeWindow = null ) - return SignedTransaction(wtx.serialized, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1)))) + return SignedTransaction(wtx, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1)))) } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index f6c14b3deb..b9ca941715 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -152,6 +152,6 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { type = TransactionType.General, timeWindow = null ) - return SignedTransaction(wtx.serialized, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1)))) + return SignedTransaction(wtx, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1)))) } } diff --git a/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt b/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt index ea574f79a0..acde772dc6 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt @@ -284,7 +284,7 @@ data class TestLedgerDSLInterpreter private constructor( override fun verifies(): EnforceVerifyOrFail { try { val usedInputs = mutableSetOf() - services.recordTransactions(transactionsUnverified.map { SignedTransaction(it.serialized, listOf(NullSignature)) }) + services.recordTransactions(transactionsUnverified.map { SignedTransaction(it, listOf(NullSignature)) }) for ((_, value) in transactionWithLocations) { val wtx = value.transaction val ltx = wtx.toLedgerTransaction(services) @@ -296,7 +296,7 @@ data class TestLedgerDSLInterpreter private constructor( throw DoubleSpentInputs(txIds) } usedInputs.addAll(wtx.inputs) - services.recordTransactions(SignedTransaction(wtx.serialized, listOf(NullSignature))) + services.recordTransactions(SignedTransaction(wtx, listOf(NullSignature))) } return EnforceVerifyOrFail.Token } catch (exception: TransactionVerificationException) { @@ -330,8 +330,6 @@ data class TestLedgerDSLInterpreter private constructor( */ fun signAll(transactionsToSign: List, extraKeys: List) = transactionsToSign.map { wtx -> check(wtx.mustSign.isNotEmpty()) - val bits = wtx.serialize() - require(bits == wtx.serialized) val signatures = ArrayList() val keyLookup = HashMap() @@ -342,7 +340,7 @@ fun signAll(transactionsToSign: List, extraKeys: List) val key = keyLookup[it] ?: throw IllegalArgumentException("Missing required key for ${it.toStringShort()}") signatures += key.sign(wtx.id) } - SignedTransaction(bits, signatures) + SignedTransaction(wtx, signatures) } /** From 800f710fbba6099c19ad790979717415d1f78bd3 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 24 Jul 2017 12:41:21 +0100 Subject: [PATCH 068/197] Generified Command so that it's more easier to use when querying from LedgerTransaction --- .../net/corda/core/contracts/Structures.kt | 7 +++---- .../corda/core/flows/ContractUpgradeFlow.kt | 9 +++++--- .../net/corda/core/serialization/Kryo.kt | 2 +- .../core/transactions/LedgerTransaction.kt | 21 +++++++++---------- .../core/transactions/MerkleTransaction.kt | 4 ++-- .../core/transactions/TransactionBuilder.kt | 12 ++++++----- .../core/transactions/WireTransaction.kt | 4 ++-- .../contracts/LedgerTransactionQueryTests.kt | 16 +++++++------- .../core/crypto/PartialMerkleTreeTest.kt | 2 +- docs/source/changelog.rst | 4 +++- .../java/net/corda/docs/FlowCookbookJava.java | 4 ++-- .../kotlin/net/corda/docs/FlowCookbook.kt | 9 +++++--- .../corda/contracts/AnotherDummyContract.kt | 2 +- .../net/corda/contracts/testing/Generators.kt | 4 ++-- .../corda/node/services/CoreFlowHandlers.kt | 10 ++++----- .../net/corda/irs/api/NodeInterestRates.kt | 6 +++--- .../kotlin/net/corda/irs/flows/FixingFlow.kt | 2 +- .../corda/irs/api/NodeInterestRatesTest.kt | 8 +++---- .../net/corda/verifier/GeneratedLedger.kt | 6 +++--- 19 files changed, 70 insertions(+), 62 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index d18e33ec80..b609101b8a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -284,14 +284,13 @@ abstract class TypeOnlyCommandData : CommandData { /** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */ @CordaSerializable -// DOCSTART 9 -data class Command(val value: CommandData, val signers: List) { -// DOCEND 9 +data class Command(val value: T, val signers: List) { + // TODO Introduce NonEmptyList? init { require(signers.isNotEmpty()) } - constructor(data: CommandData, key: PublicKey) : this(data, listOf(key)) + constructor(data: T, key: PublicKey) : this(data, listOf(key)) private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it } override fun toString() = "${commandDataToString()} with pubkeys ${signers.joinToString()}" diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index 81b9657ff3..38b24bd5b1 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -24,12 +24,15 @@ class ContractUpgradeFlow().single()) } @JvmStatic - fun verify(input: ContractState, output: ContractState, commandData: Command) { - val command = commandData.value as UpgradeCommand + fun verify(input: ContractState, output: ContractState, commandData: Command) { + val command = commandData.value val participantKeys: Set = input.participants.map { it.owningKey }.toSet() val keysThatSigned: Set = commandData.signers.toSet() @Suppress("UNCHECKED_CAST") diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index e98ef6161a..0c2d7dbe22 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -265,7 +265,7 @@ object WireTransactionSerializer : Serializer() { // Otherwise we just assume the code we need is on the classpath already. kryo.useClassLoader(attachmentsClassLoader(kryo, attachmentHashes) ?: javaClass.classLoader) { val outputs = kryo.readClassAndObject(input) as List> - val commands = kryo.readClassAndObject(input) as List + val commands = kryo.readClassAndObject(input) as List> val notary = kryo.readClassAndObject(input) as Party? val signers = kryo.readClassAndObject(input) as List val transactionType = kryo.readClassAndObject(input) as TransactionType diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 7745e7162c..2d2225a8ad 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -247,7 +247,8 @@ class LedgerTransaction( * @param index the position of the item in the commands. * @return The Command at the requested index */ - fun getCommand(index: Int): Command = Command(commands[index].value, commands[index].signers) + @Suppress("UNCHECKED_CAST") + fun getCommand(index: Int): Command = Command(commands[index].value as T, commands[index].signers) /** * Helper to simplify getting all [Command] items with a [CommandData] of a particular class, interface, or base class. @@ -255,11 +256,11 @@ class LedgerTransaction( * [clazz] must be an extension of [CommandData]. * @return the possibly empty list of commands with [CommandData] values matching the clazz restriction. */ - fun commandsOfType(clazz: Class): List { + fun commandsOfType(clazz: Class): List> { return commands.mapNotNull { (signers, _, value) -> clazz.castIfPossible(value)?.let { Command(it, signers) } } } - inline fun commandsOfType(): List = commandsOfType(T::class.java) + inline fun commandsOfType(): List> = commandsOfType(T::class.java) /** * Helper to simplify filtering [Command] items according to a [Predicate]. @@ -269,12 +270,11 @@ class LedgerTransaction( * The class filtering is applied before the predicate. * @return the possibly empty list of [Command] items with [CommandData] values matching the predicate and clazz restrictions. */ - fun filterCommands(clazz: Class, predicate: Predicate): List { - @Suppress("UNCHECKED_CAST") - return commandsOfType(clazz).filter { predicate.test(it.value as T) } + fun filterCommands(clazz: Class, predicate: Predicate): List> { + return commandsOfType(clazz).filter { predicate.test(it.value) } } - inline fun filterCommands(crossinline predicate: (T) -> Boolean): List { + inline fun filterCommands(crossinline predicate: (T) -> Boolean): List> { return filterCommands(T::class.java, Predicate { predicate(it) }) } @@ -287,12 +287,11 @@ class LedgerTransaction( * @return the [Command] item with [CommandData] values matching the predicate and clazz restrictions. * @throws IllegalArgumentException if no items, or multiple items matched the requirements. */ - fun findCommand(clazz: Class, predicate: Predicate): Command { - @Suppress("UNCHECKED_CAST") - return commandsOfType(clazz).single { predicate.test(it.value as T) } + fun findCommand(clazz: Class, predicate: Predicate): Command { + return commandsOfType(clazz).single { predicate.test(it.value) } } - inline fun findCommand(crossinline predicate: (T) -> Boolean): Command { + inline fun findCommand(crossinline predicate: (T) -> Boolean): Command { return findCommand(T::class.java, Predicate { predicate(it) }) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 29f1c3d38b..bdef4417ba 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -29,7 +29,7 @@ interface TraversableTransaction { val inputs: List val attachments: List val outputs: List> - val commands: List + val commands: List> val notary: Party? val mustSign: List val type: TransactionType? @@ -77,7 +77,7 @@ class FilteredLeaves( override val inputs: List, override val attachments: List, override val outputs: List>, - override val commands: List, + override val commands: List>, override val notary: Party?, override val mustSign: List, override val type: TransactionType?, 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 bfeaa9a81d..a802c61cd0 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -37,7 +37,7 @@ open class TransactionBuilder( protected val inputs: MutableList = arrayListOf(), protected val attachments: MutableList = arrayListOf(), protected val outputs: MutableList> = arrayListOf(), - protected val commands: MutableList = arrayListOf(), + protected val commands: MutableList> = arrayListOf(), protected val signers: MutableSet = mutableSetOf(), protected var window: TimeWindow? = null) { constructor(type: TransactionType, notary: Party) : this(type, notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()) @@ -65,7 +65,7 @@ open class TransactionBuilder( is SecureHash -> addAttachment(t) is TransactionState<*> -> addOutputState(t) is ContractState -> addOutputState(t) - is Command -> addCommand(t) + is Command<*> -> addCommand(t) is CommandData -> throw IllegalArgumentException("You passed an instance of CommandData, but that lacks the pubkey. You need to wrap it in a Command object first.") is TimeWindow -> setTimeWindow(t) else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}") @@ -105,7 +105,9 @@ open class TransactionBuilder( } @JvmOverloads - fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null) = addOutputState(TransactionState(state, notary, encumbrance)) + fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null): TransactionBuilder { + return addOutputState(TransactionState(state, notary, encumbrance)) + } /** A default notary must be specified during builder construction to use this method */ fun addOutputState(state: ContractState): TransactionBuilder { @@ -114,7 +116,7 @@ open class TransactionBuilder( return this } - fun addCommand(arg: Command): TransactionBuilder { + fun addCommand(arg: Command<*>): TransactionBuilder { // TODO: replace pubkeys in commands with 'pointers' to keys in signers signers.addAll(arg.signers) commands.add(arg) @@ -149,7 +151,7 @@ open class TransactionBuilder( fun inputStates(): List = ArrayList(inputs) fun attachments(): List = ArrayList(attachments) fun outputStates(): List> = ArrayList(outputs) - fun commands(): List = ArrayList(commands) + fun commands(): List> = ArrayList(commands) /** The signatures that have been collected so far - might be incomplete! */ @Deprecated("Signatures should be gathered on a SignedTransaction instead.") 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 7d7177f011..836bab6b6e 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -24,7 +24,7 @@ class WireTransaction( override val attachments: List, outputs: List>, /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - override val commands: List, + override val commands: List>, notary: Party?, signers: List, type: TransactionType, @@ -125,7 +125,7 @@ class WireTransaction( val buf = StringBuilder() buf.appendln("Transaction:") for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input") - for (output in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: ${output.data}") + for ((data) in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $data") for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command") for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment") return buf.toString() diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt index 752b0c9c02..0c1540d089 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt @@ -112,10 +112,10 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { @Test fun `Simple Command Indexer tests`() { val ltx = makeDummyTransaction() - assertEquals(0, (ltx.getCommand(0).value as Commands.Cmd1).id) - assertEquals(0, (ltx.getCommand(1).value as Commands.Cmd2).id) - assertEquals(3, (ltx.getCommand(6).value as Commands.Cmd1).id) - assertEquals(3, (ltx.getCommand(7).value as Commands.Cmd2).id) + assertEquals(0, ltx.getCommand(0).value.id) + assertEquals(0, ltx.getCommand(1).value.id) + assertEquals(3, ltx.getCommand(6).value.id) + assertEquals(3, ltx.getCommand(7).value.id) assertFailsWith { ltx.getOutput(10) } } @@ -178,10 +178,10 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { val ltx = makeDummyTransaction() val intCmd1 = ltx.commandsOfType(Commands.Cmd1::class.java) assertEquals(5, intCmd1.size) - assertEquals(listOf(0, 1, 2, 3, 4), intCmd1.map { (it.value as Commands.Cmd1).id }) + assertEquals(listOf(0, 1, 2, 3, 4), intCmd1.map { it.value.id }) val intCmd2 = ltx.commandsOfType() assertEquals(5, intCmd2.size) - assertEquals(listOf(0, 1, 2, 3, 4), intCmd2.map { (it.value as Commands.Cmd2).id }) + assertEquals(listOf(0, 1, 2, 3, 4), intCmd2.map { it.value.id }) val notPresentQuery = ltx.commandsOfType(FungibleAsset.Commands.Exit::class.java) assertEquals(emptyList(), notPresentQuery) } @@ -237,9 +237,9 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() { val ltx = makeDummyTransaction() val intCmds1 = ltx.filterCommands(Commands.Cmd1::class.java, Predicate { it.id.rem(2) == 0 }) assertEquals(3, intCmds1.size) - assertEquals(listOf(0, 2, 4), intCmds1.map { (it.value as Commands.Cmd1).id }) + assertEquals(listOf(0, 2, 4), intCmds1.map { it.value.id }) val intCmds2 = ltx.filterCommands { it.id == 3 } - assertEquals(3, (intCmds2.single().value as Commands.Cmd2).id) + assertEquals(3, intCmds2.single().value.id) } @Test diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index 482ba7ca3a..a7a1b18265 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -102,7 +102,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { return when (elem) { is StateRef -> true is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys - is Command -> MEGA_CORP_PUBKEY in elem.signers + is Command<*> -> MEGA_CORP_PUBKEY in elem.signers is TimeWindow -> true is PublicKey -> elem == MEGA_CORP_PUBKEY else -> false diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 6faaf0b64f..84eb64d112 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -43,7 +43,9 @@ UNRELEASED ``LedgerTransaction`` as passed in parameter. The prinicpal consequence of this is that the types of the input and output collections on the transaction object have changed, so it may be necessary to ``map`` down to the ``ContractState`` sub-properties in existing code. - It is intended that new helper methods will be added shortly to the API to reduce the impact of these changes. + +* Added various query methods to ``LedgerTransaction`` to simplify querying of states and commands. In the same vain + ``Command`` is now parameterised on the ``CommandData`` field. Milestone 13 ------------ diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index 4b5b2b7e41..ef4f13dc2c 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -263,11 +263,11 @@ public class FlowCookbookJava { // public keys. To be valid, the transaction requires a signature // matching every public key in all of the transaction's commands. // DOCSTART 24 - CommandData commandData = new DummyContract.Commands.Create(); + DummyContract.Commands.Create commandData = new DummyContract.Commands.Create(); PublicKey ourPubKey = getServiceHub().getLegalIdentityKey(); PublicKey counterpartyPubKey = counterparty.getOwningKey(); List requiredSigners = ImmutableList.of(ourPubKey, counterpartyPubKey); - Command ourCommand = new Command(commandData, requiredSigners); + Command ourCommand = new Command<>(commandData, requiredSigners); // DOCEND 24 // ``CommandData`` can either be: diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 46550b3c15..570881cb2d 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -12,7 +12,10 @@ import net.corda.core.node.services.ServiceType import net.corda.core.node.services.Vault.Page import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria -import net.corda.core.transactions.* +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step import net.corda.core.utilities.UntrustworthyData @@ -243,11 +246,11 @@ object FlowCookbook { // public keys. To be valid, the transaction requires a signature // matching every public key in all of the transaction's commands. // DOCSTART 24 - val commandData: CommandData = DummyContract.Commands.Create() + val commandData: DummyContract.Commands.Create = DummyContract.Commands.Create() val ourPubKey: PublicKey = serviceHub.legalIdentityKey val counterpartyPubKey: PublicKey = counterparty.owningKey val requiredSigners: List = listOf(ourPubKey, counterpartyPubKey) - val ourCommand: Command = Command(commandData, requiredSigners) + val ourCommand: Command = Command(commandData, requiredSigners) // DOCEND 24 // ``CommandData`` can either be: diff --git a/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt b/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt index 9d5e4d8bdd..76cd7f9091 100644 --- a/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt +++ b/finance/isolated/src/main/kotlin/net/corda/contracts/AnotherDummyContract.kt @@ -1,4 +1,4 @@ -package net.corda.contracts.isolated +package net.corda.contracts import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash diff --git a/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt b/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt index de19f715ef..dcc1793ed5 100644 --- a/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt +++ b/finance/src/test/kotlin/net/corda/contracts/testing/Generators.kt @@ -56,8 +56,8 @@ class CommandDataGenerator : Generator(CommandData::class.java) { } } -class CommandGenerator : Generator(Command::class.java) { - override fun generate(random: SourceOfRandomness, status: GenerationStatus): Command { +class CommandGenerator : Generator>(Command::class.java) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Command<*> { val signersGenerator = ArrayListGenerator() signersGenerator.addComponentGenerators(listOf(PublicKeyGenerator())) return Command(CommandDataGenerator().generate(random, status), PublicKeyGenerator().generate(random, status)) diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index ce50c1e021..84c8011334 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -1,10 +1,7 @@ package net.corda.node.services import co.paralleluniverse.fibers.Suspendable -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.TransactionType -import net.corda.core.contracts.UpgradedContract -import net.corda.core.contracts.requireThat +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.flows.* import net.corda.core.identity.AnonymousPartyAndPath @@ -121,7 +118,10 @@ class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Ac "The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade) "The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx) } - ContractUpgradeFlow.verify(oldStateAndRef.state.data, expectedTx.outRef(0).state.data, expectedTx.commands.single()) + ContractUpgradeFlow.verify( + oldStateAndRef.state.data, + expectedTx.outRef(0).state.data, + expectedTx.toLedgerTransaction(serviceHub).commandsOfType().single()) } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index d75ab5f5c8..80dea68879 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -8,7 +8,6 @@ import net.corda.contracts.Tenor import net.corda.contracts.math.CubicSplineInterpolator import net.corda.contracts.math.Interpolator import net.corda.contracts.math.InterpolatorFactory -import net.corda.core.internal.ThreadBox import net.corda.core.contracts.Command import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTreeException @@ -18,6 +17,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party +import net.corda.core.internal.ThreadBox import net.corda.core.node.PluginServiceHub import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService @@ -151,7 +151,7 @@ object NodeInterestRates { throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.") } // Performing validation of obtained FilteredLeaves. - fun commandValidator(elem: Command): Boolean { + fun commandValidator(elem: Command<*>): Boolean { if (!(identity.owningKey in elem.signers && elem.value is Fix)) throw IllegalArgumentException("Oracle received unknown command (not in signers or not Fix).") val fix = elem.value as Fix @@ -163,7 +163,7 @@ object NodeInterestRates { fun check(elem: Any): Boolean { return when (elem) { - is Command -> commandValidator(elem) + is Command<*> -> commandValidator(elem) else -> throw IllegalArgumentException("Oracle received data of different type than expected.") } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt index 1a0f55c814..e43e0ddc36 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt @@ -80,7 +80,7 @@ object FixingFlow { @Suspendable override fun filtering(elem: Any): Boolean { return when (elem) { - is Command -> oracleParty.owningKey in elem.signers && elem.value is Fix + is Command<*> -> oracleParty.owningKey in elem.signers && elem.value is Fix else -> false } } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index ac2ee52fc1..79d5d36e30 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -51,12 +51,12 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { fun fixCmdFilter(elem: Any): Boolean { return when (elem) { - is Command -> oracle.identity.owningKey in elem.signers && elem.value is Fix + is Command<*> -> oracle.identity.owningKey in elem.signers && elem.value is Fix else -> false } } - fun filterCmds(elem: Any): Boolean = elem is Command + fun filterCmds(elem: Any): Boolean = elem is Command<*> @Before fun setUp() { @@ -179,7 +179,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first() fun filtering(elem: Any): Boolean { return when (elem) { - is Command -> oracle.identity.owningKey in elem.signers && elem.value is Fix + is Command<*> -> oracle.identity.owningKey in elem.signers && elem.value is Fix is TransactionState -> true else -> false } @@ -234,7 +234,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { : RatesFixFlow(tx, oracle, fixOf, expectedRate, rateTolerance, progressTracker) { override fun filtering(elem: Any): Boolean { return when (elem) { - is Command -> oracle.owningKey in elem.signers && elem.value is Fix + is Command<*> -> oracle.owningKey in elem.signers && elem.value is Fix else -> false } } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index 3c0ccd7460..eb24c1994b 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -2,7 +2,6 @@ package net.corda.verifier import net.corda.client.mock.* import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.SecureHash import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.entropyToKeyPair @@ -12,6 +11,7 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.WireTransaction +import net.corda.testing.contracts.DummyContract import java.io.ByteArrayInputStream import java.math.BigInteger import java.security.PublicKey @@ -49,7 +49,7 @@ data class GeneratedLedger( Generator.replicatePoisson(1.0, pickOneOrMaybeNew(attachments, attachmentGenerator)) } - val commandsGenerator: Generator>> by lazy { + val commandsGenerator: Generator, Party>>> by lazy { Generator.replicatePoisson(4.0, commandGenerator(identities)) } @@ -214,7 +214,7 @@ val stateGenerator: Generator = GeneratedState(nonce, participants.map { AnonymousParty(it) }) } -fun commandGenerator(partiesToPickFrom: Collection): Generator> { +fun commandGenerator(partiesToPickFrom: Collection): Generator, Party>> { return pickOneOrMaybeNew(partiesToPickFrom, partyGenerator).combine(Generator.long()) { signer, nonce -> Pair( Command(GeneratedCommandData(nonce), signer.owningKey), From 8acbd86c7063251ce3ef537dc0597657287bc30a Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 24 Jul 2017 18:25:05 +0100 Subject: [PATCH 069/197] Uses dynamic loading of dropdown versions. Styles box slightly. --- docs/source/_static/css/custom.css | 8 ++++ docs/source/_static/versions | 11 +++++ .../_templates/layout_for_doc_website.html | 44 ++++++++----------- docs/source/conf.py | 6 ++- 4 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 docs/source/_static/versions diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 7dfbdc74cf..c7f2ab9999 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -179,3 +179,11 @@ a:visited { background-position: center top; background-origin: content box; } + + +/* Version dropdown */ + +.version-dropdown { + border-radius: 4px; + border-color: #263673; +} \ No newline at end of file diff --git a/docs/source/_static/versions b/docs/source/_static/versions new file mode 100644 index 0000000000..25910c55b3 --- /dev/null +++ b/docs/source/_static/versions @@ -0,0 +1,11 @@ +{ + "https://docs.corda.net/releases/release-M6.0": "M6.0", + "https://docs.corda.net/releases/release-M7.0": "M7.0", + "https://docs.corda.net/releases/release-M8.2": "M8.2", + "https://docs.corda.net/releases/release-M9.2": "M9.2", + "https://docs.corda.net/releases/release-M10.1": "M10.1", + "https://docs.corda.net/releases/release-M11.1": "M11.1", + "https://docs.corda.net/releases/release-M12.1": "M12.1", + "https://docs.corda.net/releases/release-M13.0": "M13.0", + "https://docs.corda.net/head/": "Master" +} \ No newline at end of file diff --git a/docs/source/_templates/layout_for_doc_website.html b/docs/source/_templates/layout_for_doc_website.html index 841ca935b3..6dcb6f7b12 100644 --- a/docs/source/_templates/layout_for_doc_website.html +++ b/docs/source/_templates/layout_for_doc_website.html @@ -10,35 +10,29 @@ API reference: Kotlin/ Slack
- +
+ {% endblock %} {% block footer %} + +